summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--generic/tkAlloc.h43
-rw-r--r--generic/tkBitField.c1536
-rw-r--r--generic/tkBitField.h188
-rw-r--r--generic/tkBitFieldPriv.h215
-rw-r--r--generic/tkBool.h31
-rw-r--r--generic/tkIntSet.c2151
-rw-r--r--generic/tkIntSet.h199
-rw-r--r--generic/tkIntSetPriv.h286
-rw-r--r--generic/tkQTree.c1146
-rw-r--r--generic/tkQTree.h186
-rw-r--r--generic/tkQTreePriv.h169
-rw-r--r--generic/tkRangeList.c690
-rw-r--r--generic/tkRangeList.h215
-rw-r--r--generic/tkRangeListPriv.h174
-rw-r--r--generic/tkText.c10307
-rw-r--r--generic/tkText.h2406
-rw-r--r--generic/tkTextBTree.c16785
-rw-r--r--generic/tkTextDisp.c14020
-rw-r--r--generic/tkTextImage.c1263
-rw-r--r--generic/tkTextIndex.c3976
-rw-r--r--generic/tkTextLineBreak.c988
-rw-r--r--generic/tkTextMark.c2821
-rw-r--r--generic/tkTextPriv.h976
-rw-r--r--generic/tkTextTag.c3477
-rw-r--r--generic/tkTextTagSet.c1738
-rw-r--r--generic/tkTextTagSet.h292
-rw-r--r--generic/tkTextTagSetPriv.h505
-rw-r--r--generic/tkTextUndo.c1073
-rw-r--r--generic/tkTextUndo.h260
-rw-r--r--generic/tkTextUndoPriv.h223
-rw-r--r--generic/tkTextWind.c1191
-rw-r--r--library/text.tcl704
-rw-r--r--tests/text.test1039
-rw-r--r--tests/textBTree.test181
-rw-r--r--tests/textDisp.test296
-rw-r--r--tests/textImage.test97
-rw-r--r--tests/textIndex.test34
-rw-r--r--tests/textMark.test22
-rw-r--r--tests/textTag.test55
-rw-r--r--tests/textWind.test36
-rw-r--r--unix/Makefile.in74
-rw-r--r--unix/tkUnixRFont.c77
-rw-r--r--win/Makefile.in14
43 files changed, 56534 insertions, 15625 deletions
diff --git a/generic/tkAlloc.h b/generic/tkAlloc.h
new file mode 100644
index 0000000..d1d06fb
--- /dev/null
+++ b/generic/tkAlloc.h
@@ -0,0 +1,43 @@
+/*
+ * tkAlloc.h --
+ *
+ * This module provides an interface to memory allocation functions, this
+ * is: malloc(), realloc(), free(). This has the following advantages:
+ *
+ * 1. The whole features of the very valuable tool Valgrind can be used,
+ * this requires to bypass the Tcl allocation functions.
+ *
+ * 2. Backport to version 8.5, this is important because the Mac version
+ * of wish8.6 has event loop issues.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TK_ALLOC
+#define _TK_ALLOC
+
+#include "tcl.h"
+
+#if TK_VALGRIND
+
+# include <stdlib.h>
+
+/* enables compiler check that these functions will not be used */
+# undef ckalloc
+# undef ckrealloc
+# undef ckfree
+
+#else /* if !TK_VALGRIND */
+
+/* the main reason for these definitions is portability to 8.5 */
+# define malloc(size) ((void *) (ckalloc(size)))
+# define realloc(ptr, size) ((void *) (ckrealloc((char *) (ptr), size)))
+# define free(ptr) ckfree((char *) (ptr))
+
+#endif /* TK_VALGRIND */
+
+#endif /* _TK_ALLOC */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkBitField.c b/generic/tkBitField.c
new file mode 100644
index 0000000..0c24415
--- /dev/null
+++ b/generic/tkBitField.c
@@ -0,0 +1,1536 @@
+/*
+ * tkBitField.c --
+ *
+ * This module implements bit field operations.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkBitField.h"
+#include "tkIntSet.h"
+#include "tkAlloc.h"
+#include <string.h>
+#include <assert.h>
+
+#if !(__STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900))
+# define _TK_NEED_IMPLEMENTATION
+# include "tkBitFieldPriv.h"
+#endif
+
+#ifndef MAX
+# define MAX(a,b) (((int) a) < ((int) b) ? b : a)
+#endif
+#ifndef MIN
+# define MIN(a,b) (((int) a) < ((int) b) ? a : b)
+#endif
+
+#if TK_CHECK_ALLOCS
+# define DEBUG_ALLOC(expr) expr
+#else
+# define DEBUG_ALLOC(expr)
+#endif
+
+
+#define NBITS TK_BIT_NBITS
+#define NWORDS(size) TK_BIT_COUNT_WORDS(size)
+#define BIT_INDEX(n) TK_BIT_INDEX(n)
+#define WORD_INDEX(n) TK_BIT_WORD_INDEX(n)
+
+#define NBYTES(words) ((words)*sizeof(TkBitWord))
+#define BYTE_SIZE(size) NBYTES(NWORDS(size))
+#define BF_SIZE(size) ((unsigned) (Tk_Offset(TkBitField, bits) + BYTE_SIZE(size)))
+#define BIT_SPAN(f,t) ((~((TkBitWord) 0) << (f)) & (~((TkBitWord) 0) >> ((NBITS - 1) - (t))))
+
+
+DEBUG_ALLOC(unsigned tkBitCountNew = 0);
+DEBUG_ALLOC(unsigned tkBitCountDestroy = 0);
+
+
+#ifdef TCL_WIDE_INT_IS_LONG
+
+/* ****************************************************************************/
+/* 64 bit implementation */
+/* ****************************************************************************/
+
+# if defined(__GNUC__) || defined(__clang__)
+
+# define LsbIndex(x) __builtin_ctzll(x)
+# define MsbIndex(x) ((sizeof(unsigned long long)*8 - 1) - __builtin_clzll(x))
+
+# else /* !(defined(__GNUC__) || defined(__clang__)) */
+
+static unsigned
+LsbIndex(uint64_t x)
+{
+ /* Source: http://chessprogramming.wikispaces.com/BitScan */
+ static const unsigned MultiplyDeBruijnBitPosition[64] = {
+ 0, 1, 48, 2, 57, 49, 28, 3, 61, 58, 50, 42, 38, 29, 17, 4,
+ 62, 55, 59, 36, 53, 51, 43, 22, 45, 39, 33, 30, 24, 18, 12, 5,
+ 63, 47, 56, 27, 60, 41, 37, 16, 54, 35, 52, 21, 44, 32, 23, 11,
+ 46, 26, 40, 15, 34, 20, 31, 10, 25, 14, 19, 9, 13, 8, 7, 6
+ };
+ return MultiplyDeBruijnBitPosition[((uint64_t) ((x & -x)*UINT64_C(0x03f79d71b4cb0a89))) >> 58];
+}
+
+static unsigned
+MsbIndex(uint64_t x)
+{
+ /* Source: http://stackoverflow.com/questions/671815/what-is-the-fastest-most-efficient-way-to-find-the-highest-set-bit-msb-in-an-i (extended to 64 bit by GC) */
+ static const uint8_t Table[16] = { -1, 0, 1,1, 2,2,2,2, 3,3,3,3,3,3,3,3 };
+
+ unsigned r = 0;
+ uint64_t xk;
+
+ if ((xk = x >> 32)) { r = 32; x = xk; }
+ if ((xk = x >> 16)) { r += 16; x = xk; }
+ if ((xk = x >> 8)) { r += 8; x = xk; }
+ if ((xk = x >> 4)) { r += 4; x = xk; }
+
+ return r + Table[x];
+}
+
+# endif /* defined(__GNUC__) || defined(__clang__) */
+
+static unsigned
+PopCount(uint64_t x)
+{
+ /* Source: http://chessprogramming.wikispaces.com/Population+Count */
+ x -= (x >> 1) & UINT64_C(0x5555555555555555);
+ x = ((x >> 2) & UINT64_C(0x3333333333333333)) + (x & UINT64_C(0x3333333333333333));
+ x = ((x >> 4) + x) & UINT64_C(0x0F0F0F0F0F0F0F0F);
+ return (x * UINT64_C(0x0101010101010101)) >> 56;
+}
+
+#else /* TCL_WIDE_INT_IS_LONG */
+
+/* ****************************************************************************/
+/* 32 bit implementation */
+/* ****************************************************************************/
+
+# if defined(__GNUC__) || defined(__clang__)
+
+# define LsbIndex(x) __builtin_ctz(x)
+# define MsbIndex(x) ((sizeof(unsigned)*8 - 1) - __builtin_clz(x))
+
+# else /* defined(__GNUC__) || defined(__clang__) */
+
+# if 1
+/* On my system this is the fastest method, only about 5% slower than __builtin_ctz(). */
+static unsigned
+LsbIndex(uint32_t x)
+{
+ /* Source: http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear */
+ static const unsigned MultiplyDeBruijnBitPosition[32] = {
+ 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8,
+ 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
+ };
+ return MultiplyDeBruijnBitPosition[((uint32_t) ((x & -x)*0x077cb531)) >> 27];
+}
+# else
+/* The "classical" method, but about 20% slower than the DeBruijn method on my system. */
+static unsigned
+LsbIndex(uint32_t x)
+{
+ unsigned ctz = 32;
+ x &= -((int32_t ) x);
+ if (x) --ctz;
+ if (x & 0x0000ffff) ctz -= 16;
+ if (x & 0x00ff00ff) ctz -= 8;
+ if (x & 0x0f0f0f0f) ctz -= 4;
+ if (x & 0x33333333) ctz -= 2;
+ if (x & 0x55555555) ctz -= 1;
+ return ctz;
+}
+# endif
+
+static unsigned
+MsbIndex(uint32_t x)
+{
+ /* Source: http://stackoverflow.com/questions/671815/what-is-the-fastest-most-efficient-way-to-find-the-highest-set-bit-msb-in-an-i */
+ static const uint8_t Table[16] = { -1 ,0, 1,1, 2,2,2,2, 3,3,3,3,3,3,3,3 };
+
+ unsigned r = 0;
+ uint32_t xk;
+
+ if ((xk = x >> 16)) { r = 16; x = xk; }
+ if ((xk = x >> 8)) { r += 8; x = xk; }
+ if ((xk = x >> 4)) { r += 4; x = xk; }
+
+ return r + Table[x];
+}
+
+# endif /* defined(__GNUC__) || defined(__clang__) */
+
+static unsigned
+PopCount(uint32_t x)
+{
+ /* Source: http://graphics.stanford.edu/~seander/bithacks.html */
+ /* NOTE: the GCC function __builtin_popcount() is slower on my system. */
+ x -= (x >> 1) & 0x55555555;
+ x = ((x >> 2) & 0x33333333) + (x & 0x33333333);
+ x = ((x >> 4) + x) & 0x0f0f0f0f;
+ return (x*0x01010101) >> 24;
+}
+
+#endif /* !TCL_WIDE_INT_IS_LONG */
+
+
+#if TK_CHECK_ALLOCS
+/*
+ * Some useful functions for finding memory leaks.
+ */
+
+static TkBitField *Used = NULL;
+static TkBitField *Last = NULL;
+
+
+static void
+Use(
+ TkBitField *bf)
+{
+ static int N = 0;
+ if (!Used) { Used = bf; }
+ if (Last) { Last->next = bf; }
+ bf->number = N++;
+ bf->next = NULL;
+ bf->prev = Last;
+ Last = bf;
+}
+
+
+static void
+Free(
+ TkBitField *bf)
+{
+ assert(bf->prev || Used == bf);
+ assert(bf->next || Last == bf);
+ if (Last == bf) { Last = bf->prev; }
+ if (Used == bf) { Used = bf->next; }
+ if (bf->prev) { bf->prev->next = bf->next; }
+ if (bf->next) { bf->next->prev = bf->prev; }
+ bf->prev = NULL;
+ bf->next = NULL;
+}
+
+
+void
+TkBitCheckAllocs()
+{
+ for ( ; Used; Used = Used->next) {
+ printf("TkBitField(number): %d\n", Used->number);
+ }
+}
+
+#endif /* TK_CHECK_ALLOCS */
+
+
+static bool
+IsEqual(
+ const TkBitWord *s,
+ const TkBitWord *t,
+ unsigned numBytes)
+{
+ const TkBitWord *e = s + numBytes;
+
+ for ( ; s < e; ++s, ++t) {
+ if (*s != *t) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+static void
+ResetUnused(
+ TkBitField *bf)
+{
+ unsigned bitIndex = BIT_INDEX(bf->size);
+
+ if (bitIndex) {
+ bf->bits[NWORDS(bf->size) - 1] &= ~BIT_SPAN(bitIndex, NBITS - 1);
+ }
+}
+
+
+void
+TkBitDestroy(
+ TkBitField **bfPtr)
+{
+ assert(bfPtr);
+
+ if (*bfPtr) {
+ DEBUG_ALLOC(Free(*bfPtr));
+ free(*bfPtr);
+ *bfPtr = NULL;
+ DEBUG_ALLOC(tkBitCountDestroy++);
+ }
+}
+
+
+TkBitField *
+TkBitResize(
+ TkBitField *bf,
+ unsigned newSize)
+{
+ if (!bf) {
+ bf = malloc(BF_SIZE(newSize));
+ DEBUG_ALLOC(Use(bf));
+ bf->size = newSize;
+ bf->refCount = 1;
+ bf->isSetFlag = false;
+ memset(bf->bits, 0, BYTE_SIZE(newSize));
+ DEBUG_ALLOC(tkBitCountNew++);
+ } else {
+ unsigned newWords;
+ unsigned oldWords;
+
+ newWords = NWORDS(newSize);
+ oldWords = NWORDS(bf->size);
+
+ if (newWords == oldWords) {
+ bf->size = newSize;
+ ResetUnused(bf);
+ return bf;
+ }
+
+ if (bf->refCount <= 1) {
+ DEBUG_ALLOC(Free(bf));
+ bf = realloc((char *) bf, BF_SIZE(newSize));
+ DEBUG_ALLOC(Use(bf));
+ } else {
+ TkBitField *newBF = malloc(BF_SIZE(newSize));
+ DEBUG_ALLOC(Use(newBF));
+ memcpy(newBF->bits, bf->bits, NBYTES(MIN(oldWords, newWords)));
+ newBF->refCount = 1;
+ newBF->isSetFlag = false;
+ bf->refCount -= 1;
+ bf = newBF;
+ DEBUG_ALLOC(tkBitCountNew++);
+ }
+
+ bf->size = newSize;
+
+ if (oldWords < newWords) {
+ memset(bf->bits + oldWords, 0, NBYTES(newWords - oldWords));
+ } else {
+ ResetUnused(bf);
+ }
+ }
+
+ return bf;
+}
+
+
+TkBitField *
+TkBitFromSet(
+ const TkIntSet *set,
+ unsigned size)
+{
+ unsigned numEntries = TkIntSetSize(set);
+ TkBitField *bf = TkBitResize(NULL, size);
+ unsigned i;
+
+ for (i = 0; i < numEntries; ++i) {
+ TkIntSetType value = TkIntSetAccess(set, i);
+
+ if (value >= size) {
+ break;
+ }
+ TkBitSet(bf, value);
+ }
+
+ return bf;
+}
+
+
+unsigned
+TkBitCount(
+ const TkBitField *bf)
+{
+ unsigned words, i;
+ unsigned count = 0;
+
+ assert(bf);
+
+ words = NWORDS(bf->size);
+
+ for (i = 0; i < words; ++i) {
+ count += PopCount(bf->bits[i]);
+ }
+
+ return count;
+}
+
+
+TkBitField *
+TkBitCopy(
+ const TkBitField *bf,
+ int size)
+{
+ TkBitField *copy;
+ unsigned oldWords, newWords;
+
+ assert(bf);
+
+ if (size < 0) {
+ size = bf->size;
+ }
+
+ copy = malloc(BF_SIZE(size));
+ DEBUG_ALLOC(Use(copy));
+ oldWords = NWORDS(bf->size);
+ newWords = NWORDS(size);
+ memcpy(copy->bits, bf->bits, NBYTES(MIN(oldWords, newWords)));
+ if (newWords > oldWords) {
+ memset(copy->bits + oldWords, 0, NBYTES(newWords - oldWords));
+ }
+ copy->size = size;
+ copy->refCount = 1;
+ copy->isSetFlag = false;
+ ResetUnused(copy);
+ DEBUG_ALLOC(tkBitCountNew++);
+ return copy;
+}
+
+
+void
+TkBitJoin(
+ TkBitField *dst,
+ const TkBitField *src)
+{
+ unsigned words, i;
+
+ assert(dst);
+ assert(src);
+ assert(TkBitSize(src) <= TkBitSize(dst));
+
+ if (src != dst && src->size > 0) {
+ for (i = 0, words = NWORDS(src->size); i < words; ++i) {
+ dst->bits[i] |= src->bits[i];
+ }
+ }
+}
+
+
+void
+TkBitJoin2(
+ TkBitField *dst,
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ unsigned words1, words2, words, i;
+
+ assert(dst);
+ assert(bf1);
+ assert(bf2);
+ assert(TkBitSize(dst) >= TkBitSize(bf1));
+ assert(TkBitSize(dst) >= TkBitSize(bf2));
+
+ words1 = NWORDS(bf1->size);
+ words2 = NWORDS(bf2->size);
+ words = MIN(words1, words2);
+
+ for (i = 0; i < words; ++i) {
+ dst->bits[i] |= bf1->bits[i] | bf2->bits[i];
+ }
+ for ( ; i < words1; ++i) {
+ dst->bits[i] |= bf1->bits[i];
+ }
+ for ( ; i < words2; ++i) {
+ dst->bits[i] |= bf2->bits[i];
+ }
+}
+
+
+void
+TkBitIntersect(
+ TkBitField *dst,
+ const TkBitField *src)
+{
+ unsigned srcWords, dstWords, i;
+
+ assert(dst);
+ assert(src);
+
+ if (src == dst || dst->size == 0) {
+ return;
+ }
+
+ srcWords = NWORDS(src->size);
+ dstWords = NWORDS(dst->size);
+
+ if (dstWords > srcWords) {
+ memset(dst->bits + srcWords, 0, NBYTES(dstWords - srcWords));
+ dstWords = srcWords;
+ }
+
+ for (i = 0; i < dstWords; ++i) {
+ dst->bits[i] &= src->bits[i];
+ }
+
+ return;
+}
+
+
+void
+TkBitRemove(
+ TkBitField *dst,
+ const TkBitField *src)
+{
+ unsigned dstWords;
+
+ assert(dst);
+ assert(src);
+
+ if (dst->size == 0 || src->size == 0) {
+ return;
+ }
+
+ dstWords = NWORDS(dst->size);
+
+ if (src == dst) {
+ memset(dst->bits, 0, NBYTES(dstWords));
+ } else {
+ unsigned words = MIN(NWORDS(src->size), dstWords);
+ unsigned i;
+
+ for (i = 0; i < words; ++i) {
+ dst->bits[i] &= ~src->bits[i];
+ }
+ }
+}
+
+
+void
+TkBitComplementTo(
+ TkBitField *dst,
+ const TkBitField *src)
+{
+ unsigned srcWords, dstWords;
+
+ assert(dst);
+ assert(src);
+ assert(TkBitSize(src) <= TkBitSize(dst));
+
+ if (dst->size == 0) {
+ return;
+ }
+
+ dstWords = NWORDS(dst->size);
+
+ if (src == dst || src->size == 0) {
+ srcWords = 0;
+ } else {
+ unsigned i;
+
+ srcWords = NWORDS(src->size);
+
+ for (i = 0; i < srcWords; ++i) {
+ dst->bits[i] = src->bits[i] & ~dst->bits[i];
+ }
+ }
+
+ memset(dst->bits + srcWords, 0, NBYTES(dstWords - srcWords));
+}
+
+
+void
+TkBitJoinComplementTo(
+ TkBitField *dst,
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ unsigned i, words, words2;
+
+ assert(dst);
+ assert(bf1);
+ assert(bf2);
+ assert(TkBitSize(dst) >= TkBitSize(bf1));
+ assert(TkBitSize(dst) >= TkBitSize(bf2));
+ assert(TkBitSize(bf2) >= TkBitSize(bf1));
+
+ if (dst == bf2 || bf2->size == 0) {
+ return;
+ }
+
+ words2 = NWORDS(bf2->size);
+ words = MIN(NWORDS(bf1->size), words2);
+
+ for (i = 0; i < words; ++i) {
+ dst->bits[i] |= bf2->bits[i] & ~bf1->bits[i];
+ }
+ for ( ; i < words2; ++i) {
+ dst->bits[i] |= bf2->bits[i];
+ }
+}
+
+
+void
+TkBitJoinNonIntersection(
+ TkBitField *dst,
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ assert(dst);
+ assert(bf1);
+ assert(bf2);
+ assert(TkBitSize(dst) >= TkBitSize(bf1));
+ assert(TkBitSize(dst) >= TkBitSize(bf2));
+
+ if (bf1 == bf2) {
+ return;
+ }
+
+ if (bf1->size == 0) {
+ TkBitJoin(dst, bf2);
+ } else if (bf2->size == 0) {
+ TkBitJoin(dst, bf1);
+ } else {
+ unsigned i, words = MIN(NWORDS(bf1->size), NWORDS(bf2->size));
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord bf1Bits = bf1->bits[i];
+ TkBitWord bf2Bits = bf2->bits[i];
+
+ dst->bits[i] |= (bf1Bits & ~bf2Bits) | (bf2Bits & ~bf1Bits);
+ }
+ }
+}
+
+
+void
+TkBitJoin2ComplementToIntersection(
+ TkBitField *dst,
+ const TkBitField *add,
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ assert(dst);
+ assert(add);
+ assert(bf1);
+ assert(bf2);
+ assert(TkBitSize(dst) >= TkBitSize(add));
+ assert(TkBitSize(dst) >= TkBitSize(bf1));
+ assert(TkBitSize(bf1) == TkBitSize(bf2));
+
+ /* dst := dst + add + ((bf1 + bf2) - (bf1 & bf2)) */
+
+ if (bf1 == bf2) {
+ TkBitJoin(dst, add);
+ } else {
+ unsigned words1 = NWORDS(add->size);
+ unsigned words2 = NWORDS(bf1->size);
+ unsigned words = MIN(words1, words2);
+ unsigned i;
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord bf1Bits = bf1->bits[i];
+ TkBitWord bf2Bits = bf2->bits[i];
+
+ dst->bits[i] |= add->bits[i] | ((bf1Bits | bf2Bits) & ~(bf1Bits & bf2Bits));
+ }
+ for ( ; i < words2; ++i) {
+ TkBitWord bf1Bits = bf1->bits[i];
+ TkBitWord bf2Bits = bf2->bits[i];
+
+ dst->bits[i] |= (bf1Bits | bf2Bits) & ~(bf1Bits & bf2Bits);
+ }
+ for ( ; i < words1; ++i) {
+ dst->bits[i] |= add->bits[i];
+ }
+ }
+}
+
+
+void
+TkBitJoinOfDifferences(
+ TkBitField *dst,
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ unsigned words, words1, words2, i;
+
+ assert(dst);
+ assert(bf1);
+ assert(bf2);
+ assert(TkBitSize(dst) >= TkBitSize(bf1));
+
+ words1 = NWORDS(bf1->size);
+ words2 = NWORDS(bf2->size);
+
+ words = MIN(words1, words2);
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord bf1Bits = bf1->bits[i];
+ TkBitWord bf2Bits = bf2->bits[i];
+
+ /* dst := (dst - bf1) + (bf1 - bf2) */
+ dst->bits[i] = (dst->bits[i] & ~bf1Bits) | (bf1Bits & ~bf2Bits);
+ }
+
+ for ( ; i < words1; ++i) {
+ /* dst := dst + bf1 */
+ dst->bits[i] |= bf1->bits[i];
+ }
+}
+
+
+void
+TkBitClear(
+ TkBitField *bf)
+{
+ assert(bf);
+ memset(bf->bits, 0, BYTE_SIZE(bf->size));
+}
+
+
+bool
+TkBitNone_(
+ const TkBitWord *bits,
+ unsigned words)
+{
+ unsigned i;
+
+ assert(bits);
+
+ for (i = 0; i < words; ++i) {
+ if (bits[i]) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+bool
+TkBitAny(
+ const TkBitField *bf)
+{
+ unsigned words, i;
+
+ assert(bf);
+
+ words = NWORDS(bf->size);
+
+ for (i = 0; i < words; ++i) {
+ if (bf->bits[i]) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+bool
+TkBitComplete(
+ const TkBitField *bf)
+{
+ unsigned words;
+
+ assert(bf);
+
+ words = NWORDS(bf->size);
+
+ if (words)
+ {
+ unsigned i, n = words - 1;
+
+ for (i = 0; i < n; ++i) {
+ if (bf->bits[i] != ~((TkBitWord) 0)) {
+ return false;
+ }
+ }
+
+ if (bf->bits[words - 1] != BIT_SPAN(0, BIT_INDEX(bf->size - 1))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkBitIsEqual(
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ unsigned words1;
+
+ assert(bf1);
+ assert(bf2);
+
+ if (bf1 == bf2) {
+ return true;
+ }
+
+ if (bf1->size > bf2->size) {
+ const TkBitField *bf = bf1;
+ bf1 = bf2;
+ bf2 = bf;
+ }
+
+ words1 = NWORDS(bf1->size);
+
+ if (!IsEqual(bf1->bits, bf2->bits, words1)) {
+ return false;
+ }
+
+ return TkBitNone_(bf2->bits + words1, NWORDS(bf2->size) - words1);
+}
+
+
+bool
+TkBitContains(
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ unsigned words1, words2, i;
+
+ assert(bf1);
+ assert(bf2);
+
+ if (bf1 == bf2) {
+ return true;
+ }
+
+ words1 = NWORDS(bf1->size);
+ words2 = NWORDS(bf2->size);
+
+ if (words1 < words2) {
+ if (!TkBitNone_(bf2->bits + words1, words2 - words1)) {
+ return false;
+ }
+ words2 = words1;
+ }
+
+ for (i = 0; i < words2; ++i) {
+ TkBitWord bits2 = bf2->bits[i];
+
+ if (bits2 != (bf1->bits[i] & bits2)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkBitDisjunctive(
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ unsigned words, i;
+
+ assert(bf1);
+ assert(bf2);
+
+ if (bf1 == bf2) {
+ return TkBitNone(bf1);
+ }
+
+ words = MIN(NWORDS(bf1->size), NWORDS(bf2->size));
+
+ for (i = 0; i < words; ++i) {
+ if (bf1->bits[i] & bf2->bits[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkBitIntersectionIsEqual(
+ const TkBitField *bf1,
+ const TkBitField *bf2,
+ const TkBitField *del)
+{
+ unsigned words, words1, words2, i;
+
+ assert(bf1);
+ assert(bf2);
+ assert(del);
+ assert(TkBitSize(bf1) <= TkBitSize(del));
+ assert(TkBitSize(bf2) <= TkBitSize(del));
+
+ if (bf1 == bf2) {
+ return true;
+ }
+ if (bf1->size == 0) {
+ return TkBitNone(bf2);
+ }
+ if (bf2->size == 0) {
+ return TkBitNone(bf1);
+ }
+
+ words1 = NWORDS(bf1->size);
+ words2 = NWORDS(bf2->size);
+ words = MIN(words1, words2);
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord bits = del->bits[i];
+ if ((bf1->bits[i] & bits) != (bf2->bits[i] & bits)) {
+ return false;
+ }
+ }
+
+ for (i = words; i < words1; ++i) {
+ if (bf1->bits[i] & del->bits[i]) {
+ return false;
+ }
+ }
+
+ for (i = words; i < words2; ++i) {
+ if (bf2->bits[i] & del->bits[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+unsigned
+TkBitFindFirst(
+ const TkBitField *bf)
+{
+ unsigned words, i;
+
+ assert(bf);
+
+ words = NWORDS(bf->size);
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord bits = bf->bits[i];
+
+ if (bits) {
+ return NBITS*i + LsbIndex(bits);
+ }
+ }
+
+ return TK_BIT_NPOS;
+}
+
+
+unsigned
+TkBitFindLast(
+ const TkBitField *bf)
+{
+ int i;
+
+ assert(bf);
+
+ for (i = NWORDS(bf->size) - 1; i >= 0; --i) {
+ TkBitWord bits = bf->bits[i];
+
+ if (bits) {
+ return NBITS*i + MsbIndex(bits);
+ }
+ }
+
+ return TK_BIT_NPOS;
+}
+
+
+unsigned
+TkBitFindFirstNot(
+ const TkBitField *bf)
+{
+ unsigned words, mask, bits, i;
+
+ assert(bf);
+
+ if (bf->size > 0) {
+ words = NWORDS(bf->size) - 1;
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord bits = bf->bits[i];
+
+ if (bits != ~((TkBitWord) 0)) {
+ return NBITS*i + LsbIndex(~bits);
+ }
+ }
+
+ mask = BIT_SPAN(0, BIT_INDEX(bf->size - 1));
+ bits = bf->bits[words];
+
+ if (bits != mask) {
+ return NBITS*words + LsbIndex(~bits & mask);
+ }
+ }
+
+ return TK_BIT_NPOS;
+}
+
+
+unsigned
+TkBitFindLastNot(
+ const TkBitField *bf)
+{
+ assert(bf);
+
+ if (bf->size > 0) {
+ TkBitWord bits,mask;
+ unsigned words;
+ int i;
+
+ words = NWORDS(bf->size) - 1;
+ mask = BIT_SPAN(0, BIT_INDEX(bf->size - 1));
+ bits = bf->bits[words];
+
+ if (bits != mask) {
+ return NBITS*words + MsbIndex(~bits & mask);
+ }
+
+ for (i = words - 1; i >= 0; --i) {
+ if ((bits = bf->bits[i]) != ~((TkBitWord) 0)) {
+ return NBITS*i + MsbIndex(~bits);
+ }
+ }
+ }
+
+ return TK_BIT_NPOS;
+}
+
+
+unsigned
+TkBitFindNext(
+ const TkBitField *bf,
+ unsigned prev)
+{
+ TkBitWord bits;
+ unsigned i, words;
+
+ assert(bf);
+ assert(prev < TkBitSize(bf));
+
+ i = WORD_INDEX(prev);
+ bits = bf->bits[i] & ~BIT_SPAN(0, BIT_INDEX(prev));
+
+ if (bits) {
+ return NBITS*i + LsbIndex(bits);
+ }
+
+ words = NWORDS(bf->size);
+
+ for (++i; i < words; ++i) {
+ if ((bits = bf->bits[i])) {
+ return NBITS*i + LsbIndex(bits);
+ }
+ }
+
+ return TK_BIT_NPOS;
+}
+
+
+unsigned
+TkBitFindNextNot(
+ const TkBitField *bf,
+ unsigned prev)
+{
+ TkBitWord bits;
+ unsigned i, words;
+
+ assert(bf);
+ assert(prev < TkBitSize(bf));
+
+ i = WORD_INDEX(prev);
+ bits = bf->bits[i] & ~BIT_SPAN(0, BIT_INDEX(prev));
+
+ if (~bits != ~((TkBitWord) 0)) {
+ return NBITS*i + LsbIndex(bits);
+ }
+
+ words = NWORDS(bf->size);
+
+ for (++i; i < words; ++i) {
+ if (bits != ~((TkBitWord) 0)) {
+ return NBITS*i + LsbIndex(~bits);
+ }
+ }
+
+ return TK_BIT_NPOS;
+}
+
+
+unsigned
+TkBitFindPrev(
+ const TkBitField *bf,
+ unsigned next)
+{
+ TkBitWord bits;
+ int i;
+
+ assert(bf);
+ assert(next < TkBitSize(bf));
+
+ i = WORD_INDEX(next);
+ bits = bf->bits[i] & ~BIT_SPAN(BIT_INDEX(next), NBITS - 1);
+
+ if (bits) {
+ return NBITS*i + MsbIndex(bits);
+ }
+
+ for (--i; i >= 0; --i) {
+ if ((bits = bf->bits[i])) {
+ return NBITS*i + MsbIndex(bits);
+ }
+ }
+
+ return TK_BIT_NPOS;
+}
+
+
+unsigned
+TkBitFindFirstInIntersection(
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ unsigned words, i;
+
+ assert(bf1);
+ assert(bf2);
+
+ words = NWORDS(MIN(bf1->size, bf2->size));
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord bits = bf1->bits[i] & bf2->bits[i];
+
+ if (bits) {
+ return LsbIndex(bits);
+ }
+ }
+
+ return TK_BIT_NPOS;
+}
+
+
+bool
+TkBitTestAndSet(
+ TkBitField *bf,
+ unsigned n)
+{
+ TkBitWord *word;
+ TkBitWord mask;
+
+ assert(bf);
+ assert(n < TkBitSize(bf));
+
+ word = bf->bits + WORD_INDEX(n);
+ mask = TK_BIT_MASK(BIT_INDEX(n));
+
+ if (*word & mask) {
+ return false;
+ }
+ *word |= mask;
+ return true;
+}
+
+
+bool
+TkBitTestAndUnset(
+ TkBitField *bf,
+ unsigned n)
+{
+ TkBitWord *word;
+ TkBitWord mask;
+
+ assert(bf);
+ assert(n < TkBitSize(bf));
+
+ word = bf->bits + WORD_INDEX(n);
+ mask = TK_BIT_MASK(BIT_INDEX(n));
+
+ if (!(*word & mask)) {
+ return false;
+ }
+ *word &= ~mask;
+ return true;
+}
+
+
+void
+TkBitFill(
+ TkBitField *bf)
+{
+ memset(bf->bits, 0xff, BYTE_SIZE(bf->size));
+ ResetUnused(bf);
+}
+
+
+#if !NDEBUG
+
+# include <stdio.h>
+
+void
+TkBitPrint(
+ const TkBitField *bf)
+{
+ unsigned i;
+ const char *comma = "";
+
+ assert(bf);
+
+ printf("%d:{ ", TkBitCount(bf));
+ for (i = TkBitFindFirst(bf); i != TK_BIT_NPOS; i = TkBitFindNext(bf, i)) {
+ printf("%s%d", comma, i);
+ comma = ", ";
+ }
+ printf(" }\n");
+}
+
+#endif /* !NDEBUG */
+
+#if TK_UNUSED_BITFIELD_FUNCTIONS
+
+/*
+ * These functions are not needed anymore, but shouldn't be removed, because sometimes
+ * any of these functions might be useful.
+ */
+
+void
+TkBitInnerJoinDifference(
+ TkBitField *dst,
+ const TkBitField *add,
+ const TkBitField *sub)
+{
+ unsigned words1, words2, i;
+
+ assert(dst);
+ assert(add);
+ assert(sub);
+ assert(TkBitSize(add) <= TkBitSize(dst));
+
+ words2 = NWORDS(add->size);
+ words1 = MIN(words2, NWORDS(sub->size));
+
+ for (i = 0; i < words1; ++i) {
+ TkBitWord addBits = add->bits[i];
+ dst->bits[i] = (dst->bits[i] & addBits) | (addBits & ~sub->bits[i]);
+ }
+
+ for ( ; i < words2; ++i) {
+ TkBitWord addBits = add->bits[i];
+ dst->bits[i] = (dst->bits[i] & addBits) | addBits;
+ }
+}
+
+
+bool
+TkBitInnerJoinDifferenceIsEmpty(
+ const TkBitField *bf,
+ const TkBitField *add,
+ const TkBitField *sub)
+{
+ unsigned words, i;
+ unsigned bfWords, addWords, subWords;
+
+ assert(bf);
+ assert(add);
+ assert(sub);
+
+ /* (bf & add) + (add - sub) == nil */
+
+ if (add->size == 0) {
+ /* nil */
+ return true;
+ }
+
+ if (add == bf) {
+ /* add == nil */
+ return TkBitNone(add);
+ }
+
+ bfWords = NWORDS(bf->size);
+ addWords = NWORDS(add->size);
+ subWords = NWORDS(sub->size);
+
+ words = MIN(bfWords, MIN(addWords, subWords));
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord addBits = add->bits[i];
+ if ((bf->bits[i] & addBits) | (addBits & ~sub->bits[i])) {
+ return false;
+ }
+ }
+
+ if (addWords == words) {
+ /* nil */
+ return true;
+ }
+
+ if (bfWords > words) {
+ assert(subWords == words);
+ /* add == nil */
+
+ for ( ; i < addWords; ++i) {
+ if (add->bits[i]) {
+ return false;
+ }
+ }
+ } else {
+ assert(bfWords == words);
+ words = MIN(addWords, subWords);
+
+ /* (add - sub) == nil */
+
+ for ( ; i < words; ++i) {
+ if (add->bits[i] & ~sub->bits[i]) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkBitIsEqualToDifference(
+ const TkBitField *bf1,
+ const TkBitField *bf2,
+ const TkBitField *sub2)
+{
+ unsigned words0, words1, words2, i;
+
+ assert(bf1);
+ assert(bf2);
+ assert(sub2);
+ assert(TkBitSize(bf2) == TkBitSize(sub2));
+
+ if (bf2->size == 0) {
+ return TkBitNone(bf1);
+ }
+ if (bf1->size == 0) {
+ return TkBitContains(sub2, bf2);
+ }
+
+ words1 = NWORDS(bf1->size);
+ words2 = NWORDS(bf2->size);
+ words0 = MIN(words1, words2);
+
+ /* bf1 == bf2 - sub2 */
+
+ for (i = 0; i < words0; ++i) {
+ if (bf1->bits[i] != (bf2->bits[i] & ~sub2->bits[i])) {
+ return false;
+ }
+ }
+
+ if (words1 > words2) {
+ return TkBitNone_(bf1->bits + words2, words1 - words2);
+ }
+
+ for ( ; i < words2; ++i) {
+ if (bf2->bits[i] & ~sub2->bits[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkBitIsEqualToInnerJoin(
+ const TkBitField *bf1,
+ const TkBitField *bf2,
+ const TkBitField *add2)
+{
+ unsigned words0, words1, words2, i;
+
+ assert(bf1);
+ assert(bf2);
+ assert(add2);
+ assert(TkBitSize(bf2) == TkBitSize(add2));
+
+ if (bf1 == bf2) {
+ return true;
+ }
+ if (bf2 == add2) {
+ return TkBitIsEqual(bf1, bf2);
+ }
+ if (bf1->size == 0) {
+ return TkBitNone(bf2);
+ }
+ if (bf2->size == 0) {
+ return TkBitNone(bf1);
+ }
+
+ words1 = NWORDS(bf1->size);
+ words2 = NWORDS(bf2->size);
+ words0 = MIN(words1, words2);
+
+ for (i = 0; i < words0; ++i) {
+ TkBitWord bf2Bits = bf2->bits[i];
+ if (bf1->bits[i] != (bf2Bits | (add2->bits[i] & bf2Bits))) {
+ return false;
+ }
+ }
+
+ if (words1 > words2) {
+ return TkBitNone_(bf1->bits + words2, words1 - words2);
+ }
+
+ for ( ; i < words2; ++i) {
+ TkBitWord bf2Bits = bf2->bits[i];
+ if (bf2Bits | (add2->bits[i] & bf2Bits)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkBitIsEqualToInnerJoinDifference(
+ const TkBitField *bf1,
+ const TkBitField *bf2,
+ const TkBitField *add2,
+ const TkBitField *sub2)
+{
+ unsigned words0, words1, words2, i;
+
+ assert(bf1);
+ assert(bf2);
+ assert(add2);
+ assert(sub2);
+ assert(TkBitSize(bf2) == TkBitSize(add2));
+ assert(TkBitSize(bf2) == TkBitSize(sub2));
+
+ if (add2->size == 0) {
+ return TkBitNone(bf1);
+ }
+ if (sub2->size == 0) {
+ return TkBitIsEqual(bf1, add2);
+ }
+
+ words1 = NWORDS(bf1->size);
+ words2 = NWORDS(bf2->size);
+ words0 = MIN(words1, words2);
+
+ for (i = 0; i < words0; ++i) {
+ TkBitWord addBits = add2->bits[i];
+ if (bf1->bits[i] != ((bf2->bits[i] & addBits) | (addBits & ~sub2->bits[i]))) {
+ return false;
+ }
+ }
+
+ if (words1 > words2) {
+ return TkBitNone_(bf1->bits + words2, words1 - words2);
+ }
+
+ for ( ; i < words2; ++i) {
+ TkBitWord addBits = add2->bits[i];
+ if ((bf2->bits[i] & addBits) | (addBits & ~sub2->bits[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+static bool
+IntersectionIsDisjunctive(
+ const TkBitField *bf1,
+ const TkBitField *bf2,
+ const TkBitField *del)
+{
+ unsigned words = NWORDS(bf1->size);
+
+ assert(TkBitSize(bf1) == TkBitSize(bf2));
+ assert(TkBitSize(bf1) == TkBitSize(del));
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord delBits = del->bits[i];
+
+ if ((bf1->bits[i] & delBits) != (bf2->bits[i] & delBits)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkBitInnerJoinDifferenceIsEqual(
+ const TkBitField *bf1,
+ const TkBitField *bf2,
+ const TkBitField *add,
+ const TkBitField *sub)
+{
+ unsigned words, i;
+
+ assert(bf1);
+ assert(bf2);
+ assert(add);
+ assert(sub);
+ assert(TkBitSize(bf1) == TkBitSize(bf2));
+ assert(TkBitSize(bf1) == TkBitSize(add));
+ assert(TkBitSize(bf1) == TkBitSize(sub));
+
+ if (add->size == 0) {
+ return true;
+ }
+
+ if (bf1->size == 0) {
+ /*
+ * We have to show: sub & add == bf1 & add
+ * (see InnerJoinDifferenceIsEqual [tkIntSet.c]).
+ */
+ return IntersectionIsDisjunctive(bf1, sub, add);
+ }
+
+ if (bf2->size == 0) {
+ return IntersectionIsDisjunctive(bf2, sub, add);
+ }
+
+ words = NWORDS(bf1->size);
+
+ for (i = 0; i < words; ++i) {
+ TkBitWord addBits = add->bits[i];
+ TkBitWord sumBits = addBits & ~sub->bits[i];
+
+ if (((bf1->bits[i] & addBits) | sumBits) != ((bf2->bits[i] & addBits) | sumBits)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#endif /* TK_UNUSED_BITFIELD_FUNCTIONS */
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+/* Additionally we need stand-alone object code. */
+#define inline extern
+inline TkBitField *TkBitNew(unsigned size);
+inline const unsigned char *TkBitData(const TkBitField *bf);
+inline unsigned TkBitByteSize(const TkBitField *bf);
+inline unsigned TkBitRefCount(const TkBitField *bf);
+inline void TkBitIncrRefCount(TkBitField *bf);
+inline unsigned TkBitDecrRefCount(TkBitField *bf);
+inline bool TkBitIsEmpty(const TkBitField *bf);
+inline unsigned TkBitSize(const TkBitField *bf);
+inline bool TkBitTest(const TkBitField *bf, unsigned n);
+inline bool TkBitNone(const TkBitField *bf);
+inline bool TkBitIntersects(const TkBitField *bf1, const TkBitField *bf2);
+inline void TkBitSet(TkBitField *bf, unsigned n);
+inline void TkBitUnset(TkBitField *bf, unsigned n);
+inline void TkBitPut(TkBitField *bf, unsigned n, bool value);
+inline unsigned TkBitAdjustSize(unsigned size);
+#endif /* __STDC_VERSION__ >= 199901L */
+
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkBitField.h b/generic/tkBitField.h
new file mode 100644
index 0000000..30229d1
--- /dev/null
+++ b/generic/tkBitField.h
@@ -0,0 +1,188 @@
+/*
+ * tkBitField.h --
+ *
+ * This module implements bit field operations.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKBITFIELD
+#define _TKBITFIELD
+
+#ifndef _TK
+#include "tk.h"
+#endif
+
+#include "tkBool.h"
+#include <stdint.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+#ifdef TCL_WIDE_INT_IS_LONG
+typedef uint64_t TkBitWord;
+#else
+typedef uint32_t TkBitWord;
+#endif
+
+#define TK_BIT_NBITS (sizeof(TkBitWord)*8) /* Number of bits in one word. */
+
+struct TkIntSet;
+
+
+/*
+ * The struct below will be shared with the struct TkIntSet, so the first two
+ * members must exactly match the first two members in struct TkIntSet. In this
+ * way we have a struct inheritance, based on the first two members. This
+ * is portable due to C99 section 6.7.2.1 bullet point 13:
+ *
+ * Within a structure object, the non-bit-field members and the units
+ * in which bit-fields reside have addresses that increase in the order
+ * in which they are declared. A pointer to a structure object, suitably
+ * converted, points to its initial member (or if that member is a
+ * bit-field, then to the unit in which it resides), and vice versa.
+ * There may be unnamed padding within a structure object, but not at
+ * beginning.
+ *
+ * This inheritance concept is also used in the portable GTK library.
+ */
+
+typedef struct TkBitField {
+ uint32_t refCount:31;
+ uint32_t isSetFlag:1;
+ uint32_t size;
+#if TK_CHECK_ALLOCS
+ struct TkBitField *next;
+ struct TkBitField *prev;
+ unsigned number;
+#endif
+ TkBitWord bits[1];
+} TkBitField;
+
+
+/*
+ * This value will be returned in case of end of iteration.
+ */
+#define TK_BIT_NPOS ((unsigned) -1)
+
+
+inline TkBitField *TkBitNew(unsigned size);
+TkBitField *TkBitResize(TkBitField *bf, unsigned newSize);
+TkBitField *TkBitFromSet(const struct TkIntSet *set, unsigned size);
+void TkBitDestroy(TkBitField **bfPtr);
+
+inline const unsigned char *TkBitData(const TkBitField *bf);
+inline unsigned TkBitByteSize(const TkBitField *bf);
+
+inline unsigned TkBitRefCount(const TkBitField *bf);
+inline void TkBitIncrRefCount(TkBitField *bf);
+inline unsigned TkBitDecrRefCount(TkBitField *bf);
+
+TkBitField *TkBitCopy(const TkBitField *bf, int size);
+
+void TkBitJoin(TkBitField *dst, const TkBitField *src);
+void TkBitIntersect(TkBitField *dst, const TkBitField *src);
+void TkBitRemove(TkBitField *dst, const TkBitField *src);
+
+/* dst := dst + bf1 + bf2 */
+void TkBitJoin2(TkBitField *dst, const TkBitField *bf1, const TkBitField *bf2);
+/* dst := src - dst */
+void TkBitComplementTo(TkBitField *dst, const TkBitField *src);
+/* dst := dst + (bf2 - bf1) */
+void TkBitJoinComplementTo(TkBitField *dst, const TkBitField *bf1, const TkBitField *bf2);
+/* dst := dst + (bf1 - bf2) + (bf2 - bf1) */
+void TkBitJoinNonIntersection(TkBitField *dst, const TkBitField *bf1, const TkBitField *bf2);
+/* dst := dst + add + ((bf1 + bf2) - (bf1 & bf2)) */
+void TkBitJoin2ComplementToIntersection(TkBitField *dst,
+ const TkBitField *add, const TkBitField *bf1, const TkBitField *bf2);
+/* dst := (dst - bf1) + (bf1 - bf2) */
+void TkBitJoinOfDifferences(TkBitField *dst, const TkBitField *bf1, const TkBitField *bf2);
+
+inline bool TkBitIsEmpty(const TkBitField *bf);
+inline unsigned TkBitSize(const TkBitField *bf);
+unsigned TkBitCount(const TkBitField *bf);
+
+inline bool TkBitTest(const TkBitField *bf, unsigned n);
+inline bool TkBitNone(const TkBitField *bf);
+bool TkBitAny(const TkBitField *bf);
+bool TkBitComplete(const TkBitField *bf);
+
+bool TkBitIsEqual(const TkBitField *bf1, const TkBitField *bf2);
+bool TkBitContains(const TkBitField *bf1, const TkBitField *bf2);
+bool TkBitDisjunctive(const TkBitField *bf1, const TkBitField *bf2);
+inline bool TkBitIntersects(const TkBitField *bf1, const TkBitField *bf2);
+bool TkBitIntersectionIsEqual(const TkBitField *bf1, const TkBitField *bf2, const TkBitField *del);
+
+unsigned TkBitFindFirst(const TkBitField *bf);
+unsigned TkBitFindLast(const TkBitField *bf);
+unsigned TkBitFindFirstNot(const TkBitField *bf);
+unsigned TkBitFindLastNot(const TkBitField *bf);
+unsigned TkBitFindNext(const TkBitField *bf, unsigned prev);
+unsigned TkBitFindNextNot(const TkBitField *bf, unsigned prev);
+unsigned TkBitFindPrev(const TkBitField *bf, unsigned prev);
+unsigned TkBitFindFirstInIntersection(const TkBitField *bf1, const TkBitField *bf2);
+
+inline void TkBitSet(TkBitField *bf, unsigned n);
+inline void TkBitUnset(TkBitField *bf, unsigned n);
+inline void TkBitPut(TkBitField *bf, unsigned n, bool value);
+bool TkBitTestAndSet(TkBitField *bf, unsigned n);
+bool TkBitTestAndUnset(TkBitField *bf, unsigned n);
+void TkBitFill(TkBitField *bf);
+void TkBitClear(TkBitField *bf);
+
+/* Return nearest multiple of TK_BIT_NBITS which is greater or equal to given argument. */
+inline unsigned TkBitAdjustSize(unsigned size);
+
+#if !NDEBUG
+void TkBitPrint(const TkBitField *bf);
+#endif
+
+#if TK_CHECK_ALLOCS
+void TkBitCheckAllocs();
+#endif
+
+
+#if TK_UNUSED_BITFIELD_FUNCTIONS
+
+/*
+ * These functions are not needed anymore, but shouldn't be removed, because sometimes
+ * any of these functions might be useful.
+ */
+
+/* dst := (dst + (add - sub)) & add */
+void TkBitInnerJoinDifference(TkBitField *dst, const TkBitField *add, const TkBitField *sub);
+/* ((bf + (add - sub)) & add) == nil */
+bool TkBitInnerJoinDifferenceIsEmpty(const TkBitField *bf, const TkBitField *add, const TkBitField *sub);
+/* bf1 == bf2 - sub2 */
+bool TkBitIsEqualToDifference(const TkBitField *bf1, const TkBitField *bf2, const TkBitField *sub2);
+/* bf1 == ((bf2 + add2) & bf2) */
+bool TkBitIsEqualToInnerJoin(const TkBitField *bf1, const TkBitField *bf2, const TkBitField *add2);
+/* bf1 == ((bf2 + (add2 - sub2) & add) */
+bool TkBitIsEqualToInnerJoinDifference(const TkBitField *bf1, const TkBitField *bf2,
+ const TkBitField *add2, const TkBitField *sub2);
+/* ((bf1 + (add - sub)) & add) == ((bf2 + (add - sub)) & add) */
+bool TkBitInnerJoinDifferenceIsEqual(const TkBitField *bf1, const TkBitField *bf2,
+ const TkBitField *add, const TkBitField *sub);
+
+#endif /* TK_UNUSED_BITFIELD_FUNCTIONS */
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+# define _TK_NEED_IMPLEMENTATION
+# include "tkBitFieldPriv.h"
+#else
+# undef inline
+#endif
+
+#endif /* _TKBITFIELD */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkBitFieldPriv.h b/generic/tkBitFieldPriv.h
new file mode 100644
index 0000000..84e530c
--- /dev/null
+++ b/generic/tkBitFieldPriv.h
@@ -0,0 +1,215 @@
+/*
+ * tkBitFieldPriv.h --
+ *
+ * Private implementation for bit field.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKBITFIELD
+# error "do not include this private header file"
+#endif
+
+
+#ifndef _TKBITFIELDPRIV
+#define _TKBITFIELDPRIV
+
+MODULE_SCOPE bool TkBitNone_(const TkBitWord *buf, unsigned words);
+
+#endif /* _TKBITFIELDPRIV */
+
+
+#ifdef _TK_NEED_IMPLEMENTATION
+
+#include <assert.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+#define TK_BIT_WORD_INDEX(n) ((n) >> ((TK_BIT_NBITS + 128) >> 5))
+#define TK_BIT_INDEX(n) ((n) & (TK_BIT_NBITS - 1))
+#define TK_BIT_MASK(n) (((TkBitWord) 1) << (n))
+#define TK_BIT_COUNT_WORDS(n) ((n + TK_BIT_NBITS - 1)/TK_BIT_NBITS)
+
+
+inline
+const unsigned char *
+TkBitData(
+ const TkBitField *bf)
+{
+ assert(bf);
+ return (const void *) bf->bits;
+}
+
+
+inline
+unsigned
+TkBitByteSize(
+ const TkBitField *bf)
+{
+ assert(bf);
+ return TK_BIT_COUNT_WORDS(bf->size);
+}
+
+
+inline
+unsigned
+TkBitAdjustSize(
+ unsigned size)
+{
+ return ((size + (TK_BIT_NBITS - 1))/TK_BIT_NBITS)*TK_BIT_NBITS;
+}
+
+
+inline
+TkBitField *
+TkBitNew(
+ unsigned size)
+{
+ TkBitField *bf = TkBitResize(NULL, size);
+ bf->refCount = 0;
+ return bf;
+}
+
+
+inline
+unsigned
+TkBitRefCount(
+ const TkBitField *bf)
+{
+ assert(bf);
+ return bf->refCount;
+}
+
+
+inline
+void
+TkBitIncrRefCount(
+ TkBitField *bf)
+{
+ assert(bf);
+ bf->refCount += 1;
+}
+
+
+inline
+unsigned
+TkBitDecrRefCount(
+ TkBitField *bf)
+{
+ unsigned refCount;
+
+ assert(bf);
+ assert(TkBitRefCount(bf) > 0);
+
+ if ((refCount = --bf->refCount) == 0) {
+ TkBitDestroy(&bf);
+ }
+ return refCount;
+}
+
+
+inline
+unsigned
+TkBitSize(
+ const TkBitField *bf)
+{
+ assert(bf);
+ return bf->size;
+}
+
+
+inline
+bool
+TkBitIsEmpty(
+ const TkBitField *bf)
+{
+ assert(bf);
+ return bf->size == 0;
+}
+
+
+inline
+bool
+TkBitNone(
+ const TkBitField *bf)
+{
+ assert(bf);
+ return bf->size == 0 || TkBitNone_(bf->bits, TK_BIT_COUNT_WORDS(bf->size));
+}
+
+
+inline
+bool
+TkBitIntersects(
+ const TkBitField *bf1,
+ const TkBitField *bf2)
+{
+ return !TkBitDisjunctive(bf1, bf2);
+}
+
+
+inline
+bool
+TkBitTest(
+ const TkBitField *bf,
+ unsigned n)
+{
+ assert(bf);
+ assert(n < TkBitSize(bf));
+ return !!(bf->bits[TK_BIT_WORD_INDEX(n)] & TK_BIT_MASK(TK_BIT_INDEX(n)));
+}
+
+
+inline
+void
+TkBitSet(
+ TkBitField *bf,
+ unsigned n)
+{
+ assert(bf);
+ assert(n < TkBitSize(bf));
+ bf->bits[TK_BIT_WORD_INDEX(n)] |= TK_BIT_MASK(TK_BIT_INDEX(n));
+}
+
+
+inline
+void
+TkBitUnset(
+ TkBitField *bf,
+ unsigned n)
+{
+ assert(bf);
+ assert(n < TkBitSize(bf));
+ bf->bits[TK_BIT_WORD_INDEX(n)] &= ~TK_BIT_MASK(TK_BIT_INDEX(n));
+}
+
+
+inline
+void
+TkBitPut(
+ TkBitField *bf,
+ unsigned n,
+ bool value)
+{
+ if (value) {
+ TkBitSet(bf, n);
+ } else {
+ TkBitUnset(bf, n);
+ }
+}
+
+#undef _TK_NEED_IMPLEMENTATION
+#endif /* _TK_NEED_IMPLEMENTATION */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkBool.h b/generic/tkBool.h
new file mode 100644
index 0000000..d7cad51
--- /dev/null
+++ b/generic/tkBool.h
@@ -0,0 +1,31 @@
+/*
+ * tkBool.h --
+ *
+ * This module provides a boolean type, conform to C++.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TK_BOOL
+#define _TK_BOOL
+
+#ifdef __cplusplus
+extern "C" {
+# define bool TkBool
+#endif
+
+typedef int bool;
+
+#ifndef __cplusplus
+enum { true = (int) 1, false = (int) 0 };
+#endif
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* _TK_BOOL */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkIntSet.c b/generic/tkIntSet.c
new file mode 100644
index 0000000..28a449c
--- /dev/null
+++ b/generic/tkIntSet.c
@@ -0,0 +1,2151 @@
+/*
+ * tkIntSet.c --
+ *
+ * This module implements an integer set.
+ *
+ * NOTE: the current implementation is for TkTextTagSet, so in general these
+ * functions are not modifying the arguments, except if this is expected.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkIntSet.h"
+#include "tkBitField.h"
+#include "tkAlloc.h"
+
+#if !(__STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900))
+# define _TK_NEED_IMPLEMENTATION
+# include "tkIntSetPriv.h"
+#endif
+
+#include <string.h>
+#include <assert.h>
+
+#ifndef MIN
+# define MIN(a,b) (((int) a) < ((int) b) ? a : b)
+#endif
+#ifndef MAX
+# define MAX(a,b) (((int) a) < ((int) b) ? b : a)
+#endif
+
+#if TK_CHECK_ALLOCS
+# define DEBUG_ALLOC(expr) expr
+#else
+# define DEBUG_ALLOC(expr)
+#endif
+
+
+#define TestIfEqual TkIntSetIsEqual__
+
+#define SET_SIZE(size) ((unsigned) (Tk_Offset(TkIntSet, buf) + (size)*sizeof(TkIntSetType)))
+
+
+DEBUG_ALLOC(unsigned tkIntSetCountNew = 0);
+DEBUG_ALLOC(unsigned tkIntSetCountDestroy = 0);
+
+
+static bool IsPowerOf2(unsigned n) { return !(n & (n - 1)); }
+
+
+static unsigned
+NextPowerOf2(
+ unsigned n)
+{
+ --n;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+
+#if !(UINT_MAX <= 4294967295u)
+ /* unsigned is 64 bit wide, this is unusual, but possible */
+ n |= n >> 32;
+#endif
+
+ return ++n;
+}
+
+
+bool
+TkIntSetIsEqual__(
+ const TkIntSetType *set1, const TkIntSetType *end1,
+ const TkIntSetType *set2, const TkIntSetType *end2)
+{
+ if (end1 - set1 != end2 - set2) {
+ return false;
+ }
+ for ( ; set1 < end1; ++set1, ++set2) {
+ if (*set1 != *set2) {
+ return false;
+ }
+ }
+ return true;
+}
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+unsigned
+TkIntSetFindFirstInIntersection(
+ const TkIntSet *set,
+ const TkBitField *bf)
+{
+ unsigned size, i;
+
+ assert(set);
+ assert(bf);
+
+ if (!TkBitNone(bf)) {
+ size = TkIntSetSize(set);
+
+ for (i = 0; i < size; ++i) {
+ TkIntSetType value = TkIntSetAccess(set, i);
+
+ if (TkBitTest(bf, value)) {
+ return value;
+ }
+ }
+ }
+
+ return TK_SET_NPOS;
+}
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+TkIntSetType *
+TkIntSetLowerBound(
+ TkIntSetType *first,
+ TkIntSetType *last,
+ TkIntSetType value)
+{
+ while (first != last) {
+ TkIntSetType *mid = first + (last - first)/2;
+
+ if (*mid < value) {
+ first = mid + 1;
+ } else {
+ last = mid;
+ }
+ }
+
+ return first;
+}
+
+
+TkIntSet *
+TkIntSetNew()
+{
+ TkIntSet *set = malloc(SET_SIZE(0));
+ set->end = set->buf;
+ set->refCount = 0;
+ set->isSetFlag = true;
+ DEBUG_ALLOC(tkIntSetCountNew++);
+ return set;
+}
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+TkIntSet *
+TkIntSetFromBits(
+ const TkBitField *bf)
+{
+ unsigned size;
+ TkIntSet *set;
+ unsigned index = 0, i;
+
+ size = TkBitCount(bf);
+ set = malloc(SET_SIZE(NextPowerOf2(size)));
+ set->end = set->buf + size;
+ set->refCount = 1;
+ set->isSetFlag = true;
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ for (i = TkBitFindFirst(bf); i != TK_BIT_NPOS; i = TkBitFindNext(bf, i)) {
+ set->buf[index++] = i;
+ }
+
+ return set;
+}
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+void
+TkIntSetDestroy(
+ TkIntSet **setPtr)
+{
+ assert(setPtr);
+
+ if (*setPtr) {
+ free(*setPtr);
+ *setPtr = NULL;
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+}
+
+
+TkIntSet *
+TkIntSetCopy(
+ const TkIntSet *set)
+{
+ TkIntSet *newSet;
+ unsigned size;
+
+ assert(set);
+
+ size = TkIntSetSize(set);
+ newSet = malloc(SET_SIZE(NextPowerOf2(size)));
+ newSet->end = newSet->buf + size;
+ newSet->refCount = 1;
+ newSet->isSetFlag = true;
+ memcpy(newSet->buf, set->buf, size*sizeof(TkIntSetType));
+ DEBUG_ALLOC(tkIntSetCountNew++);
+ return newSet;
+}
+
+
+static TkIntSetType *
+Join(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkIntSetType *add, const TkIntSetType *addEnd)
+{
+ unsigned size;
+
+ while (src < srcEnd && add < addEnd) {
+ if (*src < *add) {
+ *dst++ = *src++;
+ } else {
+ if (*src == *add) {
+ ++src;
+ }
+ *dst++ = *add++;
+ }
+ }
+
+ if ((size = srcEnd - src) > 0) {
+ memcpy(dst, src, size*sizeof(TkIntSetType));
+ dst += size;
+ } else if ((size = addEnd - add) > 0) {
+ memcpy(dst, add, size*sizeof(TkIntSetType));
+ dst += size;
+ }
+
+ return dst;
+}
+
+
+TkIntSet *
+TkIntSetJoin(
+ TkIntSet *dst,
+ const TkIntSet *src)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(src);
+ assert(dst);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst) + TkIntSetSize(src));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = Join(set->buf, dst->buf, dst->end, src->buf, src->end);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+static TkIntSetType *
+JoinBits(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkBitField *bf)
+{
+ unsigned size, i;
+
+ i = TkBitFindFirst(bf);
+
+ while (src < srcEnd && i != TK_BIT_NPOS) {
+ if (*src < i) {
+ *dst++ = *src++;
+ } else {
+ if (*src == i) {
+ ++src;
+ }
+ *dst++ = i;
+ i = TkBitFindNext(bf, i);
+ }
+ }
+
+ if ((size = srcEnd - src) > 0) {
+ memcpy(dst, src, size*sizeof(TkIntSetType));
+ dst += size;
+ } else {
+ for ( ; i != TK_BIT_NPOS; i = TkBitFindNext(bf, i)) {
+ *dst++ = i;
+ }
+ }
+
+ return dst;
+}
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+TkIntSet *
+TkIntSetJoinBits(
+ TkIntSet *dst,
+ const TkBitField *src)
+{
+ TkIntSet *set;
+
+ assert(src);
+ assert(dst);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ if (dst->buf == dst->end) {
+ set = TkIntSetNew();
+ } else {
+ unsigned capacity1, capacity2, size;
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst) + TkBitSize(src));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = JoinBits(set->buf, dst->buf, dst->end, src);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+static TkIntSetType *
+Join2(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkIntSetType *set1, const TkIntSetType *set1End,
+ const TkIntSetType *set2, const TkIntSetType *set2End)
+{
+ unsigned size;
+
+ while (src < srcEnd && set1 < set1End && set2 < set2End) {
+ if (*set1 < *set2) {
+ if (*src < *set1) {
+ *dst++ = *src++;
+ } else {
+ if (*src == *set1) {
+ src++;
+ }
+ *dst++ = *set1++;
+ }
+ } else {
+ if (*src < *set2) {
+ *dst++ = *src++;
+ } else {
+ if (*src == *set2) {
+ src++;
+ }
+ if (*set1 == *set2)
+ set1++;
+ *dst++ = *set2++;
+ }
+ }
+ }
+
+ if (src == srcEnd) {
+ dst = Join(dst, set1, set1End, set2, set2End);
+ } else if (set1 < set1End) {
+ dst = Join(dst, src, srcEnd, set1, set1End);
+ } else if (set2 < set2End) {
+ dst = Join(dst, src, srcEnd, set2, set2End);
+ } else if ((size = srcEnd - src) > 0) {
+ memcpy(dst, src, size*sizeof(TkIntSetType));
+ dst += size;
+ }
+
+ return dst;
+}
+
+
+TkIntSet *
+TkIntSetJoin2(
+ TkIntSet *dst,
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(dst);
+ assert(set1);
+ assert(set2);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst) + TkIntSetSize(set1) + TkIntSetSize(set2));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = Join2(set->buf, dst->buf, dst->end, set1->buf, set1->end, set2->buf, set2->end);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+static TkIntSetType *
+Intersect(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkIntSetType *isc, const TkIntSetType *iscEnd)
+{
+ while (src < srcEnd && isc < iscEnd) {
+ if (*src < *isc) {
+ ++src;
+ } else {
+ if (*src == *isc) {
+ *dst++ = *src++;
+ }
+ ++isc;
+ }
+ }
+
+ return dst;
+}
+
+
+TkIntSet *
+TkIntSetIntersect(
+ TkIntSet *dst,
+ const TkIntSet *src)
+{
+ TkIntSet *set;
+ unsigned size;
+ unsigned capacity1, capacity2;
+
+ assert(src);
+ assert(dst);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ size = MIN(TkIntSetSize(src), TkIntSetSize(dst));
+ capacity1 = NextPowerOf2(size);
+ set = malloc(SET_SIZE(capacity1));
+ set->end = Intersect(set->buf, dst->buf, dst->end, src->buf, src->end);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+static TkIntSetType *
+IntersectBits(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkBitField *isc)
+{
+ unsigned size = TkBitSize(isc);
+
+ for ( ; src < srcEnd; ++src) {
+ if (*src >= size) {
+ break;
+ }
+ if (TkBitTest(isc, *src)) {
+ *dst++ = *src;
+ }
+ }
+
+ return dst;
+}
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+TkIntSet *
+TkIntSetIntersectBits(
+ TkIntSet *dst,
+ const TkBitField *src)
+{
+ TkIntSet *set;
+ unsigned size;
+ unsigned capacity1, capacity2;
+
+ assert(src);
+ assert(dst);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ size = TkBitCount(src);
+
+ size = MIN(TkIntSetSize(dst), TkBitCount(src));
+ capacity1 = NextPowerOf2(size);
+ set = malloc(SET_SIZE(capacity1));
+ set->end = IntersectBits(set->buf, dst->buf, dst->end, src);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+static TkIntSetType *
+Remove(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkIntSetType *sub, const TkIntSetType *subEnd)
+{
+ while (src < srcEnd && sub < subEnd) {
+ if (*src < *sub) {
+ *dst++ = *src++;
+ } else {
+ if (*src == *sub) {
+ ++src;
+ }
+ ++sub;
+ }
+ }
+
+ if (src < srcEnd) {
+ unsigned size = srcEnd - src;
+ memcpy(dst, src, size*sizeof(TkIntSetType));
+ dst += size;
+ }
+
+ return dst;
+}
+
+
+TkIntSet *
+TkIntSetRemove(
+ TkIntSet *dst,
+ const TkIntSet *src)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(src);
+ assert(dst);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = Remove(set->buf, dst->buf, dst->end, src->buf, src->end);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+static TkIntSetType *
+RemoveBits(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkBitField *sub)
+{
+ unsigned size = TkBitSize(sub);
+
+ for ( ; src < srcEnd; ++src) {
+ if (*src >= size) {
+ break;
+ }
+ if (!TkBitTest(sub, *src)) {
+ *dst++ = *src;
+ }
+ }
+
+ if ((size = srcEnd - src) > 0) {
+ memcpy(dst, src, size*sizeof(TkIntSetType));
+ dst += size;
+ }
+
+ return dst;
+}
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+TkIntSet *
+TkIntSetRemoveBits(
+ TkIntSet *dst,
+ const TkBitField *src)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(src);
+ assert(dst);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = RemoveBits(set->buf, dst->buf, dst->end, src);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+static TkIntSetType *
+ComplementTo(
+ TkIntSetType *dst,
+ const TkIntSetType *sub, const TkIntSetType *subEnd,
+ const TkIntSetType *src, const TkIntSetType *srcEnd)
+{
+ return Remove(dst, src, srcEnd, sub, subEnd);
+}
+
+
+TkIntSet *
+TkIntSetComplementTo(
+ TkIntSet *dst,
+ const TkIntSet *src)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(src);
+ assert(dst);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(src));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = ComplementTo(set->buf, dst->buf, dst->end, src->buf, src->end);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+static TkIntSetType *
+ComplementToBits(
+ TkIntSetType *dst,
+ const TkIntSetType *sub, const TkIntSetType *subEnd,
+ const TkBitField *src)
+{
+ unsigned i = TkBitFindFirst(src);
+
+ /* dst := src - sub */
+
+ while (sub < subEnd && i != TK_BIT_NPOS) {
+ if (*sub < i) {
+ ++sub;
+ } else {
+ if (i < *sub) {
+ *dst++ = i;
+ } else {
+ ++sub;
+ }
+ i = TkBitFindNext(src, i);
+ }
+ }
+ for ( ; i != TK_BIT_NPOS; i = TkBitFindNext(src, i)) {
+ *dst++ = i;
+ }
+
+ return dst;
+}
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+TkIntSet *
+TkIntSetComplementToBits(
+ TkIntSet *dst,
+ const TkBitField *src)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(src);
+ assert(dst);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkBitSize(src));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = ComplementToBits(set->buf, dst->buf, dst->end, src);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+static TkIntSetType *
+JoinComplementTo(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkIntSetType *set1, const TkIntSetType *set1End,
+ const TkIntSetType *set2, const TkIntSetType *set2End)
+{
+ /* dst = src + (set2 - set1) */
+
+ while (src < srcEnd && set1 < set1End && set2 < set2End) {
+ if (*set2 < *set1) {
+ if (*src < *set2) {
+ *dst++ = *src++;
+ } else if (*src == *set2) {
+ *dst++ = *src++;
+ set2++;
+ } else {
+ if (*src == *set2) {
+ ++src;
+ }
+ *dst++ = *set2++;
+ }
+ } else if (*src < *set1) {
+ *dst++ = *src++;
+ } else {
+ if (*set2 == *set1) {
+ set2++;
+ }
+ if (*src == *set1) {
+ *dst++ = *src++;
+ }
+ set1++;
+ }
+ }
+
+ if (src == srcEnd) {
+ dst = ComplementTo(dst, set1, set1End, set2, set2End);
+ } else if (set2 < set2End) {
+ dst = Join(dst, src, srcEnd, set2, set2End);
+ } else {
+ unsigned size = srcEnd - src;
+ memcpy(dst, src, size*sizeof(TkIntSetType));
+ dst += size;
+ }
+
+ return dst;
+}
+
+
+TkIntSet *
+TkIntSetJoinComplementTo(
+ TkIntSet *dst,
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(dst);
+ assert(set1);
+ assert(set2);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst) + TkIntSetSize(set1));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = JoinComplementTo(
+ set->buf, dst->buf, dst->end, set1->buf, set1->end, set2->buf, set2->end);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+static TkIntSetType *
+JoinNonIntersection(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkIntSetType *set1, const TkIntSetType *set1End,
+ const TkIntSetType *set2, const TkIntSetType *set2End)
+{
+ unsigned size;
+
+ /* dst += src + (set1 - set2) + (set2 - set1) */
+
+ while (src != srcEnd && set1 != set1End && set2 != set2End) {
+ if (*set1 < *set2) {
+ /* dst += src + set1 */
+ if (*set1 < *src) {
+ *dst++ = *set1++;
+ } else {
+ if (*src == *set1) {
+ ++set1;
+ }
+ *dst++ = *src++;
+ }
+ } else if (*set2 < *set1) {
+ /* dst += src + set2 */
+ if (*set2 < *src) {
+ *dst++ = *set2++;
+ } else {
+ if (*src == *set2) {
+ ++set2;
+ }
+ *dst++ = *src++;
+ }
+ } else {
+ ++set1;
+ ++set2;
+ }
+ }
+
+ if (src == srcEnd) {
+ /* dst += (set1 - set2) + (set2 - set1) */
+
+ while (set1 != set1End && set2 != set2End) {
+ if (*set1 < *set2) {
+ *dst++ = *set1++;
+ } else if (*set2 < *set1) {
+ *dst++ = *set2++;
+ } else {
+ ++set1;
+ ++set2;
+ }
+ }
+ if (set1 == set1End) {
+ set1 = set2;
+ set1End = set2End;
+ }
+
+ /* dst += set1 */
+
+ if ((size = set1End - set1)) {
+ memcpy(dst, set1, size*sizeof(TkIntSetType));
+ dst += size;
+ }
+ } else {
+ if (set1 == set1End) {
+ set1 = set2;
+ set1End = set2End;
+ }
+
+ /* dst += src + set1 */
+
+ dst = Join(dst, src, srcEnd, set1, set1End);
+ }
+
+ return dst;
+}
+
+
+TkIntSet *
+TkIntSetJoinNonIntersection(
+ TkIntSet *dst,
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(dst);
+ assert(set1);
+ assert(set2);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst) + TkIntSetSize(set1) + TkIntSetSize(set2));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = JoinNonIntersection(
+ set->buf, dst->buf, dst->end, set1->buf, set1->end, set2->buf, set2->end);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+TkIntSet *
+TkIntSetJoin2ComplementToIntersection(
+ TkIntSet *dst,
+ const TkIntSet *add,
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ TkIntSet *set;
+ const TkIntSetType *set1P;
+ const TkIntSetType *set2P;
+ const TkIntSetType *set1End;
+ const TkIntSetType *set2End;
+ TkIntSetType *res1, *res2, *res3, *res1End, *res2End, *res3End;
+ TkIntSetType buffer[512];
+ unsigned capacity1, capacity2;
+ unsigned size, size1, size2;
+
+ assert(dst);
+ assert(add);
+ assert(set1);
+ assert(set2);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ set1P = set1->buf;
+ set2P = set2->buf;
+ set1End = set1->end;
+ set2End = set2->end;
+
+ size1 = TkIntSetSize(set1) + TkIntSetSize(set2);
+ size2 = MIN(TkIntSetSize(set1), TkIntSetSize(set2));
+ size = size1 + 2*size2;
+ res1 = size <= sizeof(buffer)/sizeof(buffer[0]) ? buffer : malloc(size*sizeof(TkIntSetType));
+ res2 = res1 + size1;
+ res3 = res2 + size2;
+
+ res1End = Join(res1, set1P, set1End, set2P, set2End);
+ res2End = Intersect(res2, set1P, set1End, set2P, set2End);
+ res3End = ComplementTo(res3, res1, res1End, res2, res2End);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst) + TkIntSetSize(add) + (res3End - res3));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = Join2(set->buf, dst->buf, dst->end, add->buf, add->end, res3, res3End);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ if (res1 != buffer) {
+ free(res1);
+ }
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+TkIntSet *
+TkIntSetJoinOfDifferences(
+ TkIntSet *dst,
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ TkIntSet *set;
+ TkIntSetType *buf1, *buf2, *end1, *end2;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(dst);
+ assert(set1);
+ assert(set2);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity2 = TkIntSetSize(dst) + TkIntSetSize(set1);
+ capacity1 = NextPowerOf2(2*capacity2);
+ set = malloc(SET_SIZE(capacity1));
+ buf1 = set->buf + capacity2;
+ buf2 = buf1 + TkIntSetSize(dst);
+ end1 = Remove(buf1, dst->buf, dst->end, set1->buf, set1->end);
+ end2 = Remove(buf2, set1->buf, set1->end, set2->buf, set2->end);
+ set->end = Join(set->buf, buf1, end1, buf2, end2);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+bool
+TkIntSetDisjunctive__(
+ const TkIntSetType *set1, const TkIntSetType *end1,
+ const TkIntSetType *set2, const TkIntSetType *end2)
+{
+ while (set1 != end1 && set2 != end2) {
+ if (*set1 == *set2) {
+ return false;
+ }
+ if (*set1 < *set2) {
+ ++set1;
+ } else {
+ ++set2;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkIntSetContains__(
+ const TkIntSetType *set1, const TkIntSetType *end1,
+ const TkIntSetType *set2, const TkIntSetType *end2)
+{
+ /*
+ * a in set1, not in set2 -> skip
+ * a in set1, and in set2 -> skip
+ * a in set2, not in set1 -> false
+ */
+
+ if (end1 - set1 < end2 - set2) {
+ return false;
+ }
+
+ while (set1 != end1 && set2 != end2) {
+ if (*set2 < *set1) {
+ return false;
+ } else if (*set1 == *set2) {
+ ++set2;
+ }
+ ++set1;
+ }
+
+ return set2 == end2;
+}
+
+
+bool
+TkIntSetIsContainedBits(
+ const TkIntSet *set,
+ const TkBitField *bf)
+{
+ unsigned setSize, bitSize, i;
+
+ assert(bf);
+ assert(set);
+
+ setSize = TkIntSetSize(set);
+ bitSize = TkBitSize(bf);
+
+ for (i = 0; i < setSize; ++i) {
+ TkIntSetType value = set->buf[i];
+ if (value >= bitSize || !TkBitTest(bf, value)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+bool
+TkIntSetIntersectionIsEqual(
+ const TkIntSet *set1,
+ const TkIntSet *set2,
+ const TkBitField *del)
+{
+ const TkIntSetType *s1;
+ const TkIntSetType *e1;
+ const TkIntSetType *s2;
+ const TkIntSetType *e2;
+
+ assert(set1);
+ assert(set2);
+ assert(del);
+ assert(TkIntSetMax(set1) < TkBitSize(del));
+ assert(TkIntSetMax(set2) < TkBitSize(del));
+
+ if (set1 == set2) {
+ return true;
+ }
+
+ s1 = set1->buf; e1 = set1->end;
+ s2 = set2->buf; e2 = set2->end;
+
+ while (s1 != e1 && s2 != e2) {
+ if (*s1 == *s2) {
+ ++s1;
+ ++s2;
+ } else if (*s1 < *s2) {
+ if (!TkBitTest(del, *s1)) {
+ return false;
+ }
+ ++s1;
+ } else { /* if (*s2 < *s1) */
+ if (!TkBitTest(del, *s2)) {
+ return false;
+ }
+ ++s2;
+ }
+ }
+ for ( ; s1 != e1; ++s1) {
+ if (!TkBitTest(del, *s1)) {
+ return false;
+ }
+ }
+ for ( ; s2 != e2; ++s2) {
+ if (!TkBitTest(del, *s2)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkIntSetIntersectionIsEqualBits(
+ const TkIntSet *set,
+ const TkBitField *bf,
+ const TkBitField *del)
+{
+ TkBitField *cp = TkBitCopy(del, -1);
+ bool test;
+
+ assert(set);
+ assert(bf);
+ assert(del);
+ assert(TkIntSetMax(set) < TkBitSize(del));
+ assert(TkBitSize(bf) <= TkBitSize(del));
+
+ TkBitIntersect(cp, bf);
+ test = TkIntSetIsEqualBits(set, cp);
+ TkBitDestroy(&cp);
+ return test;
+}
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+static TkIntSet *
+Add(
+ TkIntSet *set,
+ TkIntSetType *pos,
+ unsigned n)
+{
+ unsigned size = set->end - set->buf;
+
+ if (IsPowerOf2(size)) {
+ TkIntSet *newSet = malloc(SET_SIZE(MAX(2*size, 1)));
+ unsigned offs = pos - set->buf;
+
+ assert(offs <= size);
+ memcpy(newSet->buf, set->buf, offs*sizeof(TkIntSetType));
+ memcpy(newSet->buf + offs + 1, pos, (size - offs)*sizeof(TkIntSetType));
+ newSet->end = newSet->buf + size + 1;
+ newSet->refCount = 1;
+ newSet->isSetFlag = true;
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (--set->refCount == 0) {
+ free(set);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set = newSet;
+ pos = set->buf + offs;
+ } else {
+ memmove(pos + 1, pos, (set->end - pos)*sizeof(TkIntSetType));
+ set->end += 1;
+ }
+
+ *pos = n;
+ return set;
+}
+
+
+TkIntSet *
+TkIntSetAdd(
+ TkIntSet *set,
+ unsigned n)
+{
+ TkIntSetType *pos;
+
+ assert(set);
+ assert(TkIntSetRefCount(set) > 0);
+
+ pos = TkIntSetLowerBound(set->buf, set->end, n);
+
+ if (pos < set->end && *pos == n) {
+ return set;
+ }
+
+ return Add(set, pos, n);
+}
+
+
+static TkIntSet *
+Erase(
+ TkIntSet *set,
+ TkIntSetType *pos,
+ unsigned n)
+{
+ unsigned size = set->end - set->buf - 1;
+
+ if (IsPowerOf2(size)) {
+ TkIntSet *newSet = malloc(SET_SIZE(size));
+ unsigned offs = pos - set->buf;
+
+ memcpy(newSet->buf, set->buf, offs*sizeof(TkIntSetType));
+ memcpy(newSet->buf + offs, pos + 1, (size - offs)*sizeof(TkIntSetType));
+ newSet->end = newSet->buf + size;
+ newSet->refCount = 1;
+ newSet->isSetFlag = true;
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (--set->refCount == 0) {
+ free(set);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set = newSet;
+ } else {
+ memmove(pos, pos + 1, (set->end - pos - 1)*sizeof(TkIntSetType));
+ set->end -= 1;
+ }
+
+ return set;
+}
+
+
+TkIntSet *
+TkIntSetErase(
+ TkIntSet *set,
+ unsigned n)
+{
+ TkIntSetType *pos;
+
+ assert(set);
+ assert(TkIntSetRefCount(set) > 0);
+
+ pos = TkIntSetLowerBound(set->buf, set->end, n);
+
+ if (pos == set->end || *pos != n) {
+ return set;
+ }
+
+ return Erase(set, pos, n);
+}
+
+
+TkIntSet *
+TkIntSetTestAndSet(
+ TkIntSet *set,
+ unsigned n)
+{
+ TkIntSetType *pos;
+
+ assert(set);
+ assert(TkIntSetRefCount(set) > 0);
+
+ pos = TkIntSetLowerBound(set->buf, set->end, n);
+
+ if (pos < set->end && *pos == n) {
+ return NULL;
+ }
+
+ return Add(set, pos, n);
+}
+
+
+TkIntSet *
+TkIntSetTestAndUnset(
+ TkIntSet *set,
+ unsigned n)
+{
+ TkIntSetType *pos;
+
+ assert(set);
+ assert(TkIntSetRefCount(set) > 0);
+
+ pos = TkIntSetLowerBound(set->buf, set->end, n);
+
+ if (pos == set->end || *pos != n) {
+ return NULL;
+ }
+
+ return Erase(set, pos, n);
+}
+
+
+TkIntSet *
+TkIntSetClear(
+ TkIntSet *set)
+{
+ TkIntSet *newSet;
+
+ assert(set);
+ assert(TkIntSetRefCount(set) > 0);
+
+ if (set->buf == set->end) {
+ return set;
+ }
+
+ newSet = malloc(SET_SIZE(0));
+ newSet->end = newSet->buf;
+ newSet->refCount = 1;
+ newSet->isSetFlag = true;
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (--set->refCount == 0) {
+ free(set);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ return newSet;
+}
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+bool
+TkIntSetIsEqualBits(
+ const TkIntSet *set,
+ const TkBitField *bf)
+{
+ unsigned sizeSet, sizeBf, i;
+
+ assert(set);
+ assert(bf);
+
+ sizeSet = TkIntSetSize(set);
+
+ if (sizeSet != TkBitCount(bf)) {
+ return false;
+ }
+
+ sizeBf = TkBitSize(bf);
+
+ for (i = 0; i < sizeSet; ++i) {
+ TkIntSetType value = set->buf[i];
+
+ if (value >= sizeBf || !TkBitTest(bf, value)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkIntSetContainsBits(
+ const TkIntSet *set,
+ const TkBitField *bf)
+{
+ unsigned sizeSet, sizeBf, i;
+ unsigned count = 0;
+
+ assert(set);
+ assert(bf);
+
+ sizeSet = TkIntSetSize(set);
+ sizeBf = TkBitSize(bf);
+
+ for (i = 0; i < sizeSet; ++i) {
+ TkIntSetType value = set->buf[i];
+
+ if (value >= sizeBf) {
+ break;
+ }
+
+ if (TkBitTest(bf, value)) {
+ count += 1;
+ }
+ }
+
+ return count == TkBitCount(bf);
+}
+
+
+bool
+TkIntSetDisjunctiveBits(
+ const TkIntSet *set,
+ const TkBitField *bf)
+{
+ unsigned sizeSet, sizeBf, i;
+
+ assert(set);
+ assert(bf);
+
+ sizeSet = TkIntSetSize(set);
+ sizeBf = TkBitSize(bf);
+
+ for (i = 0; i < sizeSet; ++i) {
+ TkIntSetType value = set->buf[i];
+
+ if (value >= sizeBf) {
+ return true;
+ }
+ if (TkBitTest(bf, value)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+#if !NDEBUG
+
+# include <stdio.h>
+
+void
+TkIntSetPrint(
+ const TkIntSet *set)
+{
+ unsigned i, n;
+ const char *comma = "";
+
+ assert(set);
+
+ n = TkIntSetSize(set);
+ printf("%d:{ ", n);
+ for (i = 0; i < n; ++i) {
+ printf("%s%d", comma, set->buf[i]);
+ comma = ", ";
+ }
+ printf(" }\n");
+}
+
+#endif /* !NDEBUG */
+
+#if TK_UNUSED_INTSET_FUNCTIONS
+
+/*
+ * These functions are not needed anymore, but shouldn't be removed, because sometimes
+ * any of these functions might be useful.
+ */
+
+static TkIntSetType *
+InnerJoinDifference(
+ TkIntSetType *dst,
+ const TkIntSetType *src, const TkIntSetType *srcEnd,
+ const TkIntSetType *add, const TkIntSetType *addEnd,
+ const TkIntSetType *sub, const TkIntSetType *subEnd)
+{
+ /* dst = (src & add) + (add - sub) */
+
+ while (src != srcEnd && add != addEnd) {
+ if (*src < *add) {
+ ++src;
+ } else {
+ if (*src == *add) {
+ *dst++ = *add;
+ ++src;
+ } else {
+ for ( ; sub != subEnd && *sub < *add; ++sub) {
+ /* empty loop body */
+ }
+ if (sub == subEnd) {
+ break;
+ }
+ if (*add != *sub) {
+ *dst++ = *add;
+ }
+ }
+ ++add;
+ }
+ }
+
+ if (sub == subEnd) {
+ /* dst += add */
+ unsigned size = addEnd - add;
+ memcpy(dst, add, size*sizeof(TkIntSetType));
+ dst += size;
+ } else if (src == srcEnd) {
+ /* dst += add - sub */
+ dst = Remove(dst, add, addEnd, sub, subEnd);
+ } else { /* if (add == addEnd) */
+ /* dst += nil */
+ }
+
+ return dst;
+}
+
+
+TkIntSet *
+TkIntSetInnerJoinDifference(
+ TkIntSet *dst,
+ const TkIntSet *add,
+ const TkIntSet *sub)
+{
+ TkIntSet *set;
+ unsigned capacity1, capacity2;
+ unsigned size;
+
+ assert(dst);
+ assert(add);
+ assert(sub);
+ assert(TkIntSetRefCount(dst) > 0);
+
+ capacity1 = NextPowerOf2(TkIntSetSize(dst) + TkIntSetSize(add));
+ set = malloc(SET_SIZE(capacity1));
+ set->end = InnerJoinDifference(set->buf, dst->buf, dst->end, add->buf, add->end, sub->buf, sub->end);
+ size = set->end - set->buf;
+ capacity2 = NextPowerOf2(size);
+ assert(capacity2 <= capacity1);
+ DEBUG_ALLOC(tkIntSetCountNew++);
+
+ if (capacity2 < capacity1) {
+ set = realloc(set, SET_SIZE(capacity2));
+ set->end = set->buf + size;
+ }
+
+ if (--dst->refCount == 0) {
+ free(dst);
+ DEBUG_ALLOC(tkIntSetCountDestroy++);
+ }
+
+ set->refCount = 1;
+ set->isSetFlag = true;
+ return set;
+}
+
+
+bool
+TkIntSetInnerJoinDifferenceIsEmpty(
+ const TkIntSet *set,
+ const TkIntSet *add,
+ const TkIntSet *sub)
+{
+ const TkIntSetType *setP, *setEnd;
+ const TkIntSetType *addP, *addEnd;
+ const TkIntSetType *subP, *subEnd;
+
+ assert(set);
+ assert(add);
+ assert(sub);
+
+ /* (set & add) + (add - sub) == nil */
+
+ if (add->buf == add->end) {
+ /* nil */
+ return true;
+ }
+
+ if (add == set) {
+ /* add == nil */
+ return TkIntSetIsEmpty(add);
+ }
+
+ setP = set->buf; setEnd = set->end;
+ addP = add->buf; addEnd = add->end;
+
+ /* (set & add) == nil */
+
+ while (setP != setEnd && addP < addEnd) {
+ if (*setP == *addP) {
+ return false;
+ } else if (*setP < *addP) {
+ ++setP;
+ } else {
+ ++addP;
+ }
+ }
+
+ /* (add - sub) == nil */
+
+ addP = add->buf; addEnd = add->end;
+ subP = sub->buf; subEnd = sub->end;
+
+ while (addP != addEnd && subP != subEnd) {
+ if (*addP < *subP) {
+ return false;
+ } else if (*addP == *subP) {
+ ++addP;
+ }
+ ++subP;
+ }
+
+ return addP == addEnd;
+}
+
+
+static bool
+DifferenceIsEmpty(
+ const TkIntSetType *set, const TkIntSetType *setEnd,
+ const TkIntSetType *sub, const TkIntSetType *subEnd)
+{
+ while (set != setEnd && sub != subEnd) {
+ if (*set < *sub) {
+ return false;
+ } else {
+ if (*set == *sub) {
+ ++set;
+ }
+ ++sub;
+ }
+ }
+
+ return set == setEnd;
+}
+
+
+bool
+TkIntSetIsEqualToDifference(
+ const TkIntSet *set1,
+ const TkIntSet *set2,
+ const TkIntSet *sub2)
+{
+ const TkIntSetType *set1P, *set1End;
+ const TkIntSetType *set2P, *set2End;
+ const TkIntSetType *sub2P, *sub2End;
+
+ assert(set1);
+ assert(set2);
+ assert(sub2);
+
+ if (set2->buf == set2->end) {
+ return set1->buf == set1->end;
+ }
+
+ set1P = set1->buf; set1End = set1->end;
+ set2P = set2->buf; set2End = set2->end;
+ sub2P = sub2->buf; sub2End = sub2->end;
+
+ if (set1P == set1End) {
+ return DifferenceIsEmpty(set2P, set2End, sub2P, sub2End);
+ }
+
+ /* set1 == set2 - sub2 */
+
+ while (set1P != set1End && set2P != set2End) {
+ if (*set1P < *set2P) {
+ return false;
+ }
+ for ( ; sub2P != sub2End && *sub2P < *set2P; ++sub2P) {
+ /* empty loop body */
+ }
+ if (sub2P == sub2End) {
+ break;
+ }
+ if (*set1P == *set2P) {
+ if (*set2P == *sub2P) {
+ return false;
+ }
+ ++set1P;
+ } else {
+ if (*set2P != *sub2P) {
+ return false;
+ }
+ }
+ ++set2P;
+ }
+
+ if (set2P == set2End) {
+ /* set1 == nil */
+ return set1P == set1End;
+ }
+
+ if (sub2P == sub2End) {
+ /* set1 == set2 */
+ return TestIfEqual(set1P, set1End, set2P, set2End);
+ }
+
+ assert(set1P == set1End);
+ /* set2 - sub2 == nil */
+
+ return DifferenceIsEmpty(set2P, set2End, sub2P, sub2End);
+}
+
+
+bool
+TkIntSetIsEqualToInnerJoin(
+ const TkIntSet *set1,
+ const TkIntSet *set2,
+ const TkIntSet *add2)
+{
+ const TkIntSetType *set1P, *set1End;
+ const TkIntSetType *set2P, *set2End;
+ const TkIntSetType *add2P, *add2End;
+
+ assert(set1);
+ assert(set2);
+ assert(add2);
+
+ if (set1 == set2) {
+ /* set1 == (set1 + (add2 & set1)) */
+ return true;
+ }
+
+ set1P = set1->buf; set1End = set1->end;
+ set2P = set2->buf; set2End = set2->end;
+
+ if (set2P == set2End) {
+ /* set1 == nil */
+ return set1P == set1End;
+ }
+
+ if (set2 == add2) {
+ /* set1 == set2 */
+ return TestIfEqual(set1P, set1End, set2P, set2End);
+ }
+
+ add2P = add2->buf; add2End = add2->end;
+
+ /* set1 == (set2 + (add2 & set2)) */
+
+ while (set1P != set1End && set2P != set2End && add2P != add2End) {
+ if (*set2P < *set1P) {
+ return false;
+ } else if (*set1P == *set2P) {
+ ++set1P;
+ ++set2P;
+ /* now: *set1P < *set2P */
+ } else if (*add2P < *set2P) {
+ ++add2P;
+ } else if (*set2P < *add2P) {
+ ++set2P;
+ } else {
+ return false;
+ }
+ }
+
+ if (add2P == add2End) {
+ /* set1 == set2 */
+ return TestIfEqual(set1P, set1End, set2P, set2End);
+ }
+
+ if (set1P == set1End) {
+ /* set2 == nil */
+ return set2P == set2End;
+ }
+
+ /* set2P == set2End: set1 == nil */
+ return set1P == set1End;
+}
+
+
+static bool
+EqualToJoin(
+ const TkIntSetType *src, const TkIntSetType *send,
+ const TkIntSetType *set1, const TkIntSetType *end1,
+ const TkIntSetType *set2, const TkIntSetType *end2)
+{
+ /* src == set1 + set2 */
+
+ assert(src != send);
+
+ while (set1 != end1 && set2 != end2) {
+ if (*src == *set1) {
+ if (*src == *set2) {
+ ++set2;
+ }
+ ++set1;
+ } else if (*src == *set2) {
+ ++set2;
+ } else {
+ return false;
+ }
+ if (++src == send) {
+ return set1 == end1 && set2 == end2;
+ }
+ }
+
+ if (set1 == end1) {
+ set1 = set2;
+ end1 = end2;
+ }
+
+ return TestIfEqual(src, send, set1, end1);
+}
+
+
+bool
+TkIntSetIsEqualToInnerJoinDifference(
+ const TkIntSet *set1,
+ const TkIntSet *set2,
+ const TkIntSet *add2,
+ const TkIntSet *sub2)
+{
+ TkIntSetType buf1[100];
+ TkIntSetType buf2[100];
+ TkIntSetType *inscBuf; /* set2 & add2 */
+ TkIntSetType *diffBuf; /* add2 - sub2 */
+ TkIntSetType *inscP, *inscEnd;
+ TkIntSetType *diffP, *diffEnd;
+ unsigned inscSize;
+ unsigned diffSize;
+ bool isEqual;
+
+ assert(set1);
+ assert(set2);
+ assert(add2);
+ assert(sub2);
+
+ /* set1 == (set2 & add2) + (add2 - sub2) */
+
+ if (add2->buf == add2->end) {
+ /* set1 == nil */
+ return TkIntSetIsEmpty(set1);
+ }
+
+ if (sub2->buf == sub2->end) {
+ /* set1 == (set2 & add2) + add2 */
+ return TkIntSetIsEqualToInnerJoin(set1, add2, set2);
+ }
+
+ if (set1->buf == set1->end) {
+ /* (set2 & add2) + (add2 - sub2) == nil */
+ return TkIntSetDisjunctive(set2, add2)
+ && DifferenceIsEmpty(add2->buf, add2->end, sub2->buf, sub2->end);
+ }
+
+ diffSize = TkIntSetSize(add2);
+ inscSize = MIN(TkIntSetSize(set2), diffSize);
+ inscBuf = inscSize <= sizeof(buf1)/sizeof(buf1[0]) ? buf1 : malloc(inscSize*sizeof(buf1[0]));
+ inscEnd = Intersect(inscP = inscBuf, set2->buf, set2->end, add2->buf, add2->end);
+
+ if (inscP == inscEnd) {
+ /* set1 == (add2 - sub2) */
+ isEqual = TkIntSetIsEqualToDifference(set1, add2, sub2);
+ } else {
+ diffBuf = diffSize <= sizeof(buf2)/sizeof(buf2[0]) ? buf2 : malloc(diffSize*sizeof(buf2[0]));
+ diffEnd = Remove(diffP = diffBuf, add2->buf, add2->end, sub2->buf, sub2->end);
+
+ if (diffP == diffEnd) {
+ /* set1 == inscP */
+ isEqual = TestIfEqual(set1->buf, set1->end, inscP, inscEnd);
+ } else {
+ /* set1 == inscP + diffP */
+ isEqual = EqualToJoin(set1->buf, set1->end, inscP, inscEnd, diffP, diffEnd);
+ }
+
+ if (diffBuf != buf2) { free(diffBuf); }
+ }
+
+ if (inscBuf != buf1) { free(inscBuf); }
+
+ return isEqual;
+}
+
+
+static bool
+InnerJoinDifferenceIsEqual(
+ const TkIntSetType *set, const TkIntSetType *setEnd,
+ const TkIntSetType *add, const TkIntSetType *addEnd,
+ const TkIntSetType *sub, const TkIntSetType *subEnd)
+{
+ /*
+ * (add - sub) == (set & add) + (add - sub)
+ *
+ * This is equivalent to:
+ * (add - sub) & add == (set + (add - sub)) & add
+ *
+ * This means we have to show:
+ * For any x in add: x in (add - sub) <=> x in (set + (add - sub))
+ *
+ * So it's sufficient to show:
+ * For any x in add: x in sub => x not in set
+ * For any x in add: x in set => x not in sub
+ *
+ * But this is equivalent to:
+ * (sub & add) & (set & add) == nil
+ */
+
+ if (add != addEnd) {
+ while (set != setEnd && sub != subEnd) {
+ if (*set == *sub) {
+ while (*add < *set) {
+ if (++add == addEnd) {
+ return true;
+ }
+ }
+ if (*add == *set) {
+ return false;
+ }
+ ++set;
+ ++sub;
+ } else if (*set < *sub) {
+ ++set;
+ } else {
+ ++sub;
+ }
+ }
+ }
+
+ return true;
+}
+
+
+bool
+TkIntSetInnerJoinDifferenceIsEqual(
+ const TkIntSet *set1,
+ const TkIntSet *set2,
+ const TkIntSet *add,
+ const TkIntSet *sub)
+{
+ const TkIntSetType *set1P, *set1End;
+ const TkIntSetType *set2P, *set2End;
+ const TkIntSetType *addP, *addEnd;
+ const TkIntSetType *subP, *subEnd;
+
+ if (add->buf == add->end) {
+ return true;
+ }
+
+ set1P = set1->buf; set1End = set1->end;
+ set2P = set2->buf; set2End = set2->end;
+ addP = add->buf; addEnd = add->end;
+ subP = sub->buf; subEnd = sub->end;
+
+ if (set1P == set1End) {
+ return InnerJoinDifferenceIsEqual(set2P, set2End, addP, addEnd, subP, subEnd);
+ }
+ if (set2P == set2End) {
+ return InnerJoinDifferenceIsEqual(set1P, set1End, addP, addEnd, subP, subEnd);
+ }
+
+ /*
+ * (set1 & add) + (add - sub) == (set2 & add) + (add - sub)
+ *
+ * This is equivalent to:
+ * (set1 + (add - sub)) & add == (set2 + (add - sub)) & add
+ *
+ * This means we have to show:
+ * For any x in add: x in (set1 + (add - sub)) <=> x in (set2 + (add - sub))
+ *
+ * x in (add & sub): Proof: x in set1 <=> x in set2.
+ * x in (add - sub): Nothing to proof.
+ */
+
+ while (addP != addEnd && subP != subEnd) {
+ if (*addP < *subP) {
+ ++addP;
+ } else {
+ if (*addP == *subP) {
+ /* x in (add & sub): Proof: x in set1 <=> x in set2. */
+ for ( ; set1P != set1End && *set1P < *addP; ++set1P) {
+ /* empty loop body */
+ }
+ if (set1P == set1End) {
+ /* (add - sub) == (set2 & add) + (add - sub) */
+ return InnerJoinDifferenceIsEqual(set2P, set2End, addP, addEnd, subP, subEnd);
+ }
+ for ( ; set2P != set2End && *set2P < *addP; ++set2P) {
+ /* empty loop body */
+ }
+ if (set2P == set2End) {
+ /* (add - sub) == (set1 & add) + (add - sub) */
+ return InnerJoinDifferenceIsEqual(set1P, set1End, addP, addEnd, subP, subEnd);
+ }
+ if (*addP == *set1P) {
+ if (*addP != *set2P) {
+ return false;
+ }
+ ++set1P;
+ ++set2P;
+ } else if (*addP == *set2P) {
+ return false;
+ }
+ ++addP;
+ }
+ ++subP;
+ }
+ }
+
+ return true;
+}
+
+#endif /* TK_UNUSED_INTSET_FUNCTIONS */
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+/* Additionally we need stand-alone object code. */
+#define inline extern
+inline unsigned TkIntSetByteSize(const TkIntSet *set);
+inline const unsigned char *TkIntSetData(const TkIntSet *set);
+inline bool TkIntSetIsEmpty(const TkIntSet *set);
+inline unsigned TkIntSetSize(const TkIntSet *set);
+inline unsigned TkIntSetMax(const TkIntSet *set);
+inline unsigned TkIntSetRefCount(const TkIntSet *set);
+inline void TkIntSetIncrRefCount(TkIntSet *set);
+inline unsigned TkIntSetDecrRefCount(TkIntSet *set);
+inline TkIntSetType TkIntSetAccess(const TkIntSet *set, unsigned index);
+inline bool TkIntSetTest(const TkIntSet *set, unsigned n);
+inline bool TkIntSetNone(const TkIntSet *set);
+inline bool TkIntSetAny(const TkIntSet *set);
+inline bool TkIntSetIsEqual(const TkIntSet *set1, const TkIntSet *set2);
+inline bool TkIntSetContains(const TkIntSet *set1, const TkIntSet *set2);
+inline bool TkIntSetDisjunctive(const TkIntSet *set1, const TkIntSet *set2);
+inline bool TkIntSetIntersects(const TkIntSet *set1, const TkIntSet *set2);
+inline unsigned TkIntSetFindFirst(const TkIntSet *set);
+inline unsigned TkIntSetFindNext(const TkIntSet *set);
+inline TkIntSet *TkIntSetAddOrErase(TkIntSet *set, unsigned n, bool add);
+#endif /* __STDC_VERSION__ >= 199901L */
+
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkIntSet.h b/generic/tkIntSet.h
new file mode 100644
index 0000000..02438cb
--- /dev/null
+++ b/generic/tkIntSet.h
@@ -0,0 +1,199 @@
+/*
+ * tkSet.h --
+ *
+ * This module implements an integer set.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKINTSET
+#define _TKINTSET
+
+#ifndef _TK
+#include "tk.h"
+#endif
+
+#include "tkBool.h"
+#include <stdint.h>
+
+#if defined(__GNUC__) || defined(__clang__)
+# define __warn_unused__ __attribute__((warn_unused_result))
+#else
+# define __warn_unused__
+#endif
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+struct TkBitField;
+
+
+typedef uint32_t TkIntSetType;
+
+/*
+ * The struct below will be shared with the struct TkBitField, so the first two
+ * members must exactly match the first two members in struct TkBitField. In this
+ * way we have a struct inheritance, based on the first two members. This
+ * is portable due to C99 section 6.7.2.1 bullet point 13:
+ *
+ * Within a structure object, the non-bit-field members and the units
+ * in which bit-fields reside have addresses that increase in the order
+ * in which they are declared. A pointer to a structure object, suitably
+ * converted, points to its initial member (or if that member is a
+ * bit-field, then to the unit in which it resides), and vice versa.
+ * There may be unnamed padding within a structure object, but not at
+ * beginning.
+ *
+ * This inheritance concept is also used in the portable GTK library.
+ */
+
+typedef struct TkIntSet {
+ uint32_t refCount:31;
+ uint32_t isSetFlag:1;
+ TkIntSetType *end;
+ TkIntSetType *curr; /* mutable */
+ TkIntSetType buf[1];
+} TkIntSet;
+
+
+#define TK_SET_NPOS ((unsigned) -1)
+
+
+TkIntSet *TkIntSetNew();
+#if !TK_TEXT_DONT_USE_BITFIELDS
+TkIntSet *TkIntSetFromBits(const struct TkBitField *bf);
+#endif
+void TkIntSetDestroy(TkIntSet **setPtr);
+
+inline unsigned TkIntSetByteSize(const TkIntSet *set);
+inline const unsigned char *TkIntSetData(const TkIntSet *set);
+
+TkIntSet *TkIntSetCopy(const TkIntSet *set) __warn_unused__;
+
+TkIntSet *TkIntSetJoin(TkIntSet *dst, const TkIntSet *src) __warn_unused__;
+TkIntSet *TkIntSetIntersect(TkIntSet *dst, const TkIntSet *src) __warn_unused__;
+TkIntSet *TkIntSetRemove(TkIntSet *dst, const TkIntSet *src) __warn_unused__;
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+TkIntSet *TkIntSetJoinBits(TkIntSet *dst, const struct TkBitField *src) __warn_unused__;
+TkIntSet *TkIntSetIntersectBits(TkIntSet *dst, const struct TkBitField *src) __warn_unused__;
+TkIntSet *TkIntSetRemoveBits(TkIntSet *dst, const struct TkBitField *src) __warn_unused__;
+/* dst := src - dst */
+TkIntSet *TkIntSetComplementToBits(TkIntSet *dst, const struct TkBitField *src) __warn_unused__;
+#endif
+
+/* dst := dst + set1 + set2 */
+TkIntSet *TkIntSetJoin2(TkIntSet *dst, const TkIntSet *set1, const TkIntSet *set2) __warn_unused__;
+/* dst := src - dst */
+TkIntSet *TkIntSetComplementTo(TkIntSet *dst, const TkIntSet *src) __warn_unused__;
+/* dst := dst + (set2 - set1) */
+TkIntSet *TkIntSetJoinComplementTo(TkIntSet *dst, const TkIntSet *set1, const TkIntSet *set2)
+ __warn_unused__;
+/* dst := dst + (set1 - set2) + (set2 - set1) */
+TkIntSet *TkIntSetJoinNonIntersection(TkIntSet *dst, const TkIntSet *set1, const TkIntSet *set2)
+ __warn_unused__;
+/* dst := dst + add + ((set1 + set2) - (set1 & set2)) */
+TkIntSet *TkIntSetJoin2ComplementToIntersection(TkIntSet *dst,
+ const TkIntSet *add, const TkIntSet *set1, const TkIntSet *set2) __warn_unused__;
+/* dst := (dst - set1) + (set1 - set2) */
+TkIntSet *TkIntSetJoinOfDifferences(TkIntSet *dst, const TkIntSet *set1, const TkIntSet *set2)
+ __warn_unused__;
+
+inline bool TkIntSetIsEmpty(const TkIntSet *set);
+inline unsigned TkIntSetSize(const TkIntSet *set);
+inline unsigned TkIntSetMax(const TkIntSet *set);
+
+inline unsigned TkIntSetRefCount(const TkIntSet *set);
+inline void TkIntSetIncrRefCount(TkIntSet *set);
+inline unsigned TkIntSetDecrRefCount(TkIntSet *set);
+
+inline TkIntSetType TkIntSetAccess(const TkIntSet *set, unsigned index);
+
+inline bool TkIntSetTest(const TkIntSet *set, unsigned n);
+inline bool TkIntSetNone(const TkIntSet *set);
+inline bool TkIntSetAny(const TkIntSet *set);
+
+inline bool TkIntSetIsEqual(const TkIntSet *set1, const TkIntSet *set2);
+inline bool TkIntSetContains(const TkIntSet *set1, const TkIntSet *set2);
+inline bool TkIntSetDisjunctive(const TkIntSet *set1, const TkIntSet *set2);
+inline bool TkIntSetIntersects(const TkIntSet *set1, const TkIntSet *set2);
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+bool TkIntSetIntersectionIsEqual(const TkIntSet *set1, const TkIntSet *set2,
+ const struct TkBitField *del);
+bool TkIntSetIsEqualBits(const TkIntSet *set, const struct TkBitField *bf);
+bool TkIntSetContainsBits(const TkIntSet *set, const struct TkBitField *bf);
+bool TkIntSetDisjunctiveBits(const TkIntSet *set, const struct TkBitField *bf);
+bool TkIntSetIntersectionIsEqualBits(const TkIntSet *set, const struct TkBitField *bf,
+ const struct TkBitField *del);
+bool TkIntSetIsContainedBits(const TkIntSet *set, const struct TkBitField *bf);
+#endif
+
+inline unsigned TkIntSetFindFirst(const TkIntSet *set);
+inline unsigned TkIntSetFindNext(const TkIntSet *set);
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+unsigned TkIntSetFindFirstInIntersection(const TkIntSet *set, const struct TkBitField *bf);
+#endif
+
+TkIntSet *TkIntSetAdd(TkIntSet *set, unsigned n) __warn_unused__;
+TkIntSet *TkIntSetErase(TkIntSet *set, unsigned n) __warn_unused__;
+TkIntSet *TkIntSetTestAndSet(TkIntSet *set, unsigned n) __warn_unused__;
+TkIntSet *TkIntSetTestAndUnset(TkIntSet *set, unsigned n) __warn_unused__;
+inline TkIntSet *TkIntSetAddOrErase(TkIntSet *set, unsigned n, bool add) __warn_unused__;
+TkIntSet *TkIntSetClear(TkIntSet *set) __warn_unused__;
+
+#if !NDEBUG
+void TkIntSetPrint(const TkIntSet *set);
+#endif
+
+
+#if TK_UNUSED_INTSET_FUNCTIONS
+
+/*
+ * These functions are not needed anymore, but shouldn't be removed, because sometimes
+ * any of these functions might be useful.
+ */
+
+/* dst := (dst + (set - sub)) & set */
+TkIntSet *TkIntSetInnerJoinDifference(TkIntSet *dst, const TkIntSet *set, const TkIntSet *sub)
+ __warn_unused__;
+/* ((set + (add - sub)) & add) == nil */
+bool TkIntSetInnerJoinDifferenceIsEmpty(const TkIntSet *set, const TkIntSet *add, const TkIntSet *sub);
+/* set1 == set2 - sub2 */
+bool TkIntSetIsEqualToDifference(const TkIntSet *set1, const TkIntSet *set2, const TkIntSet *sub2);
+/* set1 == set2 + (add2 & set2) */
+bool TkIntSetIsEqualToInnerJoin(const TkIntSet *set1, const TkIntSet *set2, const TkIntSet *add2);
+/* set1 == ((set2 + (add2 - sub2)) & add2) */
+bool TkIntSetIsEqualToInnerJoinDifference(const TkIntSet *set1, const TkIntSet *set2,
+ const TkIntSet *add2, const TkIntSet *sub2);
+/* ((set1 + (add - sub)) & add) == ((set2 + (add - sub)) & add) */
+bool TkIntSetInnerJoinDifferenceIsEqual(const TkIntSet *set1, const TkIntSet *set2,
+ const TkIntSet *add, const TkIntSet *sub);
+
+#endif /* TK_UNUSED_INTSET_FUNCTIONS */
+
+
+#undef __warn_unused__
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+# define _TK_NEED_IMPLEMENTATION
+#include "tkIntSetPriv.h"
+# undef _TK_NEED_IMPLEMENTATION
+#else
+# undef inline
+#endif
+
+#endif /* _TKINTSET */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkIntSetPriv.h b/generic/tkIntSetPriv.h
new file mode 100644
index 0000000..600b173
--- /dev/null
+++ b/generic/tkIntSetPriv.h
@@ -0,0 +1,286 @@
+/*
+ * tkIntSetPriv.h --
+ *
+ * Private implementation for integer set.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKINTSET
+# error "do not include this private header file"
+#endif
+
+
+#ifndef _TKINTSETPRIV
+#define _TKINTSETPRIV
+
+MODULE_SCOPE bool TkIntSetContains__(
+ const TkIntSetType *set1, const TkIntSetType *end1,
+ const TkIntSetType *set2, const TkIntSetType *end2);
+MODULE_SCOPE bool TkIntSetDisjunctive__(
+ const TkIntSetType *set1, const TkIntSetType *end1,
+ const TkIntSetType *set2, const TkIntSetType *end2);
+MODULE_SCOPE bool TkIntSetIsEqual__(
+ const TkIntSetType *set1, const TkIntSetType *end1,
+ const TkIntSetType *set2, const TkIntSetType *end2);
+
+#endif /* _TKINTSETPRIV */
+
+
+#ifdef _TK_NEED_IMPLEMENTATION
+
+#include <assert.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+extern TkIntSetType *
+TkIntSetLowerBound(
+ TkIntSetType *first,
+ TkIntSetType *last,
+ TkIntSetType value);
+
+
+inline
+const unsigned char *
+TkIntSetData(
+ const TkIntSet *set)
+{
+ assert(set);
+ return (const void *) set->buf;
+}
+
+
+inline
+unsigned
+TkIntSetByteSize(
+ const TkIntSet *set)
+{
+ assert(set);
+ return (set->end - set->buf)*sizeof(TkIntSetType);
+}
+
+
+inline
+bool
+TkIntSetIsEmpty(
+ const TkIntSet *set)
+{
+ assert(set);
+ return set->end == set->buf;
+}
+
+
+inline
+bool
+TkIntSetIsEqual(
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ assert(set1);
+ assert(set2);
+
+ return set1 == set2 || TkIntSetIsEqual__(set1->buf, set1->end, set2->buf, set2->end);
+}
+
+
+inline
+bool
+TkIntSetContains(
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ assert(set1);
+ assert(set2);
+
+ return set1 == set2 || TkIntSetContains__(set1->buf, set1->end, set2->buf, set2->end);
+}
+
+
+inline
+bool
+TkIntSetDisjunctive(
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ assert(set1);
+ assert(set2);
+
+ if (set1 == set2) {
+ return TkIntSetIsEmpty(set1);
+ }
+ return TkIntSetDisjunctive__(set1->buf, set1->end, set2->buf, set2->end);
+}
+
+
+inline
+unsigned
+TkIntSetSize(
+ const TkIntSet *set)
+{
+ assert(set);
+ return set->end - set->buf;
+}
+
+
+inline
+unsigned
+TkIntSetMax(
+ const TkIntSet *set)
+{
+ assert(!TkIntSetIsEmpty(set));
+ return set->end[-1];
+}
+
+
+inline
+unsigned
+TkIntSetRefCount(
+ const TkIntSet *set)
+{
+ assert(set);
+ return set->refCount;
+}
+
+
+inline
+void
+TkIntSetIncrRefCount(TkIntSet *set)
+{
+ assert(set);
+ set->refCount += 1;
+}
+
+
+inline
+unsigned
+TkIntSetDecrRefCount(TkIntSet *set)
+{
+ unsigned refCount;
+
+ assert(set);
+ assert(set->refCount > 0);
+
+ if ((refCount = --set->refCount) == 0) {
+ TkIntSetDestroy(&set);
+ }
+ return refCount;
+}
+
+
+inline
+TkIntSetType
+TkIntSetAccess(
+ const TkIntSet *set,
+ unsigned index)
+{
+ assert(set);
+ assert(index < TkIntSetSize(set));
+ return set->buf[index];
+}
+
+
+inline
+void
+TkIntSetChange(
+ TkIntSet *set,
+ unsigned index,
+ unsigned n)
+{
+ assert(set);
+ assert(index < TkIntSetSize(set));
+ set->buf[index] = n;
+}
+
+
+inline
+bool
+TkIntSetTest(
+ const TkIntSet *set,
+ unsigned n)
+{
+ const TkIntSetType *pos;
+
+ assert(set);
+
+ pos = TkIntSetLowerBound(((TkIntSet *) set)->buf, ((TkIntSet *) set)->end, n);
+ return pos < set->end && *pos == n;
+}
+
+
+inline
+bool
+TkIntSetNone(
+ const TkIntSet *set)
+{
+ assert(set);
+ return set->buf == set->end;
+}
+
+
+inline
+bool
+TkIntSetAny(
+ const TkIntSet *set)
+{
+ assert(set);
+ return set->buf < set->end;
+}
+
+
+inline
+bool
+TkIntSetIntersects(
+ const TkIntSet *set1,
+ const TkIntSet *set2)
+{
+ return !TkIntSetDisjunctive(set1, set2);
+}
+
+
+inline
+unsigned
+TkIntSetFindNext(
+ const TkIntSet *set)
+{
+ assert(set);
+ return set->curr == set->end ? TK_SET_NPOS : *(((TkIntSet *) set)->curr++); /* 'curr' is mutable */
+}
+
+
+inline
+unsigned
+TkIntSetFindFirst(
+ const TkIntSet *set)
+{
+ assert(set);
+ ((TkIntSet *) set)->curr = ((TkIntSet *) set)->buf; /* 'curr' is mutable */
+ return TkIntSetFindNext(set);
+}
+
+
+inline
+TkIntSet *
+TkIntSetAddOrErase(
+ TkIntSet *set,
+ unsigned n,
+ bool add)
+{
+ assert(set);
+ return add ? TkIntSetAdd(set, n) : TkIntSetErase(set, n);
+}
+
+#undef _TK_NEED_IMPLEMENTATION
+#endif /* _TK_NEED_IMPLEMENTATION */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkQTree.c b/generic/tkQTree.c
new file mode 100644
index 0000000..ac23d10
--- /dev/null
+++ b/generic/tkQTree.c
@@ -0,0 +1,1146 @@
+/*
+ * tkQTree.c --
+ *
+ * This module provides an implementation of a Q-Tree (Quartering Tree) for
+ * fast search of rectangles containing a specific point. This provides very
+ * fast mouse hovering lookup, even insertion/deletion/update is fast.
+ *
+ * The search algorithm is working with binary division on two dimensions
+ * (in fact a quartering), so this is (in practice) the fastest possible
+ * search algorithm for testing points in a set of rectangles.
+ *
+ * Complexity of search/insert/delete/update:
+ *
+ * best case: O(log n)
+ * average case: O(log n)
+ * worst case: O(n)
+ *
+ * Complexity of configuring the tree:
+ *
+ * best case: O(n*log n)
+ * average case: O(n*log n)
+ * worst case: O(sqr n)
+ *
+ * The worst case happens if most of the rectangles are overlapping, or even
+ * equal. We could implement the Q-Tree with a worst case of O(log n) for search,
+ * if we omit the spanning items. But then it may happen under certain conditions
+ * that the tree will explode in memory, the spanning items are preventing this.
+ * In practice the search will be super-fast, despite the worst case. It's a bit
+ * similar to the heapsort/quicksort complexity, in theory heapsort is better
+ * (worst case O(n*log n)), but in practice quicksort performs better (although
+ * worst case is O(sqr n)).
+ *
+ * Note that the Q-Tree is far superior compared to the R-Tree, provided that we
+ * know the overall bounding box of all rectangles in advance, and that we are
+ * searching for points (this means we are searching in two-dimensional data).
+ * The worst case of the search inside the R-Tree occurs when all rectangles are
+ * disjoint - and this will mostly be the case when the text widget is using the
+ * Q-Tree for the bounding boxes of the images - and the search inside the R-Tree
+ * needs complex computations, but the Q-Tree doesn't need complex computations,
+ * and is much faster.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkQTree.h"
+#include "tkAlloc.h"
+
+#if !(__STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900))
+# define _TK_NEED_IMPLEMENTATION
+# include "tkQTreePriv.h"
+#endif
+
+#include <tcl.h>
+#include <string.h>
+#include <assert.h>
+
+#if TK_CHECK_ALLOCS
+# define DEBUG_ALLOC(expr) expr
+#else
+# define DEBUG_ALLOC(expr)
+#endif
+
+
+DEBUG_ALLOC(unsigned tkQTreeCountNewTree = 0);
+DEBUG_ALLOC(unsigned tkQTreeCountDestroyTree = 0);
+DEBUG_ALLOC(unsigned tkQTreeCountNewNode = 0);
+DEBUG_ALLOC(unsigned tkQTreeCountDestroyNode = 0);
+DEBUG_ALLOC(unsigned tkQTreeCountNewItem = 0);
+DEBUG_ALLOC(unsigned tkQTreeCountDestroyItem = 0);
+DEBUG_ALLOC(unsigned tkQTreeCountNewElement = 0);
+DEBUG_ALLOC(unsigned tkQTreeCountDestroyElement = 0);
+
+
+/* Based on tests this seems to provide the best performance. */
+enum { MaxNodeItems = 20 };
+
+
+typedef struct Element {
+ struct Element *prev;
+ struct Element *next;
+ TkQTreeUid uid;
+ TkQTreeRect bbox;
+ TkQTreeState state;
+ uint32_t epoch;
+} Element;
+
+typedef struct Item {
+ struct Item *next;
+ Element *elem;
+} Item;
+
+typedef struct Node {
+ Item *spanningItem;
+ int countSpanning;
+
+ Item *partialItem;
+ int countPartial;
+
+ struct Node *ne;
+ struct Node *nw;
+ struct Node *se;
+ struct Node *sw;
+} Node;
+
+struct TkQTree {
+ Node *root;
+ Element *elem;
+ TkQTreeRect bbox;
+ uint32_t epoch;
+};
+
+
+static void InsertElem(const TkQTreeRect *bbox, Node *node, Element *elem);
+
+
+static Element *
+NewElement(
+ TkQTree tree,
+ const TkQTreeRect *rect,
+ const TkQTreeUid uid,
+ TkQTreeState initialState)
+{
+ Element *elem;
+
+ assert(tree);
+ assert(rect);
+
+ DEBUG_ALLOC(tkQTreeCountNewElement++);
+ elem = malloc(sizeof(Element));
+ elem->prev = NULL;
+ if ((elem->next = tree->elem)) {
+ elem->next->prev = elem;
+ }
+ tree->elem = elem;
+ elem->bbox = *rect;
+ elem->uid = uid;
+ elem->state = initialState;
+ elem->epoch = 0;
+ return elem;
+}
+
+
+static Node *
+NewNode(int initialPartialCount)
+{
+ Node *n;
+
+ DEBUG_ALLOC(tkQTreeCountNewNode++);
+ n = memset(malloc(sizeof(Node)), 0, sizeof(Node));
+ n->countPartial = initialPartialCount;
+ return n;
+}
+
+
+static void
+FreeItems(
+ Item *item)
+{
+ while (item) {
+ Item *next = item->next;
+ free(item);
+ item = next;
+ DEBUG_ALLOC(tkQTreeCountDestroyItem++);
+ }
+}
+
+
+static void
+FreeNode(
+ Node *node)
+{
+ if (node) {
+ if (node->countPartial >= 0) {
+ FreeItems(node->partialItem);
+ } else {
+ FreeNode(node->ne);
+ FreeNode(node->nw);
+ FreeNode(node->se);
+ FreeNode(node->sw);
+ }
+ FreeItems(node->spanningItem);
+ free(node);
+ DEBUG_ALLOC(tkQTreeCountDestroyNode++);
+ }
+}
+
+
+static void
+FreeElements(
+ Element *elem)
+{
+ while (elem) {
+ Element *next = elem->next;
+ free(elem);
+ elem = next;
+ DEBUG_ALLOC(tkQTreeCountDestroyElement++);
+ }
+}
+
+
+static void
+AddItem(
+ Item **itemPtr,
+ Element *elem)
+{
+ Item *newItem;
+
+ assert(itemPtr);
+ assert(elem);
+
+ DEBUG_ALLOC(tkQTreeCountNewItem++);
+ newItem = malloc(sizeof(Item));
+ newItem->elem = elem;
+ newItem->next = *itemPtr;
+ *itemPtr = newItem;
+}
+
+
+/* Helper for function Split. */
+static Node *
+FillQuarter(
+ const TkQTreeRect *bbox,
+ const Item *partialItem,
+ Element *newElem)
+{
+ Node *node;
+
+ assert(bbox);
+ assert(newElem);
+ assert(!TkQTreeRectIsEmpty(&newElem->bbox));
+
+ if (TkQTreeRectIsEmpty(bbox)) {
+ return NULL;
+ }
+
+ node = NULL;
+
+ for ( ; partialItem; partialItem = partialItem->next) {
+ if (TkQTreeRectIntersects(&partialItem->elem->bbox, bbox)) {
+ if (!node) { node = NewNode(0); }
+ if (TkQTreeRectContainsRect(&partialItem->elem->bbox, bbox)) {
+ AddItem(&node->spanningItem, partialItem->elem);
+ node->countSpanning += 1;
+ } else {
+ AddItem(&node->partialItem, partialItem->elem);
+ node->countPartial += 1;
+ }
+ }
+ }
+
+ if (TkQTreeRectIntersects(&newElem->bbox, bbox)) {
+ if (!node) { node = NewNode(0); }
+ if (TkQTreeRectContainsRect(&newElem->bbox, bbox)) {
+ AddItem(&node->spanningItem, newElem);
+ node->countSpanning += 1;
+ } else {
+ InsertElem(bbox, node, newElem);
+ }
+ }
+
+ return node;
+}
+
+
+/* Helper for function InsertElem. */
+static void
+Split(
+ const TkQTreeRect *bbox,
+ Node *node,
+ Element *elem)
+{
+ TkQTreeCoord xmin, ymin, xmax, ymax;
+ TkQTreeCoord xh, yh;
+ TkQTreeRect quart;
+
+ assert(node);
+ assert(bbox);
+ assert(elem);
+ assert(node->countPartial == MaxNodeItems);
+ assert(!TkQTreeRectIsEmpty(bbox));
+ assert(!TkQTreeRectIsEmpty(&elem->bbox));
+ assert(!TkQTreeRectContainsRect(&elem->bbox, bbox));
+
+ xmin = bbox->xmin;
+ ymin = bbox->ymin;
+ xmax = bbox->xmax;
+ ymax = bbox->ymax;
+ xh = (xmax - xmin)/2;
+ yh = (ymax - ymin)/2;
+
+ assert(xh > 0 || yh > 0);
+
+ TkQTreeRectSet(&quart, xmin, ymin, xmin + xh, ymin + yh);
+ node->ne = FillQuarter(&quart, node->partialItem, elem);
+
+ TkQTreeRectSet(&quart, xmin + xh, ymin, xmax, ymin + yh);
+ node->nw = FillQuarter(&quart, node->partialItem, elem);
+
+ TkQTreeRectSet(&quart, xmin, ymin + yh, xmin + xh, ymax);
+ node->se = FillQuarter(&quart, node->partialItem, elem);
+
+ TkQTreeRectSet(&quart, xmin + xh, ymin + yh, xmax, ymax);
+ node->sw = FillQuarter(&quart, node->partialItem, elem);
+
+ FreeItems(node->partialItem);
+ node->partialItem = NULL;
+ node->countPartial = -1;
+}
+
+
+static void
+InsertElem(
+ const TkQTreeRect *bbox,
+ Node *node,
+ Element *elem)
+{
+ assert(node);
+ assert(elem);
+ assert(bbox);
+ assert(!TkQTreeRectIsEmpty(bbox));
+ assert(!TkQTreeRectIsEmpty(&elem->bbox));
+ assert(!TkQTreeRectContainsRect(&elem->bbox, bbox));
+
+ if (node->countPartial == MaxNodeItems) {
+ Split(bbox, node, elem);
+ } else {
+ AddItem(&node->partialItem, elem);
+ node->countPartial += 1;
+ }
+}
+
+
+static void
+InsertRect(
+ const TkQTreeRect *bbox,
+ Node *node,
+ Element *elem)
+{
+ assert(node);
+ assert(bbox);
+ assert(elem);
+ assert(!TkQTreeRectIsEmpty(bbox));
+ assert(!TkQTreeRectIsEmpty(&elem->bbox));
+
+ if (TkQTreeRectContainsRect(&elem->bbox, bbox)) {
+ AddItem(&node->spanningItem, elem);
+ node->countSpanning += 1;
+ } else if (node->countPartial >= 0) {
+ InsertElem(bbox, node, elem);
+ } else {
+ TkQTreeRect quart;
+
+ TkQTreeCoord xmin = bbox->xmin;
+ TkQTreeCoord ymin = bbox->ymin;
+ TkQTreeCoord xmax = bbox->xmax;
+ TkQTreeCoord ymax = bbox->ymax;
+ TkQTreeCoord xh = (xmax - xmin)/2;
+ TkQTreeCoord yh = (ymax - ymin)/2;
+
+ TkQTreeRectSet(&quart, xmin, ymin, xmin + xh, ymin + yh);
+ if (!TkQTreeRectIsEmpty(&quart) && TkQTreeRectIntersects(&quart, &elem->bbox)) {
+ if (!node->ne) { node->ne = NewNode(-1); }
+ InsertRect(&quart, node->ne, elem);
+ }
+
+ TkQTreeRectSet(&quart, xmin + xh, ymin, xmax, ymin + yh);
+ if (!TkQTreeRectIsEmpty(&quart) && TkQTreeRectIntersects(&quart, &elem->bbox)) {
+ if (!node->nw) { node->nw = NewNode(-1); }
+ InsertRect(&quart, node->nw, elem);
+ }
+
+ TkQTreeRectSet(&quart, xmin, ymin + yh, xmin + xh, ymax);
+ if (!TkQTreeRectIsEmpty(&quart) && TkQTreeRectIntersects(&quart, &elem->bbox)) {
+ if (!node->se) { node->se = NewNode(-1); }
+ InsertRect(&quart, node->se, elem);
+ }
+
+ TkQTreeRectSet(&quart, xmin + xh, ymin + yh, xmax, ymax);
+ assert(!TkQTreeRectIsEmpty(&quart));
+ if (TkQTreeRectIntersects(&quart, &elem->bbox)) {
+ if (!node->sw) { node->sw = NewNode(-1); }
+ InsertRect(&quart, node->sw, elem);
+ }
+ }
+}
+
+
+/* Helper for functions CountPartialItems and CountSpanningItems. */
+static unsigned
+CountItems(
+ const Item *item,
+ uint32_t epoch)
+{
+ unsigned count = 0;
+
+ for ( ; item; item = item->next) {
+ if (item->elem->epoch != epoch) {
+ item->elem->epoch = epoch;
+ if (++count == MaxNodeItems + 1) {
+ return count;
+ }
+ }
+ }
+
+ return count;
+}
+
+
+/* Helper for function DeleteRect. */
+static unsigned
+CountPartialItems(
+ const Node *node,
+ uint32_t epoch)
+{
+ unsigned count;
+
+ if (!node) {
+ count = 0;
+ } else if (node->countPartial == -1) {
+ count = MaxNodeItems + 1;
+ } else {
+ count = CountItems(node->partialItem, epoch);
+ }
+
+ return count;
+}
+
+
+/* Helper for function DeleteRect. */
+static unsigned
+CountSpanningItems(
+ const Node *node,
+ uint32_t epoch)
+{
+ return node ? CountItems(node->spanningItem, epoch) : 0;
+}
+
+
+/* Helper for function TransferItems. */
+static void
+MoveItems(
+ Item **srcPtr,
+ Node *dst,
+ uint32_t epoch)
+{
+ Item *item;
+ Item *prevItem;
+
+ assert(srcPtr);
+ assert(dst);
+
+ item = *srcPtr;
+
+ if (!item) {
+ return;
+ }
+
+ prevItem = NULL;
+
+ while (item) {
+ Item *nextItem = item->next;
+ if (item->elem->epoch != epoch) {
+ if (prevItem) {
+ prevItem->next = item->next;
+ } else {
+ *srcPtr = item->next;
+ }
+ item->next = dst->partialItem;
+ dst->partialItem = item;
+ item->elem->epoch = epoch;
+ } else {
+ prevItem = item;
+ }
+ item = nextItem;
+ }
+}
+
+
+/* Helper for function DeleteRect. */
+static void
+TransferItems(
+ Node *src,
+ Node *dst,
+ uint32_t epoch)
+{
+ if (!src) {
+ return;
+ }
+
+ assert(dst);
+
+ MoveItems(&src->partialItem, dst, epoch);
+ MoveItems(&src->spanningItem, dst, epoch);
+ FreeNode(src);
+}
+
+
+/* Helper for function DeleteRect. */
+static void
+RemoveItem(
+ Item **itemPtr,
+ int *count,
+ TkQTreeUid uid,
+ Element **elem)
+{
+ Item *item, *prevItem;
+
+ assert(itemPtr);
+ assert(count);
+
+ for (item = *itemPtr, prevItem = NULL; item; prevItem = item, item = item->next) {
+ if (item->elem->uid == uid) {
+ *elem = item->elem;
+ if (prevItem) {
+ prevItem->next = item->next;
+ } else {
+ *itemPtr = item->next;
+ }
+ free(item);
+ *count -= 1;
+ DEBUG_ALLOC(tkQTreeCountDestroyItem++);
+ return;
+ }
+ }
+}
+
+
+static Element *
+DeleteRect(
+ TkQTree tree,
+ const TkQTreeRect *bbox,
+ Node *node,
+ const TkQTreeRect *rect,
+ TkQTreeUid uid,
+ uint32_t *epoch)
+{
+ Element *elem, *e;
+
+ assert(tree);
+ assert(rect);
+ assert(!TkQTreeRectIsEmpty(rect));
+ assert(epoch);
+
+ if (!node) {
+ return NULL;
+ }
+
+ elem = NULL;
+
+ RemoveItem(&node->spanningItem, &node->countSpanning, uid, &elem);
+
+ if (node->countPartial >= 0) {
+ RemoveItem(&node->partialItem, &node->countPartial, uid, &elem);
+ } else {
+ TkQTreeRect quart;
+
+ TkQTreeCoord xmin = bbox->xmin;
+ TkQTreeCoord ymin = bbox->ymin;
+ TkQTreeCoord xmax = bbox->xmax;
+ TkQTreeCoord ymax = bbox->ymax;
+ TkQTreeCoord xh = (xmax - xmin)/2;
+ TkQTreeCoord yh = (ymax - ymin)/2;
+
+ if (node->ne) {
+ TkQTreeRectSet(&quart, xmin, ymin, xmin + xh, ymin + yh);
+ if (TkQTreeRectIntersects(&quart, rect)) {
+ if ((e = DeleteRect(tree, &quart, node->ne, rect, uid, epoch))) {
+ elem = e;
+ if (node->ne->countPartial == 0 && node->ne->countSpanning == 0) {
+ FreeNode(node->ne);
+ node->ne = NULL;
+ }
+ }
+ }
+ }
+ if (node->nw) {
+ TkQTreeRectSet(&quart, xmin + xh, ymin, xmax, ymin + yh);
+ if (TkQTreeRectIntersects(&quart, rect)) {
+ if ((e = DeleteRect(tree, &quart, node->nw, rect, uid, epoch))) {
+ elem = e;
+ if (node->nw->countPartial == 0 && node->nw->countSpanning == 0) {
+ FreeNode(node->nw);
+ node->nw = NULL;
+ }
+ }
+ }
+ }
+ if (node->se) {
+ TkQTreeRectSet(&quart, xmin, ymin + yh, xmin + xh, ymax);
+ if (TkQTreeRectIntersects(&quart, rect)) {
+ if ((e = DeleteRect(tree, &quart, node->se, rect, uid, epoch))) {
+ elem = e;
+ if (node->se->countPartial == 0 && node->se->countSpanning == 0) {
+ FreeNode(node->se);
+ node->se = NULL;
+ }
+ }
+ }
+ }
+ if (node->sw) {
+ TkQTreeRectSet(&quart, xmin + xh, ymin + yh, xmax, ymax);
+ if (TkQTreeRectIntersects(&quart, rect)) {
+ if ((e = DeleteRect(tree, &quart, node->sw, rect, uid, epoch))) {
+ elem = e;
+ if (node->sw->countPartial == 0 && node->sw->countSpanning == 0) {
+ FreeNode(node->sw);
+ node->sw = NULL;
+ }
+ }
+ }
+ }
+ }
+
+ if (elem && node->countPartial == -1) {
+ unsigned total = 0;
+
+ *epoch += 1;
+
+ total += CountPartialItems(node->ne, *epoch);
+ total += CountPartialItems(node->nw, *epoch);
+ total += CountPartialItems(node->se, *epoch);
+ total += CountPartialItems(node->sw, *epoch);
+
+ if (total <= MaxNodeItems) {
+ total += CountSpanningItems(node->ne, *epoch);
+ total += CountSpanningItems(node->nw, *epoch);
+ total += CountSpanningItems(node->se, *epoch);
+ total += CountSpanningItems(node->sw, *epoch);
+
+ if (total <= MaxNodeItems) {
+ *epoch += 1;
+ TransferItems(node->ne, node, *epoch);
+ TransferItems(node->nw, node, *epoch);
+ TransferItems(node->se, node, *epoch);
+ TransferItems(node->sw, node, *epoch);
+ node->ne = node->nw = node->se = node->sw = NULL;
+ node->countPartial = total;
+ }
+ }
+ }
+
+ return elem;
+}
+
+
+static Element *
+FindElem(
+ const TkQTreeRect *bbox,
+ const Node *node,
+ const TkQTreeRect *rect,
+ TkQTreeUid uid)
+{
+ const Item *item;
+
+ assert(node);
+ assert(rect);
+ assert(!TkQTreeRectIsEmpty(rect));
+
+ if ((item = node->spanningItem)) {
+ for ( ; item; item = item->next) {
+ if (item->elem->uid == uid) {
+ return item->elem;
+ }
+ }
+ }
+ if (node->countPartial >= 0) {
+ for (item = node->partialItem; item; item = item->next) {
+ if (item->elem->uid == uid) {
+ return item->elem;
+ }
+ }
+ } else {
+ Element *elem;
+ TkQTreeRect quart;
+
+ TkQTreeCoord xmin = bbox->xmin;
+ TkQTreeCoord ymin = bbox->ymin;
+ TkQTreeCoord xmax = bbox->xmax;
+ TkQTreeCoord ymax = bbox->ymax;
+ TkQTreeCoord xh = (xmax - xmin)/2;
+ TkQTreeCoord yh = (ymax - ymin)/2;
+
+ if (node->ne) {
+ TkQTreeRectSet(&quart, xmin, ymin, xmin + xh, ymin + yh);
+ if (TkQTreeRectIntersects(&quart, rect)) {
+ if ((elem = FindElem(&quart, node->ne, rect, uid))) { return elem; }
+ }
+ }
+ if (node->nw) {
+ TkQTreeRectSet(&quart, xmin + xh, ymin, xmax, ymin + yh);
+ if (TkQTreeRectIntersects(&quart, rect)) {
+ if ((elem = FindElem(&quart, node->nw, rect, uid))) { return elem; }
+ }
+ }
+ if (node->se) {
+ TkQTreeRectSet(&quart, xmin, ymin + yh, xmin + xh, ymax);
+ if (TkQTreeRectIntersects(&quart, rect)) {
+ if ((elem = FindElem(&quart, node->se, rect, uid))) { return elem; }
+ }
+ }
+ if (node->sw) {
+ TkQTreeRectSet(&quart, xmin + xh, ymin + yh, xmax, ymax);
+ if (TkQTreeRectIntersects(&quart, rect)) {
+ if ((elem = FindElem(&quart, node->sw, rect, uid))) { return elem; }
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+static void
+RemoveElem(
+ TkQTree tree,
+ Element *elem)
+{
+ assert(tree);
+ assert(elem);
+
+ if (elem->prev) {
+ elem->prev->next = elem->next;
+ } else {
+ tree->elem = elem->next;
+ }
+ if (elem->next) {
+ elem->next->prev = elem->prev;
+ }
+ free(elem);
+ DEBUG_ALLOC(tkQTreeCountDestroyElement++);
+}
+
+
+void
+TkQTreeTraverse(
+ TkQTree tree,
+ TkQTreeCallback cbHit,
+ TkQTreeClientData cbArg)
+{
+ Element *elem;
+
+ assert(tree);
+ assert(cbHit);
+
+ for (elem = tree->elem; elem; elem = elem->next) {
+ if (!cbHit(elem->uid, &elem->bbox, &elem->state, cbArg)) {
+ return;
+ }
+ }
+}
+
+
+bool
+TkQTreeFindState(
+ const TkQTree tree,
+ const TkQTreeRect *rect,
+ TkQTreeUid uid,
+ TkQTreeState *state)
+{
+ const Element *elem;
+
+ assert(tree);
+ assert(rect);
+
+ if (!tree->elem
+ || TkQTreeRectIsEmpty(rect)
+ || !(elem = FindElem(&tree->bbox, tree->root, rect, uid))) {
+ return false;
+ }
+ if (state) {
+ *state = elem->state;
+ }
+ return true;
+}
+
+
+bool
+TkQTreeSetState(
+ const TkQTree tree,
+ const TkQTreeRect *rect,
+ TkQTreeUid uid,
+ TkQTreeState state)
+{
+ Element *elem;
+
+ assert(tree);
+ assert(rect);
+
+ if (!tree->elem
+ || TkQTreeRectIsEmpty(rect)
+ || !(elem = FindElem(&tree->bbox, tree->root, rect, uid))) {
+ return false;
+ }
+ elem->state = state;
+ return true;
+}
+
+
+unsigned
+TkQTreeSearch(
+ const TkQTree tree,
+ TkQTreeCoord x,
+ TkQTreeCoord y,
+ TkQTreeCallback cbHit,
+ TkQTreeClientData cbArg)
+{
+ const Node *node;
+ unsigned hitCount;
+ TkQTreeRect bbox;
+
+ assert(tree);
+
+ if (!tree->elem) {
+ return 0;
+ }
+
+ if (!TkQTreeRectContainsPoint(&tree->bbox, x, y)) {
+ return 0;
+ }
+
+ node = tree->root;
+ bbox = tree->bbox;
+ hitCount = 0;
+
+ while (node) {
+ const Item *item;
+
+ for (item = node->spanningItem; item; item = item->next) {
+ Element *elem = item->elem;
+
+ hitCount += 1;
+ if (cbHit && !cbHit(elem->uid, &elem->bbox, &elem->state, cbArg)) {
+ return hitCount;
+ }
+ }
+
+ if (node->countPartial >= 0) {
+ for (item = node->partialItem; item; item = item->next) {
+ if (TkQTreeRectContainsPoint(&item->elem->bbox, x, y)) {
+ Element *elem = item->elem;
+
+ hitCount += 1;
+ if (cbHit && !cbHit(elem->uid, &elem->bbox, &elem->state, cbArg)) {
+ return hitCount;
+ }
+ }
+ }
+ node = NULL;
+ } else {
+ TkQTreeCoord xh = (bbox.xmax - bbox.xmin)/2;
+ TkQTreeCoord yh = (bbox.ymax - bbox.ymin)/2;
+
+ if (y < yh + bbox.ymin) {
+ if (x < xh + bbox.xmin) {
+ node = node->ne;
+ bbox.xmax = bbox.xmin + xh;
+ bbox.ymax = bbox.ymin + yh;
+ } else {
+ node = node->nw;
+ bbox.xmin += xh;
+ bbox.ymax = bbox.ymin + yh;
+ }
+ } else {
+ if (x < xh + bbox.xmin) {
+ node = node->se;
+ bbox.xmax = bbox.xmin + xh;
+ bbox.ymin += yh;
+ } else {
+ node = node->sw;
+ bbox.xmin += xh;
+ bbox.ymin += yh;
+ }
+ }
+ }
+ }
+
+ return hitCount;
+}
+
+
+bool
+TkQTreeInsertRect(
+ TkQTree tree,
+ const TkQTreeRect *rect,
+ TkQTreeUid uid,
+ TkQTreeState initialState)
+{
+ assert(tree);
+ assert(rect);
+
+ if (TkQTreeRectIsEmpty(rect) || !TkQTreeRectIntersects(&tree->bbox, rect)) {
+ return false;
+ }
+
+ InsertRect(&tree->bbox, tree->root, NewElement(tree, rect, uid, initialState));
+ return true;
+}
+
+
+bool
+TkQTreeDeleteRect(
+ TkQTree tree,
+ const TkQTreeRect *rect,
+ TkQTreeUid uid)
+{
+ Element *elem;
+
+ assert(tree);
+ assert(rect);
+
+ if (TkQTreeRectIsEmpty(rect)) {
+ return false;
+ }
+
+ elem = DeleteRect(tree, &tree->bbox, tree->root, rect, uid, &tree->epoch);
+
+ if (!elem) {
+ return false;
+ }
+
+ RemoveElem(tree, elem);
+ return true;
+}
+
+
+bool
+TkQTreeUpdateRect(
+ TkQTree tree,
+ const TkQTreeRect *oldRect,
+ const TkQTreeRect *newRect,
+ TkQTreeUid uid,
+ TkQTreeState newState)
+{
+ Element *elem;
+
+ assert(tree);
+ assert(newRect);
+
+ if (oldRect && TkQTreeRectIsEqual(oldRect, newRect)) {
+ return true;
+ }
+
+ elem = NULL;
+
+ if (oldRect && !TkQTreeRectIsEmpty(oldRect) && TkQTreeRectIntersects(&tree->bbox, oldRect)) {
+ if ((elem = DeleteRect(tree, &tree->bbox, tree->root, oldRect, uid, &tree->epoch))) {
+ elem->state = newState;
+ elem->bbox = *newRect;
+ }
+ }
+
+ if (TkQTreeRectIsEmpty(newRect) || !TkQTreeRectIntersects(&tree->bbox, newRect)) {
+ return false;
+ }
+
+ InsertRect(&tree->bbox, tree->root, elem ? elem : NewElement(tree, newRect, uid, newState));
+ return true;
+}
+
+
+void
+TkQTreeDestroy(
+ TkQTree *treePtr)
+{
+ assert(treePtr);
+
+ if (*treePtr) {
+ FreeNode((*treePtr)->root);
+ FreeElements((*treePtr)->elem);
+ free(*treePtr);
+ DEBUG_ALLOC(tkQTreeCountDestroyTree++);
+ *treePtr = NULL;
+ }
+}
+
+
+bool
+TkQTreeConfigure(
+ TkQTree *treePtr,
+ const TkQTreeRect *rect)
+{
+ TkQTree tree;
+ Element *elem;
+
+ assert(treePtr);
+ assert(rect);
+
+ if (TkQTreeRectIsEmpty(rect)) {
+ TkQTreeDestroy(treePtr);
+ return false;
+ }
+
+ if ((tree = *treePtr)) {
+ if (TkQTreeRectIsEqual(&tree->bbox, rect)) {
+ return true;
+ }
+ FreeNode(tree->root);
+ } else {
+ DEBUG_ALLOC(tkQTreeCountNewTree++);
+ *treePtr = tree = malloc(sizeof(struct TkQTree));
+ tree->elem = NULL;
+ tree->epoch = 0;
+ }
+
+ tree->root = NewNode(0);
+ tree->bbox = *rect;
+
+ for (elem = tree->elem; elem; ) {
+ Element *next = elem->next;
+
+ if (TkQTreeRectIntersects(&elem->bbox, &tree->bbox)) {
+ InsertRect(&tree->bbox, tree->root, elem);
+ }
+
+ elem = next;
+ }
+
+ return true;
+}
+
+
+const TkQTreeRect *
+TkQTreeGetBoundingBox(
+ const TkQTree tree)
+{
+ assert(tree);
+ return &tree->bbox;
+}
+
+
+#if QTREE_SEARCH_RECTS_CONTAINING
+
+/* *****************************************************************
+ * This is an example how to detect whether a rectangle is contained
+ * in at least one of the rectangles in the tree.
+ *
+ * We assume that TkQTreeInsertRect has been called with intitial
+ * state 0.
+ * *****************************************************************/
+
+typedef struct {
+ TkQTreeCallback cbHit;
+ TkQTreeClientData cbArg;
+ unsigned count;
+ unsigned epoch;
+} MyClientData;
+
+bool HitRectContaining1(
+ TkQTreeUid uid,
+ const TkQTreeRect *rect,
+ TkQTreeState *state,
+ TkQTreeClientData arg)
+{
+ MyClientData *cd = (MyClientData *) arg;
+ *state = arg->epoch;
+ return true;
+}
+
+bool HitRectContaining2(
+ TkQTreeUid uid,
+ const TkQTreeRect *rect,
+ TkQTreeState *state,
+ TkQTreeClientData arg)
+{
+ *state += 1;
+ return true;
+}
+
+bool HitRectContaining3(
+ TkQTreeUid uid,
+ const TkQTreeRect *rect,
+ TkQTreeState *state,
+ TkQTreeClientData arg)
+{
+ MyClientData *cd = (MyClientData *) arg;
+
+ if (++*state == arg->epoch + 3) {
+ if (cd->cbHit) {
+ cd->cbHit(uid, rect, NULL, cd->cbArg);
+ }
+ cd->count += 1;
+ }
+
+ return true;
+}
+
+unsigned
+TkQTreeSearchRectsContaining(
+ const TkQTree tree,
+ const TkQTreeRect *rect,
+ TkQTreeCallback cbHit,
+ TkQTreeClientData cbArg)
+{
+ static unsigned epoch = 0;
+
+ const Node *node;
+ unsigned hitCount;
+ TkQTreeRect bbox;
+ MyClientData cd;
+
+ assert(tree);
+
+ if (!tree->elem) {
+ return 0;
+ }
+
+ if (!TkQTreeRectContainsRect(&tree->bbox, rect)) {
+ return 0;
+ }
+
+ cd.cbHit = cbHit;
+ cd.cbArg = cbArg;
+ cd.count = 0;
+ cd.epoch = (epoch += 4);
+
+ if (TkQTreeSearch(tree, rect->xmin, rect->ymin, HitRectContaining1, NULL) > 0) {
+ if (TkQTreeSearch(tree, rect->xmax, rect->ymin, HitRectContaining2, NULL) > 0) {
+ if (TkQTreeSearch(tree, rect->xmin, rect->ymax, HitRectContaining2, NULL) > 0) {
+ TkQTreeSearch(tree, rect->xmax, rect->ymax, HitRectContaining3, (TkQTreeClientData) cd);
+ }
+ }
+ }
+
+ return cd.count;
+}
+
+#endif /* QTREE_SEARCH_RECTS_CONTAINING */
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+/* Additionally we need stand-alone object code. */
+#define inline extern
+inline bool TkQTreeRectIsEmpty(const TkQTreeRect *rect);
+inline bool TkQTreeRectIsEqual(const TkQTreeRect *rect1, const TkQTreeRect *rect2);
+inline bool TkQTreeRectContainsPoint(const TkQTreeRect *rect, TkQTreeCoord x, TkQTreeCoord y);
+inline bool TkQTreeRectContainsRect(const TkQTreeRect *rect1, const TkQTreeRect *rect2);
+inline bool TkQTreeRectIntersects(const TkQTreeRect *rect1, const TkQTreeRect *rect2);
+inline TkQTreeRect *TkQTreeRectSet(TkQTreeRect *rect,
+ TkQTreeCoord xmin, TkQTreeCoord ymin, TkQTreeCoord xmax, TkQTreeCoord ymax);
+inline TkQTreeRect *TkQTreeRectTranslate(TkQTreeRect *rect, TkQTreeCoord dx, TkQTreeCoord dy);
+#endif /* __STDC_VERSION__ >= 199901L */
+
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkQTree.h b/generic/tkQTree.h
new file mode 100644
index 0000000..81ebebd
--- /dev/null
+++ b/generic/tkQTree.h
@@ -0,0 +1,186 @@
+/*
+ * tkQTree.h --
+ *
+ * Declarations for the Q-Tree implementation.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKQTREE
+#define _TKQTREE
+
+#ifndef _TKINT
+#include "tkInt.h"
+#endif
+
+#include "tkBool.h"
+#include <stdint.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+/* =========================================================================
+ * Definitions for rectangle support.
+ * ========================================================================= */
+
+typedef int32_t TkQTreeCoord;
+
+typedef struct TkQTreeRect {
+ TkQTreeCoord xmin, ymin, xmax, ymax;
+} TkQTreeRect;
+
+/* Return whether rectangle is empty? */
+inline bool TkQTreeRectIsEmpty(const TkQTreeRect *rect);
+
+/* Return whether both rectangles are equal. */
+inline bool TkQTreeRectIsEqual(const TkQTreeRect *rect1, const TkQTreeRect *rect2);
+
+/* Return whether this rectangle contains the specified point. */
+inline bool TkQTreeRectContainsPoint(const TkQTreeRect *rect, TkQTreeCoord x, TkQTreeCoord y);
+
+/* Return whether the first rectangle contains the second one. */
+inline bool TkQTreeRectContainsRect(const TkQTreeRect *rect1, const TkQTreeRect *rect2);
+
+/* Return whether both rectangles are overlapping. */
+inline bool TkQTreeRectIntersects(const TkQTreeRect *rect1, const TkQTreeRect *rect2);
+
+/* Setup a rectangle. */
+inline TkQTreeRect *TkQTreeRectSet(TkQTreeRect *rect,
+ TkQTreeCoord xmin, TkQTreeCoord ymin, TkQTreeCoord xmax, TkQTreeCoord ymax);
+
+/* Translate a rectangle. */
+inline TkQTreeRect *TkQTreeRectTranslate(TkQTreeRect *rect, TkQTreeCoord dx, TkQTreeCoord dy);
+
+/* =========================================================================
+ * Definitions for the Q-Tree (Quartering Tree).
+ * ========================================================================= */
+
+typedef int32_t TkQTreeState;
+typedef uintptr_t TkQTreeUid;
+typedef void *TkQTreeClientData;
+
+struct TkQTree;
+typedef struct TkQTree *TkQTree;
+
+/*
+ * This is defining the callback function for searching and traversing the tree.
+ * Note that argument 'rect' will contain the coordinates according to the current
+ * scroll position. When returning false the search will be terminated, otherwise
+ * the search continues.
+ */
+typedef bool (*TkQTreeCallback)(
+ TkQTreeUid uid, const TkQTreeRect *rect, TkQTreeState *state, TkQTreeClientData arg);
+
+/*
+ * Configure the dimensions of the given Q-Tree. A new tree will be created if
+ * given tree (derefernced treePtr) is NULL. This function returns false if the
+ * specified bounding box is empty, in this case the tree cannot be used.
+ */
+bool TkQTreeConfigure(TkQTree *treePtr, const TkQTreeRect *rect);
+
+/*
+ * Destroy the given tree. Will do nothing if given tree (derefernced treePtr)
+ * is NULL.
+ */
+void TkQTreeDestroy(TkQTree *treePtr);
+
+/*
+ * Return the bounding box of given tree.
+ */
+const TkQTreeRect *TkQTreeGetBoundingBox(const TkQTree tree);
+
+/*
+ * Insert a rectangle into the tree. Each rectangle must be associated with an
+ * unique ID (argument 'uid'). This function returns whether the insertion was
+ * successful. The insertion fails if the given rectangle is empty, or if it
+ * does not overlap with the bounding box of the tree, in these case the
+ * return value will be false.
+ */
+bool TkQTreeInsertRect(TkQTree tree, const TkQTreeRect *rect, TkQTreeUid uid, TkQTreeState initialState);
+
+/*
+ * Update the rectangle which belongs to the given unique ID. The argument
+ * 'oldRect' must exactly match the last provided rectangle (with
+ * TkQTreeInsertRect, or TkQTreeUpdateRect). For convenience it is allowed
+ * to insert a new rectangle (this means new unique ID), in this case
+ * argument 'oldRect' must be NULL. This function returns whether the
+ * insertion/update was successful. The insertion/update fails if the new
+ * rectangle is empty, or if it does not overlap with the bounding box of
+ * the tree, in these cases the return value will be false.
+ */
+bool TkQTreeUpdateRect(TkQTree tree, const TkQTreeRect *oldRect,
+ const TkQTreeRect *newRect, TkQTreeUid uid, TkQTreeState newState);
+
+/*
+ * Delete the specified rectangle from the tree. It is mandatory that the specified
+ * rectangle is exactly matching the last provided rectangle for given uid (with
+ * TkQTreeInsertRect, or TkQTreeUpdateRect). This function returns whether the
+ * deletion was successful.
+ */
+bool TkQTreeDeleteRect(TkQTree tree, const TkQTreeRect *rect, TkQTreeUid uid);
+
+/*
+ * Search for all rectangles containing the given point. For each hit the given
+ * function will be triggered. This function returns the number of hits. It is
+ * allowed to use NULL for argument 'cbHit'.
+ */
+unsigned TkQTreeSearch(const TkQTree tree, TkQTreeCoord x, TkQTreeCoord y,
+ TkQTreeCallback cbHit, TkQTreeClientData cbArg);
+
+/*
+ * Trigger the given callback function for any rectangle in this tree.
+ */
+void TkQTreeTraverse(const TkQTree tree, TkQTreeCallback cbHit, TkQTreeClientData cbArg);
+
+/*
+ * Find the current state of the specified rectangle. This functions returns true
+ * if and only if the specified rectangle has been found. It is allowed to use
+ * NULL for argument 'state', in this case only the existence of the specified
+ * rectangle will be tested.
+ */
+bool TkQTreeFindState(const TkQTree tree, const TkQTreeRect *rect, TkQTreeUid uid, TkQTreeState *state);
+
+/*
+ * Set the current state of the specified rectangle. This functions returns true
+ * if successful, otherwise, if the specified rectangle is not exisiting, false
+ * will be returned.
+ */
+bool TkQTreeSetState(const TkQTree tree, const TkQTreeRect *rect, TkQTreeUid uid, TkQTreeState state);
+
+#if QTREE_SEARCH_RECTS_CONTAINING
+
+/*
+ * Search for all rectangles which are containing the given rectangle. Note that
+ * here the user will receive NULL for parameter 'state' in callback function,
+ * this means that this parameter cannot be used with the use of this function.
+ * This function returns the number of hits. It is allowed to use NULL for
+ * argument 'cbHit'.
+ */
+
+unsigned TkQTreeSearchRectsContaining(const TkQTree tree, const TkQTreeRect *rect,
+ TkQTreeCallback cbHit, TkQTreeClientData cbArg);
+
+#endif /* QTREE_SEARCH_RECTS_CONTAINING */
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+# define _TK_NEED_IMPLEMENTATION
+#include "tkQTreePriv.h"
+# undef _TK_NEED_IMPLEMENTATION
+#else
+# undef inline
+#endif
+
+#endif /* _TKQTREE */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkQTreePriv.h b/generic/tkQTreePriv.h
new file mode 100644
index 0000000..81cae21
--- /dev/null
+++ b/generic/tkQTreePriv.h
@@ -0,0 +1,169 @@
+/*
+ * tkQTreePriv.h --
+ *
+ * Private implementation for Q-Tree.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKQTREE
+# error "do not include this private header file"
+#endif
+
+
+#ifdef _TK_NEED_IMPLEMENTATION
+
+#include <assert.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+inline
+bool
+TkQTreeRectIsEmpty(
+ const TkQTreeRect *rect)
+{
+ assert(rect);
+
+ /*
+ * Normally this natural statement will be used here:
+ *
+ * return rect->xmin >= rect->xmax || rect->ymin >= rect->ymax;
+ *
+ * GCC gives the following warning when optimization is enabled:
+ *
+ * assuming signed overflow does not occur when assuming that (X + c) < X
+ * is always false [-Wstrict-overflow]
+ *
+ * I guess that GCC is converting (optimizing) this statement into:
+ *
+ * return rect->xmin - rect->xmax >= 0 || rect->ymin - rect->ymax >= 0;
+ *
+ * So I will use the latter statement to avoid this warning. Unfortunately
+ * this has the drawback that on machines with different architectures
+ * the transformed statement can be a bit slower than the natural statement,
+ * but I will assume that on such architectures GCC will transform it (back)
+ * to the natural one (when optimization is enabled), without giving a warning,
+ * because the natural statement doesn't use arithmetic.
+ *
+ * Note that this problem only happen with signed values.
+ */
+
+ return rect->xmin - rect->xmax >= 0 || rect->ymin - rect->ymax >= 0;
+}
+
+
+inline
+bool
+TkQTreeRectIsEqual(
+ const TkQTreeRect *rect1,
+ const TkQTreeRect *rect2)
+{
+ assert(rect1);
+ assert(rect2);
+
+ return rect1->xmin == rect2->xmin
+ && rect1->xmax == rect2->xmax
+ && rect1->ymin == rect2->ymin
+ && rect1->ymax == rect2->ymax;
+}
+
+
+inline
+bool
+TkQTreeRectContainsPoint(
+ const TkQTreeRect *rect,
+ TkQTreeCoord x,
+ TkQTreeCoord y)
+{
+ assert(rect);
+
+ return y < rect->ymax && rect->ymin <= y && x < rect->xmax && rect->xmin <= x;
+}
+
+
+inline
+bool
+TkQTreeRectContainsRect(
+ const TkQTreeRect *rect1,
+ const TkQTreeRect *rect2)
+{
+ assert(rect1);
+ assert(rect2);
+
+ return rect1->xmin <= rect2->xmin
+ && rect2->xmax <= rect1->xmax
+ && rect1->ymin <= rect2->ymin
+ && rect2->ymax <= rect1->ymax;
+}
+
+
+inline
+bool
+TkQTreeRectIntersects(
+ const TkQTreeRect *rect1,
+ const TkQTreeRect *rect2)
+{
+ assert(rect1);
+ assert(rect2);
+
+ return rect1->xmin < rect2->xmax
+ && rect2->xmin < rect1->xmax
+ && rect1->ymin < rect2->ymax
+ && rect2->ymin < rect1->ymax;
+}
+
+
+inline
+TkQTreeRect *
+TkQTreeRectSet(
+ TkQTreeRect *rect,
+ TkQTreeCoord xmin,
+ TkQTreeCoord ymin,
+ TkQTreeCoord xmax,
+ TkQTreeCoord ymax)
+{
+ assert(rect);
+ assert(xmin <= xmax);
+ assert(ymin <= ymax);
+
+ rect->xmin = xmin;
+ rect->ymin = ymin;
+ rect->xmax = xmax;
+ rect->ymax = ymax;
+
+ return rect;
+}
+
+
+inline
+TkQTreeRect *
+TkQTreeRectTranslate(
+ TkQTreeRect *rect,
+ TkQTreeCoord dx,
+ TkQTreeCoord dy)
+{
+ assert(rect);
+
+ rect->xmin += dx;
+ rect->xmax += dx;
+ rect->ymin += dy;
+ rect->ymax += dy;
+
+ return rect;
+}
+
+#undef _TK_NEED_IMPLEMENTATION
+#endif /* _TK_NEED_IMPLEMENTATION */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkRangeList.c b/generic/tkRangeList.c
new file mode 100644
index 0000000..5d29b9d
--- /dev/null
+++ b/generic/tkRangeList.c
@@ -0,0 +1,690 @@
+/*
+ * tkRangeList.c --
+ *
+ * This module implements operations on a list of integer ranges.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkRangeList.h"
+#include "tkAlloc.h"
+
+#include <tk.h>
+#include <string.h>
+#include <assert.h>
+
+#if !(__STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900))
+# define _TK_NEED_IMPLEMENTATION
+# include "tkRangeListPriv.h"
+#endif
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? a : b)
+#endif
+#ifndef MAX
+# define MAX(a,b) ((a) < (b) ? b : a)
+#endif
+
+#if TK_CHECK_ALLOCS
+# define DEBUG_ALLOC(expr) expr
+#else
+# define DEBUG_ALLOC(expr)
+#endif
+
+
+#define MEM_SIZE(size) ((unsigned) (Tk_Offset(TkRangeList, items) + (size)*sizeof(TkRange)))
+
+
+DEBUG_ALLOC(unsigned tkRangeListCountNew = 0);
+DEBUG_ALLOC(unsigned tkRangeListCountDestroy = 0);
+
+
+#if !NDEBUG
+
+static int
+ComputeRangeSize(
+ const TkRangeList *ranges)
+{
+ unsigned i;
+ int count = 0;
+
+ for (i = 0; i < ranges->size; ++i) {
+ count += TkRangeSpan(ranges->items + i);
+ }
+
+ return count;
+}
+
+#endif /* !NDEBUG */
+
+
+static TkRange *
+LowerBound(
+ TkRange *first,
+ TkRange *last,
+ int low)
+{
+ /*
+ * Note that we want to amalgamate adjacent ranges, and this binary
+ * search is designed for this requirement.
+ *
+ * Example for ranges={{2,3}{6,7}}:
+ *
+ * low < 5 -> {2,3}
+ * low = 5,6,7,8 -> {6,7}
+ * low > 8 -> last
+ */
+
+ if (first == last) {
+ return first;
+ }
+
+ low -= 1;
+
+ do {
+ TkRange *mid = first + (last - first)/2;
+
+ if (mid->high < low) {
+ first = mid + 1;
+ } else {
+ last = mid;
+ }
+ } while (first != last);
+
+ return first;
+}
+
+
+static TkRange *
+Increase(
+ TkRangeList **rangesPtr)
+{
+ TkRangeList *ranges = *rangesPtr;
+
+ if (ranges->size == ranges->capacity) {
+ ranges->capacity = MAX(1, 2*ranges->capacity);
+ ranges = realloc(ranges, MEM_SIZE(ranges->capacity));
+ *rangesPtr = ranges;
+ }
+
+ return ranges->items + ranges->size++;
+}
+
+
+static TkRange *
+Insert(
+ TkRangeList **rangesPtr,
+ TkRange *entry)
+{
+ TkRangeList *ranges = *rangesPtr;
+ unsigned pos = entry - ranges->items;
+
+ if (ranges->size == ranges->capacity) {
+ TkRangeList *newRanges;
+ TkRange *newEntry;
+
+ ranges->capacity = MAX(1, 2*ranges->capacity);
+ newRanges = malloc(MEM_SIZE(ranges->capacity));
+ newRanges->capacity = ranges->capacity;
+ newRanges->size = ranges->size + 1;
+ newRanges->count = ranges->count;
+ newEntry = newRanges->items + pos;
+ memcpy(newRanges->items, ranges->items, pos*sizeof(TkRange));
+ memcpy(newEntry + 1, entry, (ranges->size - pos)*sizeof(TkRange));
+ free(ranges);
+ *rangesPtr = ranges = newRanges;
+ entry = newEntry;
+ } else {
+ memmove(entry + 1, entry, (ranges->size - pos)*sizeof(TkRange));
+ ranges->size += 1;
+ }
+
+ return entry;
+}
+
+
+static void
+Amalgamate(
+ TkRangeList *ranges,
+ TkRange *curr)
+{
+ const TkRange *last = ranges->items + ranges->size;
+ TkRange *next = curr + 1;
+ int high = curr->high;
+
+ while (next != last && high + 1 >= next->low) {
+ if (high >= next->high) {
+ ranges->count -= TkRangeSpan(next);
+ } else if (high >= next->low) {
+ ranges->count -= high - next->low + 1;
+ }
+ next += 1;
+ }
+
+ if (next != curr + 1) {
+ curr->high = MAX((next - 1)->high, high);
+ memmove(curr + 1, next, (last - next)*sizeof(TkRange));
+ ranges->size -= (next - curr) - 1;
+ assert(ComputeRangeSize(ranges) == ranges->count);
+ }
+}
+
+
+TkRangeList *
+TkRangeListCreate(unsigned capacity)
+{
+ TkRangeList *ranges;
+
+ ranges = malloc(MEM_SIZE(capacity));
+ ranges->size = 0;
+ ranges->capacity = capacity;
+ ranges->count = 0;
+ DEBUG_ALLOC(tkRangeListCountNew++);
+ return ranges;
+}
+
+
+TkRangeList *
+TkRangeListCopy(
+ const TkRangeList *ranges)
+{
+ TkRangeList *copy;
+ unsigned memSize;
+
+ assert(ranges);
+
+ copy = malloc(memSize = MEM_SIZE(ranges->size));
+ memcpy(copy, ranges, memSize);
+ DEBUG_ALLOC(tkRangeListCountNew++);
+ return copy;
+}
+
+
+void
+TkRangeListDestroy(
+ TkRangeList **rangesPtr)
+{
+ assert(rangesPtr);
+
+ if (*rangesPtr) {
+ free(*rangesPtr);
+ *rangesPtr = NULL;
+ DEBUG_ALLOC(tkRangeListCountDestroy++);
+ }
+}
+
+
+void
+TkRangeListClear(
+ TkRangeList *ranges)
+{
+ assert(ranges);
+
+ ranges->size = 0;
+ ranges->count = 0;
+}
+
+
+bool
+TkRangeListContainsAny(
+ const TkRangeList *ranges,
+ int low,
+ int high)
+{
+ const TkRange *last;
+ const TkRange *entry;
+
+ assert(ranges);
+
+ last = ranges->items + ranges->size;
+ entry = LowerBound((TkRange *) ranges->items, (TkRange *) last, low);
+
+ if (entry == last) {
+ return false;
+ }
+
+ if (entry->high == low + 1 && ++entry == last) {
+ return false;
+ }
+
+ return high >= entry->low;
+}
+
+
+void
+TkRangeListTruncateAtFront(
+ TkRangeList *ranges,
+ int untilThisValue)
+{
+ TkRange *last;
+ TkRange *curr;
+ TkRange *r;
+
+ assert(ranges);
+
+ last = ranges->items + ranges->size;
+ curr = LowerBound(ranges->items, last, untilThisValue);
+
+ if (curr == last) {
+ return;
+ }
+
+ if (curr->low <= untilThisValue) {
+ if (untilThisValue < curr->high) {
+ ranges->count -= untilThisValue - curr->low + 1;
+ curr->low = untilThisValue + 1;
+ } else {
+ curr += 1;
+ }
+ }
+
+ if (curr != ranges->items) {
+ for (r = ranges->items; r != curr; ++r) {
+ ranges->count -= TkRangeSpan(r);
+ }
+ memmove(ranges->items, curr, (last - curr)*sizeof(TkRange));
+ ranges->size -= curr - ranges->items;
+ }
+
+ assert(ComputeRangeSize(ranges) == ranges->count);
+}
+
+
+void
+TkRangeListTruncateAtEnd(
+ TkRangeList *ranges,
+ int maxValue)
+{
+ TkRange *last;
+ TkRange *curr;
+
+ assert(ranges);
+
+ last = ranges->items + ranges->size;
+ curr = LowerBound(ranges->items, last, maxValue);
+
+ if (curr == last) {
+ return;
+ }
+
+ if (curr->low <= maxValue) {
+ if (curr->high > maxValue) {
+ ranges->count -= curr->high - maxValue;
+ curr->high = maxValue;
+ }
+ curr += 1;
+ }
+
+ ranges->size -= last - curr;
+
+ for ( ; curr != last; ++curr) {
+ ranges->count -= TkRangeSpan(curr);
+ }
+
+ assert(ComputeRangeSize(ranges) == ranges->count);
+}
+
+
+const TkRange *
+TkRangeListFind(
+ const TkRangeList *ranges,
+ int value)
+{
+ const TkRange *last;
+ const TkRange *entry;
+
+ assert(ranges);
+
+ last = ranges->items + ranges->size;
+ entry = LowerBound((TkRange *) ranges->items, (TkRange *) last, value);
+
+ if (entry == last || entry->low > value || value > entry->high) {
+ return NULL;
+ }
+ return entry;
+}
+
+
+const TkRange *
+TkRangeListFindNearest(
+ const TkRangeList *ranges,
+ int value)
+{
+ const TkRange *last;
+ const TkRange *entry;
+
+ assert(ranges);
+
+ last = ranges->items + ranges->size;
+ entry = LowerBound((TkRange *) ranges->items, (TkRange *) last, value);
+
+ if (entry == last) {
+ return NULL;
+ }
+ if (value > entry->high) {
+ if (++entry == last) {
+ return NULL;
+ }
+ }
+ return entry;
+}
+
+
+TkRangeList *
+TkRangeListAdd(
+ TkRangeList *ranges,
+ int low,
+ int high)
+{
+ TkRange *last;
+ TkRange *curr;
+
+ assert(low <= high);
+
+ last = ranges->items + ranges->size;
+
+ if (ranges->size == 0) {
+ curr = last;
+ } else if (low >= (last - 1)->low) {
+ /* catch a frequent case */
+ curr = (low > (last - 1)->high + 1) ? last : last - 1;
+ } else {
+ curr = LowerBound(ranges->items, last, low);
+ }
+
+ if (curr == last) {
+ /* append new entry */
+ curr = Increase(&ranges);
+ curr->low = low;
+ curr->high = high;
+ ranges->count += high - low + 1;
+ } else if (low + 1 < curr->low) {
+ if (curr->low <= high + 1) {
+ /* new lower bound of current range */
+ ranges->count += curr->low - low;
+ curr->low = low;
+ } else {
+ /* insert new entry before current */
+ curr = Insert(&ranges, curr);
+ curr->low = low;
+ curr->high = high;
+ ranges->count += high - low + 1;
+ }
+ } else {
+ if (low + 1 == curr->low) {
+ /* new lower bound of current range */
+ ranges->count += 1;
+ curr->low = low;
+ }
+ if (last - 1 != curr && (last - 1)->high <= high) {
+ /* catch a frequent case: we don't need the succeeding items */
+ for (--last; last > curr; --last) {
+ ranges->count -= TkRangeSpan(last);
+ }
+ ranges->count += high - curr->high;
+ ranges->size = (curr + 1) - ranges->items;
+ curr->high = high;
+ } else if (curr->high < high) {
+ /* new upper bound of current range */
+ ranges->count += high - curr->high;
+ curr->high = high;
+ /* possibly we have to amalgamate succeeding items */
+ Amalgamate(ranges, curr);
+ }
+ }
+
+ return ranges;
+}
+
+
+TkRangeList *
+TkRangeListInsert(
+ TkRangeList *ranges,
+ int low,
+ int high)
+{
+ TkRange *curr;
+ TkRange *last;
+ int span = high - low + 1;
+
+ assert(ranges);
+ assert(low <= high);
+
+ last = ranges->items + ranges->size;
+ curr = LowerBound(ranges->items, last, low);
+
+ /* {2,2} : insert {0,0} -> {0,0}{3,3} */
+ /* {2,2} : insert {1,1} -> {1,1}{3,3} */
+ /* {2,2} : insert {2,2} -> {2,3} */
+ /* {2,2} : insert {3,3} -> {2,3} */
+ /* {2,4} : insert {3,3} -> {2,5} */
+ /* {2,4} : insert {4,4} -> {2,5} */
+ /* {2,4} : insert {5,5} -> {2,5} */
+
+ if (curr == last || low > curr->high + 1) {
+ /* append new entry */
+ curr = Increase(&ranges);
+ curr->low = low;
+ curr->high = high;
+ } else {
+ if (low >= curr->low) {
+ /* new upper bound of current range */
+ curr->high += span;
+ } else {
+ /* insert new entry before current */
+ curr = Insert(&ranges, curr);
+ curr->low = low;
+ curr->high = high;
+ }
+ /* adjust all successors */
+ last = ranges->items + ranges->size;
+ for (++curr; curr != last; ++curr) {
+ curr->low += span;
+ curr->high += span;
+ }
+ }
+
+ ranges->count += span;
+ assert(ComputeRangeSize(ranges) == ranges->count);
+ return ranges;
+}
+
+
+TkRangeList *
+TkRangeListRemove(
+ TkRangeList *ranges,
+ int low,
+ int high)
+{
+ TkRange *curr;
+ TkRange *last;
+ int span;
+
+ assert(ranges);
+ assert(low <= high);
+
+ if (ranges->size == 0) {
+ return ranges;
+ }
+
+ last = ranges->items + ranges->size;
+ low = MAX(low, ranges->items[0].low);
+ high = MIN(high, (last - 1)->high);
+
+ if (low > high) {
+ return ranges;
+ }
+
+ span = high - low + 1;
+ curr = LowerBound(ranges->items, last, low);
+
+ if (curr != last) {
+ TkRange *next;
+ unsigned size;
+
+ if (high < curr->high) {
+ if (curr->low < low) {
+ /* Example: cur:{1,4} - arg:{2,3} -> {1,1}{4,4} */
+ int h = curr->high;
+ ranges->count -= span;
+ curr->high = low - 1;
+ low = high + 1;
+ curr = (curr == last) ? Increase(&ranges) : Insert(&ranges, curr + 1);
+ curr->low = low;
+ curr->high = h;
+ } else if (curr->low <= high) {
+ /* Example: cur:{1,4} - arg:{1,3} -> {4,4} */
+ int low = high + 1;
+ ranges->count -= low - curr->low;
+ curr->low = low;
+ }
+ } else {
+ if (curr->low < low && low <= curr->high) {
+ /* Example: cur:{1,7} - arg:{2,5} -> {1,1} */
+ /* Example: cur:{1,3} - arg:{3,6} -> {1,2} */
+ /* Example: cur:{1,1} - arg:{2,5} -> {1,1} */
+ int high = low - 1;
+ ranges->count -= curr->high - high;
+ curr->high = high;
+ curr += 1;
+ } else if (curr->high < low) {
+ curr += 1;
+ }
+
+ for (next = curr; next != last && next->high <= high; ++next) {
+ ranges->count -= TkRangeSpan(next);
+ }
+
+ memmove(curr, next, (last - next)*sizeof(TkRange));
+ ranges->size -= (size = next - curr);
+ last -= size;
+
+ if (curr != last) {
+ if (curr->low <= high) {
+ ranges->count -= high + 1 - curr->low;
+ curr->low = high + 1;
+ }
+ }
+ }
+ }
+
+ assert(ComputeRangeSize(ranges) == ranges->count);
+ return ranges;
+}
+
+
+TkRangeList *
+TkRangeListDelete(
+ TkRangeList *ranges,
+ int low,
+ int high)
+{
+ TkRange *curr;
+ TkRange *last;
+ int span;
+ int lower;
+
+ assert(ranges);
+ assert(low <= high);
+
+ if (ranges->size == 0 || low > TkRangeListHigh(ranges)) {
+ return ranges;
+ }
+
+ last = ranges->items + ranges->size;
+ span = high - low + 1;
+ low = MAX(low, TkRangeListLow(ranges));
+ high = MIN(high, TkRangeListHigh(ranges));
+ curr = LowerBound(ranges->items, last, low);
+ lower = high;
+
+ if (curr != last) {
+ TkRange *next;
+ unsigned size;
+
+ if (curr->low < low && low <= curr->high) {
+ /* Example: cur:{1,7} - arg:{2,5} -> {1,3} */
+ /* Example: cur:{1,3} - arg:{3,6} -> {1,2} */
+ /* Example: cur:{1,1} - arg:{2,5} -> {1,1} */
+ int high = MAX(low - 1, curr->high - span);
+ ranges->count -= curr->high - high;
+ curr->high = high;
+ next = curr + 1;
+ if (next != last && curr->high + 1 >= next->low - span) {
+ /* Example: curr:{0,3}{8,9}{29,33} - arg:{1,30} -> {0,3} */
+ lower = curr->low;
+ } else {
+ curr = next;
+ }
+ } else if (curr->high < low) {
+ curr += 1;
+ }
+
+ for (next = curr; next != last && next->high <= high; ++next) {
+ ranges->count -= TkRangeSpan(next);
+ }
+
+ memmove(curr, next, (last - next)*sizeof(TkRange));
+ ranges->size -= (size = next - curr);
+ last -= size;
+
+ if (curr != last) {
+ if (lower < curr->low) {
+ lower += span;
+ ranges->count -= lower - curr->low;
+ curr->low = lower;
+ } else if (curr->low <= high) {
+ ranges->count -= high + 1 - curr->low;
+ curr->low = high + 1;
+ }
+ for (next = curr; next != last; next += 1) {
+ next->low -= span;
+ next->high -= span;
+ }
+ }
+ }
+
+ assert(ComputeRangeSize(ranges) == ranges->count);
+ return ranges;
+}
+
+
+#if !NDEBUG
+
+void
+TkRangeListPrint(
+ const TkRangeList *ranges)
+{
+ unsigned i;
+
+ for (i = 0; i < ranges->size; ++i) {
+ printf("{%d,%d} ", ranges->items[i].low, ranges->items[i].high);
+ }
+ printf("(%d)\n", ranges->count);
+}
+
+#endif /* !NDEBUG */
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+/* Additionally we need stand-alone object code. */
+#define inline extern
+inline int TkRangeSpan(const TkRange *range);
+inline bool TkRangeTest(const TkRange *range, int value);
+inline int TkRangeListLow(const TkRangeList *ranges);
+inline int TkRangeListHigh(const TkRangeList *ranges);
+inline unsigned TkRangeListSpan(const TkRangeList *ranges);
+inline unsigned TkRangeListCount(const TkRangeList *ranges);
+inline unsigned TkRangeListSize(const TkRangeList *ranges);
+inline const TkRange *TkRangeListAccess(const TkRangeList *ranges, unsigned index);
+inline const TkRange *TkRangeListFirst(const TkRangeList *ranges);
+inline const TkRange *TkRangeListNext(const TkRangeList *ranges, const TkRange *item);
+inline bool TkRangeListIsEmpty(const TkRangeList *ranges);
+inline bool TkRangeListContains(const TkRangeList *ranges, int value);
+inline bool TkRangeListContainsRange(const TkRangeList *ranges, int low, int high);
+#endif /* __STDC_VERSION__ >= 199901L */
+
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkRangeList.h b/generic/tkRangeList.h
new file mode 100644
index 0000000..5bc0d81
--- /dev/null
+++ b/generic/tkRangeList.h
@@ -0,0 +1,215 @@
+/*
+ * tkRangeList.h --
+ *
+ * This module implements operations on a list of integer ranges.
+ * Note that the current implementation expects short lists of
+ * ranges, it is quite slow for large list sizes (large number of
+ * range items).
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKRANGELIST
+#define _TKRANGELIST
+
+#include "tkBool.h"
+
+#if defined(__GNUC__) || defined(__clang__)
+# define __warn_unused__ __attribute__((warn_unused_result))
+#else
+# define __warn_unused__
+#endif
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+typedef struct TkRange {
+ int low;
+ int high;
+} TkRange;
+
+/*
+ * Return the span of given range.
+ */
+inline int TkRangeSpan(const TkRange *range);
+
+/*
+ * Test whether given range contains the specified value.
+ */
+inline bool TkRangeTest(const TkRange *range, int value);
+
+
+typedef struct TkRangeList {
+ unsigned size;
+ unsigned capacity;
+ unsigned count;
+ TkRange items[1];
+} TkRangeList;
+
+/*
+ * Create a range list, and reserve some space for range entries.
+ */
+TkRangeList *TkRangeListCreate(unsigned capacity) __warn_unused__;
+
+/*
+ * Make a copy of given list.
+ */
+TkRangeList *TkRangeListCopy(const TkRangeList *ranges) __warn_unused__;
+
+/*
+ * Destroy the range list, the derefenced pointer can be NULL (in this case the
+ * list is already destroyed).
+ */
+void TkRangeListDestroy(TkRangeList **rangesPtr);
+
+/*
+ * Clear the given list of ranges.
+ */
+void TkRangeListClear(TkRangeList *ranges);
+
+/*
+ * Truncate this list at front until given value (inclusive). This means that after
+ * truncation the lowest value in this list will be larger than 'untilThisValue'.
+ */
+void TkRangeListTruncateAtFront(TkRangeList *ranges, int untilThisValue);
+
+/*
+ * Truncate this list at end with given value (exclusive). This means that after
+ * truncation the highest value in this list will be less or equal to 'maxValue'.
+ */
+void TkRangeListTruncateAtEnd(TkRangeList *ranges, int maxValue);
+
+/*
+ * Return the lower value of the entry with lowest order (ths lowest value inside
+ * the whole list).
+ */
+inline int TkRangeListLow(const TkRangeList *ranges);
+
+/*
+ * Return the upper value of the entry with highest order (ths highest value inside
+ * the whole list).
+ */
+inline int TkRangeListHigh(const TkRangeList *ranges);
+
+/*
+ * Return the span of the whole list (= TkRangeListHigh(ranges) - TkRangeListLow(ranges) + 1).
+ */
+inline unsigned TkRangeListSpan(const TkRangeList *ranges);
+
+/*
+ * Return the number of integers contained in this list.
+ */
+inline unsigned TkRangeListCount(const TkRangeList *ranges);
+
+/*
+ * Return the number of entries (pairs) in this list.
+ */
+inline unsigned TkRangeListSize(const TkRangeList *ranges);
+
+/*
+ * Return a specific entry (pair), the index must not exceed the size of this list.
+ */
+inline const TkRange *TkRangeListAccess(const TkRangeList *ranges, unsigned index);
+
+/*
+ * Find entry (pair) which contains the given value. NULL will be returned if
+ * this value is not contained in this list.
+ */
+const TkRange *TkRangeListFind(const TkRangeList *ranges, int value);
+
+/*
+ * Find entry (pair) which contains the given value. If this value is not contained
+ * in given list then return the item with a low value nearest to specified value.
+ * But it never returns an item with a high value less than given value, so it's
+ * possible that NULL we returned.
+ */
+const TkRange *TkRangeListFindNearest(const TkRangeList *ranges, int value);
+
+/*
+ * Return the first item in given list, can be NULL if list is empty.
+ */
+inline const TkRange *TkRangeListFirst(const TkRangeList *ranges);
+
+/*
+ * Return the next item in given list, can be NULL if at end of list.
+ */
+inline const TkRange *TkRangeListNext(const TkRangeList *ranges, const TkRange *item);
+
+/*
+ * Return whether this list is empty.
+ */
+inline bool TkRangeListIsEmpty(const TkRangeList *ranges);
+
+/*
+ * Return whether the given value is contained in this list.
+ */
+inline bool TkRangeListContains(const TkRangeList *ranges, int value);
+
+/*
+ * Return whether the given range is contained in this list.
+ */
+inline bool TkRangeListContainsRange(const TkRangeList *ranges, int low, int high);
+
+/*
+ * Return whether any value of the given range is contained in this list.
+ */
+bool TkRangeListContainsAny(const TkRangeList *ranges, int low, int high);
+
+/*
+ * Add given range to this list. Adjacent entries (pairs) will be amalgamated
+ * automatically.
+ */
+TkRangeList *TkRangeListAdd(TkRangeList *ranges, int low, int high) __warn_unused__;
+
+/*
+ * Remove given range from list. Adjacent entries (pairs) will be amalgamated
+ * automatically.
+ */
+TkRangeList *TkRangeListRemove(TkRangeList *ranges, int low, int high);
+
+/*
+ * Insert given range to this list. This method has the side effect that all contained
+ * integers with a value higher than the 'high' value will be increased by the span of
+ * the given range (high - low + 1). Adjacent entries (pairs) will be amalgamated
+ * automatically.
+ *
+ * Example: TkRangeListInsert({{5,6} {8,9}}, 1, 5) -> {{1,5} {10,11} {13,14}}
+ */
+TkRangeList *TkRangeListInsert(TkRangeList *ranges, int low, int high) __warn_unused__;
+
+/*
+ * Delete given range from list. This method has the side effect that all contained
+ * integers with a value higher than the 'low' value will be decreased by the span of
+ * the given range (high - low + 1). Adjacent entries (pairs) will be amalgamated
+ * automatically.
+ *
+ * Example: TkRangeListDelete({{5,6} {8,9}}, 1, 5) -> {{1} {3,4}}
+ */
+TkRangeList *TkRangeListDelete(TkRangeList *ranges, int low, int high);
+
+#if !NDEBUG
+void TkRangeListPrint(const TkRangeList *ranges);
+#endif
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+# define _TK_NEED_IMPLEMENTATION
+# include "tkRangeListPriv.h"
+# undef _TK_NEED_IMPLEMENTATION
+#else
+# undef inline
+#endif
+
+#endif /* _TKRANGELIST */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkRangeListPriv.h b/generic/tkRangeListPriv.h
new file mode 100644
index 0000000..3541b43
--- /dev/null
+++ b/generic/tkRangeListPriv.h
@@ -0,0 +1,174 @@
+/*
+ * tkRangeListPriv.h --
+ *
+ * Private implementation for range list.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKRANGELIST
+# error "do not include this private header file"
+#endif
+
+
+#ifdef _TK_NEED_IMPLEMENTATION
+
+#include <stddef.h>
+#include <assert.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+inline
+int
+TkRangeSpan(
+ const TkRange *range)
+{
+ assert(range);
+ return range->high - range->low + 1;
+}
+
+
+inline
+bool
+TkRangeTest(
+ const TkRange *range,
+ int value)
+{
+ assert(range);
+ return range->low <= value && value <= range->high;
+}
+
+
+inline
+bool
+TkRangeListIsEmpty(
+ const TkRangeList *ranges)
+{
+ assert(ranges);
+ return ranges->size == 0;
+}
+
+
+inline
+int
+TkRangeListLow(
+ const TkRangeList *ranges)
+{
+ assert(ranges);
+ assert(!TkRangeListIsEmpty(ranges));
+ return ranges->items[0].low;
+}
+
+
+inline
+int
+TkRangeListHigh(
+ const TkRangeList *ranges)
+{
+ assert(ranges);
+ assert(!TkRangeListIsEmpty(ranges));
+ return ranges->items[ranges->size - 1].high;
+}
+
+
+inline
+unsigned
+TkRangeListSpan(
+ const TkRangeList *ranges)
+{
+ assert(ranges);
+ return ranges->size ? TkRangeListHigh(ranges) - TkRangeListLow(ranges) + 1 : 0;
+}
+
+
+inline
+unsigned
+TkRangeListSize(
+ const TkRangeList *ranges)
+{
+ assert(ranges);
+ return ranges->size;
+}
+
+
+inline
+unsigned
+TkRangeListCount(
+ const TkRangeList *ranges)
+{
+ assert(ranges);
+ return ranges->count;
+}
+
+
+inline
+const TkRange *
+TkRangeListAccess(
+ const TkRangeList *ranges,
+ unsigned index)
+{
+ assert(ranges);
+ assert(index < TkRangeListSize(ranges));
+ return &ranges->items[index];
+}
+
+
+inline
+bool
+TkRangeListContains(
+ const TkRangeList *ranges,
+ int value)
+{
+ return !!TkRangeListFind(ranges, value);
+}
+
+
+inline
+bool
+TkRangeListContainsRange(
+ const TkRangeList *ranges,
+ int low,
+ int high)
+{
+ const TkRange *range = TkRangeListFind(ranges, low);
+ return range && range->high <= high;
+}
+
+
+inline
+const TkRange *
+TkRangeListFirst(
+ const TkRangeList *ranges)
+{
+ assert(ranges);
+ return ranges->size == 0 ? NULL : ranges->items;
+}
+
+
+inline
+const TkRange *
+TkRangeListNext(
+ const TkRangeList *ranges,
+ const TkRange *item)
+{
+ assert(item);
+ assert(ranges);
+ assert(ranges->items <= item && item < ranges->items + ranges->size);
+ return ++item == ranges->items + ranges->size ? NULL : item;
+}
+
+#undef _TK_NEED_IMPLEMENTATION
+#endif /* _TK_NEED_IMPLEMENTATION */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkText.c b/generic/tkText.c
index 4f25222..e935ccf 100644
--- a/generic/tkText.c
+++ b/generic/tkText.c
@@ -9,6 +9,7 @@
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1996 Sun Microsystems, Inc.
* Copyright (c) 1999 by Scriptics Corporation.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -16,11 +17,42 @@
#include "default.h"
#include "tkInt.h"
-#include "tkUndo.h"
+#include "tkText.h"
+#include "tkTextUndo.h"
+#include "tkTextTagSet.h"
+#include "tkBitField.h"
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+
+#if !(__STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900))
+# define _TK_NEED_IMPLEMENTATION
+# include "tkTextPriv.h"
+#endif
-#if defined(MAC_OSX_TK)
-#define Style TkStyle
-#define DInfo TkDInfo
+#ifndef MAX
+# define MAX(a,b) ((a) < (b) ? b : a)
+#endif
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? a : b)
+#endif
+
+#if NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
+
+/*
+ * Support of tk8.5.
+ */
+#ifdef CONST
+# undef CONST
+#endif
+#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5
+# define CONST
+#else
+# define CONST const
#endif
/*
@@ -29,7 +61,7 @@
* which are the equivalent of at least a character width apart.
*/
-#if (TK_MAJOR_VERSION < 9)
+#if TK_MAJOR_VERSION < 9
#define _TK_ALLOW_DECREASING_TABS
#endif
@@ -40,7 +72,7 @@
* commonly used functions. Must be > 0.
*/
-#define PIXEL_CLIENTS 5
+#define PIXEL_CLIENTS 8
/*
* The 'TkTextState' enum in tkText.h is used to define a type for the -state
@@ -48,8 +80,26 @@
* table below.
*/
-static const char *const stateStrings[] = {
- "disabled", "normal", NULL
+static const char *CONST stateStrings[] = {
+ "disabled", "normal", "readonly", NULL
+};
+
+/*
+ * The 'TkTextTagging' enum in tkText.h is used to define a type for the -tagging
+ * option of the Text widget. These values are used as indices into the string table below.
+ */
+
+static const char *CONST taggingStrings[] = {
+ "within", "gravity", "none", NULL
+};
+
+/*
+ * The 'TkTextJustify' enum in tkText.h is used to define a type for the -justify option of
+ * the Text widget. These values are used as indices into the string table below.
+ */
+
+static const char *CONST justifyStrings[] = {
+ "left", "right", "full", "center", NULL
};
/*
@@ -58,8 +108,18 @@ static const char *const stateStrings[] = {
* table below.
*/
-static const char *const wrapStrings[] = {
- "char", "none", "word", NULL
+static const char *CONST wrapStrings[] = {
+ "char", "none", "word", "codepoint", NULL
+};
+
+/*
+ * The 'TkSpacing' enum in tkText.h is used to define a type for the -spacing
+ * option of the Text widget. These values are used as indices into the string
+ * table below.
+ */
+
+static const char *CONST spaceModeStrings[] = {
+ "none", "exact", "trim", NULL
};
/*
@@ -68,7 +128,7 @@ static const char *const wrapStrings[] = {
* the string table below.
*/
-static const char *const tabStyleStrings[] = {
+static const char *CONST tabStyleStrings[] = {
"tabular", "wordprocessor", NULL
};
@@ -78,11 +138,24 @@ static const char *const tabStyleStrings[] = {
* indice into the string table below.
*/
-static const char *const insertUnfocussedStrings[] = {
+static const char *CONST insertUnfocussedStrings[] = {
"hollow", "none", "solid", NULL
};
/*
+ * The 'TkTextHyphenRule' enum in tkText.h is used to define a type for the
+ * -hyphenrules option of the Text widget. These values are used for applying
+ * hyphen rules to soft hyphens.
+ */
+
+static const char *const hyphenRuleStrings[] = {
+ "ck", "doubledigraph", "doublevowel", "gemination", "repeathyphen", "trema",
+ "tripleconsonant" /* don't appebd a trailing NULL */
+};
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+
+/*
* The following functions and custom option type are used to define the
* "line" option type, and thereby handle the text widget '-startline',
* '-endline' configuration options which are of that type.
@@ -93,20 +166,15 @@ static const char *const insertUnfocussedStrings[] = {
* freeing.
*/
-static int SetLineStartEnd(ClientData clientData,
- Tcl_Interp *interp, Tk_Window tkwin,
- Tcl_Obj **value, char *recordPtr,
- int internalOffset, char *oldInternalPtr,
+static int SetLineStartEnd(ClientData clientData, Tcl_Interp *interp, Tk_Window tkwin,
+ Tcl_Obj **value, char *recordPtr, int internalOffset, char *oldInternalPtr,
int flags);
-static Tcl_Obj * GetLineStartEnd(ClientData clientData,
- Tk_Window tkwin, char *recordPtr,
+static Tcl_Obj * GetLineStartEnd(ClientData clientData, Tk_Window tkwin, char *recordPtr,
int internalOffset);
-static void RestoreLineStartEnd(ClientData clientData,
- Tk_Window tkwin, char *internalPtr,
+static void RestoreLineStartEnd(ClientData clientData, Tk_Window tkwin, char *internalPtr,
char *oldInternalPtr);
-static int ObjectIsEmpty(Tcl_Obj *objPtr);
-static const Tk_ObjCustomOption lineOption = {
+static CONST Tk_ObjCustomOption lineOption = {
"line", /* name */
SetLineStartEnd, /* setProc */
GetLineStartEnd, /* getProc */
@@ -115,146 +183,176 @@ static const Tk_ObjCustomOption lineOption = {
0
};
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+/*
+ * The following functions and custom option type are used to define the
+ * "index" option type, and thereby handle the text widget '-startindex',
+ * '-endindex' configuration options which are of that type.
+ */
+
+static int SetTextStartEnd(ClientData clientData, Tcl_Interp *interp, Tk_Window tkwin,
+ Tcl_Obj **value, char *recordPtr, int internalOffset, char *oldInternalPtr,
+ int flags);
+static Tcl_Obj * GetTextStartEnd(ClientData clientData, Tk_Window tkwin, char *recordPtr,
+ int internalOffset);
+static void RestoreTextStartEnd(ClientData clientData, Tk_Window tkwin, char *internalPtr,
+ char *oldInternalPtr);
+static void FreeTextStartEnd(ClientData clientData, Tk_Window tkwin, char *internalPtr);
+
+static CONST Tk_ObjCustomOption startEndMarkOption = {
+ "index", /* name */
+ SetTextStartEnd, /* setProc */
+ GetTextStartEnd, /* getProc */
+ RestoreTextStartEnd, /* restoreProc */
+ FreeTextStartEnd, /* freeProc */
+ 0
+};
+
/*
* Information used to parse text configuration options:
*/
static const Tk_OptionSpec optionSpecs[] = {
{TK_OPTION_BOOLEAN, "-autoseparators", "autoSeparators",
- "AutoSeparators", DEF_TEXT_AUTO_SEPARATORS, -1,
- Tk_Offset(TkText, autoSeparators),
+ "AutoSeparators", DEF_TEXT_AUTO_SEPARATORS, -1, Tk_Offset(TkText, autoSeparators),
TK_OPTION_DONT_SET_DEFAULT, 0, 0},
{TK_OPTION_BORDER, "-background", "background", "Background",
- DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border),
- 0, DEF_TEXT_BG_MONO, 0},
+ DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border), 0, DEF_TEXT_BG_MONO, TK_TEXT_LINE_REDRAW},
{TK_OPTION_SYNONYM, "-bd", NULL, NULL,
- NULL, 0, -1, 0, "-borderwidth",
- TK_TEXT_LINE_GEOMETRY},
+ NULL, 0, -1, 0, "-borderwidth", TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_SYNONYM, "-bg", NULL, NULL,
- NULL, 0, -1, 0, "-background", 0},
+ NULL, 0, -1, 0, "-background", TK_TEXT_LINE_REDRAW},
{TK_OPTION_BOOLEAN, "-blockcursor", "blockCursor",
- "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1,
- Tk_Offset(TkText, insertCursorType), 0, 0, 0},
+ "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1, Tk_Offset(TkText, blockCursorType), 0, 0, 0},
{TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
- DEF_TEXT_BORDER_WIDTH, -1, Tk_Offset(TkText, borderWidth),
- 0, 0, TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_BORDER_WIDTH, -1, Tk_Offset(TkText, borderWidth), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
- DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor),
- TK_OPTION_NULL_OK, 0, 0},
+ DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor), TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_CUSTOM, "-endindex", NULL, NULL,
+ NULL, -1, Tk_Offset(TkText, newEndIndex), 0, &startEndMarkOption, TK_TEXT_INDEX_RANGE},
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
{TK_OPTION_CUSTOM, "-endline", NULL, NULL,
- NULL, -1, Tk_Offset(TkText, end), TK_OPTION_NULL_OK,
- &lineOption, TK_TEXT_LINE_RANGE},
+ NULL, -1, Tk_Offset(TkText, endLine), TK_OPTION_NULL_OK, &lineOption, TK_TEXT_LINE_RANGE},
+#endif
+ {TK_OPTION_STRING, "-eolchar", "eolChar", "EolChar",
+ NULL, Tk_Offset(TkText, eolCharPtr), -1, TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_COLOR, "-eolcolor", "eolColor", "EolColor",
+ DEF_TEXT_FG, -1, Tk_Offset(TkText, eolColor), TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_REDRAW},
{TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
- "ExportSelection", DEF_TEXT_EXPORT_SELECTION, -1,
- Tk_Offset(TkText, exportSelection), 0, 0, 0},
+ "ExportSelection", DEF_TEXT_EXPORT_SELECTION, -1, Tk_Offset(TkText, exportSelection), 0, 0, 0},
{TK_OPTION_SYNONYM, "-fg", "foreground", NULL,
- NULL, 0, -1, 0, "-foreground", 0},
+ NULL, 0, -1, 0, "-foreground", TK_TEXT_LINE_REDRAW},
{TK_OPTION_FONT, "-font", "font", "Font",
- DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0,
- TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
- DEF_TEXT_FG, -1, Tk_Offset(TkText, fgColor), 0,
- 0, 0},
+ DEF_TEXT_FG, -1, Tk_Offset(TkText, fgColor), 0, 0, TK_TEXT_LINE_REDRAW},
{TK_OPTION_PIXELS, "-height", "height", "Height",
DEF_TEXT_HEIGHT, -1, Tk_Offset(TkText, height), 0, 0, 0},
- {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
- "HighlightBackground", DEF_TEXT_HIGHLIGHT_BG,
- -1, Tk_Offset(TkText, highlightBgColorPtr),
- 0, 0, 0},
+ {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground", "HighlightBackground",
+ DEF_TEXT_HIGHLIGHT_BG, -1, Tk_Offset(TkText, highlightBgColorPtr), 0, 0, 0},
{TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
- DEF_TEXT_HIGHLIGHT, -1, Tk_Offset(TkText, highlightColorPtr),
- 0, 0, 0},
- {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
- "HighlightThickness", DEF_TEXT_HIGHLIGHT_WIDTH, -1,
- Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_BORDER, "-inactiveselectbackground","inactiveSelectBackground",
- "Foreground",
- DEF_TEXT_INACTIVE_SELECT_COLOR,
- -1, Tk_Offset(TkText, inactiveSelBorder),
+ DEF_TEXT_HIGHLIGHT, -1, Tk_Offset(TkText, highlightColorPtr), 0, 0, 0},
+ {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness",
+ DEF_TEXT_HIGHLIGHT_WIDTH, -1, Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_STRING, "-hyphenrules", NULL, NULL,
+ NULL, Tk_Offset(TkText, hyphenRulesPtr), -1, TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_COLOR, "-hyphencolor", "hyphenColor", "HyphenColor",
+ DEF_TEXT_FG, -1, Tk_Offset(TkText, hyphenColor), TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_REDRAW},
+ {TK_OPTION_BOOLEAN, "-hyphens", "hyphens", "Hyphens",
+ "0", -1, Tk_Offset(TkText, hyphens), 0, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_BORDER, "-inactiveselectbackground", "inactiveSelectBackground", "Foreground",
+ DEF_TEXT_INACTIVE_SELECT_COLOR, -1, Tk_Offset(TkText, inactiveSelBorder),
TK_OPTION_NULL_OK, DEF_TEXT_SELECT_MONO, 0},
{TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground",
- DEF_TEXT_INSERT_BG,
- -1, Tk_Offset(TkText, insertBorder),
- 0, 0, 0},
+ DEF_TEXT_INSERT_BG, -1, Tk_Offset(TkText, insertBorder), 0, 0, 0},
{TK_OPTION_PIXELS, "-insertborderwidth", "insertBorderWidth",
- "BorderWidth", DEF_TEXT_INSERT_BD_COLOR, -1,
- Tk_Offset(TkText, insertBorderWidth), 0,
+ "BorderWidth", DEF_TEXT_INSERT_BD_COLOR, -1, Tk_Offset(TkText, insertBorderWidth), 0,
(ClientData) DEF_TEXT_INSERT_BD_MONO, 0},
+ {TK_OPTION_COLOR, "-insertforeground", "insertForeground", "InsertForeground",
+ DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, insertFgColorPtr), 0, 0, 0},
{TK_OPTION_INT, "-insertofftime", "insertOffTime", "OffTime",
- DEF_TEXT_INSERT_OFF_TIME, -1, Tk_Offset(TkText, insertOffTime),
- 0, 0, 0},
+ DEF_TEXT_INSERT_OFF_TIME, -1, Tk_Offset(TkText, insertOffTime), 0, 0, 0},
{TK_OPTION_INT, "-insertontime", "insertOnTime", "OnTime",
- DEF_TEXT_INSERT_ON_TIME, -1, Tk_Offset(TkText, insertOnTime),
- 0, 0, 0},
+ DEF_TEXT_INSERT_ON_TIME, -1, Tk_Offset(TkText, insertOnTime), 0, 0, 0},
{TK_OPTION_STRING_TABLE,
"-insertunfocussed", "insertUnfocussed", "InsertUnfocussed",
DEF_TEXT_INSERT_UNFOCUSSED, -1, Tk_Offset(TkText, insertUnfocussed),
0, insertUnfocussedStrings, 0},
{TK_OPTION_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
- DEF_TEXT_INSERT_WIDTH, -1, Tk_Offset(TkText, insertWidth),
- 0, 0, 0},
+ DEF_TEXT_INSERT_WIDTH, -1, Tk_Offset(TkText, insertWidth), 0, 0, 0},
+ {TK_OPTION_STRING_TABLE, "-justify", "justify", "Justify",
+ "left", -1, Tk_Offset(TkText, justify), 0, justifyStrings, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_STRING, "-lang", "lang", "Lang",
+ NULL, Tk_Offset(TkText, langPtr), -1, TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_INT, "-maxundo", "maxUndo", "MaxUndo",
- DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndo),
- TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndoDepth), TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ {TK_OPTION_INT, "-maxundosize", "maxUndoSize", "MaxUndoSize",
+ DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndoSize), TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ {TK_OPTION_INT, "-maxredo", "maxRedo", "MaxRedo",
+ "-1", -1, Tk_Offset(TkText, maxRedoDepth), TK_OPTION_DONT_SET_DEFAULT, 0, 0},
{TK_OPTION_PIXELS, "-padx", "padX", "Pad",
- DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0,
- TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-pady", "padY", "Pad",
DEF_TEXT_PADY, -1, Tk_Offset(TkText, padY), 0, 0, 0},
{TK_OPTION_RELIEF, "-relief", "relief", "Relief",
DEF_TEXT_RELIEF, -1, Tk_Offset(TkText, relief), 0, 0, 0},
+ {TK_OPTION_INT, "-responsiveness", "responsiveness", "Responsiveness",
+ "50", -1, Tk_Offset(TkText, responsiveness), 0, 0, 0},
{TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
- DEF_TEXT_SELECT_COLOR, -1, Tk_Offset(TkText, selBorder),
- 0, DEF_TEXT_SELECT_MONO, 0},
- {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth",
- "BorderWidth", DEF_TEXT_SELECT_BD_COLOR,
- Tk_Offset(TkText, selBorderWidthPtr),
- Tk_Offset(TkText, selBorderWidth),
- TK_OPTION_NULL_OK, DEF_TEXT_SELECT_BD_MONO, 0},
+ DEF_TEXT_SELECT_COLOR, -1, Tk_Offset(TkText, selBorder), 0, DEF_TEXT_SELECT_MONO, 0},
+ {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
+ DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(TkText, selBorderWidthPtr),
+ Tk_Offset(TkText, selBorderWidth), TK_OPTION_NULL_OK, DEF_TEXT_SELECT_BD_MONO, 0},
{TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
DEF_TEXT_SELECT_FG_COLOR, -1, Tk_Offset(TkText, selFgColorPtr),
TK_OPTION_NULL_OK, DEF_TEXT_SELECT_FG_MONO, 0},
{TK_OPTION_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
DEF_TEXT_SET_GRID, -1, Tk_Offset(TkText, setGrid), 0, 0, 0},
+ {TK_OPTION_BOOLEAN, "-showendofline", "showEndOfLine", "ShowEndOfLine",
+ "0", -1, Tk_Offset(TkText, showEndOfLine), 0, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_BOOLEAN, "-showinsertforeground", "showInsertForeground", "ShowInsertForeground",
+ "0", -1, Tk_Offset(TkText, showInsertFgColor), 0, 0, 0},
+ {TK_OPTION_STRING_TABLE, "-spacemode", "spaceMode", "SpaceMode",
+ "none", -1, Tk_Offset(TkText, spaceMode), 0, spaceModeStrings, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-spacing1", "spacing1", "Spacing",
- DEF_TEXT_SPACING1, -1, Tk_Offset(TkText, spacing1),
- 0, 0 , TK_TEXT_LINE_GEOMETRY },
+ DEF_TEXT_SPACING1, -1, Tk_Offset(TkText, spacing1), 0, 0 , TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-spacing2", "spacing2", "Spacing",
- DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2),
- 0, 0 , TK_TEXT_LINE_GEOMETRY },
+ DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2), 0, 0 , TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-spacing3", "spacing3", "Spacing",
- DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3),
- 0, 0 , TK_TEXT_LINE_GEOMETRY },
+ DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3), 0, 0 , TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_CUSTOM, "-startindex", NULL, NULL,
+ NULL, -1, Tk_Offset(TkText, newStartIndex), 0, &startEndMarkOption, TK_TEXT_INDEX_RANGE},
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
{TK_OPTION_CUSTOM, "-startline", NULL, NULL,
- NULL, -1, Tk_Offset(TkText, start), TK_OPTION_NULL_OK,
- &lineOption, TK_TEXT_LINE_RANGE},
+ NULL, -1, Tk_Offset(TkText, startLine), TK_OPTION_NULL_OK, &lineOption, TK_TEXT_LINE_RANGE},
+#endif
{TK_OPTION_STRING_TABLE, "-state", "state", "State",
- DEF_TEXT_STATE, -1, Tk_Offset(TkText, state),
- 0, stateStrings, 0},
+ DEF_TEXT_STATE, -1, Tk_Offset(TkText, state), 0, stateStrings, 0},
+ {TK_OPTION_BOOLEAN, "-steadymarks", "steadyMarks", "SteadyMarks",
+ "0", -1, Tk_Offset(TkText, steadyMarks), TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ {TK_OPTION_INT, "-synctime", "syncTime", "SyncTime", "150", -1, Tk_Offset(TkText, syncTime),
+ 0, 0, TK_TEXT_SYNCHRONIZE},
{TK_OPTION_STRING, "-tabs", "tabs", "Tabs",
- DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionPtr), -1,
- TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionPtr), -1, TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING_TABLE, "-tabstyle", "tabStyle", "TabStyle",
- DEF_TEXT_TABSTYLE, -1, Tk_Offset(TkText, tabStyle),
- 0, tabStyleStrings, TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_TABSTYLE, -1, Tk_Offset(TkText, tabStyle), 0, tabStyleStrings, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_STRING_TABLE, "-tagging", "tagging", "Tagging",
+ "within", -1, Tk_Offset(TkText, tagging), 0, taggingStrings, 0},
{TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
- DEF_TEXT_TAKE_FOCUS, -1, Tk_Offset(TkText, takeFocus),
- TK_OPTION_NULL_OK, 0, 0},
+ DEF_TEXT_TAKE_FOCUS, -1, Tk_Offset(TkText, takeFocus), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_BOOLEAN, "-undo", "undo", "Undo",
- DEF_TEXT_UNDO, -1, Tk_Offset(TkText, undo),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0},
+ DEF_TEXT_UNDO, -1, Tk_Offset(TkText, undo), TK_OPTION_DONT_SET_DEFAULT, 0 ,0},
+ {TK_OPTION_BOOLEAN, "-useunibreak", "useUniBreak", "UseUniBreak",
+ "0", -1, Tk_Offset(TkText, useUniBreak), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_INT, "-width", "width", "Width",
- DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0,
- TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING_TABLE, "-wrap", "wrap", "Wrap",
- DEF_TEXT_WRAP, -1, Tk_Offset(TkText, wrapMode),
- 0, wrapStrings, TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_WRAP, -1, Tk_Offset(TkText, wrapMode), 0, wrapStrings, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
- DEF_TEXT_XSCROLL_COMMAND, -1, Tk_Offset(TkText, xScrollCmd),
- TK_OPTION_NULL_OK, 0, 0},
+ DEF_TEXT_XSCROLL_COMMAND, -1, Tk_Offset(TkText, xScrollCmd), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
- DEF_TEXT_YSCROLL_COMMAND, -1, Tk_Offset(TkText, yScrollCmd),
- TK_OPTION_NULL_OK, 0, 0},
+ DEF_TEXT_YSCROLL_COMMAND, -1, Tk_Offset(TkText, yScrollCmd), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_END, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0}
};
@@ -272,55 +370,43 @@ static const Tk_OptionSpec optionSpecs[] = {
struct SearchSpec; /* Forward declaration. */
-typedef ClientData SearchAddLineProc(int lineNum,
- struct SearchSpec *searchSpecPtr,
- Tcl_Obj *theLine, int *lenPtr,
- int *extraLinesPtr);
-typedef int SearchMatchProc(int lineNum,
- struct SearchSpec *searchSpecPtr,
- ClientData clientData, Tcl_Obj *theLine,
- int matchOffset, int matchLength);
-typedef int SearchLineIndexProc(Tcl_Interp *interp,
- Tcl_Obj *objPtr, struct SearchSpec *searchSpecPtr,
- int *linePosPtr, int *offsetPosPtr);
+typedef ClientData SearchAddLineProc(int lineNum, struct SearchSpec *searchSpecPtr,
+ Tcl_Obj *theLine, int *lenPtr, int *extraLinesPtr);
+typedef bool SearchMatchProc(int lineNum, struct SearchSpec *searchSpecPtr,
+ ClientData clientData, Tcl_Obj *theLine, int matchOffset, int matchLength);
+typedef int SearchLineIndexProc(Tcl_Interp *interp, Tcl_Obj *objPtr,
+ struct SearchSpec *searchSpecPtr, int *linePosPtr, int *offsetPosPtr);
typedef struct SearchSpec {
- int exact; /* Whether search is exact or regexp. */
- int noCase; /* Case-insenstivive? */
- int noLineStop; /* If not set, a regexp search will use the
- * TCL_REG_NLSTOP flag. */
- int overlap; /* If set, results from multiple searches
- * (-all) are allowed to overlap each
- * other. */
- int strictLimits; /* If set, matches must be completely inside
- * the from,to range. Otherwise the limits
- * only apply to the start of each match. */
- int all; /* Whether all or the first match should be
- * reported. */
+ TkText *textPtr; /* Information about widget. */
+ bool exact; /* Whether search is exact or regexp. */
+ bool noCase; /* Case-insenstivive? */
+ bool noLineStop; /* If not set, a regexp search will use the TCL_REG_NLSTOP flag. */
+ bool overlap; /* If set, results from multiple searches (-all) are allowed to
+ * overlap each other. */
+ bool strictLimits; /* If set, matches must be completely inside the from,to range.
+ * Otherwise the limits only apply to the start of each match. */
+ bool all; /* Whether all or the first match should be reported. */
+ bool backwards; /* Searching forwards or backwards. */
+ bool searchElide; /* Search in hidden text as well. */
+ bool searchHyphens; /* Search in soft hyhens as well. */
int startLine; /* First line to examine. */
int startOffset; /* Index in first line to start at. */
- int stopLine; /* Last line to examine, or -1 when we search
- * all available text. */
- int stopOffset; /* Index to stop at, provided stopLine is not
- * -1. */
+ int stopLine; /* Last line to examine, or -1 when we search all available text. */
+ int stopOffset; /* Index to stop at, provided stopLine is not -1. */
int numLines; /* Total lines which are available. */
- int backwards; /* Searching forwards or backwards. */
- Tcl_Obj *varPtr; /* If non-NULL, store length(s) of match(es)
- * in this variable. */
+ Tcl_Obj *varPtr; /* If non-NULL, store length(s) of match(es) in this variable. */
Tcl_Obj *countPtr; /* Keeps track of currently found lengths. */
Tcl_Obj *resPtr; /* Keeps track of currently found locations */
- int searchElide; /* Search in hidden text as well. */
SearchAddLineProc *addLineProc;
- /* Function to call when we need to add
- * another line to the search string so far */
+ /* Function to call when we need to add another line to the search
+ * string so far */
SearchMatchProc *foundMatchProc;
- /* Function to call when we have found a
- * match. */
+ /* Function to call when we have found a match. */
SearchLineIndexProc *lineIndexProc;
- /* Function to call when we have found a
- * match. */
- ClientData clientData; /* Information about structure being searched,
- * in this case a text widget. */
+ /* Function to call when we have found a match. */
+ ClientData clientData; /* Information about structure being searched, in this case a text
+ * widget. */
} SearchSpec;
/*
@@ -328,98 +414,147 @@ typedef struct SearchSpec {
* handling both regexp and exact searches.
*/
-static int SearchCore(Tcl_Interp *interp,
- SearchSpec *searchSpecPtr, Tcl_Obj *patObj);
-static int SearchPerform(Tcl_Interp *interp,
- SearchSpec *searchSpecPtr, Tcl_Obj *patObj,
- Tcl_Obj *fromPtr, Tcl_Obj *toPtr);
+static int SearchCore(Tcl_Interp *interp, SearchSpec *searchSpecPtr, Tcl_Obj *patObj);
+static int SearchPerform(Tcl_Interp *interp, SearchSpec *searchSpecPtr, Tcl_Obj *patObj,
+ Tcl_Obj *fromPtr, Tcl_Obj *toPtr);
/*
- * Boolean variable indicating whether or not special debugging code should be
- * executed.
+ * We need a simple linked list for strings:
*/
-int tkTextDebug = 0;
+typedef struct TkTextStringList {
+ struct TkTextStringList *nextPtr;
+ Tcl_Obj *strObjPtr;
+} TkTextStringList;
+
+/*
+ * Boolean variable indicating whether or not special debugging code should be executed.
+ */
+
+bool tkTextDebug = false;
+
+typedef const TkTextUndoAtom * (*InspectUndoStackProc)(TkTextUndoStack stack);
/*
* Forward declarations for functions defined later in this file:
*/
-static int ConfigureText(Tcl_Interp *interp,
- TkText *textPtr, int objc, Tcl_Obj *const objv[]);
-static int DeleteIndexRange(TkSharedText *sharedPtr,
- TkText *textPtr, const TkTextIndex *indexPtr1,
- const TkTextIndex *indexPtr2, int viewUpdate);
-static int CountIndices(const TkText *textPtr,
- const TkTextIndex *indexPtr1,
- const TkTextIndex *indexPtr2,
- TkTextCountType type);
+static bool DeleteIndexRange(TkSharedText *sharedTextPtr, TkText *textPtr,
+ const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2, int flags,
+ bool viewUpdate, bool triggerWatchDelete, bool triggerWatchInsert,
+ bool userFlag, bool final);
+static int CountIndices(const TkText *textPtr, const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2, TkTextCountType type);
static void DestroyText(TkText *textPtr);
-static int InsertChars(TkSharedText *sharedTextPtr,
- TkText *textPtr, TkTextIndex *indexPtr,
- Tcl_Obj *stringPtr, int viewUpdate);
+static void ClearText(TkText *textPtr, bool clearTags);
+static void FireWidgetViewSyncEvent(ClientData clientData);
+static void FreeEmbeddedWindows(TkText *textPtr);
+static void InsertChars(TkText *textPtr, TkTextIndex *index1Ptr, TkTextIndex *index2Ptr,
+ char const *string, unsigned length, bool viewUpdate,
+ TkTextTagSet *tagInfoPtr, TkTextTag *hyphenTagPtr, bool parseHyphens);
static void TextBlinkProc(ClientData clientData);
static void TextCmdDeletedProc(ClientData clientData);
-static int CreateWidget(TkSharedText *sharedPtr, Tk_Window tkwin,
- Tcl_Interp *interp, const TkText *parent,
- int objc, Tcl_Obj *const objv[]);
-static void TextEventProc(ClientData clientData,
- XEvent *eventPtr);
-static int TextFetchSelection(ClientData clientData, int offset,
- char *buffer, int maxBytes);
-static int TextIndexSortProc(const void *first,
- const void *second);
-static int TextInsertCmd(TkSharedText *sharedTextPtr,
- TkText *textPtr, Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[],
- const TkTextIndex *indexPtr, int viewUpdate);
+static int CreateWidget(TkSharedText *sharedTextPtr, Tk_Window tkwin, Tcl_Interp *interp,
+ const TkText *parent, int objc, Tcl_Obj *const objv[]);
+static void TextEventProc(ClientData clientData, XEvent *eventPtr);
+static void ProcessConfigureNotify(TkText *textPtr, bool updateLineGeometry);
+static int TextFetchSelection(ClientData clientData, int offset, char *buffer,
+ int maxBytes);
+static int TextIndexSortProc(const void *first, const void *second);
+static int TextInsertCmd(TkText *textPtr, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[], const TkTextIndex *indexPtr,
+ bool viewUpdate, bool triggerWatchDelete, bool triggerWatchInsert,
+ bool userFlag, bool *destroyed, bool parseHyphens);
static int TextReplaceCmd(TkText *textPtr, Tcl_Interp *interp,
- const TkTextIndex *indexFromPtr,
- const TkTextIndex *indexToPtr,
- int objc, Tcl_Obj *const objv[], int viewUpdate);
+ const TkTextIndex *indexFromPtr, const TkTextIndex *indexToPtr,
+ int objc, Tcl_Obj *const objv[], bool viewUpdate, bool triggerWatch,
+ bool userFlag, bool *destroyed, bool parseHyphens);
static int TextSearchCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
static int TextEditCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
static int TextWidgetObjCmd(ClientData clientData,
- Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]);
-static int SharedTextObjCmd(ClientData clientData,
- Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]);
+ Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
static void TextWorldChangedCallback(ClientData instanceData);
static void TextWorldChanged(TkText *textPtr, int mask);
+static void UpdateLineMetrics(TkText *textPtr, unsigned lineNum, unsigned endLine);
+static int TextChecksumCmd(TkText *textPtr, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
static int TextDumpCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-static int DumpLine(Tcl_Interp *interp, TkText *textPtr,
+static int TextInspectCmd(TkText *textPtr, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static bool DumpLine(Tcl_Interp *interp, TkText *textPtr,
int what, TkTextLine *linePtr, int start, int end,
- int lineno, Tcl_Obj *command);
-static int DumpSegment(TkText *textPtr, Tcl_Interp *interp,
- const char *key, const char *value,
- Tcl_Obj *command, const TkTextIndex *index,
- int what);
-static int TextEditUndo(TkText *textPtr);
-static int TextEditRedo(TkText *textPtr);
-static Tcl_Obj * TextGetText(const TkText *textPtr,
- const TkTextIndex *index1,
- const TkTextIndex *index2, int visibleOnly);
-static void GenerateModifiedEvent(TkText *textPtr);
-static void GenerateUndoStackEvent(TkText *textPtr);
-static void UpdateDirtyFlag(TkSharedText *sharedPtr);
+ int lineno, Tcl_Obj *command, TkTextTag **prevTagPtr);
+static bool DumpSegment(TkText *textPtr, Tcl_Interp *interp, const char *key,
+ const char *value, Tcl_Obj *command, const TkTextIndex *index, int what);
+static void InspectUndoStack(const TkSharedText *sharedTextPtr,
+ InspectUndoStackProc firstAtomProc, InspectUndoStackProc nextAtomProc,
+ Tcl_Obj *objPtr);
+static void InspectRetainedUndoItems(const TkSharedText *sharedTextPtr, Tcl_Obj *objPtr);
+static Tcl_Obj * TextGetText(TkText *textPtr, const TkTextIndex *index1,
+ const TkTextIndex *index2, TkTextIndex *lastIndexPtr, Tcl_Obj *resultPtr,
+ unsigned maxBytes, bool visibleOnly, bool includeHyphens);
+static void GenerateEvent(TkSharedText *sharedTextPtr, const char *type);
static void RunAfterSyncCmd(ClientData clientData);
-static void TextPushUndoAction(TkText *textPtr,
- Tcl_Obj *undoString, int insert,
- const TkTextIndex *index1Ptr,
- const TkTextIndex *index2Ptr);
-static int TextSearchIndexInLine(const SearchSpec *searchSpecPtr,
- TkTextLine *linePtr, int byteIndex);
+static void UpdateModifiedFlag(TkSharedText *sharedTextPtr, bool flag);
+static Tcl_Obj * MakeEditInfo(Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *arrayPtr);
+static unsigned TextSearchIndexInLine(const SearchSpec *searchSpecPtr, TkTextLine *linePtr,
+ int byteIndex);
static int TextPeerCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-static TkUndoProc TextUndoRedoCallback;
+static int TextWatchCmd(TkText *textPtr, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static bool TriggerWatchEdit(TkText *textPtr, const char *operation,
+ const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2,
+ const char *info, bool final);
+static void TriggerUndoStackEvent(TkSharedText *sharedTextPtr);
+static void PushRetainedUndoTokens(TkSharedText *sharedTextPtr);
+static bool IsEmpty(const TkSharedText *sharedTextPtr, const TkText *textPtr);
+static bool IsClean(const TkSharedText *sharedTextPtr, const TkText *textPtr,
+ bool discardSelection);
+static TkTextUndoPerformProc TextUndoRedoCallback;
+static TkTextUndoFreeProc TextUndoFreeCallback;
+static TkTextUndoStackContentChangedProc TextUndoStackContentChangedCallback;
/*
- * Declarations of the three search procs required by the multi-line search
- * routines.
+ * Some definitions for controlling "dump", "inspect", and "checksum".
+ */
+
+enum {
+ TK_DUMP_TEXT = SEG_GROUP_CHAR,
+ TK_DUMP_CHARS = TK_DUMP_TEXT|SEG_GROUP_HYPHEN,
+ TK_DUMP_MARK = SEG_GROUP_MARK,
+ TK_DUMP_ELIDE = SEG_GROUP_BRANCH,
+ TK_DUMP_TAG = SEG_GROUP_TAG,
+ TK_DUMP_WIN = SEG_GROUP_WINDOW,
+ TK_DUMP_IMG = SEG_GROUP_IMAGE,
+ TK_DUMP_NODE = 1 << 20,
+ TK_DUMP_DUMP_ALL = TK_DUMP_TEXT|TK_DUMP_CHARS|TK_DUMP_MARK|TK_DUMP_TAG|TK_DUMP_WIN|TK_DUMP_IMG,
+
+ TK_DUMP_DISPLAY = 1 << 21,
+ TK_DUMP_DISPLAY_CHARS = TK_DUMP_CHARS|TK_DUMP_DISPLAY,
+ TK_DUMP_DISPLAY_TEXT = TK_DUMP_TEXT|TK_DUMP_DISPLAY,
+ TK_DUMP_CRC_DFLT = TK_DUMP_TEXT|SEG_GROUP_WINDOW|SEG_GROUP_IMAGE,
+ TK_DUMP_CRC_ALL = TK_DUMP_TEXT|TK_DUMP_CHARS|TK_DUMP_DISPLAY_TEXT|SEG_GROUP_WINDOW|
+ SEG_GROUP_IMAGE|TK_DUMP_MARK|TK_DUMP_TAG,
+
+ TK_DUMP_NESTED = 1 << 22,
+ TK_DUMP_TEXT_CONFIGS = 1 << 23,
+ TK_DUMP_TAG_CONFIGS = 1 << 24,
+ TK_DUMP_TAG_BINDINGS = 1 << 25,
+ TK_DUMP_INSERT_MARK = 1 << 26,
+ TK_DUMP_DISCARD_SEL = 1 << 27,
+ TK_DUMP_DONT_RESOLVE = 1 << 28,
+ TK_DUMP_INSPECT_DFLT = TK_DUMP_DUMP_ALL|TK_DUMP_TEXT_CONFIGS|TK_DUMP_TAG_CONFIGS,
+ TK_DUMP_INSPECT_ALL = TK_DUMP_INSPECT_DFLT|TK_DUMP_CHARS|TK_DUMP_TAG_BINDINGS|
+ TK_DUMP_DISPLAY_TEXT|TK_DUMP_DISCARD_SEL|TK_DUMP_INSERT_MARK|
+ TK_DUMP_DONT_RESOLVE|TK_DUMP_NESTED|TK_DUMP_ELIDE
+};
+
+/*
+ * Declarations of the three search procs required by the multi-line search routines.
*/
static SearchMatchProc TextSearchFoundMatch;
@@ -431,13 +566,212 @@ static SearchLineIndexProc TextSearchGetLineIndex;
* can be invoked from generic window code.
*/
-static const Tk_ClassProcs textClass = {
+static CONST Tk_ClassProcs textClass = {
sizeof(Tk_ClassProcs), /* size */
TextWorldChangedCallback, /* worldChangedProc */
- NULL, /* createProc */
- NULL /* modalProc */
+ NULL, /* createProc */
+ NULL /* modalProc */
};
+#if TK_CHECK_ALLOCS
+
+/*
+ * Some stuff for memory checks, and allocation statistic.
+ */
+
+unsigned tkTextCountNewShared = 0;
+unsigned tkTextCountDestroyShared = 0;
+unsigned tkTextCountNewPeer = 0;
+unsigned tkTextCountDestroyPeer = 0;
+unsigned tkTextCountNewPixelInfo = 0;
+unsigned tkTextCountDestroyPixelInfo = 0;
+unsigned tkTextCountNewSegment = 0;
+unsigned tkTextCountDestroySegment = 0;
+unsigned tkTextCountNewTag = 0;
+unsigned tkTextCountDestroyTag = 0;
+unsigned tkTextCountNewUndoToken = 0;
+unsigned tkTextCountDestroyUndoToken = 0;
+unsigned tkTextCountNewNode = 0;
+unsigned tkTextCountDestroyNode = 0;
+unsigned tkTextCountNewLine = 0;
+unsigned tkTextCountDestroyLine = 0;
+unsigned tkTextCountNewSection = 0;
+unsigned tkTextCountDestroySection = 0;
+
+extern unsigned tkIntSetCountDestroy;
+extern unsigned tkIntSetCountNew;
+extern unsigned tkBitCountNew;
+extern unsigned tkBitCountDestroy;
+extern unsigned tkQTreeCountNewTree;
+extern unsigned tkQTreeCountDestroyTree;
+extern unsigned tkQTreeCountNewNode;
+extern unsigned tkQTreeCountDestroyNode;
+extern unsigned tkQTreeCountNewItem;
+extern unsigned tkQTreeCountDestroyItem;
+extern unsigned tkQTreeCountNewElement;
+extern unsigned tkQTreeCountDestroyElement;
+
+typedef struct WatchShared {
+ TkSharedText *sharedTextPtr;
+ struct WatchShared *nextPtr;
+} WatchShared;
+
+static unsigned widgetNumber = 0;
+static WatchShared *watchShared;
+
+static void
+AllocStatistic()
+{
+ const WatchShared *wShared;
+
+ if (!tkBTreeDebug) {
+ return;
+ }
+
+ for (wShared = watchShared; wShared; wShared = wShared->nextPtr) {
+ const TkText *peer;
+
+ for (peer = wShared->sharedTextPtr->peers; peer; peer = peer->next) {
+ printf("Unreleased text widget %d\n", peer->widgetNumber);
+ }
+ }
+
+ printf("---------------------------------\n");
+ printf("ALLOCATION: new destroy\n");
+ printf("---------------------------------\n");
+ printf("Shared: %8u - %8u\n", tkTextCountNewShared, tkTextCountDestroyShared);
+ printf("Peer: %8u - %8u\n", tkTextCountNewPeer, tkTextCountDestroyPeer);
+ printf("Segment: %8u - %8u\n", tkTextCountNewSegment, tkTextCountDestroySegment);
+ printf("Tag: %8u - %8u\n", tkTextCountNewTag, tkTextCountDestroyTag);
+ printf("UndoToken: %8u - %8u\n", tkTextCountNewUndoToken, tkTextCountDestroyUndoToken);
+ printf("Node: %8u - %8u\n", tkTextCountNewNode, tkTextCountDestroyNode);
+ printf("Line: %8u - %8u\n", tkTextCountNewLine, tkTextCountDestroyLine);
+ printf("Section: %8u - %8u\n", tkTextCountNewSection, tkTextCountDestroySection);
+ printf("PixelInfo: %8u - %8u\n", tkTextCountNewPixelInfo, tkTextCountDestroyPixelInfo);
+ printf("BitField: %8u - %8u\n", tkBitCountNew, tkBitCountDestroy);
+ printf("IntSet: %8u - %8u\n", tkIntSetCountNew, tkIntSetCountDestroy);
+ printf("Tree: %8u - %8u\n", tkQTreeCountNewTree, tkQTreeCountDestroyTree);
+ printf("Tree-Node: %8u - %8u\n", tkQTreeCountNewNode, tkQTreeCountDestroyNode);
+ printf("Tree-Item: %8u - %8u\n", tkQTreeCountNewItem, tkQTreeCountDestroyItem);
+ printf("Tree-Element: %8u - %8u\n", tkQTreeCountNewElement, tkQTreeCountDestroyElement);
+ printf("--------------------------------\n");
+
+ if (tkTextCountNewShared != tkTextCountDestroyShared
+ || tkTextCountNewPeer != tkTextCountDestroyPeer
+ || tkTextCountNewSegment != tkTextCountDestroySegment
+ || tkTextCountNewTag != tkTextCountDestroyTag
+ || tkTextCountNewUndoToken != tkTextCountDestroyUndoToken
+ || tkTextCountNewNode != tkTextCountDestroyNode
+ || tkTextCountNewLine != tkTextCountDestroyLine
+ || tkTextCountNewSection != tkTextCountDestroySection
+ || tkTextCountNewPixelInfo != tkTextCountDestroyPixelInfo
+ || tkBitCountNew != tkBitCountDestroy
+ || tkIntSetCountNew != tkIntSetCountDestroy
+ || tkQTreeCountNewTree != tkQTreeCountDestroyTree
+ || tkQTreeCountNewElement != tkQTreeCountDestroyElement
+ || tkQTreeCountNewNode != tkQTreeCountDestroyNode
+ || tkQTreeCountNewItem != tkQTreeCountDestroyItem) {
+ printf("*** memory leak detected ***\n");
+ printf("----------------------------\n");
+ /* TkBitCheckAllocs(); */
+ }
+}
+#endif /* TK_CHECK_ALLOCS */
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+
+/*
+ * Some helpers.
+ */
+
+static void WarnAboutDeprecatedStartLineOption() {
+ static bool printWarning = true;
+ if (printWarning) {
+ fprintf(stderr, "Option \"-startline\" is deprecated, please use option \"-startindex\"\n");
+ printWarning = false;
+ }
+}
+static void WarnAboutDeprecatedEndLineOption() {
+ static bool printWarning = true;
+ if (printWarning) {
+ fprintf(stderr, "Option \"-endline\" is deprecated, please use option \"-endindex\"\n");
+ printWarning = false;
+ }
+}
+
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+/*
+ * Wee need a helper for sending virtual events, because in newer Tk version
+ * the footprint of TkSendVirtualEvent has changed. (Note that this source has
+ * backports for 8.5, and older versions of 8.6).
+ */
+
+static void
+SendVirtualEvent(
+ Tk_Window tkwin,
+ char const *eventName,
+ Tcl_Obj *detail)
+{
+#if TK_MAJOR_VERSION > 8 \
+ || (TK_MAJOR_VERSION == 8 \
+ && (TK_MINOR_VERSION > 6 || (TK_MINOR_VERSION == 6 && TK_RELEASE_SERIAL >= 6)))
+ /* new footprint since 8.6.6 */
+ TkSendVirtualEvent(tkwin, eventName, detail);
+#else
+# if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 6
+ if (!detail) {
+ /* new function since 8.6.0, and valid until 8.6.5 */
+ TkSendVirtualEvent(tkwin, eventName);
+ return;
+ }
+# endif
+ {
+ /* backport to 8.5 */
+ union { XEvent general; XVirtualEvent virtual; } event;
+
+ memset(&event, 0, sizeof(event));
+ event.general.xany.type = VirtualEvent;
+ event.general.xany.serial = NextRequest(Tk_Display(tkwin));
+ event.general.xany.send_event = False;
+ event.general.xany.window = Tk_WindowId(tkwin);
+ event.general.xany.display = Tk_Display(tkwin);
+ event.virtual.name = Tk_GetUid(eventName);
+ event.virtual.user_data = detail;
+ Tk_HandleEvent(&event.general);
+ }
+#endif
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * GetByteLength --
+ *
+ * This function should be defined by Tcl, but it isn't defined,
+ * so we are doing this.
+ *
+ * Results:
+ * The length of the string.
+ *
+ * Side effects:
+ * Calls Tcl_GetString(objPtr) if objPtr->bytes is not yet resolved.
+ *
+ *--------------------------------------------------------------
+ */
+
+static unsigned
+GetByteLength(
+ Tcl_Obj *objPtr)
+{
+ assert(objPtr);
+
+ if (!objPtr->bytes) {
+ Tcl_GetString(objPtr);
+ }
+ return objPtr->length;
+}
+
/*
*--------------------------------------------------------------
*
@@ -469,10 +803,122 @@ Tk_TextObjCmd(
return TCL_ERROR;
}
+ if (!tkwin) {
+ tkwin = Tk_MainWindow(interp);
+ }
return CreateWidget(NULL, tkwin, interp, NULL, objc, objv);
}
/*
+ *----------------------------------------------------------------------
+ *
+ * PushRetainedUndoTokens --
+ *
+ * Push the retained undo tokens onto the stack.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Same as TkTextPushUndoToken, additionaly 'undoTagList' and
+ * 'undoMarkList' will be cleared.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+PushRetainedUndoTokens(
+ TkSharedText *sharedTextPtr)
+{
+ unsigned i;
+
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->undoStack);
+
+ for (i = 0; i < sharedTextPtr->undoTagListCount; ++i) {
+ TkTextPushUndoTagTokens(sharedTextPtr, sharedTextPtr->undoTagList[i]);
+ }
+
+ for (i = 0; i < sharedTextPtr->undoMarkListCount; ++i) {
+ TkTextPushUndoMarkTokens(sharedTextPtr, &sharedTextPtr->undoMarkList[i]);
+ }
+
+ sharedTextPtr->undoTagListCount = 0;
+ sharedTextPtr->undoMarkListCount = 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextPushUndoToken --
+ *
+ * This function is pushing the given undo/redo token. Don't use
+ * TkTextUndoPushItem, because some of the prepared undo tokens
+ * are retained.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Same as TkTextUndoPushItem, furthermore all retained items
+ * will be pushed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextPushUndoToken(
+ TkSharedText *sharedTextPtr,
+ void *token,
+ unsigned byteSize)
+{
+ TkTextUndoAction action;
+
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->undoStack);
+ assert(token);
+
+ action = ((TkTextUndoToken *) token)->undoType->action;
+
+ if (action == TK_TEXT_UNDO_INSERT || action == TK_TEXT_UNDO_DELETE) {
+ sharedTextPtr->insertDeleteUndoTokenCount += 1;
+ }
+
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoPushItem(sharedTextPtr->undoStack, token, byteSize);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextPushRedoToken --
+ *
+ * This function is pushing the given redo token. This function
+ * is useful only for the reconstruction of the undo stack.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Same as TkTextUndoPushRedoItem.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextPushRedoToken(
+ TkSharedText *sharedTextPtr,
+ void *token,
+ unsigned byteSize)
+{
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->undoStack);
+ assert(token);
+
+ TkTextUndoPushRedoItem(sharedTextPtr->undoStack, token, byteSize);
+}
+
+/*
*--------------------------------------------------------------
*
* CreateWidget --
@@ -495,7 +941,7 @@ Tk_TextObjCmd(
static int
CreateWidget(
- TkSharedText *sharedPtr, /* Shared widget info, or NULL. */
+ TkSharedText *sharedTextPtr,/* Shared widget info, or NULL. */
Tk_Window tkwin, /* Main window associated with interpreter. */
Tcl_Interp *interp, /* Current interpreter. */
const TkText *parent, /* If non-NULL then take default start, end
@@ -503,7 +949,7 @@ CreateWidget(
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
- register TkText *textPtr;
+ TkText *textPtr;
Tk_OptionTable optionTable;
TkTextIndex startIndex;
Tk_Window newWin;
@@ -512,57 +958,101 @@ CreateWidget(
* Create the window.
*/
- newWin = Tk_CreateWindowFromPath(interp, tkwin, Tcl_GetString(objv[1]),
- NULL);
- if (newWin == NULL) {
+ if (!(newWin = Tk_CreateWindowFromPath(interp, tkwin, Tcl_GetString(objv[1]), NULL))) {
return TCL_ERROR;
}
+ if (!sharedTextPtr) {
+ sharedTextPtr = memset(malloc(sizeof(TkSharedText)), 0, sizeof(TkSharedText));
+
+ Tcl_InitHashTable(&sharedTextPtr->tagTable, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&sharedTextPtr->markTable, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&sharedTextPtr->windowTable, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&sharedTextPtr->imageTable, TCL_STRING_KEYS);
+ sharedTextPtr->usedTags = TkBitResize(NULL, 256);
+ sharedTextPtr->elisionTags = TkBitResize(NULL, 256);
+ sharedTextPtr->selectionTags = TkBitResize(NULL, 256);
+ sharedTextPtr->dontUndoTags = TkBitResize(NULL, 256);
+ sharedTextPtr->affectDisplayTags = TkBitResize(NULL, 256);
+ sharedTextPtr->notAffectDisplayTags = TkBitResize(NULL, 256);
+ sharedTextPtr->affectDisplayNonSelTags = TkBitResize(NULL, 256);
+ sharedTextPtr->affectGeometryTags = TkBitResize(NULL, 256);
+ sharedTextPtr->affectGeometryNonSelTags = TkBitResize(NULL, 256);
+ sharedTextPtr->affectLineHeightTags = TkBitResize(NULL, 256);
+ sharedTextPtr->tagLookup = malloc(256*sizeof(TkTextTag *));
+ sharedTextPtr->emptyTagInfoPtr = TkTextTagSetResize(NULL, 0);
+ sharedTextPtr->maxRedoDepth = -1;
+ sharedTextPtr->autoSeparators = true;
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
+ sharedTextPtr->lastUndoTokenType = -1;
+ sharedTextPtr->startMarker = TkTextMakeStartEndMark(NULL, &tkTextLeftMarkType);
+ sharedTextPtr->endMarker = TkTextMakeStartEndMark(NULL, &tkTextRightMarkType);
+ sharedTextPtr->protectionMark[0] = TkTextMakeMark(NULL, NULL);
+ sharedTextPtr->protectionMark[1] = TkTextMakeMark(NULL, NULL);
+ sharedTextPtr->protectionMark[0]->typePtr = &tkTextProtectionMarkType;
+ sharedTextPtr->protectionMark[1]->typePtr = &tkTextProtectionMarkType;
+
+ DEBUG(memset(sharedTextPtr->tagLookup, 0, 256*sizeof(TkTextTag *)));
+
+ sharedTextPtr->mainPeer = memset(malloc(sizeof(TkText)), 0, sizeof(TkText));
+ sharedTextPtr->mainPeer->startMarker = sharedTextPtr->startMarker;
+ sharedTextPtr->mainPeer->endMarker = sharedTextPtr->endMarker;
+ sharedTextPtr->mainPeer->sharedTextPtr = sharedTextPtr;
+ DEBUG_ALLOC(tkTextCountNewPeer++);
+
+#if TK_CHECK_ALLOCS
+ if (tkTextCountNewShared++ == 0) {
+ atexit(AllocStatistic);
+ }
+ /*
+ * Add this shared resource to global list.
+ */
+ {
+ WatchShared *wShared = malloc(sizeof(WatchShared));
+ wShared->sharedTextPtr = sharedTextPtr;
+ wShared->nextPtr = watchShared;
+ watchShared = wShared;
+ }
+#endif
+
+ /*
+ * The construction of the tree requires a valid setup of the shared resource.
+ */
+
+ sharedTextPtr->tree = TkBTreeCreate(sharedTextPtr, 1);
+ }
+
+ DEBUG_ALLOC(tkTextCountNewPeer++);
+
/*
* Create the text widget and initialize everything to zero, then set the
* necessary initial (non-NULL) values. It is important that the 'set' tag
* and 'insert', 'current' mark pointers are all NULL to start.
*/
- textPtr = ckalloc(sizeof(TkText));
- memset(textPtr, 0, sizeof(TkText));
-
+ textPtr = memset(malloc(sizeof(TkText)), 0, sizeof(TkText));
textPtr->tkwin = newWin;
textPtr->display = Tk_Display(newWin);
textPtr->interp = interp;
- textPtr->widgetCmd = Tcl_CreateObjCommand(interp,
- Tk_PathName(textPtr->tkwin), TextWidgetObjCmd,
- textPtr, TextCmdDeletedProc);
-
- if (sharedPtr == NULL) {
- sharedPtr = ckalloc(sizeof(TkSharedText));
- memset(sharedPtr, 0, sizeof(TkSharedText));
-
- sharedPtr->refCount = 0;
- sharedPtr->peers = NULL;
- sharedPtr->tree = TkBTreeCreate(sharedPtr);
-
- Tcl_InitHashTable(&sharedPtr->tagTable, TCL_STRING_KEYS);
- Tcl_InitHashTable(&sharedPtr->markTable, TCL_STRING_KEYS);
- Tcl_InitHashTable(&sharedPtr->windowTable, TCL_STRING_KEYS);
- Tcl_InitHashTable(&sharedPtr->imageTable, TCL_STRING_KEYS);
- sharedPtr->undoStack = TkUndoInitStack(interp,0);
- sharedPtr->undo = 0;
- sharedPtr->isDirty = 0;
- sharedPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
- sharedPtr->autoSeparators = 1;
- sharedPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
- sharedPtr->stateEpoch = 0;
- }
+ textPtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(textPtr->tkwin),
+ TextWidgetObjCmd, textPtr, TextCmdDeletedProc);
+ DEBUG_ALLOC(textPtr->widgetNumber = ++widgetNumber);
/*
* Add the new widget to the shared list.
*/
- textPtr->sharedTextPtr = sharedPtr;
- sharedPtr->refCount++;
- textPtr->next = sharedPtr->peers;
- sharedPtr->peers = textPtr;
+ textPtr->sharedTextPtr = sharedTextPtr;
+ sharedTextPtr->refCount += 1;
+ textPtr->next = sharedTextPtr->peers;
+ sharedTextPtr->peers = textPtr;
+
+ /*
+ * Clear the indices, do this after the shared widget is created.
+ */
+
+ TkTextIndexClear(&textPtr->topIndex, textPtr);
+ TkTextIndexClear(&textPtr->selIndex, textPtr);
/*
* This refCount will be held until DestroyText is called. Note also that
@@ -577,68 +1067,76 @@ CreateWidget(
* start, end where given as configuration options.
*/
- if (parent != NULL) {
- textPtr->start = parent->start;
- textPtr->end = parent->end;
+ if (parent) {
+ (textPtr->startMarker = parent->startMarker)->refCount += 1;
+ (textPtr->endMarker = parent->endMarker)->refCount += 1;
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+ textPtr->startLine = parent->startLine;
+ textPtr->endLine = parent->endLine;
+#endif
} else {
- textPtr->start = NULL;
- textPtr->end = NULL;
+ (textPtr->startMarker = sharedTextPtr->startMarker)->refCount += 1;
+ (textPtr->endMarker = sharedTextPtr->endMarker)->refCount += 1;
}
- textPtr->state = TK_TEXT_STATE_NORMAL;
- textPtr->relief = TK_RELIEF_FLAT;
- textPtr->cursor = None;
- textPtr->charWidth = 1;
- textPtr->charHeight = 10;
- textPtr->wrapMode = TEXT_WRAPMODE_CHAR;
- textPtr->prevWidth = Tk_Width(newWin);
- textPtr->prevHeight = Tk_Height(newWin);
-
/*
* Register with the B-tree. In some sense it would be best if we could do
* this later (after configuration options), so that any changes to
* start,end do not require a total recalculation.
*/
- TkBTreeAddClient(sharedPtr->tree, textPtr, textPtr->charHeight);
+ TkBTreeAddClient(sharedTextPtr->tree, textPtr, textPtr->lineHeight);
+
+ /*
+ * Also the image binding support has to be enabled if required.
+ * This has to be done after TkBTreeAddClient has been called.
+ */
+
+ TkTextImageAddClient(sharedTextPtr, textPtr);
+
+ textPtr->state = TK_TEXT_STATE_NORMAL;
+ textPtr->relief = TK_RELIEF_FLAT;
+ textPtr->cursor = None;
+ textPtr->charWidth = 1;
+ textPtr->spaceWidth = 1;
+ textPtr->lineHeight = -1;
+ textPtr->prevWidth = Tk_Width(newWin);
+ textPtr->prevHeight = Tk_Height(newWin);
+ textPtr->hyphens = -1;
+ textPtr->currNearbyFlag = -1;
+ textPtr->prevSyncState = -1;
/*
* This will add refCounts to textPtr.
*/
TkTextCreateDInfo(textPtr);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &startIndex);
+ TkTextIndexSetupToStartOfText(&startIndex, textPtr, sharedTextPtr->tree);
TkTextSetYView(textPtr, &startIndex, 0);
- textPtr->exportSelection = 1;
+ textPtr->exportSelection = true;
textPtr->pickEvent.type = LeaveNotify;
+ textPtr->steadyMarks = textPtr->sharedTextPtr->steadyMarks;
textPtr->undo = textPtr->sharedTextPtr->undo;
- textPtr->maxUndo = textPtr->sharedTextPtr->maxUndo;
+ textPtr->maxUndoDepth = textPtr->sharedTextPtr->maxUndoDepth;
+ textPtr->maxRedoDepth = textPtr->sharedTextPtr->maxRedoDepth;
+ textPtr->maxUndoSize = textPtr->sharedTextPtr->maxUndoSize;
textPtr->autoSeparators = textPtr->sharedTextPtr->autoSeparators;
- textPtr->tabOptionPtr = NULL;
/*
* Create the "sel" tag and the "current" and "insert" marks.
- */
-
- textPtr->selBorder = NULL;
- textPtr->inactiveSelBorder = NULL;
- textPtr->selBorderWidth = 0;
- textPtr->selBorderWidthPtr = NULL;
- textPtr->selFgColorPtr = NULL;
-
- /*
* Note: it is important that textPtr->selTagPtr is NULL before this
* initial call.
*/
textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel", NULL);
- textPtr->selTagPtr->reliefString =
- ckalloc(sizeof(DEF_TEXT_SELECT_RELIEF));
+ textPtr->selTagPtr->reliefString = malloc(sizeof(DEF_TEXT_SELECT_RELIEF));
strcpy(textPtr->selTagPtr->reliefString, DEF_TEXT_SELECT_RELIEF);
Tk_GetRelief(interp, DEF_TEXT_SELECT_RELIEF, &textPtr->selTagPtr->relief);
- textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &startIndex);
textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &startIndex);
+ textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &startIndex);
+ textPtr->currentMarkIndex = startIndex;
+
+ sharedTextPtr->numPeers += 1;
/*
* Create the option table for this widget class. If it has already been
@@ -652,21 +1150,18 @@ CreateWidget(
textPtr->optionTable = optionTable;
Tk_CreateEventHandler(textPtr->tkwin,
- ExposureMask|StructureNotifyMask|FocusChangeMask,
- TextEventProc, textPtr);
+ ExposureMask|StructureNotifyMask|FocusChangeMask, TextEventProc, textPtr);
Tk_CreateEventHandler(textPtr->tkwin, KeyPressMask|KeyReleaseMask
|ButtonPressMask|ButtonReleaseMask|EnterWindowMask
|LeaveWindowMask|PointerMotionMask|VirtualEventMask,
TkTextBindProc, textPtr);
- Tk_CreateSelHandler(textPtr->tkwin, XA_PRIMARY, XA_STRING,
- TextFetchSelection, textPtr, XA_STRING);
+ Tk_CreateSelHandler(textPtr->tkwin, XA_PRIMARY, XA_STRING, TextFetchSelection, textPtr, XA_STRING);
- if (Tk_InitOptions(interp, (char *) textPtr, optionTable, textPtr->tkwin)
- != TCL_OK) {
+ if (Tk_InitOptions(interp, (char *) textPtr, optionTable, textPtr->tkwin) != TCL_OK) {
Tk_DestroyWindow(textPtr->tkwin);
return TCL_ERROR;
}
- if (ConfigureText(interp, textPtr, objc-2, objv+2) != TCL_OK) {
+ if (TkConfigureText(interp, textPtr, objc - 2, objv + 2) != TCL_OK) {
Tk_DestroyWindow(textPtr->tkwin);
return TCL_ERROR;
}
@@ -678,6 +1173,35 @@ CreateWidget(
/*
*--------------------------------------------------------------
*
+ * UpdateLineMetrics --
+ *
+ * This function updates the pixel height calculations of a range of
+ * lines in the widget.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Line heights may be recalculated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+UpdateLineMetrics(
+ TkText *textPtr, /* Information about widget. */
+ unsigned startLine, /* Start at this line. */
+ unsigned endLine) /* Go no further than this line. */
+{
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ ProcessConfigureNotify(textPtr, true);
+ }
+ TkTextUpdateLineMetrics(textPtr, startLine, endLine);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
* TextWidgetObjCmd --
*
* This function is invoked to process the Tcl command that corresponds
@@ -693,6 +1217,96 @@ CreateWidget(
*--------------------------------------------------------------
*/
+static void
+ErrorNotAllowed(
+ Tcl_Interp *interp,
+ const char *text)
+{
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(text, -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NOT_ALLOWED", NULL);
+}
+
+static bool
+TestIfTriggerUserMod(
+ TkSharedText *sharedTextPtr,
+ Tcl_Obj *indexObjPtr)
+{
+ return sharedTextPtr->triggerWatchCmd && strcmp(Tcl_GetString(indexObjPtr), "insert") == 0;
+}
+
+static bool
+TestIfPerformingUndoRedo(
+ Tcl_Interp *interp,
+ const TkSharedText *sharedTextPtr,
+ int *result)
+{
+ if (sharedTextPtr->undoStack && TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ /*
+ * It's possible that this command command will be invoked inside the "watch" callback,
+ * but this is not allowed when performing undo/redo.
+ */
+
+ ErrorNotAllowed(interp, "cannot modify inside undo/redo operation");
+ if (result) {
+ *result = TCL_ERROR;
+ }
+ return true;
+ }
+ return false;
+}
+
+static bool
+TestIfDisabled(
+ Tcl_Interp *interp,
+ const TkText *textPtr,
+ int *result)
+{
+ assert(result);
+
+ if (textPtr->state == TK_TEXT_STATE_DISABLED) {
+#if !SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET
+ ErrorNotAllowed(interp, "attempt to modify disabled widget");
+ *result = TCL_ERROR;
+#endif
+ return true;
+ }
+ return false;
+}
+
+static bool
+TestIfDead(
+ Tcl_Interp *interp,
+ const TkText *textPtr,
+ int *result)
+{
+ assert(result);
+
+ if (TkTextIsDeadPeer(textPtr)) {
+#if !SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET
+ ErrorNotAllowed(interp, "attempt to modify dead widget");
+ *result = TCL_ERROR;
+#endif
+ return true;
+ }
+ return false;
+}
+
+static Tcl_Obj *
+AppendScript(
+ const char *oldScript,
+ const char *script)
+{
+ int lenOfNew = strlen(script);
+ int lenOfOld = strlen(oldScript);
+ int totalLen = lenOfOld + lenOfNew + 1;
+ char *newScript = malloc(totalLen + 1);
+
+ memcpy(newScript, oldScript, lenOfOld);
+ newScript[lenOfOld] = '\n';
+ memcpy(newScript + lenOfOld + 1, script, lenOfNew + 1);
+ return Tcl_NewStringObj(newScript, totalLen);
+}
+
static int
TextWidgetObjCmd(
ClientData clientData, /* Information about text widget. */
@@ -700,23 +1314,27 @@ TextWidgetObjCmd(
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
- register TkText *textPtr = clientData;
+ TkText *textPtr = clientData;
+ TkSharedText *sharedTextPtr;
int result = TCL_OK;
- int index;
+ int commandIndex = -1;
+ bool oldUndoStackEvent;
static const char *const optionStrings[] = {
- "bbox", "cget", "compare", "configure", "count", "debug", "delete",
- "dlineinfo", "dump", "edit", "get", "image", "index", "insert",
- "mark", "peer", "pendingsync", "replace", "scan", "search",
- "see", "sync", "tag", "window", "xview", "yview", NULL
+ "tk_bindvar", "tk_textInsert", "tk_textReplace",
+ "bbox", "brks", "checksum", "cget", "clear", "compare", "configure",
+ "count", "debug", "delete", "dlineinfo", "dump", "edit", "get", "image",
+ "index", "insert", "inspect", "isclean", "isdead", "isempty", "lineno",
+ "load", "mark", "peer", "pendingsync", "replace", "scan", "search",
+ "see", "sync", "tag", "watch", "window", "xview", "yview", NULL
};
enum options {
- TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_COUNT,
- TEXT_DEBUG, TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT,
- TEXT_GET, TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK,
- TEXT_PEER, TEXT_PENDINGSYNC, TEXT_REPLACE, TEXT_SCAN,
- TEXT_SEARCH, TEXT_SEE, TEXT_SYNC, TEXT_TAG, TEXT_WINDOW,
- TEXT_XVIEW, TEXT_YVIEW
+ TEXT_TK_BINDVAR, TEXT_TK_TEXTINSERT, TEXT_TK_TEXTREPLACE,
+ TEXT_BBOX, TEXT_BRKS, TEXT_CHECKSUM, TEXT_CGET, TEXT_CLEAR, TEXT_COMPARE, TEXT_CONFIGURE,
+ TEXT_COUNT, TEXT_DEBUG, TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT, TEXT_GET, TEXT_IMAGE,
+ TEXT_INDEX, TEXT_INSERT, TEXT_INSPECT, TEXT_ISCLEAN, TEXT_ISDEAD, TEXT_ISEMPTY, TEXT_LINENO,
+ TEXT_LOAD, TEXT_MARK, TEXT_PEER, TEXT_PENDINGSYNC, TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH,
+ TEXT_SEE, TEXT_SYNC, TEXT_TAG, TEXT_WATCH, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
};
if (objc < 2) {
@@ -725,29 +1343,75 @@ TextWidgetObjCmd(
}
if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings,
- sizeof(char *), "option", 0, &index) != TCL_OK) {
+ sizeof(char *), "option", 0, &commandIndex) != TCL_OK) {
+ /*
+ * Hide the first three options, generating the error description with
+ * the side effects of Tcl_GetIndexFromObjStruct.
+ */
+
+ (void) Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings + 3,
+ sizeof(char *), "option", 0, &commandIndex);
return TCL_ERROR;
}
- textPtr->refCount++;
- switch ((enum options) index) {
+ textPtr->refCount += 1;
+ sharedTextPtr = textPtr->sharedTextPtr;
+ oldUndoStackEvent = sharedTextPtr->undoStackEvent;
+ sharedTextPtr->undoStackEvent = false;
+
+ /*
+ * Clear saved insert cursor position.
+ */
+
+ TkTextIndexClear(&textPtr->insertIndex, textPtr);
+
+ /*
+ * Check if we need to update the "current" mark segment.
+ */
+
+ if (sharedTextPtr->haveToSetCurrentMark) {
+ TkTextUpdateCurrentMark(sharedTextPtr);
+ }
+
+ switch ((enum options) commandIndex) {
+ case TEXT_TK_BINDVAR: {
+ TkTextStringList *listPtr;
+
+ /*
+ * Bind a variable to this widget, this variable will be released (Tcl_UnsetVar2)
+ * when the widget will be destroyed.
+ *
+ * I suggest to provide a general support for binding variables to widgets in a
+ * future Tk version.
+ */
+
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "varname");
+ result = TCL_ERROR;
+ goto done;
+ }
+
+ listPtr = malloc(sizeof(TkTextStringList));
+ Tcl_IncrRefCount(listPtr->strObjPtr = objv[2]);
+ listPtr->nextPtr = textPtr->varBindingList;
+ textPtr->varBindingList = listPtr;
+ break;
+ }
case TEXT_BBOX: {
int x, y, width, height;
- const TkTextIndex *indexPtr;
+ TkTextIndex index;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index");
result = TCL_ERROR;
goto done;
}
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
result = TCL_ERROR;
goto done;
}
- if (TkTextIndexBbox(textPtr, indexPtr, &x, &y, &width, &height,
- NULL) == 0) {
- Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
+ if (TkTextIndexBbox(textPtr, &index, &x, &y, &width, &height, NULL) == 0) {
+ Tcl_Obj *listObj = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(x));
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(y));
@@ -758,16 +1422,89 @@ TextWidgetObjCmd(
}
break;
}
+ case TEXT_BRKS: {
+ Tcl_Obj *arrPtr;
+ unsigned length, i;
+ char const *lang = NULL;
+ char buf[1];
+
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "index");
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (objc == 4) {
+ if (!TkTextTestLangCode(interp, objv[3])) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (!TkTextComputeBreakLocations(interp, "", 0, "en", buf)) {
+ ErrorNotAllowed(interp, "external library libunibreak/liblinebreak is not available");
+ result = TCL_ERROR;
+ goto done;
+ }
+ lang = Tcl_GetString(objv[3]);
+ }
+ if ((length = GetByteLength(objv[2])) < textPtr->brksBufferSize) {
+ textPtr->brksBufferSize = MAX(length, textPtr->brksBufferSize + 512);
+ textPtr->brksBuffer = realloc(textPtr->brksBuffer, textPtr->brksBufferSize);
+ }
+ TkTextComputeBreakLocations(interp, Tcl_GetString(objv[2]), length, lang, textPtr->brksBuffer);
+ arrPtr = Tcl_NewObj();
+
+ for (i = 0; i < length; ++i) {
+ int value;
+
+ switch (textPtr->brksBuffer[i]) {
+ case LINEBREAK_INSIDEACHAR: continue;
+ case LINEBREAK_MUSTBREAK: value = 2; break;
+ case LINEBREAK_ALLOWBREAK: value = 1; break;
+ default: value = 0; break;
+ }
+ Tcl_ListObjAppendElement(interp, arrPtr, Tcl_NewIntObj(value));
+ }
+
+ Tcl_SetObjResult(interp, arrPtr);
+ break;
+ }
+ case TEXT_CHECKSUM:
+ result = TextChecksumCmd(textPtr, interp, objc, objv);
+ break;
case TEXT_CGET:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "option");
result = TCL_ERROR;
goto done;
} else {
- Tcl_Obj *objPtr = Tk_GetOptionValue(interp, (char *) textPtr,
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+
+ Tcl_Obj *objPtr, *optionObj = NULL;
+
+ if (strcmp(Tcl_GetString(objv[2]), "-start") == 0) {
+ optionObj = Tcl_NewStringObj(textPtr->startLine ? "-startline" : "-startindex", -1);
+ } else if (strncmp(Tcl_GetString(objv[2]), "-startl", 7) == 0) {
+ optionObj = Tcl_NewStringObj("-startline", -1);
+ } else if (strcmp(Tcl_GetString(objv[2]), "-end") == 0) {
+ optionObj = Tcl_NewStringObj(textPtr->endLine ? "-endline" : "-endindex", -1);
+ } else if (strncmp(Tcl_GetString(objv[2]), "-endl", 5) == 0) {
+ optionObj = Tcl_NewStringObj("-endline", -1);
+ } else {
+ Tcl_IncrRefCount(optionObj = objv[2]);
+ }
+
+ Tcl_IncrRefCount(optionObj);
+ objPtr = Tk_GetOptionValue(interp, (char *) textPtr,
+ textPtr->optionTable, optionObj, textPtr->tkwin);
+ Tcl_DecrRefCount(optionObj);
+
+#else /* if !SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+ objPtr = Tk_GetOptionValue(interp, (char *) textPtr,
textPtr->optionTable, objv[2], textPtr->tkwin);
- if (objPtr == NULL) {
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+ if (!objPtr) {
result = TCL_ERROR;
goto done;
}
@@ -775,250 +1512,197 @@ TextWidgetObjCmd(
result = TCL_OK;
}
break;
+ case TEXT_CLEAR:
+ if (TestIfPerformingUndoRedo(interp, sharedTextPtr, &result)) {
+ goto done;
+ }
+ ClearText(textPtr, true);
+ TkTextRelayoutWindow(textPtr, TK_TEXT_LINE_GEOMETRY);
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+ break;
case TEXT_COMPARE: {
int relation, value;
- const char *p;
- const TkTextIndex *index1Ptr, *index2Ptr;
+ TkTextIndex index1, index2;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 2, objv, "index1 op index2");
result = TCL_ERROR;
goto done;
}
- index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- index2Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[4]);
- if (index1Ptr == NULL || index2Ptr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index1)
+ || !TkTextGetIndexFromObj(interp, textPtr, objv[4], &index2)) {
result = TCL_ERROR;
goto done;
}
- relation = TkTextIndexCmp(index1Ptr, index2Ptr);
- p = Tcl_GetString(objv[3]);
- if (p[0] == '<') {
- value = (relation < 0);
- if ((p[1] == '=') && (p[2] == 0)) {
- value = (relation <= 0);
- } else if (p[1] != 0) {
- goto compareError;
- }
- } else if (p[0] == '>') {
- value = (relation > 0);
- if ((p[1] == '=') && (p[2] == 0)) {
- value = (relation >= 0);
- } else if (p[1] != 0) {
- goto compareError;
- }
- } else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
- value = (relation == 0);
- } else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
- value = (relation != 0);
+ relation = TkTextIndexCompare(&index1, &index2);
+ value = TkTextTestRelation(interp, relation, Tcl_GetString(objv[3]));
+ if (value == -1) {
+ result = TCL_ERROR;
} else {
- goto compareError;
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
}
- Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
break;
-
- compareError:
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "bad comparison operator \"%s\": must be"
- " <, <=, ==, >=, >, or !=", Tcl_GetString(objv[3])));
- Tcl_SetErrorCode(interp, "TK", "VALUE", "COMPARISON", NULL);
- result = TCL_ERROR;
- goto done;
}
case TEXT_CONFIGURE:
if (objc <= 3) {
Tcl_Obj *objPtr = Tk_GetOptionInfo(interp, (char *) textPtr,
- textPtr->optionTable, ((objc == 3) ? objv[2] : NULL),
- textPtr->tkwin);
+ textPtr->optionTable, objc == 3 ? objv[2] : NULL, textPtr->tkwin);
- if (objPtr == NULL) {
+ if (!objPtr) {
result = TCL_ERROR;
goto done;
}
Tcl_SetObjResult(interp, objPtr);
} else {
- result = ConfigureText(interp, textPtr, objc-2, objv+2);
+ result = TkConfigureText(interp, textPtr, objc - 2, objv + 2);
}
break;
case TEXT_COUNT: {
- const TkTextIndex *indexFromPtr, *indexToPtr;
- int i, found = 0, update = 0;
+ TkTextIndex indexFrom, indexTo;
Tcl_Obj *objPtr = NULL;
+ bool update = false;
+ int i, found = 0;
if (objc < 4) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "?-option value ...? index1 index2");
+ Tcl_WrongNumArgs(interp, 2, objv, "?-option value ...? index1 index2");
result = TCL_ERROR;
goto done;
}
- indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[objc-2]);
- if (indexFromPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[objc-1]);
- if (indexToPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[objc - 2], &indexFrom)
+ || !TkTextGetIndexFromObj(interp, textPtr, objv[objc - 1], &indexTo)) {
result = TCL_ERROR;
goto done;
}
- for (i = 2; i < objc-2; i++) {
- int value, length;
+ for (i = 2; i < objc - 2; i++) {
+ int length;
+ int value = INT_MIN;
const char *option = Tcl_GetString(objv[i]);
- char c;
- length = objv[i]->length;
+ length = GetByteLength(objv[i]);
if (length < 2 || option[0] != '-') {
goto badOption;
}
- c = option[1];
- if (c == 'c' && !strncmp("-chars", option, (unsigned) length)) {
- value = CountIndices(textPtr, indexFromPtr, indexToPtr,
- COUNT_CHARS);
- } else if (c == 'd' && (length > 8)
- && !strncmp("-displaychars", option, (unsigned) length)) {
- value = CountIndices(textPtr, indexFromPtr, indexToPtr,
- COUNT_DISPLAY_CHARS);
- } else if (c == 'd' && (length > 8)
- && !strncmp("-displayindices", option,(unsigned)length)) {
- value = CountIndices(textPtr, indexFromPtr, indexToPtr,
- COUNT_DISPLAY_INDICES);
- } else if (c == 'd' && (length > 8)
- && !strncmp("-displaylines", option, (unsigned) length)) {
- TkTextLine *fromPtr, *lastPtr;
- TkTextIndex index, index2;
-
- int compare = TkTextIndexCmp(indexFromPtr, indexToPtr);
- value = 0;
-
- if (compare == 0) {
- goto countDone;
+ switch (option[1]) {
+ case 'c':
+ if (strncmp("-chars", option, length) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_CHARS);
}
+ break;
+ case 'd':
+ if (length > 8 && strncmp("-display", option, 8) == 0) {
+ switch (option[8]) {
+ case 'c':
+ if (strcmp("chars", option + 8) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_DISPLAY_CHARS);
+ }
+ break;
+ case 'h':
+ if (strcmp("hyphens", option + 8) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_DISPLAY_HYPHENS);
+ }
+ break;
+ case 'i':
+ if (strcmp("indices", option + 8) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_DISPLAY_INDICES);
+ }
+ break;
+ case 'l':
+ if (strcmp("lines", option + 8) == 0) {
+ int compare = TkTextIndexCompare(&indexFrom, &indexTo);
- if (compare > 0) {
- const TkTextIndex *tmpPtr = indexFromPtr;
+ if (compare == 0) {
+ value = 0;
+ } else {
+ const TkTextIndex *indexPtr1;
+ const TkTextIndex *indexPtr2;
- indexFromPtr = indexToPtr;
- indexToPtr = tmpPtr;
+ if (compare < 0) {
+ indexPtr1 = &indexFrom;
+ indexPtr2 = &indexTo;
+ } else {
+ indexPtr1 = &indexTo;
+ indexPtr2 = &indexFrom;
+ }
+ if (!sharedTextPtr->allowUpdateLineMetrics) {
+ ProcessConfigureNotify(textPtr, true);
+ }
+ value = TkTextCountDisplayLines(textPtr, indexPtr1, indexPtr2);
+ if (compare > 0) {
+ value = -value;
+ }
+ }
+ }
+ break;
+ case 't':
+ if (strcmp("text", option + 8) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_DISPLAY_TEXT);
+ }
+ break;
+ }
}
-
- lastPtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree,textPtr));
- fromPtr = indexFromPtr->linePtr;
- if (fromPtr == lastPtr) {
- goto countDone;
+ break;
+ case 'h':
+ if (strncmp("-hyphens", option, length) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_HYPHENS);
}
-
- /*
- * Caution: we must NEVER call TkTextUpdateOneLine with the
- * last artificial line in the widget.
- */
-
- index = *indexFromPtr;
- index.byteIndex = 0;
-
- /*
- * We're going to count up all display lines in the logical
- * line of 'indexFromPtr' up to, but not including the logical
- * line of 'indexToPtr' (except if this line is elided), and
- * then subtract off what came in too much from elided lines,
- * also subtract off what we didn't want from 'from' and add
- * on what we didn't count from 'to'.
- */
-
- while (TkTextIndexCmp(&index,indexToPtr) < 0) {
- value += TkTextUpdateOneLine(textPtr, index.linePtr,
- 0, &index, 0);
+ break;
+ case 'i':
+ if (strncmp("-indices", option, length) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_INDICES);
}
-
- index2 = index;
-
- /*
- * Now we need to adjust the count to:
- * - subtract off the number of display lines between
- * indexToPtr and index2, since we might have skipped past
- * indexToPtr, if we have several logical lines in a
- * single display line
- * - subtract off the number of display lines overcounted
- * in the first logical line
- * - add on the number of display lines in the last logical
- * line
- * This logic is still ok if both indexFromPtr and indexToPtr
- * are in the same logical line.
- */
-
- index = *indexToPtr;
- index.byteIndex = 0;
- while (TkTextIndexCmp(&index,&index2) < 0) {
- value -= TkTextUpdateOneLine(textPtr, index.linePtr,
- 0, &index, 0);
+ break;
+ case 'l':
+ if (strncmp("-lines", option, length) == 0) {
+ TkTextBTree tree = sharedTextPtr->tree;
+ value = TkBTreeLinesTo(tree, textPtr, TkTextIndexGetLine(&indexTo), NULL)
+ - TkBTreeLinesTo(tree, textPtr, TkTextIndexGetLine(&indexFrom), NULL);
}
- index.linePtr = indexFromPtr->linePtr;
- index.byteIndex = 0;
- while (1) {
- TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL);
- if (TkTextIndexCmp(&index,indexFromPtr) >= 0) {
- break;
- }
- TkTextIndexForwBytes(textPtr, &index, 1, &index);
- value--;
-
+ break;
+ case 't':
+ if (strncmp("-text", option, length) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_TEXT);
}
- if (indexToPtr->linePtr != lastPtr) {
- index.linePtr = indexToPtr->linePtr;
- index.byteIndex = 0;
- while (1) {
- TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL);
- if (TkTextIndexCmp(&index,indexToPtr) >= 0) {
- break;
- }
- TkTextIndexForwBytes(textPtr, &index, 1, &index);
- value++;
- }
+ break;
+ case 'u':
+ if (strncmp("-update", option, length) == 0) {
+ update = true;
+ continue;
}
-
- if (compare > 0) {
- value = -value;
+ break;
+ case 'x':
+ if (strncmp("-xpixels", option, length) == 0) {
+ int x1, x2;
+ TkTextIndex index;
+
+ index = indexFrom;
+ TkTextFindDisplayIndex(textPtr, &index, 0, &x1);
+ index = indexTo;
+ TkTextFindDisplayIndex(textPtr, &index, 0, &x2);
+ value = x2 - x1;
}
- } else if (c == 'i'
- && !strncmp("-indices", option, (unsigned) length)) {
- value = CountIndices(textPtr, indexFromPtr, indexToPtr,
- COUNT_INDICES);
- } else if (c == 'l'
- && !strncmp("-lines", option, (unsigned) length)) {
- value = TkBTreeLinesTo(textPtr, indexToPtr->linePtr)
- - TkBTreeLinesTo(textPtr, indexFromPtr->linePtr);
- } else if (c == 'u'
- && !strncmp("-update", option, (unsigned) length)) {
- update = 1;
- continue;
- } else if (c == 'x'
- && !strncmp("-xpixels", option, (unsigned) length)) {
- int x1, x2;
- TkTextIndex index;
-
- index = *indexFromPtr;
- TkTextFindDisplayLineEnd(textPtr, &index, 0, &x1);
- index = *indexToPtr;
- TkTextFindDisplayLineEnd(textPtr, &index, 0, &x2);
- value = x2 - x1;
- } else if (c == 'y'
- && !strncmp("-ypixels", option, (unsigned) length)) {
- if (update) {
- TkTextUpdateLineMetrics(textPtr,
- TkBTreeLinesTo(textPtr, indexFromPtr->linePtr),
- TkBTreeLinesTo(textPtr, indexToPtr->linePtr), -1);
+ break;
+ case 'y':
+ if (strncmp("-ypixels", option, length) == 0) {
+ int from, to;
+
+ if (update) {
+ from = TkTextIndexGetLineNumber(&indexFrom, textPtr);
+ to = TkTextIndexGetLineNumber(&indexTo, textPtr);
+ UpdateLineMetrics(textPtr, from, to);
+ }
+ from = TkTextIndexYPixels(textPtr, &indexFrom);
+ to = TkTextIndexYPixels(textPtr, &indexTo);
+ value = to - from;
}
- value = TkTextIndexYPixels(textPtr, indexToPtr)
- - TkTextIndexYPixels(textPtr, indexFromPtr);
- } else {
+ break;
+ }
+ if (value == INT_MIN) {
goto badOption;
}
- countDone:
- found++;
+ found += 1;
if (found == 1) {
Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
} else {
@@ -1041,9 +1725,7 @@ TextWidgetObjCmd(
* Use the default '-indices'.
*/
- int value = CountIndices(textPtr, indexFromPtr, indexToPtr,
- COUNT_INDICES);
-
+ int value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_INDICES);
Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
} else if (found > 1) {
Tcl_SetObjResult(interp, objPtr);
@@ -1052,9 +1734,9 @@ TextWidgetObjCmd(
badOption:
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "bad option \"%s\" must be -chars, -displaychars, "
- "-displayindices, -displaylines, -indices, -lines, -update, "
- "-xpixels, or -ypixels", Tcl_GetString(objv[i])));
+ "bad option \"%s\": must be -chars, -displaychars, -displayhyphens, -displayindices, "
+ "-displaylines, -displaytext, -hyphens, -indices, -lines, -text, -update, -xpixels, "
+ "or -ypixels", Tcl_GetString(objv[i])));
Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
result = TCL_ERROR;
goto done;
@@ -1068,177 +1750,215 @@ TextWidgetObjCmd(
if (objc == 2) {
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(tkBTreeDebug));
} else {
- if (Tcl_GetBooleanFromObj(interp, objv[2],
- &tkBTreeDebug) != TCL_OK) {
+ if (Tcl_GetBooleanFromObj(interp, objv[2], (int *) &tkBTreeDebug) != TCL_OK) {
result = TCL_ERROR;
goto done;
}
tkTextDebug = tkBTreeDebug;
}
break;
- case TEXT_DELETE:
+ case TEXT_DELETE: {
+ int i, flags = 0;
+ bool ok = true;
+
+ for (i = 2; i < objc - 1; i++) {
+ const char *option = Tcl_GetString(objv[i]);
+ int length;
+
+ if (option[0] != '-') {
+ break;
+ }
+ length = GetByteLength(objv[i]);
+ if (strncmp("-marks", option, length) == 0) {
+ flags |= DELETE_MARKS;
+ } else if (strncmp("-inclusive", option, length) == 0) {
+ flags |= DELETE_INCLUSIVE;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -marks, or -inclusive", Tcl_GetString(objv[i])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ }
+
+ objv += i - 2;
+ objc -= i - 2;
+
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
+ Tcl_WrongNumArgs(interp, 2, objv, "?-marks? ?-inclusive? index1 ?index2 ...?");
result = TCL_ERROR;
goto done;
}
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- if (objc < 5) {
- /*
- * Simple case requires no predetermination of indices.
- */
+ if (TestIfDisabled(interp, textPtr, &result)
+ || TestIfDead(interp, textPtr, &result)
+ || TestIfPerformingUndoRedo(interp, sharedTextPtr, &result)) {
+ goto done;
+ }
+ if (objc < 5) {
+ /*
+ * Simple case requires no predetermination of indices.
+ */
- const TkTextIndex *indexPtr1, *indexPtr2;
+ TkTextIndex index1, index2, *index2Ptr;
+ bool triggerUserMod = TestIfTriggerUserMod(sharedTextPtr, objv[2]);
+ bool triggerWatch = triggerUserMod || textPtr->triggerAlways;
- /*
- * Parse the starting and stopping indices.
- */
+ if (triggerWatch) {
+ TkTextSaveCursorIndex(textPtr);
+ }
+
+ /*
+ * Parse the starting and stopping indices.
+ */
- indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, textPtr,
- objv[2]);
- if (indexPtr1 == NULL) {
+ if (!TkTextGetIndexFromObj(textPtr->interp, textPtr, objv[2], &index1)) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (objc == 4) {
+ if (!TkTextGetIndexFromObj(textPtr->interp, textPtr, objv[3], index2Ptr = &index2)) {
result = TCL_ERROR;
goto done;
}
- if (objc == 4) {
- indexPtr2 = TkTextGetIndexFromObj(textPtr->interp,
- textPtr, objv[3]);
- if (indexPtr2 == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- } else {
- indexPtr2 = NULL;
- }
- DeleteIndexRange(NULL, textPtr, indexPtr1, indexPtr2, 1);
} else {
- /*
- * Multi-index pair case requires that we prevalidate the
- * indices and sort from last to first so that deletes occur
- * in the exact (unshifted) text. It also needs to handle
- * partial and fully overlapping ranges. We have to do this
- * with multiple passes.
- */
-
- TkTextIndex *indices, *ixStart, *ixEnd, *lastStart;
- char *useIdx;
- int i;
+ index2Ptr = NULL;
+ }
+ ok = DeleteIndexRange(NULL, textPtr, &index1, index2Ptr, flags, true,
+ triggerWatch, triggerWatch, triggerUserMod, true);
+ } else {
+ /*
+ * Multi-index pair case requires that we prevalidate the
+ * indices and sort from last to first so that deletes occur
+ * in the exact (unshifted) text. It also needs to handle
+ * partial and fully overlapping ranges. We have to do this
+ * with multiple passes.
+ */
- objc -= 2;
- objv += 2;
- indices = ckalloc((objc + 1) * sizeof(TkTextIndex));
+ TkTextIndex *indices, *ixStart, *ixEnd, *lastStart;
+ char *useIdx;
+ int lastUsed, i;
- /*
- * First pass verifies that all indices are valid.
- */
+ objc -= 2;
+ objv += 2;
+ indices = malloc((objc + 1)*sizeof(TkTextIndex));
- for (i = 0; i < objc; i++) {
- const TkTextIndex *indexPtr =
- TkTextGetIndexFromObj(interp, textPtr, objv[i]);
+ /*
+ * First pass verifies that all indices are valid.
+ */
- if (indexPtr == NULL) {
- result = TCL_ERROR;
- ckfree(indices);
- goto done;
- }
- indices[i] = *indexPtr;
+ for (i = 0; i < objc; i++) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i], &indices[i])) {
+ result = TCL_ERROR;
+ free(indices);
+ goto done;
}
+ }
- /*
- * Pad out the pairs evenly to make later code easier.
- */
+ /*
+ * Pad out the pairs evenly to make later code easier.
+ */
- if (objc & 1) {
- indices[i] = indices[i-1];
- TkTextIndexForwChars(NULL, &indices[i], 1, &indices[i],
- COUNT_INDICES);
- objc++;
- }
- useIdx = ckalloc(objc);
- memset(useIdx, 0, (unsigned) objc);
+ if (objc & 1) {
+ indices[i] = indices[i - 1];
+ TkTextIndexForwChars(textPtr, &indices[i], 1, &indices[i], COUNT_INDICES);
+ objc += 1;
+ }
+ useIdx = malloc(objc);
+ memset(useIdx, 0, (unsigned) objc);
- /*
- * Do a decreasing order sort so that we delete the end ranges
- * first to maintain index consistency.
- */
+ /*
+ * Do a decreasing order sort so that we delete the end ranges
+ * first to maintain index consistency.
+ */
- qsort(indices, (unsigned) objc / 2,
- 2 * sizeof(TkTextIndex), TextIndexSortProc);
- lastStart = NULL;
+ qsort(indices, (unsigned) objc/2, 2*sizeof(TkTextIndex), TextIndexSortProc);
+ lastStart = NULL;
+ lastUsed = 0; /* otherwise GCC complains */
- /*
- * Second pass will handle bogus ranges (end < start) and
- * overlapping ranges.
- */
+ /*
+ * Second pass will handle bogus ranges (end < start) and
+ * overlapping ranges.
+ */
+
+ for (i = 0; i < objc; i += 2) {
+ ixStart = &indices[i];
+ ixEnd = &indices[i + 1];
+ if (TkTextIndexCompare(ixEnd, ixStart) <= 0) {
+ continue;
+ }
+ if (lastStart) {
+ if (TkTextIndexCompare(ixStart, lastStart) == 0) {
+ /*
+ * Start indices were equal, and the sort placed
+ * the longest range first, so skip this one.
+ */
- for (i = 0; i < objc; i += 2) {
- ixStart = &indices[i];
- ixEnd = &indices[i+1];
- if (TkTextIndexCmp(ixEnd, ixStart) <= 0) {
continue;
- }
- if (lastStart) {
- if (TkTextIndexCmp(ixStart, lastStart) == 0) {
- /*
- * Start indices were equal, and the sort placed
- * the longest range first, so skip this one.
- */
+ } else if (TkTextIndexCompare(lastStart, ixEnd) < 0) {
+ /*
+ * The next pair has a start range before the end
+ * point of the last range. Constrain the delete
+ * range, but use the pointer values.
+ */
+ *ixEnd = *lastStart;
+ if (TkTextIndexCompare(ixEnd, ixStart) <= 0) {
continue;
- } else if (TkTextIndexCmp(lastStart, ixEnd) < 0) {
- /*
- * The next pair has a start range before the end
- * point of the last range. Constrain the delete
- * range, but use the pointer values.
- */
-
- *ixEnd = *lastStart;
- if (TkTextIndexCmp(ixEnd, ixStart) <= 0) {
- continue;
- }
}
}
- lastStart = ixStart;
- useIdx[i] = 1;
}
+ lastStart = ixStart;
+ useIdx[i] = 1;
+ lastUsed = i;
+ }
- /*
- * Final pass take the input from the previous and deletes the
- * ranges which are flagged to be deleted.
- */
+ /*
+ * Final pass take the input from the previous and deletes the
+ * ranges which are flagged to be deleted.
+ */
- for (i = 0; i < objc; i += 2) {
- if (useIdx[i]) {
- /*
- * We don't need to check the return value because all
- * indices are preparsed above.
- */
+ for (i = 0; i < objc && ok; i += 2) {
+ if (useIdx[i]) {
+ bool triggerUserMod = TestIfTriggerUserMod(sharedTextPtr, objv[i]);
+ bool triggerWatch = triggerUserMod || textPtr->triggerAlways;
- DeleteIndexRange(NULL, textPtr, &indices[i],
- &indices[i+1], 1);
+ if (triggerWatch) {
+ TkTextSaveCursorIndex(textPtr);
}
+
+ /*
+ * We don't need to check the return value because all
+ * indices are preparsed above.
+ */
+
+ ok = DeleteIndexRange(NULL, textPtr, &indices[i], &indices[i + 1],
+ flags, true, triggerWatch, triggerWatch, triggerUserMod, i == lastUsed);
}
- ckfree(indices);
}
+ free(indices);
+ }
+
+ if (!ok) {
+ return TCL_OK; /* widget has been destroyed */
}
break;
+ }
case TEXT_DLINEINFO: {
int x, y, width, height, base;
- const TkTextIndex *indexPtr;
+ TkTextIndex index;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index");
result = TCL_ERROR;
goto done;
}
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
result = TCL_ERROR;
goto done;
}
- if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width, &height,
- &base) == 0) {
- Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
+ if (TkTextGetDLineInfo(textPtr, &index, &x, &y, &width, &height, &base)) {
+ Tcl_Obj *listObj = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(x));
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(y));
@@ -1257,46 +1977,81 @@ TextWidgetObjCmd(
result = TextEditCmd(textPtr, interp, objc, objv);
break;
case TEXT_GET: {
- Tcl_Obj *objPtr = NULL;
- int i, found = 0, visible = 0;
- const char *name;
- int length;
+ Tcl_Obj *objPtr;
+ int i, found;
+ bool includeHyphens;
+ bool visibleOnly;
+ unsigned countOptions;
+ const char *option;
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "?-displaychars? ?--? index1 ?index2 ...?");
+ Tcl_WrongNumArgs(interp, 2, objv, "?-option? ?--? index1 ?index2 ...?");
result = TCL_ERROR;
goto done;
}
- /*
- * Simple, restrictive argument parsing. The only options are -- and
- * -displaychars (or any unique prefix).
- */
-
+ objPtr = NULL;
+ found = 0;
+ includeHyphens = true;
+ visibleOnly = false;
+ countOptions = 0;
i = 2;
- if (objc > 3) {
- name = Tcl_GetString(objv[i]);
- length = objv[i]->length;
- if (length > 1 && name[0] == '-') {
- if (strncmp("-displaychars", name, (unsigned) length) == 0) {
- i++;
- visible = 1;
- name = Tcl_GetString(objv[i]);
- length = objv[i]->length;
+
+ while (objc > i + 1 && (option = Tcl_GetString(objv[i]))[0] == '-') {
+ bool badOption = false;
+
+ i += 1;
+
+ if (option[1] == '-') {
+ if (option[2] == '\0') {
+ break;
}
- if ((i < objc-1) && (length == 2) && !strcmp("--", name)) {
- i++;
+ badOption = true;
+ } else if (++countOptions > 1) {
+ i -= 1;
+ break;
+ } else {
+ switch (option[1]) {
+ case 'c':
+ if (strcmp("-chars", option) != 0) {
+ badOption = true;
+ }
+ break;
+ case 't':
+ if (strcmp("-text", option) != 0) {
+ badOption = true;
+ }
+ includeHyphens = false;
+ break;
+ case 'd':
+ if (strcmp("-displaychars", option) == 0) {
+ visibleOnly = true;
+ } else if (strcmp("-displaytext", option) == 0) {
+ visibleOnly = true;
+ includeHyphens = false;
+ } else {
+ badOption = true;
+ }
+ break;
+ default:
+ badOption = true;
+ break;
}
}
+
+ if (badOption) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": "
+ "must be -chars, -displaychars, -displaytext, or -text", option));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
}
for (; i < objc; i += 2) {
- const TkTextIndex *index1Ptr, *index2Ptr;
- TkTextIndex index2;
+ TkTextIndex index1, index2;
- index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i]);
- if (index1Ptr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i], &index1)) {
if (objPtr) {
Tcl_DecrRefCount(objPtr);
}
@@ -1304,47 +2059,43 @@ TextWidgetObjCmd(
goto done;
}
- if (i+1 == objc) {
- TkTextIndexForwChars(NULL, index1Ptr, 1, &index2,
- COUNT_INDICES);
- index2Ptr = &index2;
+ if (i + 1 == objc) {
+ TkTextIndexForwChars(textPtr, &index1, 1, &index2, COUNT_INDICES);
} else {
- index2Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i+1]);
- if (index2Ptr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i + 1], &index2)) {
if (objPtr) {
Tcl_DecrRefCount(objPtr);
}
result = TCL_ERROR;
goto done;
}
+ if (TkTextIndexCompare(&index1, &index2) >= 0) {
+ goto done;
+ }
}
- if (TkTextIndexCmp(index1Ptr, index2Ptr) < 0) {
- /*
- * We want to move the text we get from the window into the
- * result, but since this could in principle be a megabyte or
- * more, we want to do it efficiently!
- */
+ /*
+ * We want to move the text we get from the window into the
+ * result, but since this could in principle be a megabyte or
+ * more, we want to do it efficiently!
+ */
- Tcl_Obj *get = TextGetText(textPtr, index1Ptr, index2Ptr,
- visible);
+ Tcl_Obj *get = TextGetText(textPtr, &index1, &index2, NULL, NULL, UINT_MAX,
+ visibleOnly, includeHyphens);
- found++;
- if (found == 1) {
- Tcl_SetObjResult(interp, get);
- } else {
- if (found == 2) {
- /*
- * Move the first item we put into the result into the
- * first element of the list object.
- */
+ if (++found == 1) {
+ Tcl_SetObjResult(interp, get);
+ } else {
+ if (found == 2) {
+ /*
+ * Move the first item we put into the result into the
+ * first element of the list object.
+ */
- objPtr = Tcl_NewObj();
- Tcl_ListObjAppendElement(NULL, objPtr,
- Tcl_GetObjResult(interp));
- }
- Tcl_ListObjAppendElement(NULL, objPtr, get);
+ objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_GetObjResult(interp));
}
+ Tcl_ListObjAppendElement(NULL, objPtr, get);
}
}
if (found > 1) {
@@ -1356,78 +2107,174 @@ TextWidgetObjCmd(
result = TkTextImageCmd(textPtr, interp, objc, objv);
break;
case TEXT_INDEX: {
- const TkTextIndex *indexPtr;
+ TkTextIndex index;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index");
result = TCL_ERROR;
goto done;
}
-
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
result = TCL_ERROR;
goto done;
}
- Tcl_SetObjResult(interp, TkTextNewIndexObj(textPtr, indexPtr));
+ Tcl_SetObjResult(interp, TkTextNewIndexObj(&index));
break;
}
- case TEXT_INSERT: {
- const TkTextIndex *indexPtr;
+ case TEXT_INSERT:
+ case TEXT_TK_TEXTINSERT: {
+ TkTextIndex index;
+ bool triggerUserMod, triggerWatch;
+ bool destroyed;
if (objc < 4) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "index chars ?tagList chars tagList ...?");
+ const char *args = (commandIndex == TEXT_TK_TEXTINSERT) ?
+ "?-hyphentags tags? index chars ?tagList chars tagList ...?" :
+ "index chars ?tagList chars tagList ...?";
+ Tcl_WrongNumArgs(interp, 2, objv, args);
result = TCL_ERROR;
goto done;
}
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
result = TCL_ERROR;
goto done;
}
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- result = TextInsertCmd(NULL, textPtr, interp, objc-3, objv+3,
- indexPtr, 1);
+ if (TestIfDisabled(interp, textPtr, &result)
+ || TestIfDead(interp, textPtr, &result)
+ || TestIfPerformingUndoRedo(interp, sharedTextPtr, &result)) {
+ goto done;
+ }
+
+ triggerUserMod = TestIfTriggerUserMod(sharedTextPtr, objv[2]);
+ triggerWatch = triggerUserMod || textPtr->triggerAlways;
+
+ if (triggerWatch) {
+ TkTextSaveCursorIndex(textPtr);
+ }
+ result = TextInsertCmd(textPtr, interp, objc - 3, objv + 3, &index, true, triggerWatch,
+ triggerWatch, triggerUserMod, &destroyed, commandIndex == TEXT_TK_TEXTINSERT);
+ if (destroyed) {
+ return result; /* widget has been destroyed */
}
break;
}
- case TEXT_MARK:
- result = TkTextMarkCmd(textPtr, interp, objc, objv);
+ case TEXT_INSPECT:
+ result = TextInspectCmd(textPtr, interp, objc, objv);
break;
- case TEXT_PEER:
- result = TextPeerCmd(textPtr, interp, objc, objv);
+ case TEXT_ISCLEAN: {
+ bool discardSelection = false;
+ const TkText *myTextPtr = textPtr;
+ int i;
+
+ for (i = 2; i < objc; ++i) {
+ char const * opt = Tcl_GetString(objv[i]);
+
+ if (strcmp(opt, "-overall") == 0) {
+ myTextPtr = NULL;
+ } else if (strcmp(opt, "-discardselection") == 0) {
+ discardSelection = true;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be -overall", opt));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ }
+
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(IsClean(sharedTextPtr, myTextPtr, discardSelection)));
break;
- case TEXT_PENDINGSYNC: {
- if (objc != 2) {
- Tcl_WrongNumArgs(interp, 2, objv, NULL);
+ }
+ case TEXT_ISDEAD:
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(TkTextIsDeadPeer(textPtr)));
+ break;
+ case TEXT_ISEMPTY: {
+ bool overall = false;
+ int i;
+
+ for (i = 2; i < objc; ++i) {
+ char const * opt = Tcl_GetString(objv[i]);
+
+ if (strcmp(opt, "-overall") == 0) {
+ overall = true;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be -overall", opt));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ }
+
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(IsEmpty(sharedTextPtr, overall ? NULL : textPtr)));
+ break;
+ }
+ case TEXT_LINENO: {
+ TkTextIndex index;
+ int lineno;
+
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "index");
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
result = TCL_ERROR;
goto done;
}
- Tcl_SetObjResult(interp,
- Tcl_NewBooleanObj(TkTextPendingsync(textPtr)));
+ lineno = TkTextIsDeadPeer(textPtr) ? 0 : TkTextIndexGetLineNumber(&index, textPtr) + 1;
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(lineno));
break;
}
- case TEXT_REPLACE: {
- const TkTextIndex *indexFromPtr, *indexToPtr;
-
- if (objc < 5) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "index1 index2 chars ?tagList chars tagList ...?");
+ case TEXT_LOAD: {
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "textcontent");
result = TCL_ERROR;
goto done;
}
- indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexFromPtr == NULL) {
+ if (TestIfPerformingUndoRedo(interp, sharedTextPtr, &result)) {
+ goto done;
+ }
+ ClearText(textPtr, false);
+ TkTextRelayoutWindow(textPtr, TK_TEXT_LINE_GEOMETRY);
+ if ((result = TkBTreeLoad(textPtr, objv[2])) != TCL_OK) {
+ ClearText(textPtr, false);
+ }
+ break;
+ }
+ case TEXT_MARK:
+ result = TkTextMarkCmd(textPtr, interp, objc, objv);
+ break;
+ case TEXT_PEER:
+ result = TextPeerCmd(textPtr, interp, objc, objv);
+ break;
+ case TEXT_PENDINGSYNC: {
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (!sharedTextPtr->allowUpdateLineMetrics) {
+ ProcessConfigureNotify(textPtr, true);
+ }
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(TkTextPendingSync(textPtr)));
+ break;
+ }
+ case TEXT_REPLACE:
+ case TEXT_TK_TEXTREPLACE: {
+ TkTextIndex indexFrom, indexTo, index;
+ bool triggerUserMod, triggerWatch;
+ bool destroyed;
+
+ if (objc < 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "index1 index2 chars ?tagList chars tagList ...?");
result = TCL_ERROR;
goto done;
}
- indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[3]);
- if (indexToPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &indexFrom)
+ || !TkTextGetIndexFromObj(interp, textPtr, objv[3], &indexTo)) {
result = TCL_ERROR;
goto done;
}
- if (TkTextIndexCmp(indexFromPtr, indexToPtr) > 0) {
+ if (TkTextIndexCompare(&indexFrom, &indexTo) > 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"index \"%s\" before \"%s\" in the text",
Tcl_GetString(objv[3]), Tcl_GetString(objv[2])));
@@ -1435,80 +2282,85 @@ TextWidgetObjCmd(
result = TCL_ERROR;
goto done;
}
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- int lineNum, byteIndex;
- TkTextIndex index;
+ if (TestIfDisabled(interp, textPtr, &result) || TestIfDead(interp, textPtr, &result)) {
+ goto done;
+ }
- /*
- * The 'replace' operation is quite complex to do correctly,
- * because we want a number of criteria to hold:
- *
- * 1. The insertion point shouldn't move, unless it is within the
- * deleted range. In this case it should end up after the new
- * text.
- *
- * 2. The window should not change the text it shows - should not
- * scroll vertically - unless the result of the replace is
- * that the insertion position which used to be on-screen is
- * now off-screen.
- */
+ destroyed = false;
+ triggerUserMod = TestIfTriggerUserMod(sharedTextPtr, objv[2]);
+ triggerWatch = triggerUserMod || textPtr->triggerAlways;
- byteIndex = textPtr->topIndex.byteIndex;
- lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr);
+ /*
+ * The 'replace' operation is quite complex to do correctly,
+ * because we want a number of criteria to hold:
+ *
+ * 1. The insertion point shouldn't move, unless it is within the
+ * deleted range. In this case it should end up after the new
+ * text.
+ *
+ * 2. The window should not change the text it shows - should not
+ * scroll vertically - unless the result of the replace is
+ * that the insertion position which used to be on-screen is
+ * now off-screen.
+ */
- TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- if ((TkTextIndexCmp(indexFromPtr, &index) < 0)
- && (TkTextIndexCmp(indexToPtr, &index) > 0)) {
- /*
- * The insertion point is inside the range to be replaced, so
- * we have to do some calculations to ensure it doesn't move
- * unnecessarily.
- */
+ TkTextIndexSave(&textPtr->topIndex);
+ if (triggerWatch) {
+ TkTextSaveCursorIndex(textPtr);
+ }
- int deleteInsertOffset, insertLength, j;
+ TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
+ if (TkTextIndexCompare(&indexFrom, &index) < 0
+ && TkTextIndexCompare(&index, &indexTo) <= 0) {
+ /*
+ * The insertion point is inside the range to be replaced, so
+ * we have to do some calculations to ensure it doesn't move
+ * unnecessarily.
+ */
- insertLength = 0;
- for (j = 4; j < objc; j += 2) {
- insertLength += Tcl_GetCharLength(objv[j]);
- }
+ int deleteInsertOffset, insertLength, j;
- /*
- * Calculate 'deleteInsertOffset' as an offset we will apply
- * to the insertion point after this operation.
- */
+ insertLength = 0;
+ for (j = 4; j < objc; j += 2) {
+ insertLength += Tcl_GetCharLength(objv[j]);
+ }
- deleteInsertOffset = CountIndices(textPtr, indexFromPtr,
- &index, COUNT_CHARS);
- if (deleteInsertOffset > insertLength) {
- deleteInsertOffset = insertLength;
- }
+ /*
+ * Calculate 'deleteInsertOffset' as an offset we will apply
+ * to the insertion point after this operation.
+ */
- result = TextReplaceCmd(textPtr, interp, indexFromPtr,
- indexToPtr, objc, objv, 0);
+ deleteInsertOffset = CountIndices(textPtr, &indexFrom, &index, COUNT_CHARS);
+ if (deleteInsertOffset > insertLength) {
+ deleteInsertOffset = insertLength;
+ }
- if (result == TCL_OK) {
- /*
- * Move the insertion position to the correct place.
- */
+ result = TextReplaceCmd(textPtr, interp, &indexFrom, &indexTo, objc, objv, false,
+ triggerWatch, triggerUserMod, &destroyed, commandIndex == TEXT_TK_TEXTREPLACE);
+ if (destroyed) { return result; /* widget has been destroyed */ }
- TkTextIndexForwChars(NULL, indexFromPtr,
- deleteInsertOffset, &index, COUNT_INDICES);
- TkBTreeUnlinkSegment(textPtr->insertMarkPtr,
- textPtr->insertMarkPtr->body.mark.linePtr);
- TkBTreeLinkSegment(textPtr->insertMarkPtr, &index);
- }
- } else {
- result = TextReplaceCmd(textPtr, interp, indexFromPtr,
- indexToPtr, objc, objv, 1);
- }
if (result == TCL_OK) {
/*
- * Now ensure the top-line is in the right place.
+ * Move the insertion position to the correct place.
*/
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineNum, byteIndex, &index);
- TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST);
+ TkTextIndexForwChars(textPtr, &indexFrom, deleteInsertOffset, &index, COUNT_INDICES);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->insertMarkPtr);
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->insertMarkPtr, &index);
+ textPtr->insertIndex = index;
+ }
+ } else {
+ result = TextReplaceCmd(textPtr, interp, &indexFrom, &indexTo, objc, objv, false,
+ triggerWatch, triggerUserMod, &destroyed, commandIndex == TEXT_TK_TEXTREPLACE);
+ if (destroyed) { return result; /* widget has been destroyed */ }
+ }
+ if (result == TCL_OK) {
+ /*
+ * Now ensure the top-line is in the right place.
+ */
+
+ if (!TkTextIndexRebuild(&textPtr->topIndex)) {
+ TkTextSetYView(textPtr, &textPtr->topIndex, TK_TEXT_NOPIXELADJUST);
}
}
break;
@@ -1523,41 +2375,98 @@ TextWidgetObjCmd(
result = TkTextSeeCmd(textPtr, interp, objc, objv);
break;
case TEXT_SYNC: {
- if (objc == 4) {
- Tcl_Obj *cmd = objv[3];
+ bool wrongNumberOfArgs = false;
+
+ if (objc == 3 || objc == 4) {
const char *option = Tcl_GetString(objv[2]);
- if (strncmp(option, "-command", objv[2]->length)) {
- Tcl_AppendResult(interp, "wrong option \"", option, "\": should be \"-command\"", NULL);
+ if (*option != '-') {
+ wrongNumberOfArgs = true;
+ } else if (strncmp(option, "-command", objv[2]->length) != 0) {
+ Tcl_AppendResult(interp, "wrong option \"", option,
+ "\": should be \"-command\"", NULL);
result = TCL_ERROR;
goto done;
}
- Tcl_IncrRefCount(cmd);
- if (TkTextPendingsync(textPtr)) {
- if (textPtr->afterSyncCmd) {
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- }
- textPtr->afterSyncCmd = cmd;
- } else {
- textPtr->afterSyncCmd = cmd;
- Tcl_DoWhenIdle(RunAfterSyncCmd, (ClientData) textPtr);
- }
- break;
} else if (objc != 2) {
- Tcl_WrongNumArgs(interp, 2, objv, "?-command command?");
+ wrongNumberOfArgs = true;
+ }
+ if (wrongNumberOfArgs) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?-command ?command??");
result = TCL_ERROR;
goto done;
}
- if (textPtr->afterSyncCmd) {
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ if (!sharedTextPtr->allowUpdateLineMetrics) {
+ ProcessConfigureNotify(textPtr, true);
+ }
+ if (objc == 3) {
+ if (textPtr->afterSyncCmd) {
+ Tcl_SetObjResult(interp, textPtr->afterSyncCmd);
+ }
+ } else if (objc == 4) {
+ Tcl_Obj *cmd = objv[3];
+ const char *script = Tcl_GetString(cmd);
+ bool append = false;
+
+ if (*script == '+') {
+ script += 1;
+ append = true;
+ }
+
+ if (!textPtr->afterSyncCmd) {
+ if (append) {
+ cmd = Tcl_NewStringObj(script, -1);
+ }
+ Tcl_IncrRefCount(textPtr->afterSyncCmd = cmd);
+ } else {
+ if (!append && *script == '\0') {
+ if (textPtr->pendingAfterSync) {
+ Tcl_CancelIdleCall(RunAfterSyncCmd, (ClientData) textPtr);
+ textPtr->pendingAfterSync = false;
+ }
+ cmd = NULL;
+ } else {
+ if (append) {
+ cmd = AppendScript(Tcl_GetString(textPtr->afterSyncCmd), script);
+ }
+ Tcl_IncrRefCount(cmd);
+ }
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ textPtr->afterSyncCmd = cmd;
+ }
+ if (!textPtr->pendingAfterSync) {
+ textPtr->pendingAfterSync = true;
+ if (!TkTextPendingSync(textPtr)) {
+ Tcl_DoWhenIdle(RunAfterSyncCmd, (ClientData) textPtr);
+ }
+ }
+ } else {
+ textPtr->sendSyncEvent = true;
+
+ if (!TkTextPendingSync(textPtr)) {
+ /*
+ * There is nothing to sync, so fire the <<WidgetViewSync>> event,
+ * because nobody else will do this when no update is pending.
+ */
+ TkTextGenerateWidgetViewSyncEvent(textPtr, false);
+ } else {
+ UpdateLineMetrics(textPtr, 0, TkBTreeNumLines(sharedTextPtr->tree, textPtr));
+ }
}
- textPtr->afterSyncCmd = NULL;
- TkTextUpdateLineMetrics(textPtr, 1,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), -1);
break;
}
case TEXT_TAG:
result = TkTextTagCmd(textPtr, interp, objc, objv);
break;
+ case TEXT_WATCH: {
+ Tcl_Obj *cmd = textPtr->watchCmd;
+
+ result = TextWatchCmd(textPtr, interp, objc, objv);
+ if (cmd) {
+ Tcl_SetObjResult(interp, cmd);
+ Tcl_DecrRefCount(cmd);
+ }
+ break;
+ }
case TEXT_WINDOW:
result = TkTextWindowCmd(textPtr, interp, objc, objv);
break;
@@ -1570,119 +2479,306 @@ TextWidgetObjCmd(
}
done:
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
+ if (--textPtr->refCount == 0) {
+ bool sharedIsReleased = textPtr->sharedIsReleased;
+
+ free(textPtr);
+ if (sharedIsReleased) {
+ return result;
+ }
+ textPtr = NULL;
+ } else if (textPtr->watchCmd) {
+ TkTextTriggerWatchCursor(textPtr);
+ }
+ if (sharedTextPtr->undoStackEvent) {
+ TriggerUndoStackEvent(sharedTextPtr);
+ }
+ sharedTextPtr->undoStackEvent = oldUndoStackEvent;
+
+ if (textPtr && textPtr->syncTime == 0) {
+ UpdateLineMetrics(textPtr, 0, TkBTreeNumLines(sharedTextPtr->tree, textPtr));
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
}
+
return result;
}
/*
*--------------------------------------------------------------
*
- * SharedTextObjCmd --
+ * IsEmpty --
*
- * This function is invoked to process commands on the shared portion of
- * a text widget. Currently it is not actually exported as a Tcl command,
- * and is only used internally to process parts of undo/redo scripts.
- * See the user documentation for 'text' for details on what it does -
- * the only subcommands it currently supports are 'insert' and 'delete'.
+ * Test whether this widget is empty. The widget is empty
+ * if it contains exact two single newline characters.
+ *
+ * Results:
+ * Returns true if the widget is empty, and false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+DoesNotContainTextSegments(
+ const TkTextSegment *segPtr1,
+ const TkTextSegment *segPtr2)
+{
+ for ( ; segPtr1 != segPtr2; segPtr1 = segPtr1->nextPtr) {
+ if (segPtr1->size > 0) {
+ return !segPtr1->nextPtr; /* ignore trailing newline */
+ }
+ }
+
+ return true;
+}
+
+static bool
+IsEmpty(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr) /* Can be NULL. */
+{
+ TkTextSegment *startMarker;
+ TkTextSegment *endMarker;
+
+ assert(sharedTextPtr);
+
+ if (TkBTreeNumLines(sharedTextPtr->tree, textPtr) > 1) {
+ return false;
+ }
+
+ if (textPtr) {
+ startMarker = textPtr->startMarker;
+ endMarker = textPtr->endMarker;
+ } else {
+ startMarker = sharedTextPtr->startMarker;
+ endMarker = sharedTextPtr->endMarker;
+ }
+
+ return DoesNotContainTextSegments(startMarker, endMarker);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * IsClean --
+ *
+ * Test whether this widget is clean. The widget is clean
+ * if it is empty, if no mark is set, and if the solely
+ * newline of this widget is untagged.
+ *
+ * Results:
+ * Returns true if the widget is clean, and false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+ContainsAnySegment(
+ const TkTextSegment *segPtr1,
+ const TkTextSegment *segPtr2)
+{
+ for ( ; segPtr1 != segPtr2; segPtr1 = segPtr1->nextPtr) {
+ if (segPtr1->size > 0 || segPtr1->normalMarkFlag) {
+ return !!segPtr1->nextPtr; /* ignore trailing newline */
+ }
+ }
+
+ return false;
+}
+
+static bool
+IsClean(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* Can be NULL. */
+ bool discardSelection)
+{
+ const TkTextTagSet *tagInfoPtr;
+ const TkTextSegment *startMarker;
+ const TkTextSegment *endMarker;
+ const TkTextLine *endLine;
+
+ assert(sharedTextPtr);
+
+ if (TkBTreeNumLines(sharedTextPtr->tree, textPtr) > 1) {
+ return false;
+ }
+
+ if (textPtr) {
+ startMarker = textPtr->startMarker;
+ endMarker = textPtr->endMarker;
+ } else {
+ startMarker = sharedTextPtr->startMarker;
+ endMarker = sharedTextPtr->endMarker;
+ }
+
+ if (ContainsAnySegment(startMarker, endMarker)) {
+ return false;
+ }
+
+ endLine = endMarker->sectionPtr->linePtr;
+
+ if (!textPtr && ContainsAnySegment(endLine->segPtr, NULL)) {
+ /* This widget contains any mark on very last line. */
+ return false;
+ }
+
+ tagInfoPtr = endLine->prevPtr->lastPtr->tagInfoPtr;
+
+ return discardSelection ?
+ TkTextTagBitContainsSet(sharedTextPtr->selectionTags, tagInfoPtr) :
+ tagInfoPtr == sharedTextPtr->emptyTagInfoPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextTestRelation --
+ *
+ * Given a relation (>0 for greater, =0 for equal, and <0 for
+ * less), this function computes whether the given operator
+ * satisfies this relation.
+ *
+ * Results:
+ * Returns 1 if the relation will be satsified, 0 if it will
+ * not be satisifed, and -1 if the operator is invalid.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static int
+BadComparisonOperator(
+ Tcl_Interp *interp,
+ char const *op)
+{
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad comparison operator \"%s\": must be <, <=, ==, >=, >, or !=", op));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "COMPARISON", NULL);
+ return -1;
+}
+
+int
+TkTextTestRelation(
+ Tcl_Interp *interp, /* Current interpreter. */
+ int relation, /* Test this relation... */
+ char const *op) /* ...whether it will be satisifed by this operator. */
+{
+ int value;
+
+ if (op[0] == '<') {
+ value = (relation < 0);
+ if (op[1] == '=' && op[2] == 0) {
+ value = (relation <= 0);
+ } else if (op[1] != 0) {
+ return BadComparisonOperator(interp, op);
+ }
+ } else if (op[0] == '>') {
+ value = (relation > 0);
+ if (op[1] == '=' && op[2] == 0) {
+ value = (relation >= 0);
+ } else if (op[1] != 0) {
+ return BadComparisonOperator(interp, op);
+ }
+ } else if (op[0] == '=' && op[1] == '=' && op[2] == 0) {
+ value = (relation == 0);
+ } else if (op[0] == '!' && op[1] == '=' && op[2] == 0) {
+ value = (relation != 0);
+ } else {
+ return BadComparisonOperator(interp, op);
+ }
+
+ return value;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TextWatchCmd --
+ *
+ * This function is invoked to process the "text watch" Tcl command. See
+ * the user documentation for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
- * See the user documentation for "text".
+ * See the user documentation.
*
*--------------------------------------------------------------
*/
static int
-SharedTextObjCmd(
- ClientData clientData, /* Information about shared test B-tree. */
+TextWatchCmd(
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
- register TkSharedText *sharedPtr = clientData;
- int result = TCL_OK;
- int index;
-
- static const char *const optionStrings[] = {
- "delete", "insert", NULL
- };
- enum options {
- TEXT_DELETE, TEXT_INSERT
- };
-
- if (objc < 2) {
- Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
- return TCL_ERROR;
- }
+ TkSharedText *sharedTextPtr;
- if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings,
- sizeof(char *), "option", 0, &index) != TCL_OK) {
+ if (objc > 4) {
+ /* NOTE: avoid trigraph "??-" in string. */
+ Tcl_WrongNumArgs(interp, 4, objv, "\?\?-always? commandPrefix?");
return TCL_ERROR;
}
- switch ((enum options) index) {
- case TEXT_DELETE:
- if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
- return TCL_ERROR;
- }
- if (objc < 5) {
- /*
- * Simple case requires no predetermination of indices.
- */
+ sharedTextPtr = textPtr->sharedTextPtr;
- TkTextIndex index1;
+ if (objc <= 2) {
+ TkText *tPtr;
- /*
- * Parse the starting and stopping indices.
- */
+ if (textPtr->watchCmd) {
+ textPtr->triggerAlways = false;
+ textPtr->watchCmd = NULL;
+ }
+ sharedTextPtr->triggerWatchCmd = false;
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr->watchCmd) {
+ sharedTextPtr->triggerWatchCmd = true;
+ }
+ }
+ } else {
+ const char *script;
+ Tcl_Obj *cmd;
+ int argnum = 2;
- result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[2],
- &index1);
- if (result != TCL_OK) {
- return result;
+ if (objc == 4) {
+ if (strcmp(Tcl_GetString(objv[2]), "-always") != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -always", Tcl_GetString(objv[2])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "WATCH_OPTION", NULL);
+ return TCL_ERROR;
}
- if (objc == 4) {
- TkTextIndex index2;
+ textPtr->triggerAlways = true;
+ argnum = 3;
+ }
- result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[3],
- &index2);
- if (result != TCL_OK) {
- return result;
- }
- DeleteIndexRange(sharedPtr, NULL, &index1, &index2, 1);
+ cmd = objv[argnum];
+ script = Tcl_GetString(cmd);
+
+ if (*script == '+') {
+ script += 1;
+ if (textPtr->watchCmd) {
+ cmd = AppendScript(Tcl_GetString(textPtr->watchCmd), script);
} else {
- DeleteIndexRange(sharedPtr, NULL, &index1, NULL, 1);
+ cmd = Tcl_NewStringObj(script, -1);
}
- return TCL_OK;
- } else {
- /* Too many arguments */
- return TCL_ERROR;
+ } else if (argnum == 2) {
+ textPtr->triggerAlways = false;
}
- break;
- case TEXT_INSERT: {
- TkTextIndex index1;
- if (objc < 4) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "index chars ?tagList chars tagList ...?");
- return TCL_ERROR;
- }
- result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[2],
- &index1);
- if (result != TCL_OK) {
- return result;
- }
- return TextInsertCmd(sharedPtr, NULL, interp, objc-3, objv+3, &index1,
- 1);
- }
- default:
- return TCL_OK;
+ textPtr->sharedTextPtr->triggerWatchCmd = true;
+ Tcl_IncrRefCount(textPtr->watchCmd = cmd);
}
+
+ return TCL_OK;
}
/*
@@ -1712,18 +2808,13 @@ TextPeerCmd(
Tk_Window tkwin = textPtr->tkwin;
int index;
- static const char *const peerOptionStrings[] = {
- "create", "names", NULL
- };
- enum peerOptions {
- PEER_CREATE, PEER_NAMES
- };
+ static const char *const peerOptionStrings[] = { "create", "names", NULL };
+ enum peerOptions { PEER_CREATE, PEER_NAMES };
if (objc < 3) {
Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
return TCL_ERROR;
}
-
if (Tcl_GetIndexFromObjStruct(interp, objv[2], peerOptionStrings,
sizeof(char *), "peer option", 0, &index) != TCL_OK) {
return TCL_ERROR;
@@ -1735,8 +2826,7 @@ TextPeerCmd(
Tcl_WrongNumArgs(interp, 3, objv, "pathName ?-option value ...?");
return TCL_ERROR;
}
- return CreateWidget(textPtr->sharedTextPtr, tkwin, interp, textPtr,
- objc-2, objv+2);
+ return CreateWidget(textPtr->sharedTextPtr, tkwin, interp, textPtr, objc - 2, objv + 2);
case PEER_NAMES: {
TkText *tPtr = textPtr->sharedTextPtr->peers;
Tcl_Obj *peersObj;
@@ -1746,10 +2836,9 @@ TextPeerCmd(
return TCL_ERROR;
}
peersObj = Tcl_NewObj();
- while (tPtr != NULL) {
+ while (tPtr) {
if (tPtr != textPtr) {
- Tcl_ListObjAppendElement(NULL, peersObj,
- TkNewWindowObj(tPtr->tkwin));
+ Tcl_ListObjAppendElement(NULL, peersObj, TkNewWindowObj(tPtr->tkwin));
}
tPtr = tPtr->next;
}
@@ -1791,8 +2880,28 @@ TextReplaceCmd(
/* Index to which to replace. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[], /* Argument objects. */
- int viewUpdate) /* Update vertical view if set. */
+ bool viewUpdate, /* Update vertical view if set. */
+ bool triggerWatch, /* Should we trigger the watch command? */
+ bool userFlag, /* Trigger due to user modification? */
+ bool *destroyed, /* Store whether the widget has been destroyed. */
+ bool parseHyphens) /* Should we parse hyphens (tk_textReplace)? */
{
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ bool *stillExisting = sharedTextPtr->stillExisting;
+ int origAutoSep = sharedTextPtr->autoSeparators;
+ int result = TCL_OK;
+ TkTextIndex indexTmp;
+ bool notDestroyed;
+ bool existing;
+
+ assert(destroyed);
+ assert(!TkTextIsDeadPeer(textPtr));
+
+ if (!stillExisting) {
+ sharedTextPtr->stillExisting = &existing;
+ existing = true;
+ }
+
/*
* Perform the deletion and insertion, but ensure no undo-separator is
* placed between the two operations. Since we are using the helper
@@ -1801,34 +2910,41 @@ TextReplaceCmd(
* undo-separator between the delete and insert.
*/
- int origAutoSep = textPtr->sharedTextPtr->autoSeparators;
- int result, lineNumber;
- TkTextIndex indexTmp;
-
- if (textPtr->sharedTextPtr->undo) {
- textPtr->sharedTextPtr->autoSeparators = 0;
- if (origAutoSep &&
- textPtr->sharedTextPtr->lastEditMode!=TK_TEXT_EDIT_REPLACE) {
- TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack);
+ if (sharedTextPtr->undoStack) {
+ sharedTextPtr->autoSeparators = false;
+ if (origAutoSep && sharedTextPtr->lastEditMode != TK_TEXT_EDIT_REPLACE) {
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoPushSeparator(sharedTextPtr->undoStack, true);
+ sharedTextPtr->lastUndoTokenType = -1;
}
}
- /*
- * Must save and restore line in indexFromPtr based on line number; can't
- * keep the line itself as that might be eliminated/invalidated when
- * deleting the range. [Bug 1602537]
- */
-
+ /* The line and segment storage may change when deleting. */
indexTmp = *indexFromPtr;
- lineNumber = TkBTreeLinesTo(textPtr, indexFromPtr->linePtr);
- DeleteIndexRange(NULL, textPtr, indexFromPtr, indexToPtr, viewUpdate);
- indexTmp.linePtr = TkBTreeFindLine(indexTmp.tree, textPtr, lineNumber);
- result = TextInsertCmd(NULL, textPtr, interp, objc-4, objv+4,
- &indexTmp, viewUpdate);
+ TkTextIndexSave(&indexTmp);
+
+ notDestroyed = DeleteIndexRange(NULL, textPtr, indexFromPtr, indexToPtr,
+ 0, viewUpdate, triggerWatch, false, userFlag, true);
+
+ if (notDestroyed) {
+ TkTextIndexRebuild(&indexTmp);
+ result = TextInsertCmd(textPtr, interp, objc - 4, objv + 4, &indexTmp,
+ viewUpdate, false, triggerWatch, userFlag, destroyed, parseHyphens);
+ if (*destroyed) {
+ notDestroyed = false;
+ }
+ } else {
+ *destroyed = true;
+ }
- if (textPtr->sharedTextPtr->undo) {
- textPtr->sharedTextPtr->lastEditMode = TK_TEXT_EDIT_REPLACE;
- textPtr->sharedTextPtr->autoSeparators = origAutoSep;
+ if (*sharedTextPtr->stillExisting) {
+ if (sharedTextPtr->undoStack) {
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_REPLACE;
+ sharedTextPtr->autoSeparators = origAutoSep;
+ }
+ if (sharedTextPtr->stillExisting == &existing) {
+ sharedTextPtr->stillExisting = NULL;
+ }
}
return result;
@@ -1843,9 +2959,9 @@ TextReplaceCmd(
* *decreasing* order (last to first).
*
* Results:
- * The return value is -1 if the first argument should be before the
- * second element, 0 if it's equivalent, and 1 if it should be after the
- * second element.
+ * The return value is less than zero if the first argument should be before
+ * the second element, 0 if it's equivalent, and greater than zero if it should
+ * be after the second element.
*
* Side effects:
* None.
@@ -1860,7 +2976,7 @@ TextIndexSortProc(
{
TkTextIndex *pair1 = (TkTextIndex *) first;
TkTextIndex *pair2 = (TkTextIndex *) second;
- int cmp = TkTextIndexCmp(&pair1[1], &pair2[1]);
+ int cmp = TkTextIndexCompare(&pair1[1], &pair2[1]);
if (cmp == 0) {
/*
@@ -1869,14 +2985,247 @@ TextIndexSortProc(
* index pair.
*/
- cmp = TkTextIndexCmp(&pair1[0], &pair2[0]);
+ cmp = TkTextIndexCompare(&pair1[0], &pair2[0]);
}
- if (cmp > 0) {
- return -1;
- } else if (cmp < 0) {
- return 1;
+
+ return -cmp;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeEmbeddedWindows --
+ *
+ * Free up any embedded windows which belong to this widget.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * All embedded windows of this widget will be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeEmbeddedWindows(
+ TkText *textPtr) /* The concerned text widget. */
+{
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+
+ for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->windowTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
+ TkTextSegment *ewPtr = Tcl_GetHashValue(hPtr);
+ TkTextEmbWindowClient *client = ewPtr->body.ew.clients;
+ TkTextEmbWindowClient **prev = &ewPtr->body.ew.clients;
+
+ while (client) {
+ TkTextEmbWindowClient *next = client->next;
+ if (client->textPtr == textPtr && client->hPtr == hPtr) {
+ TkTextWinFreeClient(hPtr, client);
+ *prev = next;
+ } else {
+ prev = &client->next;
+ }
+ client = next;
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClearText --
+ *
+ * This function is invoked when we reset a text widget to it's intitial
+ * state, but without resetting options. We will free up many of the
+ * internal structure.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Almost everything associated with the text content is cleared.
+ * Note that all the peers of the shared structure will be cleared.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ClearRetainedUndoTokens(
+ TkSharedText *sharedTextPtr)
+{
+ unsigned i;
+
+ assert(sharedTextPtr);
+
+ for (i = 0; i < sharedTextPtr->undoTagListCount; ++i) {
+ TkTextReleaseUndoTagToken(sharedTextPtr, sharedTextPtr->undoTagList[i]);
+ }
+
+ for (i = 0; i < sharedTextPtr->undoMarkListCount; ++i) {
+ TkTextReleaseUndoMarkTokens(sharedTextPtr, &sharedTextPtr->undoMarkList[i]);
+ }
+
+ sharedTextPtr->undoTagListCount = 0;
+ sharedTextPtr->undoMarkListCount = 0;
+}
+
+static void
+ClearText(
+ TkText *textPtr, /* Clean up this text widget. */
+ bool clearTags) /* Also clear all tags? */
+{
+ TkTextSegment *retainedMarks;
+ TkTextIndex startIndex;
+ TkText *tPtr;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ unsigned oldEpoch = TkBTreeEpoch(sharedTextPtr->tree);
+ bool steadyMarks = textPtr->sharedTextPtr->steadyMarks;
+ bool debug = tkBTreeDebug;
+
+ tkBTreeDebug = false; /* debugging is not wanted here */
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ /*
+ * Always clean up the widget-specific tags first. Common tags (i.e. most)
+ * will only be cleaned up when the shared structure is cleaned up.
+ *
+ * We also need to clean up widget-specific marks ('insert', 'current'),
+ * since otherwise marks will never disappear from the B-tree.
+ *
+ * Do not clear the after sync commands, otherwise the widget may hang.
+ */
+
+ tPtr->refCount += 1;
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->insertMarkPtr);
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->currentMarkPtr);
+ if (clearTags) {
+ TkTextFreeAllTags(tPtr);
+ }
+ TkQTreeDestroy(&tPtr->imageBboxTree);
+ FreeEmbeddedWindows(tPtr);
+ TkTextFreeDInfo(tPtr);
+ textPtr->dInfoPtr = NULL;
+ textPtr->dontRepick = false;
+ tPtr->abortSelections = true;
+ tPtr->configureBboxTree = false;
+ tPtr->hoveredImageArrSize = 0;
+ tPtr->currNearbyFlag = -1;
+ tPtr->refCount -= 1;
+ tPtr->startLine = NULL;
+ tPtr->endLine = NULL;
+
+ if (tPtr->startMarker->refCount == 1) {
+ assert(textPtr->startMarker != textPtr->sharedTextPtr->startMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->startMarker);
+ FREE_SEGMENT(tPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ (tPtr->startMarker = sharedTextPtr->startMarker)->refCount += 1;
+ }
+ if (tPtr->endMarker->refCount == 1) {
+ assert(textPtr->endMarker != textPtr->sharedTextPtr->endMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->endMarker);
+ FREE_SEGMENT(tPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ (tPtr->endMarker = sharedTextPtr->endMarker)->refCount += 1;
+ }
+ }
+
+ ClearRetainedUndoTokens(sharedTextPtr);
+ TkBTreeUnlinkSegment(sharedTextPtr, sharedTextPtr->startMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, sharedTextPtr->endMarker);
+ sharedTextPtr->startMarker->nextPtr = sharedTextPtr->startMarker->prevPtr = NULL;
+ sharedTextPtr->endMarker->nextPtr = sharedTextPtr->endMarker->prevPtr = NULL;
+ TkBTreeDestroy(sharedTextPtr->tree);
+ retainedMarks = TkTextFreeMarks(sharedTextPtr, true);
+ Tcl_DeleteHashTable(&sharedTextPtr->imageTable);
+ Tcl_DeleteHashTable(&sharedTextPtr->windowTable);
+
+ if (sharedTextPtr->imageBindingTable) {
+ Tk_DeleteBindingTable(sharedTextPtr->imageBindingTable);
+ }
+
+ if (clearTags) {
+ Tcl_DeleteHashTable(&sharedTextPtr->tagTable);
+ if (sharedTextPtr->tagBindingTable) {
+ Tk_DeleteBindingTable(sharedTextPtr->tagBindingTable);
+ }
+ sharedTextPtr->numMotionEventBindings = 0;
+ sharedTextPtr->numElisionTags = 0;
+ }
+
+ /*
+ * Rebuild the internal structures.
+ */
+
+ Tcl_InitHashTable(&sharedTextPtr->windowTable, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&sharedTextPtr->imageTable, TCL_STRING_KEYS);
+ TkTextUndoResetStack(sharedTextPtr->undoStack);
+ TkBitClear(sharedTextPtr->elisionTags);
+ TkBitClear(sharedTextPtr->selectionTags);
+ TkBitClear(sharedTextPtr->dontUndoTags);
+ TkBitClear(sharedTextPtr->affectDisplayTags);
+ TkBitClear(sharedTextPtr->notAffectDisplayTags);
+ TkBitClear(sharedTextPtr->affectDisplayNonSelTags);
+ TkBitClear(sharedTextPtr->affectGeometryTags);
+ TkBitClear(sharedTextPtr->affectGeometryNonSelTags);
+ TkBitClear(sharedTextPtr->affectLineHeightTags);
+ sharedTextPtr->imageBindingTable = NULL;
+ sharedTextPtr->isAltered = false;
+ sharedTextPtr->isModified = false;
+ sharedTextPtr->isIrreversible = false;
+ sharedTextPtr->userHasSetModifiedFlag = false;
+ sharedTextPtr->haveToSetCurrentMark = false;
+ sharedTextPtr->undoLevel = 0;
+ sharedTextPtr->imageCount = 0;
+ sharedTextPtr->tree = TkBTreeCreate(sharedTextPtr, oldEpoch + 1);
+ sharedTextPtr->insertDeleteUndoTokenCount = 0;
+
+ if (clearTags) {
+ sharedTextPtr->tagInfoSize = 0;
+ sharedTextPtr->tagBindingTable = NULL;
+ sharedTextPtr->numTags = 0;
+ sharedTextPtr->numEnabledTags = sharedTextPtr->numPeers; /* because the "sel" tag will survive */
+ Tcl_InitHashTable(&sharedTextPtr->tagTable, TCL_STRING_KEYS);
+ TkBitClear(sharedTextPtr->usedTags);
+ DEBUG(memset(sharedTextPtr->tagLookup, 0,
+ TkBitSize(sharedTextPtr->usedTags)*sizeof(TkTextTag *)));
+ }
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ TkTextCreateDInfo(tPtr);
+ TkBTreeAddClient(sharedTextPtr->tree, tPtr, tPtr->lineHeight);
+ TkTextIndexSetupToStartOfText(&startIndex, tPtr, sharedTextPtr->tree);
+ TkTextSetYView(tPtr, &startIndex, 0);
+ sharedTextPtr->tagLookup[tPtr->selTagPtr->index] = tPtr->selTagPtr;
+ TkBitSet(sharedTextPtr->usedTags, tPtr->selTagPtr->index);
+ tPtr->haveToSetCurrentMark = false;
+ TkBTreeLinkSegment(sharedTextPtr, tPtr->insertMarkPtr, &startIndex);
+ TkBTreeLinkSegment(sharedTextPtr, tPtr->currentMarkPtr, &startIndex);
+ tPtr->currentMarkIndex = startIndex;
+ }
+
+ sharedTextPtr->steadyMarks = false;
+ while (retainedMarks) {
+ TkTextSegment *nextPtr = retainedMarks->nextPtr;
+ TkTextIndexSetupToStartOfText(&startIndex, NULL, sharedTextPtr->tree);
+ TkBTreeLinkSegment(sharedTextPtr, retainedMarks, &startIndex);
+ retainedMarks = nextPtr;
+ }
+ sharedTextPtr->steadyMarks = steadyMarks;
+
+ TkTextResetDInfo(textPtr);
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
+ sharedTextPtr->lastUndoTokenType = -1;
+
+ if (debug) {
+ tkBTreeDebug = true;
+ TkBTreeCheck(sharedTextPtr->tree);
}
- return 0;
}
/*
@@ -1906,153 +3255,346 @@ static void
DestroyText(
TkText *textPtr) /* Info about text widget. */
{
- Tcl_HashSearch search;
- Tcl_HashEntry *hPtr;
- TkTextTag *tagPtr;
TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextStringList *listPtr;
+ bool debug = tkBTreeDebug;
+
+ tkBTreeDebug = false; /* debugging is not wanted here */
+
+ /*
+ * Firstly, remove pending idle commands, and free the array.
+ */
+
+ if (textPtr->pendingAfterSync) {
+ Tcl_CancelIdleCall(RunAfterSyncCmd, (ClientData) textPtr);
+ textPtr->pendingAfterSync = false;
+ }
+ if (textPtr->pendingFireEvent) {
+ Tcl_CancelIdleCall(FireWidgetViewSyncEvent, (ClientData) textPtr);
+ textPtr->pendingFireEvent = false;
+ }
+ if (textPtr->afterSyncCmd) {
+ Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ }
/*
* Free up all the stuff that requires special handling. We have already
* called let Tk_FreeConfigOptions to handle all the standard
* option-related stuff (and so none of that exists when we are called).
+ *
* Special note: free up display-related information before deleting the
* B-tree, since display-related stuff may refer to stuff in the B-tree.
*/
TkTextFreeDInfo(textPtr);
textPtr->dInfoPtr = NULL;
+ textPtr->undo = false;
/*
- * Remove ourselves from the peer list.
+ * Always clean up the widget-specific tags first. Common tags (i.e. most)
+ * will only be cleaned up when the shared structure is cleaned up.
+ *
+ * At first clean up the array of bounding boxes for the images.
*/
- if (sharedTextPtr->peers == textPtr) {
- sharedTextPtr->peers = textPtr->next;
- } else {
- TkText *nextPtr = sharedTextPtr->peers;
- while (nextPtr != NULL) {
- if (nextPtr->next == textPtr) {
- nextPtr->next = textPtr->next;
- break;
- }
- nextPtr = nextPtr->next;
- }
+ TkQTreeDestroy(&textPtr->imageBboxTree);
+
+ /*
+ * Unset all the variables bound to this widget.
+ */
+
+ listPtr = textPtr->varBindingList;
+ while (listPtr) {
+ TkTextStringList *nextPtr = listPtr->nextPtr;
+
+ Tcl_UnsetVar2(textPtr->interp, Tcl_GetString(listPtr->strObjPtr), NULL, TCL_GLOBAL_ONLY);
+ Tcl_DecrRefCount(listPtr->strObjPtr);
+ free(listPtr);
+ listPtr = nextPtr;
}
/*
- * Always clean up the widget-specific tags first. Common tags (i.e. most)
- * will only be cleaned up when the shared structure is cleaned up.
- *
+ * Unset the watch command.
+ */
+
+ if (textPtr->watchCmd) {
+ Tcl_DecrRefCount(textPtr->watchCmd);
+ }
+ TextWatchCmd(textPtr, NULL, 0, NULL);
+
+ /*
* We also need to clean up widget-specific marks ('insert', 'current'),
* since otherwise marks will never disappear from the B-tree.
*/
- TkTextDeleteTag(textPtr, textPtr->selTagPtr);
- TkBTreeUnlinkSegment(textPtr->insertMarkPtr,
- textPtr->insertMarkPtr->body.mark.linePtr);
- ckfree(textPtr->insertMarkPtr);
- TkBTreeUnlinkSegment(textPtr->currentMarkPtr,
- textPtr->currentMarkPtr->body.mark.linePtr);
- ckfree(textPtr->currentMarkPtr);
+ TkTextDeleteTag(textPtr, textPtr->selTagPtr, NULL);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->insertMarkPtr);
+ FREE_SEGMENT(textPtr->insertMarkPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->currentMarkPtr);
+ FREE_SEGMENT(textPtr->currentMarkPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ FreeEmbeddedWindows(textPtr);
+
+ /*
+ * Clean up the -start/-end markers, do this after cleanup of other segments (not before).
+ */
+
+ if (textPtr->startMarker->refCount == 1) {
+ assert(textPtr->startMarker != sharedTextPtr->startMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ FREE_SEGMENT(textPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ } else {
+ DEBUG(textPtr->startMarker->refCount -= 1);
+ }
+ if (textPtr->endMarker->refCount == 1) {
+ assert(textPtr->endMarker != sharedTextPtr->endMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ FREE_SEGMENT(textPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ } else {
+ DEBUG(textPtr->endMarker->refCount -= 1);
+ }
/*
* Now we've cleaned up everything of relevance to us in the B-tree, so we
- * disassociate outselves from it.
+ * disassociate ourselves from it.
*
* When the refCount reaches zero, it's time to clean up the shared
* portion of the text widget.
*/
- if (sharedTextPtr->refCount-- > 1) {
- TkBTreeRemoveClient(sharedTextPtr->tree, textPtr);
+ sharedTextPtr->refCount -= 1;
+
+ if (sharedTextPtr->refCount > 0) {
+ sharedTextPtr->numPeers -= 1;
/*
- * Free up any embedded windows which belong to this widget.
+ * No need to call 'TkBTreeRemoveClient' first, since this will do
+ * everything in one go, more quickly.
*/
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->windowTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- TkTextEmbWindowClient *loop;
- TkTextSegment *ewPtr = Tcl_GetHashValue(hPtr);
+ TkBTreeRemoveClient(sharedTextPtr->tree, textPtr);
- loop = ewPtr->body.ew.clients;
- if (loop->textPtr == textPtr) {
- ewPtr->body.ew.clients = loop->next;
- TkTextWinFreeClient(hPtr, loop);
- } else {
- TkTextEmbWindowClient *client = ewPtr->body.ew.clients;
+ /*
+ * Remove ourselves from the peer list.
+ */
- client = loop->next;
- while (client != NULL) {
- if (client->textPtr == textPtr) {
- loop->next = client->next;
- TkTextWinFreeClient(hPtr, client);
- break;
- } else {
- loop = loop->next;
- }
- client = loop->next;
+ if (sharedTextPtr->peers == textPtr) {
+ sharedTextPtr->peers = textPtr->next;
+ } else {
+ TkText *nextPtr = sharedTextPtr->peers;
+ while (nextPtr) {
+ if (nextPtr->next == textPtr) {
+ nextPtr->next = textPtr->next;
+ break;
}
+ nextPtr = nextPtr->next;
}
}
} else {
- /*
- * No need to call 'TkBTreeRemoveClient' first, since this will do
- * everything in one go, more quickly.
- */
+ /* Prevent that this resource will be released too early. */
+ textPtr->refCount += 1;
+ ClearRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoDestroyStack(&sharedTextPtr->undoStack);
+ free(sharedTextPtr->undoTagList);
+ free(sharedTextPtr->undoMarkList);
TkBTreeDestroy(sharedTextPtr->tree);
+ assert(sharedTextPtr->startMarker->refCount == 1);
+ FREE_SEGMENT(sharedTextPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ assert(sharedTextPtr->endMarker->refCount == 1);
+ FREE_SEGMENT(sharedTextPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ FREE_SEGMENT(sharedTextPtr->protectionMark[0]);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ FREE_SEGMENT(sharedTextPtr->protectionMark[1]);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ TkTextFreeAllTags(textPtr);
+ Tcl_DeleteHashTable(&sharedTextPtr->tagTable);
+ TkTextFreeMarks(sharedTextPtr, false);
+ TkBitDestroy(&sharedTextPtr->usedTags);
+ TkBitDestroy(&sharedTextPtr->elisionTags);
+ TkBitDestroy(&sharedTextPtr->selectionTags);
+ TkBitDestroy(&sharedTextPtr->dontUndoTags);
+ TkBitDestroy(&sharedTextPtr->affectDisplayTags);
+ TkBitDestroy(&sharedTextPtr->notAffectDisplayTags);
+ TkBitDestroy(&sharedTextPtr->affectDisplayNonSelTags);
+ TkBitDestroy(&sharedTextPtr->affectGeometryTags);
+ TkBitDestroy(&sharedTextPtr->affectGeometryNonSelTags);
+ TkBitDestroy(&sharedTextPtr->affectLineHeightTags);
+ TkTextTagSetDestroy(&sharedTextPtr->emptyTagInfoPtr);
+ Tcl_DeleteHashTable(&sharedTextPtr->windowTable);
+ Tcl_DeleteHashTable(&sharedTextPtr->imageTable);
+ TkTextDeleteBreakInfoTableEntries(&sharedTextPtr->breakInfoTable);
+ Tcl_DeleteHashTable(&sharedTextPtr->breakInfoTable);
+ free(sharedTextPtr->mainPeer);
+ DEBUG_ALLOC(tkTextCountDestroyPeer++);
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- tagPtr = Tcl_GetHashValue(hPtr);
-
- /*
- * No need to use 'TkTextDeleteTag' since we've already removed
- * the B-tree completely.
- */
-
- TkTextFreeTag(textPtr, tagPtr);
+ if (sharedTextPtr->tagBindingTable) {
+ Tk_DeleteBindingTable(sharedTextPtr->tagBindingTable);
}
- Tcl_DeleteHashTable(&sharedTextPtr->tagTable);
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->markTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- ckfree(Tcl_GetHashValue(hPtr));
+ if (sharedTextPtr->imageBindingTable) {
+ Tk_DeleteBindingTable(sharedTextPtr->imageBindingTable);
+ }
+ if (sharedTextPtr->stillExisting) {
+ *sharedTextPtr->stillExisting = false;
}
- Tcl_DeleteHashTable(&sharedTextPtr->markTable);
- TkUndoFreeStack(sharedTextPtr->undoStack);
+ free(sharedTextPtr);
+ DEBUG_ALLOC(tkTextCountDestroyShared++);
- Tcl_DeleteHashTable(&sharedTextPtr->windowTable);
- Tcl_DeleteHashTable(&sharedTextPtr->imageTable);
+ textPtr->sharedIsReleased = true;
+ textPtr->refCount -= 1;
+
+#if TK_CHECK_ALLOCS
+ /*
+ * Remove this shared resource from global list.
+ */
+ {
+ WatchShared *thisPtr = watchShared;
+ WatchShared *prevPtr = NULL;
+
+ while (thisPtr->sharedTextPtr != sharedTextPtr) {
+ prevPtr = thisPtr;
+ thisPtr = thisPtr->nextPtr;
+ assert(thisPtr);
+ }
- if (sharedTextPtr->bindingTable != NULL) {
- Tk_DeleteBindingTable(sharedTextPtr->bindingTable);
+ if (prevPtr) {
+ prevPtr->nextPtr = thisPtr->nextPtr;
+ } else {
+ watchShared = thisPtr->nextPtr;
+ }
+
+ free(thisPtr);
}
- ckfree(sharedTextPtr);
+#endif
}
- if (textPtr->tabArrayPtr != NULL) {
- ckfree(textPtr->tabArrayPtr);
+ if (textPtr->tabArrayPtr) {
+ free(textPtr->tabArrayPtr);
}
- if (textPtr->insertBlinkHandler != NULL) {
+ if (textPtr->insertBlinkHandler) {
Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
}
textPtr->tkwin = NULL;
Tcl_DeleteCommandFromToken(textPtr->interp, textPtr->widgetCmd);
- if (textPtr->afterSyncCmd){
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- textPtr->afterSyncCmd = NULL;
+ if (--textPtr->refCount == 0) {
+ free(textPtr);
}
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
+
+ tkBTreeDebug = debug;
+ DEBUG_ALLOC(tkTextCountDestroyPeer++);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextDecrRefCountAndTestIfDestroyed --
+ *
+ * This function is decrementing the reference count of the text
+ * widget and destroys the widget if the reference count has been
+ * gone to zero.
+ *
+ * Results:
+ * Returns whether the widget has been destroyed.
+ *
+ * Side effects:
+ * Memory might be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextDecrRefCountAndTestIfDestroyed(
+ TkText *textPtr)
+{
+ if (--textPtr->refCount == 0) {
+ assert(textPtr->flags & DESTROYED);
+ free(textPtr);
+ return true;
}
+ return !!(textPtr->flags & DESTROYED);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextReleaseIfDestroyed --
+ *
+ * This function is decrementing the reference count of the text
+ * widget if it has been destroyed. In this case also the memory
+ * will be released.
+ *
+ * Results:
+ * Returns whether the widget was already destroyed.
+ *
+ * Side effects:
+ * Memory might be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextReleaseIfDestroyed(
+ TkText *textPtr)
+{
+ if (!(textPtr->flags & DESTROYED)) {
+ return false;
+ }
+ if (--textPtr->refCount == 0) {
+ free(textPtr);
+ }
+ return true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextTestLangCode --
+ *
+ * Test the given language code, whether it satsifies ISO 539-1,
+ * and set an error message if the code is invalid.
+ *
+ * Results:
+ * The return value is 'tue' if given language code will be accepted,
+ * otherwise 'false' will be returned.
+ *
+ * Side effects:
+ * An error message in the interpreter may be set.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextTestLangCode(
+ Tcl_Interp *interp,
+ Tcl_Obj *langCodePtr)
+{
+ char const *lang = Tcl_GetString(langCodePtr);
+
+ if (UCHAR(lang[0]) >= 0x80
+ || UCHAR(lang[1]) >= 0x80
+ || !isalpha(lang[0])
+ || !isalpha(lang[1])
+ || !islower(lang[0])
+ || !islower(lang[1])
+ || lang[2] != '\0') {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad lang \"%s\": "
+ "must have the form of an ISO 639-1 language code, or empty", lang));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "LANG", NULL);
+ return false;
+ }
+ return true;
}
/*
*----------------------------------------------------------------------
*
- * ConfigureText --
+ * TkConfigureText --
*
* This function is called to process an objv/objc list, plus the Tk
* option database, in order to configure (or reconfigure) a text widget.
@@ -2068,33 +3610,352 @@ DestroyText(
*----------------------------------------------------------------------
*/
-static int
-ConfigureText(
+static bool
+IsNumberOrEmpty(
+ const char *str)
+{
+ for ( ; *str; ++str) {
+ if (!isdigit(*str)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+int
+TkConfigureText(
Tcl_Interp *interp, /* Used for error reporting. */
- register TkText *textPtr, /* Information about widget; may or may not
+ TkText *textPtr, /* Information about widget; may or may not
* already have values for some fields. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
Tk_SavedOptions savedOptions;
- int oldExport = textPtr->exportSelection;
+ TkTextIndex start, end, current;
+ unsigned currentEpoch;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextBTree tree = sharedTextPtr->tree;
+ bool oldExport = textPtr->exportSelection;
+ bool oldTextDebug = tkTextDebug;
+ bool didHyphenate = textPtr->hyphenate;
+ int oldHyphenRules = textPtr->hyphenRules;
int mask = 0;
+ bool copyDownFlags = false;
+
+ tkTextDebug = false; /* debugging is not useful here */
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+
+ /*
+ * We want also to support the "-start", and "-end" abbreviations. The thing that
+ * Tcl supports abbreviated options is a real crux.
+ */
+
+ {
+ Tcl_Obj **myObjv;
+ Tcl_Obj *startLineObj = NULL;
+ Tcl_Obj *endLineObj = NULL;
+ Tcl_Obj *startIndexObj = NULL;
+ Tcl_Obj *endIndexObj = NULL;
+ int i, rc;
+
+ myObjv = malloc(objc * sizeof(Tcl_Obj *));
+
+ for (i = 0; i < objc; ++i) {
+ Tcl_Obj *obj = objv[i];
+
+ if (!(i & 1)) {
+ if (strcmp(Tcl_GetString(objv[i]), "-start") == 0) {
+ if (i + 1 < objc && IsNumberOrEmpty(Tcl_GetString(objv[i + 1]))) {
+ if (!startLineObj) {
+ Tcl_IncrRefCount(startLineObj = Tcl_NewStringObj("-startline", -1));
+ }
+ obj = startLineObj;
+ WarnAboutDeprecatedStartLineOption();
+ } else {
+ if (!startIndexObj) {
+ Tcl_IncrRefCount(startIndexObj = Tcl_NewStringObj("-startindex", -1));
+ }
+ obj = startIndexObj;
+ }
+ } else if (strncmp(Tcl_GetString(objv[i]), "-startl", 7) == 0) {
+ if (!startLineObj) {
+ Tcl_IncrRefCount(startLineObj = Tcl_NewStringObj("-startline", -1));
+ }
+ obj = startLineObj;
+ WarnAboutDeprecatedStartLineOption();
+ } else if (strncmp(Tcl_GetString(objv[i]), "-starti", 7) == 0) {
+ if (!startIndexObj) {
+ Tcl_IncrRefCount(startIndexObj = Tcl_NewStringObj("-startindex", -1));
+ }
+ obj = startIndexObj;
+ } else if (strcmp(Tcl_GetString(objv[i]), "-end") == 0) {
+ if (i + 1 < objc && IsNumberOrEmpty(Tcl_GetString(objv[i + 1]))) {
+ if (!endLineObj) {
+ Tcl_IncrRefCount(endLineObj = Tcl_NewStringObj("-endline", -1));
+ }
+ obj = endLineObj;
+ WarnAboutDeprecatedEndLineOption();
+ } else {
+ if (!endIndexObj) {
+ Tcl_IncrRefCount(endIndexObj = Tcl_NewStringObj("-endindex", -1));
+ }
+ obj = endIndexObj;
+ }
+ } else if (strncmp(Tcl_GetString(objv[i]), "-endl", 5) == 0) {
+ if (!endLineObj) {
+ Tcl_IncrRefCount(endLineObj = Tcl_NewStringObj("-endline", -1));
+ }
+ obj = endLineObj;
+ WarnAboutDeprecatedEndLineOption();
+ } else if (strncmp(Tcl_GetString(objv[i]), "-endi", 5) == 0) {
+ if (!endIndexObj) {
+ Tcl_IncrRefCount(endIndexObj = Tcl_NewStringObj("-endindex", -1));
+ }
+ obj = endIndexObj;
+ }
+ }
+ myObjv[i] = obj;
+ }
+
+ rc = Tk_SetOptions(interp, (char *) textPtr, textPtr->optionTable,
+ objc, myObjv, textPtr->tkwin, &savedOptions, &mask);
+
+ if (rc != TCL_OK) {
+ if (startLineObj && startIndexObj) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "cannot use both, -startindex, and deprecated -startline", -1));
+ Tk_RestoreSavedOptions(&savedOptions);
+ rc = TCL_ERROR;
+ }
+ if (endLineObj && endIndexObj) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "cannot use both, -endindex, and deprecated -endline", -1));
+ Tk_RestoreSavedOptions(&savedOptions);
+ rc = TCL_ERROR;
+ }
+ }
+
+ if (startLineObj) { Tcl_DecrRefCount(startLineObj); }
+ if (endLineObj) { Tcl_DecrRefCount(endLineObj); }
+ if (startIndexObj) { Tcl_DecrRefCount(startIndexObj); }
+ if (endIndexObj) { Tcl_DecrRefCount(endIndexObj); }
+
+ free(myObjv);
+
+ if (rc != TCL_OK) {
+ tkTextDebug = oldTextDebug;
+ return rc;
+ }
+ }
+
+ if ((mask & TK_TEXT_LINE_RANGE) == TK_TEXT_LINE_RANGE) {
+ TkTextIndexClear2(&start, NULL, tree);
+ TkTextIndexClear2(&end, NULL, tree);
+ TkTextIndexSetToStartOfLine2(&start, textPtr->startLine ?
+ textPtr->startLine : TkBTreeGetStartLine(textPtr));
+ TkTextIndexSetToStartOfLine2(&end, textPtr->endLine ?
+ textPtr->endLine : TkBTreeGetLastLine(textPtr));
+ if (textPtr->endLine && textPtr->startLine != textPtr->endLine) {
+ TkTextIndexBackChars(textPtr, &end, 1, &end, COUNT_INDICES);
+ }
+
+ if (TkTextIndexCompare(&start, &end) > 0) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "-startline must be less than or equal to -endline", -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_ORDER", NULL);
+ Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+ }
+
+ if (textPtr->endLine && textPtr->endLine != sharedTextPtr->endMarker->sectionPtr->linePtr) {
+ if (textPtr->endMarker->refCount > 1) {
+ textPtr->endMarker->refCount -= 1;
+ textPtr->endMarker = TkTextMakeStartEndMark(textPtr, &tkTextRightMarkType);
+ } else {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ }
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->endMarker, &end);
+ } else if (textPtr->endMarker != sharedTextPtr->endMarker) {
+ if (--textPtr->endMarker->refCount == 0) {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ FREE_SEGMENT(textPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ (textPtr->endMarker = sharedTextPtr->endMarker)->refCount += 1;
+ }
+ if (textPtr->startLine
+ && textPtr->startLine != sharedTextPtr->startMarker->sectionPtr->linePtr) {
+ if (textPtr->startMarker->refCount > 1) {
+ textPtr->startMarker->refCount -= 1;
+ textPtr->startMarker = TkTextMakeStartEndMark(textPtr, &tkTextLeftMarkType);
+ } else {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ }
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->startMarker, &start);
+ } else if (textPtr->startMarker != sharedTextPtr->startMarker) {
+ if (--textPtr->startMarker->refCount == 0) {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ FREE_SEGMENT(textPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ (textPtr->startMarker = sharedTextPtr->startMarker)->refCount += 1;
+ }
+ }
+
+#else /* if !SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
if (Tk_SetOptions(interp, (char *) textPtr, textPtr->optionTable,
objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) {
+ tkTextDebug = oldTextDebug;
return TCL_ERROR;
}
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+ if (sharedTextPtr->steadyMarks != textPtr->steadyMarks) {
+ if (!IsClean(sharedTextPtr, NULL, true)) {
+ ErrorNotAllowed(interp, "setting this option is possible only if the widget is clean");
+ Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+ }
+ }
+
+ /*
+ * Copy up shared flags.
+ */
+
+ /* This flag cannot alter if we have peers. */
+ sharedTextPtr->steadyMarks = textPtr->steadyMarks;
+
+ if (sharedTextPtr->autoSeparators != textPtr->autoSeparators) {
+ sharedTextPtr->autoSeparators = textPtr->autoSeparators;
+ copyDownFlags = true;
+ }
+
+ if (textPtr->undo != sharedTextPtr->undo) {
+ if (TestIfPerformingUndoRedo(interp, sharedTextPtr, NULL)) {
+ Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+ }
+
+ assert(sharedTextPtr->undo == !!sharedTextPtr->undoStack);
+ sharedTextPtr->undo = textPtr->undo;
+ copyDownFlags = true;
+
+ if (sharedTextPtr->undo) {
+ sharedTextPtr->undoStack = TkTextUndoCreateStack(
+ sharedTextPtr->maxUndoDepth,
+ sharedTextPtr->maxRedoDepth,
+ sharedTextPtr->maxUndoSize,
+ TextUndoRedoCallback,
+ TextUndoFreeCallback,
+ TextUndoStackContentChangedCallback);
+ TkTextUndoSetContext(sharedTextPtr->undoStack, sharedTextPtr);
+ sharedTextPtr->undoLevel = 0;
+ sharedTextPtr->isIrreversible = false;
+ sharedTextPtr->isAltered = false;
+ } else {
+ sharedTextPtr->isIrreversible = TkTextUndoContentIsModified(sharedTextPtr->undoStack);
+ ClearRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoDestroyStack(&sharedTextPtr->undoStack);
+ }
+ }
+
+ /* normalize values */
+ textPtr->maxUndoDepth = MAX(textPtr->maxUndoDepth, 0);
+ textPtr->maxRedoDepth = MAX(-1, textPtr->maxRedoDepth);
+ textPtr->maxUndoSize = MAX(textPtr->maxUndoSize, 0);
+
+ if (sharedTextPtr->maxUndoDepth != textPtr->maxUndoDepth
+ || sharedTextPtr->maxRedoDepth != textPtr->maxRedoDepth
+ || sharedTextPtr->maxUndoSize != textPtr->maxUndoSize) {
+ if (sharedTextPtr->undoStack) {
+ TkTextUndoSetMaxStackDepth(sharedTextPtr->undoStack,
+ textPtr->maxUndoDepth, textPtr->maxRedoDepth);
+ TkTextUndoSetMaxStackSize(sharedTextPtr->undoStack, textPtr->maxUndoSize, false);
+ }
+ sharedTextPtr->maxUndoDepth = textPtr->maxUndoDepth;
+ sharedTextPtr->maxRedoDepth = textPtr->maxRedoDepth;
+ sharedTextPtr->maxUndoSize = textPtr->maxUndoSize;
+ copyDownFlags = true;
+ }
+
+ if (copyDownFlags) {
+ TkText *tPtr;
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ tPtr->autoSeparators = sharedTextPtr->autoSeparators;
+ tPtr->maxUndoDepth = sharedTextPtr->maxUndoDepth;
+ tPtr->maxRedoDepth = sharedTextPtr->maxRedoDepth;
+ tPtr->maxUndoSize = sharedTextPtr->maxUndoSize;
+ tPtr->undo = sharedTextPtr->undo;
+ }
+ }
+
/*
- * Copy down shared flags.
+ * Check soft hyphen support.
*/
- textPtr->sharedTextPtr->undo = textPtr->undo;
- textPtr->sharedTextPtr->maxUndo = textPtr->maxUndo;
- textPtr->sharedTextPtr->autoSeparators = textPtr->autoSeparators;
+ textPtr->hyphenate = textPtr->hyphens
+ && textPtr->state != TK_TEXT_STATE_NORMAL
+ && (textPtr->wrapMode == TEXT_WRAPMODE_WORD || textPtr->wrapMode == TEXT_WRAPMODE_CODEPOINT);
+ if (didHyphenate != textPtr->hyphenate) {
+ mask |= TK_TEXT_LINE_GEOMETRY;
+ }
- TkUndoSetMaxDepth(textPtr->sharedTextPtr->undoStack,
- textPtr->sharedTextPtr->maxUndo);
+ /*
+ * Parse hyphen rules.
+ */
+
+ if (textPtr->hyphenRulesPtr) {
+ if (TkTextParseHyphenRules(textPtr, textPtr->hyphenRulesPtr, &textPtr->hyphenRules) != TCL_OK) {
+ Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+ }
+ } else {
+ textPtr->hyphenRules = 0;
+ }
+ if (oldHyphenRules != textPtr->hyphenRules && textPtr->hyphenate) {
+ mask |= TK_TEXT_LINE_GEOMETRY;
+ }
+
+ /*
+ * Parse tab stops.
+ */
+
+ if (textPtr->tabArrayPtr) {
+ free(textPtr->tabArrayPtr);
+ textPtr->tabArrayPtr = NULL;
+ }
+ if (textPtr->tabOptionPtr) {
+ textPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr, textPtr->tabOptionPtr);
+ if (!textPtr->tabArrayPtr) {
+ Tcl_AddErrorInfo(interp, "\n (while processing -tabs option)");
+ Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+ }
+ }
+
+ /*
+ * Check language support.
+ */
+
+ if (textPtr->langPtr) {
+ if (!TkTextTestLangCode(interp, textPtr->langPtr)) {
+ Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+ }
+ memcpy(textPtr->lang, Tcl_GetString(textPtr->langPtr), 3);
+ } else {
+ memset(textPtr->lang, 0, 3);
+ }
/*
* A few other options also need special processing, such as parsing the
@@ -2103,45 +3964,98 @@ ConfigureText(
Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border);
- if (mask & TK_TEXT_LINE_RANGE) {
- int start, end, current;
- TkTextIndex index1, index2, index3;
-
- /*
- * Line start and/or end have been adjusted. We need to validate the
- * first displayed line and arrange for re-layout.
- */
-
- TkBTreeClientRangeChanged(textPtr, textPtr->charHeight);
+ /*
+ * Now setup the -startindex/-setindex range. This step cannot be restored,
+ * so this function must not return with an error code after this processing.
+ */
- if (textPtr->start != NULL) {
- start = TkBTreeLinesTo(NULL, textPtr->start);
- } else {
- start = 0;
+ if (mask & TK_TEXT_INDEX_RANGE) {
+ if (textPtr->newStartIndex) {
+ if (!TkTextGetIndexFromObj(interp, sharedTextPtr->mainPeer,
+ textPtr->newStartIndex, &start)) {
+ Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+ }
}
- if (textPtr->end != NULL) {
- end = TkBTreeLinesTo(NULL, textPtr->end);
- } else {
- end = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL);
+ if (textPtr->newEndIndex) {
+ if (!TkTextGetIndexFromObj(interp, sharedTextPtr->mainPeer, textPtr->newEndIndex, &end)) {
+ Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+ }
}
- if (start > end) {
+ if (TkTextIndexCompare(&start, &end) > 0) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "-startline must be less than or equal to -endline", -1));
+ "-startindex must be less than or equal to -endindex", -1));
Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_ORDER", NULL);
Tk_RestoreSavedOptions(&savedOptions);
+ tkTextDebug = oldTextDebug;
return TCL_ERROR;
}
- current = TkBTreeLinesTo(NULL, textPtr->topIndex.linePtr);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, start, 0,
- &index1);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, end, 0,
- &index2);
- if (current < start || current > end) {
+
+ start.textPtr = NULL;
+ end.textPtr = NULL;
+
+ if (textPtr->newEndIndex) {
+ if (TkTextIndexIsEndOfText(&end)) {
+ if (--textPtr->endMarker->refCount == 0) {
+ assert(textPtr->endMarker != sharedTextPtr->endMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ FREE_SEGMENT(textPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ (textPtr->endMarker = sharedTextPtr->endMarker)->refCount += 1;
+ } else {
+ if (textPtr->endMarker->refCount > 1) {
+ textPtr->endMarker->refCount -= 1;
+ textPtr->endMarker = TkTextMakeStartEndMark(textPtr, &tkTextRightMarkType);
+ } else {
+ assert(textPtr->endMarker != sharedTextPtr->endMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ }
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->endMarker, &end);
+ }
+ Tcl_DecrRefCount(textPtr->newEndIndex);
+ textPtr->newEndIndex = NULL;
+ }
+
+ if (textPtr->newStartIndex) {
+ if (TkTextIndexIsStartOfText(&start)) {
+ if (--textPtr->startMarker->refCount == 0) {
+ assert(textPtr->startMarker != sharedTextPtr->startMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ FREE_SEGMENT(textPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ (textPtr->startMarker = sharedTextPtr->startMarker)->refCount += 1;
+ } else {
+ if (textPtr->startMarker->refCount > 1) {
+ textPtr->startMarker->refCount -= 1;
+ textPtr->startMarker = TkTextMakeStartEndMark(textPtr, &tkTextLeftMarkType);
+ } else {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ }
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->startMarker, &start);
+ }
+ Tcl_DecrRefCount(textPtr->newStartIndex);
+ textPtr->newStartIndex = NULL;
+ }
+
+ /*
+ * Line start and/or end have been adjusted. We need to validate the
+ * first displayed line and arrange for re-layout.
+ */
+
+ TkBTreeClientRangeChanged(textPtr, textPtr->lineHeight);
+ TkTextMakeByteIndex(tree, NULL, TkTextIndexGetLineNumber(&textPtr->topIndex, NULL), 0, &current);
+
+ if (TkTextIndexCompare(&current, &start) < 0 || TkTextIndexCompare(&end, &current) < 0) {
TkTextSearch search;
TkTextIndex first, last;
- int selChanged = 0;
+ bool selChanged = false;
- TkTextSetYView(textPtr, &index1, 0);
+ TkTextSetYView(textPtr, &start, 0);
/*
* We may need to adjust the selection. So we have to check
@@ -2149,30 +4063,16 @@ ConfigureText(
* current start,end.
*/
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, 0, 0,
- &first);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL),
- 0, &last);
- TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search);
- if (!TkBTreeCharTagged(&first, textPtr->selTagPtr)
- && !TkBTreeNextTag(&search)) {
- /* Nothing tagged with "sel" */
+ TkTextMakeByteIndex(tree, NULL, 0, 0, &first);
+ TkBTreeStartSearch(&first, &start, textPtr->selTagPtr, &search, SEARCH_NEXT_TAGON);
+ if (TkBTreeNextTag(&search)) {
+ selChanged = true;
} else {
- int line = TkBTreeLinesTo(NULL, search.curIndex.linePtr);
-
- if (line < start) {
- selChanged = 1;
- } else {
- TkTextLine *linePtr = search.curIndex.linePtr;
-
- while (TkBTreeNextTag(&search)) {
- linePtr = search.curIndex.linePtr;
- }
- line = TkBTreeLinesTo(NULL, linePtr);
- if (line >= end) {
- selChanged = 1;
- }
+ TkTextMakeByteIndex(tree, NULL, TkBTreeNumLines(tree, NULL), 0, &last);
+ TkBTreeStartSearchBack(&end, &last, textPtr->selTagPtr,
+ &search, SEARCH_EITHER_TAGON_TAGOFF);
+ if (TkBTreePrevTag(&search)) {
+ selChanged = true;
}
}
if (selChanged) {
@@ -2182,69 +4082,57 @@ ConfigureText(
*/
TkTextSelectionEvent(textPtr);
- textPtr->abortSelections = 1;
+ textPtr->abortSelections = true;
}
}
- /* Indices are potentially obsolete after changing -startline and/or
- * -endline, therefore increase the epoch.
+ /* Indices are potentially obsolete after changing -start and/or
+ * -end, therefore increase the epoch.
* Also, clamp the insert and current (unshared) marks to the new
- * -startline/-endline range limits of the widget. All other (shared)
+ * -start/-end range limits of the widget. All other (shared)
* marks are unchanged.
- * The return value of TkTextMarkNameToIndex does not need to be
- * checked: "insert" and "current" marks always exist, and the
- * purpose of the code below precisely is to move them inside the
- * -startline/-endline range.
+ * The return value of TkTextMarkNameToIndex does not need to be
+ * checked: "insert" and "current" marks always exist, and the
+ * purpose of the code below precisely is to move them inside the
+ * -start/-end range.
*/
- textPtr->sharedTextPtr->stateEpoch++;
- TkTextMarkNameToIndex(textPtr, "insert", &index3);
- if (TkTextIndexCmp(&index3, &index1) < 0) {
- textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &index1);
- }
- if (TkTextIndexCmp(&index3, &index2) > 0) {
- textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &index2);
- }
- TkTextMarkNameToIndex(textPtr, "current", &index3);
- if (TkTextIndexCmp(&index3, &index1) < 0) {
- textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &index1);
- }
- if (TkTextIndexCmp(&index3, &index2) > 0) {
- textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &index2);
+ currentEpoch = TkBTreeIncrEpoch(tree);
+ start.textPtr = textPtr;
+ end.textPtr = textPtr;
+
+ TkTextMarkNameToIndex(textPtr, "current", &current);
+ if (TkTextIndexCompare(&current, &start) < 0) {
+ textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &start);
+ } else if (TkTextIndexCompare(&current, &end) > 0) {
+ textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &end);
}
+ } else {
+ currentEpoch = TkBTreeEpoch(tree);
}
/*
* Don't allow negative spacings.
*/
- if (textPtr->spacing1 < 0) {
- textPtr->spacing1 = 0;
- }
- if (textPtr->spacing2 < 0) {
- textPtr->spacing2 = 0;
- }
- if (textPtr->spacing3 < 0) {
- textPtr->spacing3 = 0;
- }
+ textPtr->spacing1 = MAX(textPtr->spacing1, 0);
+ textPtr->spacing2 = MAX(textPtr->spacing2, 0);
+ textPtr->spacing3 = MAX(textPtr->spacing3, 0);
/*
- * Parse tab stops.
+ * Also the following widths shouldn't be negative.
*/
- if (textPtr->tabArrayPtr != NULL) {
- ckfree(textPtr->tabArrayPtr);
- textPtr->tabArrayPtr = NULL;
- }
- if (textPtr->tabOptionPtr != NULL) {
- textPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr,
- textPtr->tabOptionPtr);
- if (textPtr->tabArrayPtr == NULL) {
- Tcl_AddErrorInfo(interp,"\n (while processing -tabs option)");
- Tk_RestoreSavedOptions(&savedOptions);
- return TCL_ERROR;
- }
- }
+ textPtr->highlightWidth = MAX(textPtr->highlightWidth, 0);
+ textPtr->selBorderWidth = MAX(textPtr->selBorderWidth, 0);
+ textPtr->borderWidth = MAX(textPtr->borderWidth, 0);
+ textPtr->insertWidth = MAX(textPtr->insertWidth, 0);
+
+ /*
+ * Don't allow negative sync timeout.
+ */
+
+ textPtr->syncTime = MAX(0, textPtr->syncTime);
/*
* Make sure that configuration options are properly mirrored between the
@@ -2253,73 +4141,37 @@ ConfigureText(
* replaced in the widget record.
*/
- if (textPtr->selTagPtr->selBorder == NULL) {
- textPtr->selTagPtr->border = textPtr->selBorder;
- } else {
+ if (textPtr->selTagPtr->selBorder) {
textPtr->selTagPtr->selBorder = textPtr->selBorder;
+ } else {
+ textPtr->selTagPtr->border = textPtr->selBorder;
}
if (textPtr->selTagPtr->borderWidthPtr != textPtr->selBorderWidthPtr) {
textPtr->selTagPtr->borderWidthPtr = textPtr->selBorderWidthPtr;
textPtr->selTagPtr->borderWidth = textPtr->selBorderWidth;
}
- if (textPtr->selTagPtr->selFgColor == NULL) {
- textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
- } else {
+ if (textPtr->selTagPtr->selFgColor) {
textPtr->selTagPtr->selFgColor = textPtr->selFgColorPtr;
+ } else {
+ textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
}
- textPtr->selTagPtr->affectsDisplay = 0;
- textPtr->selTagPtr->affectsDisplayGeometry = 0;
- if ((textPtr->selTagPtr->elideString != NULL)
- || (textPtr->selTagPtr->tkfont != None)
- || (textPtr->selTagPtr->justifyString != NULL)
- || (textPtr->selTagPtr->lMargin1String != NULL)
- || (textPtr->selTagPtr->lMargin2String != NULL)
- || (textPtr->selTagPtr->offsetString != NULL)
- || (textPtr->selTagPtr->rMarginString != NULL)
- || (textPtr->selTagPtr->spacing1String != NULL)
- || (textPtr->selTagPtr->spacing2String != NULL)
- || (textPtr->selTagPtr->spacing3String != NULL)
- || (textPtr->selTagPtr->tabStringPtr != NULL)
- || (textPtr->selTagPtr->wrapMode != TEXT_WRAPMODE_NULL)) {
- textPtr->selTagPtr->affectsDisplay = 1;
- textPtr->selTagPtr->affectsDisplayGeometry = 1;
- }
- if ((textPtr->selTagPtr->border != NULL)
- || (textPtr->selTagPtr->selBorder != NULL)
- || (textPtr->selTagPtr->reliefString != NULL)
- || (textPtr->selTagPtr->bgStipple != None)
- || (textPtr->selTagPtr->fgColor != NULL)
- || (textPtr->selTagPtr->selFgColor != NULL)
- || (textPtr->selTagPtr->fgStipple != None)
- || (textPtr->selTagPtr->overstrikeString != NULL)
- || (textPtr->selTagPtr->overstrikeColor != NULL)
- || (textPtr->selTagPtr->underlineString != NULL)
- || (textPtr->selTagPtr->underlineColor != NULL)
- || (textPtr->selTagPtr->lMarginColor != NULL)
- || (textPtr->selTagPtr->rMarginColor != NULL)) {
- textPtr->selTagPtr->affectsDisplay = 1;
- }
- TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, 1);
+ TkTextUpdateTagDisplayFlags(textPtr->selTagPtr);
+ TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, false);
/*
* Claim the selection if we've suddenly started exporting it and there
* are tagged characters.
*/
- if (textPtr->exportSelection && (!oldExport)) {
+ if (textPtr->exportSelection && !oldExport) {
TkTextSearch search;
TkTextIndex first, last;
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &first);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr),
- 0, &last);
- TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search);
- if (TkBTreeCharTagged(&first, textPtr->selTagPtr)
- || TkBTreeNextTag(&search)) {
- Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, TkTextLostSelection,
- textPtr);
+ TkTextIndexSetupToStartOfText(&first, textPtr, tree);
+ TkTextIndexSetupToEndOfText(&last, textPtr, tree);
+ TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search, SEARCH_NEXT_TAGON);
+ if (TkBTreeNextTag(&search)) {
+ Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, TkTextLostSelection, textPtr);
textPtr->flags |= GOT_SELECTION;
}
}
@@ -2328,7 +4180,7 @@ ConfigureText(
* Account for state changes that would reenable blinking cursor state.
*/
- if (textPtr->flags & GOT_FOCUS) {
+ if (textPtr->flags & HAVE_FOCUS) {
Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
textPtr->insertBlinkHandler = NULL;
TextBlinkProc(textPtr);
@@ -2339,14 +4191,99 @@ ConfigureText(
* window to be redisplayed.
*/
- if (textPtr->width <= 0) {
- textPtr->width = 1;
- }
- if (textPtr->height <= 0) {
- textPtr->height = 1;
- }
+ textPtr->width = MAX(textPtr->width, 1);
+ textPtr->height = MAX(textPtr->height, 1);
+
Tk_FreeSavedOptions(&savedOptions);
TextWorldChanged(textPtr, mask);
+
+ if (textPtr->syncTime == 0 && (mask & TK_TEXT_SYNCHRONIZE)) {
+ UpdateLineMetrics(textPtr, 0, TkBTreeNumLines(sharedTextPtr->tree, textPtr));
+ }
+
+ /*
+ * At least handle the "watch" command, and set the insert cursor.
+ */
+
+ if (mask & TK_TEXT_INDEX_RANGE) {
+ /*
+ * Setting the "insert" mark must be done at the end, because the "watch" command
+ * will be triggered. Be sure to use the actual range, mind the epoch.
+ */
+
+ TkTextMarkNameToIndex(textPtr, "insert", &current);
+
+ if (start.stateEpoch != currentEpoch) {
+ /*
+ * The "watch" command did change the content.
+ */
+ TkTextIndexSetupToStartOfText(&start, textPtr, tree);
+ TkTextIndexSetupToEndOfText(&end, textPtr, tree);
+ }
+
+ start.textPtr = textPtr;
+ end.textPtr = textPtr;
+
+ if (TkTextIndexCompare(&current, &start) < 0) {
+ textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &start);
+ } else if (TkTextIndexCompare(&current, &end) >= 0) {
+ textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &end);
+ }
+ }
+
+ tkTextDebug = oldTextDebug;
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+
+ return TCL_OK;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TkTextParseHyphenRules --
+ *
+ * This function is parsing the object containing the hyphen rules.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TkTextParseHyphenRules(
+ TkText *textPtr,
+ Tcl_Obj *objPtr,
+ int *rulesPtr)
+{
+ int rules = 0;
+ Tcl_Obj **argv;
+ int argc, i, k;
+
+ assert(rulesPtr);
+
+ if (Tcl_ListObjGetElements(textPtr->interp, objPtr, &argc, &argv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ for (i = 0; i < argc; ++i) {
+ char const *rule = Tcl_GetString(argv[i]);
+ int r = rules;
+
+ for (k = 0; k < sizeof(hyphenRuleStrings)/sizeof(hyphenRuleStrings[0]); ++k) {
+ if (strcmp(rule, hyphenRuleStrings[k]) == 0) {
+ rules |= (1 << k);
+ }
+ }
+ if (r == rules) {
+ Tcl_SetObjResult(textPtr->interp, Tcl_ObjPrintf("unknown hyphen rule \"%s\"", rule));
+ Tcl_SetErrorCode(textPtr->interp, "TK", "TEXT", "VALUE", NULL);
+ return TCL_ERROR;
+ }
+ }
+ *rulesPtr = rules;
return TCL_OK;
}
@@ -2374,9 +4311,7 @@ static void
TextWorldChangedCallback(
ClientData instanceData) /* Information about widget. */
{
- TkText *textPtr = instanceData;
-
- TextWorldChanged(textPtr, TK_TEXT_LINE_GEOMETRY);
+ TextWorldChanged((TkText *) instanceData, TK_TEXT_LINE_GEOMETRY);
}
/*
@@ -2402,30 +4337,25 @@ TextWorldChangedCallback(
static void
TextWorldChanged(
TkText *textPtr, /* Information about widget. */
- int mask) /* OR'd collection of bits showing what has
- * changed. */
+ int mask) /* OR'd collection of bits showing what has changed. */
{
Tk_FontMetrics fm;
int border;
- int oldCharHeight = textPtr->charHeight;
+ int oldLineHeight = textPtr->lineHeight;
- textPtr->charWidth = Tk_TextWidth(textPtr->tkfont, "0", 1);
- if (textPtr->charWidth <= 0) {
- textPtr->charWidth = 1;
- }
Tk_GetFontMetrics(textPtr->tkfont, &fm);
+ textPtr->lineHeight = MAX(1, fm.linespace);
+ textPtr->charWidth = MAX(1, Tk_TextWidth(textPtr->tkfont, "0", 1));
+ textPtr->spaceWidth = MAX(1, Tk_TextWidth(textPtr->tkfont, " ", 1));
- textPtr->charHeight = fm.linespace;
- if (textPtr->charHeight <= 0) {
- textPtr->charHeight = 1;
- }
- if (textPtr->charHeight != oldCharHeight) {
- TkBTreeClientRangeChanged(textPtr, textPtr->charHeight);
+ if (oldLineHeight != textPtr->lineHeight) {
+ TkTextFontHeightChanged(textPtr);
}
+
border = textPtr->borderWidth + textPtr->highlightWidth;
Tk_GeometryRequest(textPtr->tkwin,
- textPtr->width * textPtr->charWidth + 2*textPtr->padX + 2*border,
- textPtr->height*(fm.linespace+textPtr->spacing1+textPtr->spacing3)
+ textPtr->width*textPtr->charWidth + 2*textPtr->padX + 2*border,
+ textPtr->height*(fm.linespace + textPtr->spacing1 + textPtr->spacing3)
+ 2*textPtr->padY + 2*border);
Tk_SetInternalBorderEx(textPtr->tkwin,
@@ -2433,12 +4363,13 @@ TextWorldChanged(
border + textPtr->padY, border + textPtr->padY);
if (textPtr->setGrid) {
Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
- textPtr->charWidth, textPtr->charHeight);
+ textPtr->charWidth, textPtr->lineHeight);
} else {
Tk_UnsetGrid(textPtr->tkwin);
}
TkTextRelayoutWindow(textPtr, mask);
+ TK_BTREE_DEBUG(TkBTreeCheck(textPtr->sharedTextPtr->tree));
}
/*
@@ -2461,94 +4392,160 @@ TextWorldChanged(
*/
static void
-TextEventProc(
- ClientData clientData, /* Information about window. */
- register XEvent *eventPtr) /* Information about event. */
+ProcessConfigureNotify(
+ TkText *textPtr,
+ bool updateLineGeometry)
{
- register TkText *textPtr = clientData;
- TkTextIndex index, index2;
+ int mask = updateLineGeometry ? TK_TEXT_LINE_GEOMETRY : 0;
- if (eventPtr->type == Expose) {
- TkTextRedrawRegion(textPtr, eventPtr->xexpose.x,
- eventPtr->xexpose.y, eventPtr->xexpose.width,
- eventPtr->xexpose.height);
- } else if (eventPtr->type == ConfigureNotify) {
- if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
- || (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
- int mask = 0;
+ /*
+ * Do not allow line height computations before we accept the first
+ * ConfigureNotify event. The problem is the very poor performance
+ * in CalculateDisplayLineHeight() with very small widget width.
+ */
- if (textPtr->prevWidth != Tk_Width(textPtr->tkwin)) {
- mask = TK_TEXT_LINE_GEOMETRY;
- }
- TkTextRelayoutWindow(textPtr, mask);
- textPtr->prevWidth = Tk_Width(textPtr->tkwin);
- textPtr->prevHeight = Tk_Height(textPtr->tkwin);
- }
- } else if (eventPtr->type == DestroyNotify) {
- /*
- * NOTE: we must zero out selBorder, selBorderWidthPtr and
- * selFgColorPtr: they are duplicates of information in the "sel" tag,
- * which will be freed up when we delete all tags. Hence we don't want
- * the automatic config options freeing process to delete them as
- * well.
- */
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ textPtr->sharedTextPtr->allowUpdateLineMetrics = true;
+ updateLineGeometry = true;
+ TkTextEventuallyRepick(textPtr);
+ }
- textPtr->selBorder = NULL;
- textPtr->selBorderWidthPtr = NULL;
- textPtr->selBorderWidth = 0;
- textPtr->selFgColorPtr = NULL;
- if (textPtr->setGrid) {
- Tk_UnsetGrid(textPtr->tkwin);
- textPtr->setGrid = 0;
- }
- if (!(textPtr->flags & OPTIONS_FREED)) {
- Tk_FreeConfigOptions((char *) textPtr, textPtr->optionTable,
- textPtr->tkwin);
- textPtr->flags |= OPTIONS_FREED;
- }
- textPtr->flags |= DESTROYED;
+ if (textPtr->prevHeight != Tk_Height(textPtr->tkwin)
+ || textPtr->prevWidth != Tk_Width(textPtr->tkwin)) {
+ mask |= TK_TEXT_LINE_REDRAW_BOTTOM_LINE;
+ }
+ TkTextRelayoutWindow(textPtr, mask);
+ TK_BTREE_DEBUG(TkBTreeCheck(textPtr->sharedTextPtr->tree));
- /*
- * Call 'DestroyTest' to handle the deletion for us. The actual
- * textPtr may still exist after this, if there are some outstanding
- * references. But we have flagged it as DESTROYED just above, so
- * nothing will try to make use of it very extensively.
- */
+ textPtr->prevWidth = Tk_Width(textPtr->tkwin);
+ textPtr->prevHeight = Tk_Height(textPtr->tkwin);
+
+ if (textPtr->imageBboxTree) {
+ textPtr->configureBboxTree = true;
+ }
+}
+
+static void
+ProcessDestroyNotify(
+ TkText *textPtr)
+{
+ /*
+ * NOTE: we must zero out selBorder, selBorderWidthPtr and
+ * selFgColorPtr: they are duplicates of information in the "sel" tag,
+ * which will be freed up when we delete all tags. Hence we don't want
+ * the automatic config options freeing process to delete them as
+ * well.
+ */
+
+ textPtr->selBorder = NULL;
+ textPtr->selBorderWidthPtr = NULL;
+ textPtr->selBorderWidth = 0;
+ textPtr->selFgColorPtr = NULL;
+ if (textPtr->setGrid) {
+ Tk_UnsetGrid(textPtr->tkwin);
+ textPtr->setGrid = false;
+ }
+ if (!(textPtr->flags & OPTIONS_FREED)) {
+ Tk_FreeConfigOptions((char *) textPtr, textPtr->optionTable, textPtr->tkwin);
+ textPtr->flags |= OPTIONS_FREED;
+ }
+ textPtr->flags |= DESTROYED;
+
+ /*
+ * Call 'DestroyTest' to handle the deletion for us. The actual
+ * textPtr may still exist after this, if there are some outstanding
+ * references. But we have flagged it as DESTROYED just above, so
+ * nothing will try to make use of it very extensively.
+ */
- DestroyText(textPtr);
- } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
- if (eventPtr->xfocus.detail == NotifyInferior
- || eventPtr->xfocus.detail == NotifyAncestor
- || eventPtr->xfocus.detail == NotifyNonlinear) {
- Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
- if (eventPtr->type == FocusIn) {
- textPtr->flags |= GOT_FOCUS | INSERT_ON;
- if (textPtr->insertOffTime != 0) {
- textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
- textPtr->insertOnTime, TextBlinkProc, textPtr);
+ DestroyText(textPtr);
+}
+
+static void
+ProcessFocusInOut(
+ TkText *textPtr,
+ XEvent *eventPtr)
+{
+ TkTextIndex index, index2;
+
+ if (eventPtr->xfocus.detail == NotifyInferior
+ || eventPtr->xfocus.detail == NotifyAncestor
+ || eventPtr->xfocus.detail == NotifyNonlinear) {
+ if (eventPtr->type == FocusIn) {
+ textPtr->flags |= HAVE_FOCUS | INSERT_ON;
+ } else {
+ textPtr->flags &= ~(HAVE_FOCUS | INSERT_ON);
+ }
+ if (textPtr->state == TK_TEXT_STATE_NORMAL) {
+ if (eventPtr->type == FocusOut) {
+ if (textPtr->insertBlinkHandler) {
+ Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
+ textPtr->insertBlinkHandler = NULL;
}
- } else {
- textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
- textPtr->insertBlinkHandler = NULL;
- }
- if (textPtr->inactiveSelBorder != textPtr->selBorder) {
- TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr,
- 1);
+ } else if (textPtr->insertOffTime && !textPtr->insertBlinkHandler) {
+ textPtr->insertBlinkHandler =
+ Tcl_CreateTimerHandler(textPtr->insertOnTime, TextBlinkProc, textPtr);
}
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
+ TkTextIndexForwChars(textPtr, &index, 1, &index2, COUNT_INDICES);
+ TkTextChanged(NULL, textPtr, &index, &index2);
+ }
+ if (textPtr->inactiveSelBorder != textPtr->selBorder) {
+ TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, false);
+ }
+ if (textPtr->highlightWidth > 0) {
+ TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth, textPtr->highlightWidth);
+ }
+ }
+}
+static void
+TextEventProc(
+ ClientData clientData, /* Information about window. */
+ XEvent *eventPtr) /* Information about event. */
+{
+ TkText *textPtr = clientData;
+
+ switch (eventPtr->type) {
+ case ConfigureNotify:
+ if (textPtr->prevWidth != Tk_Width(textPtr->tkwin)
+ || textPtr->prevHeight != Tk_Height(textPtr->tkwin)) {
/*
- * While we wish to redisplay, no heights have changed, so no need
- * to call TkTextInvalidateLineMetrics.
+ * We don't need display computations until the widget is mapped
+ * or as long as the width seems to be unrealistic (not yet expanded
+ * by the geometry manager), see ProcessConfigureNotify() for more
+ * information.
*/
- TkTextChanged(NULL, textPtr, &index, &index2);
- if (textPtr->highlightWidth > 0) {
- TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth,
- textPtr->highlightWidth);
+ if (Tk_IsMapped(textPtr->tkwin)
+ || (Tk_Width(textPtr->tkwin) >
+ MAX(1, 2*(textPtr->highlightWidth + textPtr->borderWidth + textPtr->padX)))) {
+ ProcessConfigureNotify(textPtr, textPtr->prevWidth != Tk_Width(textPtr->tkwin));
}
}
+ break;
+ case DestroyNotify:
+ ProcessDestroyNotify(textPtr);
+ break;
+ default:
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ /*
+ * I don't know whether this can happen, but we want to be sure,
+ * probably we have rejected all ConfigureNotify events before
+ * first Expose arrives.
+ */
+ ProcessConfigureNotify(textPtr, true);
+ }
+ switch (eventPtr->type) {
+ case Expose:
+ TkTextRedrawRegion(textPtr, eventPtr->xexpose.x, eventPtr->xexpose.y,
+ eventPtr->xexpose.width, eventPtr->xexpose.height);
+ break;
+ case FocusIn:
+ case FocusOut:
+ ProcessFocusInOut(textPtr, eventPtr);
+ break;
+ }
}
}
@@ -2587,7 +4584,7 @@ TextCmdDeletedProc(
if (!(textPtr->flags & DESTROYED)) {
if (textPtr->setGrid) {
Tk_UnsetGrid(textPtr->tkwin);
- textPtr->setGrid = 0;
+ textPtr->setGrid = false;
}
textPtr->flags |= DESTROYED;
Tk_DestroyWindow(tkwin);
@@ -2615,39 +4612,315 @@ TextCmdDeletedProc(
*----------------------------------------------------------------------
*/
-static int
+static void
+InitPosition(
+ TkSharedText *sharedTextPtr, /* Shared portion of peer widgets. */
+ TkTextPosition *positions) /* Initialise this position array. */
+{
+ unsigned i;
+
+ for (i = 0; i < sharedTextPtr->numPeers; ++i, ++positions) {
+ positions->lineIndex = -1;
+ positions->byteIndex = 0;
+ }
+}
+
+static void
+FindNewTopPosition(
+ TkSharedText *sharedTextPtr, /* Shared portion of peer widgets. */
+ TkTextPosition *positions, /* Fill this position array. */
+ const TkTextIndex *index1Ptr, /* Start position of this insert/delete. */
+ const TkTextIndex *index2Ptr, /* End position of this delete, is NULL in case of insert. */
+ unsigned lengthOfInsertion) /* Length of inserted string, is zero in case of delete. */
+{
+ TkTextBTree tree = sharedTextPtr->tree;
+ TkText *tPtr;
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next, ++positions) {
+ int lineIndex = -1;
+ int byteIndex = 0;
+
+ if (index2Ptr == NULL) {
+ if (TkTextIndexGetLine(index1Ptr) == TkTextIndexGetLine(&tPtr->topIndex)) {
+ lineIndex = TkBTreeLinesTo(tree, NULL, TkTextIndexGetLine(index1Ptr), NULL);
+ byteIndex = TkTextIndexGetByteIndex(&tPtr->topIndex);
+ if (byteIndex > TkTextIndexGetByteIndex(index1Ptr)) {
+ byteIndex += lengthOfInsertion;
+ }
+ }
+ } else if (TkTextIndexCompare(index2Ptr, &tPtr->topIndex) >= 0) {
+ if (TkTextIndexCompare(index1Ptr, &tPtr->topIndex) <= 0) {
+ /*
+ * Deletion range straddles topIndex: use the beginning of the
+ * range as the new topIndex.
+ */
+
+ lineIndex = TkBTreeLinesTo(tree, NULL, TkTextIndexGetLine(index1Ptr), NULL);
+ byteIndex = TkTextIndexGetByteIndex(index1Ptr);
+ } else if (TkTextIndexGetLine(index1Ptr) == TkTextIndexGetLine(&tPtr->topIndex)) {
+ /*
+ * Deletion range starts on top line but after topIndex. Use
+ * the current topIndex as the new one.
+ */
+
+ lineIndex = TkBTreeLinesTo(tree, NULL, TkTextIndexGetLine(index1Ptr), NULL);
+ byteIndex = TkTextIndexGetByteIndex(&tPtr->topIndex);
+ } else {
+ /*
+ * Deletion range starts after the top line. This peers's view
+ * will not need to be reset. Nothing to do.
+ */
+ }
+ } else if (TkTextIndexGetLine(index2Ptr) == TkTextIndexGetLine(&tPtr->topIndex)) {
+ /*
+ * Deletion range ends on top line but before topIndex. Figure out
+ * what will be the new character index for the character
+ * currently pointed to by topIndex.
+ */
+
+ lineIndex = TkBTreeLinesTo(tree, NULL, TkTextIndexGetLine(index2Ptr), NULL);
+ byteIndex = TkTextIndexGetByteIndex(&tPtr->topIndex) - TkTextIndexGetByteIndex(index2Ptr);
+ if (TkTextIndexGetLine(index1Ptr) == TkTextIndexGetLine(index2Ptr)) {
+ byteIndex += TkTextIndexGetByteIndex(index1Ptr);
+ }
+ } else {
+ /*
+ * Deletion range ends before the top line. This peers's view
+ * will not need to be reset. Nothing to do.
+ */
+ }
+
+ if (lineIndex != -1) {
+ if (lineIndex == positions->lineIndex) {
+ positions->byteIndex = MAX(positions->byteIndex, byteIndex);
+ } else {
+ positions->lineIndex = MAX(positions->lineIndex, lineIndex);
+ positions->byteIndex = byteIndex;
+ }
+ }
+ }
+}
+
+static void
+SetNewTopPosition(
+ TkSharedText *sharedTextPtr, /* Shared portion of peer widgets. */
+ TkText *textPtr, /* Current peer widget, can be NULL. */
+ const TkTextPosition *positions, /* New top positions. */
+ bool viewUpdate) /* Update the view of current widget if set. */
+{
+ TkText *tPtr;
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next, ++positions) {
+ if (positions->lineIndex != -1) {
+ TkTextIndex index;
+
+ if (tPtr == textPtr && !viewUpdate) {
+ continue;
+ }
+
+ TkTextMakeByteIndex(sharedTextPtr->tree, NULL, positions->lineIndex, 0, &index);
+ TkTextIndexForwBytes(tPtr, &index, positions->byteIndex, &index);
+
+ if (tPtr == textPtr) {
+ /*
+ * Line cannot be before -startindex of textPtr because this line
+ * corresponds to an index which is necessarily between "begin"
+ * and "end" relative to textPtr. Therefore no need to clamp line
+ * to the -start/-end range.
+ */
+ } else {
+ TkTextIndex start;
+
+ /*
+ * Line may be before -startindex of tPtr and must be clamped to -startindex
+ * before providing it to TkTextSetYView otherwise lines before -startindex
+ * would be displayed. There is no need to worry about -endline however,
+ * because the view will only be reset if the deletion involves the TOP
+ * line of the screen.
+ */
+
+ TkTextIndexClear2(&start, tPtr, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&start, tPtr->startMarker);
+ if (TkTextIndexCompare(&index, &start) < 0) {
+ index = start;
+ }
+ }
+
+ TkTextSetYView(tPtr, &index, 0);
+ }
+ }
+}
+
+static void
+ParseHyphens(
+ const char *string,
+ const char *end,
+ char *buffer)
+{
+ /*
+ * Preparing a string for hyphenation support. Note that 0xff is not allowed in
+ * UTF-8 strings, so we can use this value for special purposes.
+ */
+
+ while (string != end) {
+ if (*string == '\\') {
+ switch (*++string) {
+ case '\0':
+ *buffer++ = '\\';
+ break;
+ case '-':
+ *buffer++ = 0xff;
+ *buffer++ = '-';
+ string += 1;
+ break;
+ case '+':
+ *buffer++ = 0xff;
+ *buffer++ = '+';
+ string += 1;
+ break;
+ case ':':
+ switch (string[1]) {
+ case 'c':
+ if (strncmp(string, ":ck:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_CK;
+ string += 4;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ case 'd':
+ if (strncmp(string, ":dd:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_DOUBLE_DIGRAPH;
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":dv:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_DOUBLE_VOWEL;
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":doubledigraph:", 15) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_DOUBLE_DIGRAPH;
+ string += 15;
+ break;
+ }
+ if (strncmp(string, ":doublevowel:", 13) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_DOUBLE_VOWEL;
+ string += 13;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ case 'g':
+ if (strncmp(string, ":ge:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_GEMINATION;
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":gemination:", 12) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_GEMINATION;
+ string += 12;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ case 'r':
+ if (strncmp(string, ":rh:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_REPEAT;
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":repeathyphen:", 14) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_REPEAT;
+ string += 14;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ case 't':
+ if (strncmp(string, ":tr:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_TREMA;
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":tc:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT;
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":trema:", 7) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_TREMA;
+ string += 7;
+ break;
+ }
+ if (strncmp(string, ":tripleconsonant:", 17) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = 1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT;
+ string += 17;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ default:
+ *buffer++ = *string++;
+ break;
+ }
+ }
+ } else {
+ *buffer++ = *string++;
+ }
+ }
+ *buffer = '\0';
+}
+
+static void
InsertChars(
- TkSharedText *sharedTextPtr,
TkText *textPtr, /* Overall information about text widget. */
- TkTextIndex *indexPtr, /* Where to insert new characters. May be
- * modified if the index is not valid for
- * insertion (e.g. if at "end"). */
- Tcl_Obj *stringPtr, /* Null-terminated string containing new
- * information to add to text. */
- int viewUpdate) /* Update the view if set. */
+ TkTextIndex *index1Ptr, /* Where to insert new characters. May be modified if the index
+ * is not valid for insertion (e.g. if at "end"). */
+ TkTextIndex *index2Ptr, /* Out: Index at the end of the inserted text. */
+ char const *string, /* Null-terminated string containing new information to add to text. */
+ unsigned length, /* Length of string content. */
+ bool viewUpdate, /* Update the view if set. */
+ TkTextTagSet *tagInfoPtr, /* Add these tags to the inserted text, can be NULL. */
+ TkTextTag *hyphenTagPtr, /* Associate this tag with soft hyphens, can be NULL. */
+ bool parseHyphens) /* Should we parse hyphens (tk_textInsert)? */
{
- int lineIndex, length;
+ TkSharedText *sharedTextPtr;
TkText *tPtr;
- int *lineAndByteIndex;
- int resetViewCount;
- int pixels[2*PIXEL_CLIENTS];
- const char *string = Tcl_GetString(stringPtr);
+ TkTextPosition *textPosition;
+ TkTextPosition textPosBuf[PIXEL_CLIENTS];
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr;
+ TkTextIndex startIndex;
+ const char *text = string;
+ char textBuf[4096];
- length = stringPtr->length;
- if (sharedTextPtr == NULL) {
- sharedTextPtr = textPtr->sharedTextPtr;
- }
+ assert(textPtr);
+ assert(length > 0);
+ assert(!TkTextIsDeadPeer(textPtr));
+
+ sharedTextPtr = textPtr->sharedTextPtr;
/*
* Don't allow insertions on the last (dummy) line of the text. This is
- * the only place in this function where the indexPtr is modified.
+ * the only place in this function where the index1Ptr is modified.
*/
- lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
- if (lineIndex == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) {
- lineIndex--;
- TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineIndex, 1000000,
- indexPtr);
+ if (TkTextIndexGetLine(index1Ptr) == TkBTreeGetLastLine(textPtr)) {
+ TkTextIndexBackChars(textPtr, index1Ptr, 1, index1Ptr, COUNT_INDICES);
}
/*
@@ -2657,368 +4930,421 @@ InsertChars(
* insertion, since the insertion could invalidate it.
*/
- resetViewCount = 0;
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- lineAndByteIndex = ckalloc(sizeof(int) * 2 * sharedTextPtr->refCount);
+ if (sharedTextPtr->numPeers > sizeof(textPosition)/sizeof(textPosition[0])) {
+ textPosition = malloc(sizeof(textPosition[0])*sharedTextPtr->numPeers);
} else {
- lineAndByteIndex = pixels;
- }
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- lineAndByteIndex[resetViewCount] = -1;
- if (indexPtr->linePtr == tPtr->topIndex.linePtr) {
- lineAndByteIndex[resetViewCount] =
- TkBTreeLinesTo(tPtr, indexPtr->linePtr);
- lineAndByteIndex[resetViewCount+1] = tPtr->topIndex.byteIndex;
- if (lineAndByteIndex[resetViewCount+1] > indexPtr->byteIndex) {
- lineAndByteIndex[resetViewCount+1] += length;
- }
- }
- resetViewCount += 2;
+ textPosition = textPosBuf;
}
+ InitPosition(sharedTextPtr, textPosition);
+ FindNewTopPosition(sharedTextPtr, textPosition, index1Ptr, NULL, length);
- TkTextChanged(sharedTextPtr, NULL, indexPtr, indexPtr);
+ TkTextChanged(sharedTextPtr, NULL, index1Ptr, index1Ptr);
+ undoInfoPtr = TkTextUndoStackIsFull(sharedTextPtr->undoStack) ? NULL : &undoInfo;
+ startIndex = *index1Ptr;
+ TkTextIndexToByteIndex(&startIndex); /* we need the byte position after insertion */
- sharedTextPtr->stateEpoch++;
+ if (parseHyphens) {
+ text = (length >= sizeof(textBuf)) ? malloc(length + 1) : textBuf;
+ ParseHyphens(string, string + length, (char *) text);
+ }
- TkBTreeInsertChars(sharedTextPtr->tree, indexPtr, string);
+ TkBTreeInsertChars(sharedTextPtr->tree, index1Ptr, text, tagInfoPtr, hyphenTagPtr, undoInfoPtr);
/*
- * Push the insertion on the undo stack, and update the modified status of
- * the widget.
+ * Push the insertion on the undo stack, and update the modified status of the widget.
+ * Try to join with previously pushed undo token, if possible.
*/
- if (length > 0) {
- if (sharedTextPtr->undo) {
- TkTextIndex toIndex;
-
- if (sharedTextPtr->autoSeparators &&
- sharedTextPtr->lastEditMode != TK_TEXT_EDIT_INSERT) {
- TkUndoInsertUndoSeparator(sharedTextPtr->undoStack);
- }
+ if (undoInfoPtr) {
+ const TkTextUndoSubAtom *subAtom;
+ bool triggerStackEvent = false;
- sharedTextPtr->lastEditMode = TK_TEXT_EDIT_INSERT;
+ assert(undoInfo.byteSize == 0);
- TkTextIndexForwBytes(textPtr, indexPtr, length, &toIndex);
- TextPushUndoAction(textPtr, stringPtr, 1, indexPtr, &toIndex);
+ if (sharedTextPtr->autoSeparators && sharedTextPtr->lastEditMode != TK_TEXT_EDIT_INSERT) {
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoPushSeparator(sharedTextPtr->undoStack, true);
+ sharedTextPtr->lastUndoTokenType = -1;
}
-
- UpdateDirtyFlag(sharedTextPtr);
- }
-
- resetViewCount = 0;
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- if (lineAndByteIndex[resetViewCount] != -1) {
- if ((tPtr != textPtr) || viewUpdate) {
- TkTextIndex newTop;
-
- TkTextMakeByteIndex(sharedTextPtr->tree, tPtr,
- lineAndByteIndex[resetViewCount], 0, &newTop);
- TkTextIndexForwBytes(tPtr, &newTop,
- lineAndByteIndex[resetViewCount+1], &newTop);
- TkTextSetYView(tPtr, &newTop, 0);
- }
+ if (sharedTextPtr->lastUndoTokenType != TK_TEXT_UNDO_INSERT
+ || !((subAtom = TkTextUndoGetLastUndoSubAtom(sharedTextPtr->undoStack))
+ && (triggerStackEvent = TkBTreeJoinUndoInsert(
+ subAtom->item, subAtom->size, undoInfo.token, undoInfo.byteSize)))) {
+ TkTextPushUndoToken(sharedTextPtr, undoInfo.token, undoInfo.byteSize);
+ } else {
+ assert(!undoInfo.token->undoType->destroyProc);
+ free(undoInfo.token);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
}
- resetViewCount += 2;
+ if (triggerStackEvent) {
+ sharedTextPtr->undoStackEvent = true; /* TkBTreeJoinUndoInsert didn't trigger */
+ }
+ assert(undoInfo.token->undoType->rangeProc);
+ sharedTextPtr->prevUndoStartIndex = ((TkTextUndoTokenRange *) undoInfo.token)->startIndex;
+ sharedTextPtr->prevUndoEndIndex = ((TkTextUndoTokenRange *) undoInfo.token)->endIndex;
+ sharedTextPtr->lastUndoTokenType = TK_TEXT_UNDO_INSERT;
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_INSERT;
}
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- ckfree(lineAndByteIndex);
+
+ *index2Ptr = *index1Ptr;
+ *index1Ptr = startIndex;
+ UpdateModifiedFlag(sharedTextPtr, true);
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ SetNewTopPosition(sharedTextPtr, textPtr, textPosition, viewUpdate);
+ if (textPosition != textPosBuf) {
+ free(textPosition);
}
/*
* Invalidate any selection retrievals in progress.
*/
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- tPtr->abortSelections = 1;
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ tPtr->abortSelections = true;
}
- /*
- * For convenience, return the length of the string.
- */
-
- return length;
+ if (parseHyphens && text != textBuf) {
+ free((char *) text);
+ }
}
/*
*----------------------------------------------------------------------
*
- * TextPushUndoAction --
+ * TextUndoRedoCallback --
*
- * Shared by insert and delete actions. Stores the appropriate scripts
- * into our undo stack. We will add a single refCount to the 'undoString'
- * object, so, if it previously had a refCount of zero, the caller should
- * not free it.
+ * This function is registered with the generic undo/redo code to handle
+ * 'insert' and 'delete' actions on all text widgets. We cannot perform
+ * those actions on any particular text widget, because that text widget
+ * might have been deleted by the time we get here.
*
* Results:
* None.
*
* Side effects:
- * Items pushed onto stack.
+ * Will change anything, depending on the undo token.
*
*----------------------------------------------------------------------
*/
static void
-TextPushUndoAction(
- TkText *textPtr, /* Overall information about text widget. */
- Tcl_Obj *undoString, /* New text. */
- int insert, /* 1 if insert, else delete. */
- const TkTextIndex *index1Ptr,
- /* Index describing first location. */
- const TkTextIndex *index2Ptr)
- /* Index describing second location. */
+TriggerWatchUndoRedo(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *token,
+ bool isRedo,
+ bool isFinal,
+ TkText **peers,
+ int numPeers)
{
- TkUndoSubAtom *iAtom, *dAtom;
- int canUndo, canRedo;
- char lMarkName[20] = "tk::undoMarkL";
- char rMarkName[20] = "tk::undoMarkR";
- char stringUndoMarkId[7] = "";
+ TkTextIndex index1, index2;
+ Tcl_Obj *cmdPtr;
+ char arg[100];
+ int i;
- /*
- * Create the helpers.
- */
+ assert(sharedTextPtr->triggerWatchCmd);
+ assert(token->undoType->rangeProc);
+ assert(token->undoType->commandProc);
- Tcl_Obj *seeInsertObj = Tcl_NewObj();
- Tcl_Obj *markSet1InsertObj = Tcl_NewObj();
- Tcl_Obj *markSet2InsertObj = NULL;
- Tcl_Obj *insertCmdObj = Tcl_NewObj();
- Tcl_Obj *deleteCmdObj = Tcl_NewObj();
- Tcl_Obj *markSetLUndoMarkCmdObj = Tcl_NewObj();
- Tcl_Obj *markSetRUndoMarkCmdObj = NULL;
- Tcl_Obj *markGravityLUndoMarkCmdObj = Tcl_NewObj();
- Tcl_Obj *markGravityRUndoMarkCmdObj = NULL;
+ sharedTextPtr->triggerWatchCmd = false;
+ token->undoType->rangeProc(sharedTextPtr, token, &index1, &index2);
+ Tcl_IncrRefCount(cmdPtr = token->undoType->commandProc(sharedTextPtr, token));
+ snprintf(arg, sizeof(arg), "{%s} %s", Tcl_GetString(cmdPtr), isFinal ? "yes" : "no");
+ Tcl_DecrRefCount(cmdPtr);
- /*
- * Get the index positions.
- */
+ for (i = 0; i < numPeers; ++i) {
+ TkText *tPtr = peers[i];
- Tcl_Obj *index1Obj = TkTextNewIndexObj(NULL, index1Ptr);
- Tcl_Obj *index2Obj = TkTextNewIndexObj(NULL, index2Ptr);
+ if (!(tPtr->flags & DESTROYED)) {
+ char idx[2][TK_POS_CHARS];
+ const char *info = isRedo ? "redo" : "undo";
- /*
- * These need refCounts, because they are used more than once below.
- */
+ TkTextPrintIndex(tPtr, &index1, idx[0]);
+ TkTextPrintIndex(tPtr, &index2, idx[1]);
+ TkTextTriggerWatchCmd(tPtr, info, idx[0], idx[1], arg, false);
+ }
+ }
- Tcl_IncrRefCount(seeInsertObj);
- Tcl_IncrRefCount(index1Obj);
- Tcl_IncrRefCount(index2Obj);
-
- Tcl_ListObjAppendElement(NULL, seeInsertObj,
- Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
- Tcl_ListObjAppendElement(NULL, seeInsertObj, Tcl_NewStringObj("see", 3));
- Tcl_ListObjAppendElement(NULL, seeInsertObj,
- Tcl_NewStringObj("insert", 6));
-
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj,
- Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj,
- Tcl_NewStringObj("mark", 4));
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj,
- Tcl_NewStringObj("set", 3));
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj,
- Tcl_NewStringObj("insert", 6));
- markSet2InsertObj = Tcl_DuplicateObj(markSet1InsertObj);
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj, index1Obj);
- Tcl_ListObjAppendElement(NULL, markSet2InsertObj, index2Obj);
-
- Tcl_ListObjAppendElement(NULL, insertCmdObj,
- Tcl_NewStringObj("insert", 6));
- Tcl_ListObjAppendElement(NULL, insertCmdObj, index1Obj);
+ sharedTextPtr->triggerWatchCmd = true;
+}
- /*
- * Only use of 'undoString' is here.
- */
+void
+TextUndoRedoCallback(
+ TkTextUndoStack stack,
+ const TkTextUndoAtom *atom)
+{
+ TkSharedText *sharedTextPtr = (TkSharedText *) TkTextUndoGetContext(stack);
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo redoInfo;
+ TkTextUndoInfo *redoInfoPtr;
+ TkTextPosition *textPosition = NULL;
+ TkTextPosition textPosBuf[PIXEL_CLIENTS];
+ bool eventuallyRepick = false;
+ TkText *peerArr[20];
+ TkText **peers = peerArr;
+ TkText *tPtr;
+ int i, k, countPeers = 0;
- Tcl_ListObjAppendElement(NULL, insertCmdObj, undoString);
-
- Tcl_ListObjAppendElement(NULL, deleteCmdObj,
- Tcl_NewStringObj("delete", 6));
- Tcl_ListObjAppendElement(NULL, deleteCmdObj, index1Obj);
- Tcl_ListObjAppendElement(NULL, deleteCmdObj, index2Obj);
-
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj,
- Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj,
- Tcl_NewStringObj("mark", 4));
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj,
- Tcl_NewStringObj("set", 3));
- markSetRUndoMarkCmdObj = Tcl_DuplicateObj(markSetLUndoMarkCmdObj);
- textPtr->sharedTextPtr->undoMarkId++;
- sprintf(stringUndoMarkId, "%d", textPtr->sharedTextPtr->undoMarkId);
- strcat(lMarkName, stringUndoMarkId);
- strcat(rMarkName, stringUndoMarkId);
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj,
- Tcl_NewStringObj(lMarkName, -1));
- Tcl_ListObjAppendElement(NULL, markSetRUndoMarkCmdObj,
- Tcl_NewStringObj(rMarkName, -1));
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj, index1Obj);
- Tcl_ListObjAppendElement(NULL, markSetRUndoMarkCmdObj, index2Obj);
-
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj("mark", 4));
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj("gravity", 7));
- markGravityRUndoMarkCmdObj = Tcl_DuplicateObj(markGravityLUndoMarkCmdObj);
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj(lMarkName, -1));
- Tcl_ListObjAppendElement(NULL, markGravityRUndoMarkCmdObj,
- Tcl_NewStringObj(rMarkName, -1));
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj("left", 4));
- Tcl_ListObjAppendElement(NULL, markGravityRUndoMarkCmdObj,
- Tcl_NewStringObj("right", 5));
+ assert(stack);
- /*
- * Note: we don't wish to use textPtr->widgetCmd in these callbacks
- * because if we delete the textPtr, but peers still exist, we will then
- * have references to a non-existent Tcl_Command in the undo stack, which
- * will lead to crashes later. Also, the behaviour of the widget w.r.t.
- * bindings (%W substitutions) always uses the widget path name, so there
- * is no good reason the undo stack should do otherwise.
- *
- * For the 'insert' and 'delete' actions, we have to register a functional
- * callback, because these actions are defined to operate on the
- * underlying data shared by all peers.
- */
+ if (sharedTextPtr->triggerWatchCmd) {
+ if (sharedTextPtr->numPeers > sizeof(peerArr) / sizeof(peerArr[0])) {
+ peers = malloc(sharedTextPtr->numPeers * sizeof(peerArr[0]));
+ }
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr->watchCmd) {
+ TkTextSaveCursorIndex(tPtr);
+ peers[countPeers++] = tPtr;
+ tPtr->refCount += 1;
+ }
+ }
+ }
- iAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
- insertCmdObj, NULL);
- TkUndoMakeCmdSubAtom(NULL, markSet2InsertObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, seeInsertObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, markSetLUndoMarkCmdObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, markSetRUndoMarkCmdObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, markGravityLUndoMarkCmdObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, markGravityRUndoMarkCmdObj, iAtom);
-
- dAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
- deleteCmdObj, NULL);
- TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, markSetLUndoMarkCmdObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, markSetRUndoMarkCmdObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, markGravityLUndoMarkCmdObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, markGravityRUndoMarkCmdObj, dAtom);
-
- Tcl_DecrRefCount(seeInsertObj);
- Tcl_DecrRefCount(index1Obj);
- Tcl_DecrRefCount(index2Obj);
-
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
+ memset(&undoInfo, 0, sizeof(undoInfo));
+ redoInfoPtr = TkTextUndoStackIsFull(stack) ? NULL : &redoInfo;
- /*
- * Depending whether the action is to insert or delete, we provide the
- * appropriate second and third arguments to TkUndoPushAction. (The first
- * is the 'actionCommand', and the second the 'revertCommand').
- */
+ for (i = atom->arraySize - 1; i >= 0; --i) {
+ TkTextIndex index1, index2;
+ const TkTextUndoSubAtom *subAtom = atom->array + i;
+ TkTextUndoToken *token = subAtom->item;
+ bool isDelete = token->undoType->action == TK_TEXT_UNDO_INSERT
+ || token->undoType->action == TK_TEXT_REDO_DELETE;
+ bool isInsert = token->undoType->action == TK_TEXT_UNDO_DELETE
+ || token->undoType->action == TK_TEXT_REDO_INSERT;
- if (insert) {
- TkUndoPushAction(textPtr->sharedTextPtr->undoStack, iAtom, dAtom);
- } else {
- TkUndoPushAction(textPtr->sharedTextPtr->undoStack, dAtom, iAtom);
+ if (!isInsert) {
+ token->undoType->rangeProc(sharedTextPtr, token, &index1, &index2);
+ }
+
+ if (isInsert || isDelete) {
+ const TkTextUndoTokenRange *range = (const TkTextUndoTokenRange *) token;
+
+ if (isDelete && sharedTextPtr->triggerWatchCmd) {
+ TriggerWatchUndoRedo(sharedTextPtr, token, subAtom->redo, i == 0, peers, countPeers);
+ }
+ if (!textPosition) {
+ if (sharedTextPtr->numPeers > sizeof(textPosBuf)/sizeof(textPosBuf[0])) {
+ textPosition = malloc(sizeof(textPosition[0])*sharedTextPtr->numPeers);
+ } else {
+ textPosition = textPosBuf;
+ }
+ InitPosition(sharedTextPtr, textPosition);
+ }
+ if (isInsert) {
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &range->startIndex, &index1);
+ TkTextChanged(sharedTextPtr, NULL, &index1, &index1);
+ FindNewTopPosition(sharedTextPtr, textPosition, &index1, NULL, subAtom->size);
+ } else {
+ TkTextChanged(sharedTextPtr, NULL, &index1, &index2);
+ FindNewTopPosition(sharedTextPtr, textPosition, &index1, &index2, 0);
+ }
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (!tPtr->abortSelections) {
+ if (isInsert) {
+ tPtr->abortSelections = true;
+ } else {
+ if (range->startIndex.lineIndex < range->endIndex.lineIndex
+ && TkBTreeTag(sharedTextPtr, NULL, &index1, &index2,
+ tPtr->selTagPtr, false, NULL, TkTextRedrawTag)) {
+ TkTextSelectionEvent(tPtr);
+ tPtr->abortSelections = true;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Now perform the undo/redo action.
+ */
+
+ if (redoInfoPtr) {
+ memset(redoInfoPtr, 0, sizeof(redoInfo));
+ }
+ undoInfo.token = token;
+ undoInfo.byteSize = atom->size;
+ token->undoType->undoProc(sharedTextPtr, &undoInfo, redoInfoPtr, atom->redo);
+
+ if (token->undoType->action == TK_TEXT_UNDO_TAG) {
+ eventuallyRepick = true;
+ }
+ if (isInsert) {
+ token->undoType->rangeProc(sharedTextPtr, token, &index1, &index2);
+ }
+ if (redoInfoPtr) {
+ if (redoInfo.token == token) {
+ /*
+ * We are re-using a token, this is possible because the current undo token
+ * will expire after this action.
+ */
+ if (!subAtom->redo) {
+ if (token->undoType->action == TK_TEXT_UNDO_INSERT
+ || token->undoType->action == TK_TEXT_UNDO_DELETE) {
+ assert(sharedTextPtr->insertDeleteUndoTokenCount > 0);
+ sharedTextPtr->insertDeleteUndoTokenCount -= 1;
+ }
+ }
+ if (token->undoType->destroyProc) {
+ /* We need a balanced call of perform/destroy. */
+ token->undoType->destroyProc(sharedTextPtr, subAtom->item, true);
+ }
+ /*
+ * Do not free this item.
+ */
+ ((TkTextUndoSubAtom *) subAtom)->item = NULL;
+ }
+ TkTextPushUndoToken(sharedTextPtr, redoInfo.token, redoInfo.byteSize);
+ }
+ if (!isDelete && sharedTextPtr->triggerWatchCmd) {
+ TriggerWatchUndoRedo(sharedTextPtr, token, subAtom->redo, i == 0, peers, countPeers);
+ }
}
- if (!canUndo || canRedo) {
- GenerateUndoStackEvent(textPtr);
+ if (eventuallyRepick) {
+ for (k = 0; k < countPeers; ++k) {
+ TkText *tPtr = peers[k];
+
+ if (!(tPtr->flags & DESTROYED)) {
+ TkTextEventuallyRepick(tPtr);
+ }
+ }
+ }
+
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
+ sharedTextPtr->lastUndoTokenType = -1;
+ UpdateModifiedFlag(sharedTextPtr, false);
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+
+ if (textPosition) {
+ SetNewTopPosition(sharedTextPtr, NULL, textPosition, true);
+ if (textPosition != textPosBuf) {
+ free(textPosition);
+ }
+ }
+
+ if (sharedTextPtr->triggerWatchCmd) {
+ for (i = 0; i < countPeers; ++i) {
+ TkText *tPtr = peers[i];
+
+ if (!(tPtr->flags & DESTROYED)) {
+ TkTextIndexClear(&tPtr->insertIndex, tPtr);
+ TkTextTriggerWatchCursor(tPtr);
+ }
+ if (--tPtr->refCount == 0) {
+ free(tPtr);
+ }
+ }
+ }
+
+ /*
+ * Freeing the peer array has to be done even if sharedTextPtr->triggerWatchCmd
+ * is false, possibly the user has cleared the watch command inside the trigger
+ * callback.
+ */
+
+ if (peers != peerArr) {
+ free(peers);
}
}
/*
*----------------------------------------------------------------------
*
- * TextUndoRedoCallback --
+ * TextUndoStackContentChangedCallback --
*
* This function is registered with the generic undo/redo code to handle
- * 'insert' and 'delete' actions on all text widgets. We cannot perform
- * those actions on any particular text widget, because that text widget
- * might have been deleted by the time we get here.
+ * undo/redo stack changes.
*
* Results:
- * A standard Tcl result.
+ * None.
*
* Side effects:
- * Will insert or delete text, depending on the first word contained in
- * objPtr.
+ * None.
*
*----------------------------------------------------------------------
*/
-int
-TextUndoRedoCallback(
- Tcl_Interp *interp, /* Current interpreter. */
- ClientData clientData, /* Passed from undo code, but contains our
- * shared text data structure. */
- Tcl_Obj *objPtr) /* Arguments of a command to be handled by the
- * shared text data structure. */
+static void
+TextUndoStackContentChangedCallback(
+ const TkTextUndoStack stack)
+{
+ ((TkSharedText *) TkTextUndoGetContext(stack))->undoStackEvent = true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TriggerUndoStackEvent --
+ *
+ * This function is triggering the <<UndoStack>> event for all peers.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May force the text window (and all peers) into existence.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TriggerUndoStackEvent(
+ TkSharedText *sharedTextPtr)
{
- TkSharedText *sharedPtr = clientData;
- int res, objc;
- Tcl_Obj **objv;
TkText *textPtr;
- res = Tcl_ListObjGetElements(interp, objPtr, &objc, &objv);
- if (res != TCL_OK) {
- return res;
+ assert(sharedTextPtr->undoStackEvent);
+ sharedTextPtr->undoStackEvent = false;
+
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
+ if (!(textPtr->flags & DESTROYED)) {
+ Tk_MakeWindowExist(textPtr->tkwin);
+ SendVirtualEvent(textPtr->tkwin, "UndoStack", NULL);
+ }
}
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextUndoFreeCallback --
+ *
+ * This function is registered with the generic undo/redo code to handle
+ * the freeing operation of undo/redo items.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some memory will be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TextUndoFreeCallback(
+ const TkTextUndoStack stack,
+ const TkTextUndoSubAtom *subAtom) /* Destroy this token. */
+{
+ TkTextUndoToken *token = (TkTextUndoToken *) subAtom->item;
/*
- * If possible, use a real text widget to perform the undo/redo action
- * (i.e. insertion or deletion of text). This provides maximum
- * compatibility with older versions of Tk, in which the user may rename
- * the text widget to allow capture of undo or redo actions.
- *
- * In particular, this sorting of capture is useful in text editors based
- * on the Tk text widget, which need to know which new text needs
- * re-coloring.
- *
- * It would be better if the text widget provided some other mechanism to
- * allow capture of this information ("What has just changed in the text
- * widget?"). What we have here is not entirely satisfactory under all
- * circumstances.
+ * Consider that the token is possibly null.
*/
- textPtr = sharedPtr->peers;
- while (textPtr != NULL) {
- if (textPtr->start == NULL && textPtr->end == NULL) {
- Tcl_Obj *cmdNameObj, *evalObj;
-
- evalObj = Tcl_NewObj();
- Tcl_IncrRefCount(evalObj);
-
- /*
- * We might wish to use the real, current command-name for the
- * widget, but this will break any code that has over-ridden the
- * widget, and is expecting to observe the insert/delete actions
- * which are caused by undo/redo operations.
- *
- * cmdNameObj = Tcl_NewObj();
- * Tcl_GetCommandFullName(interp, textPtr->widgetCmd, cmdNameObj);
- *
- * While such interception is not explicitly documented as
- * supported, it does occur, and so until we can provide some
- * alternative mechanism for such code to do what it needs, we
- * allow it to take place here.
- */
+ if (token) {
+ TkTextUndoAction action = token->undoType->action;
- cmdNameObj = Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1);
- Tcl_ListObjAppendElement(NULL, evalObj, cmdNameObj);
- Tcl_ListObjAppendList(NULL, evalObj, objPtr);
- res = Tcl_EvalObjEx(interp, evalObj, TCL_EVAL_GLOBAL);
- Tcl_DecrRefCount(evalObj);
- return res;
+ if (action == TK_TEXT_UNDO_INSERT || action == TK_TEXT_UNDO_DELETE) {
+ TkSharedText *sharedTextPtr = (TkSharedText *) TkTextUndoGetContext(stack);
+ assert(sharedTextPtr->insertDeleteUndoTokenCount > 0);
+ sharedTextPtr->insertDeleteUndoTokenCount -= 1;
}
- textPtr = textPtr->next;
+ if (token->undoType->destroyProc) {
+ token->undoType->destroyProc(TkTextUndoGetContext(stack), subAtom->item, false);
+ }
+ free(subAtom->item);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
}
-
- /*
- * If there's no current text widget which shows everything, then we fall
- * back on acting directly. This means there is no way to intercept from
- * the Tcl level.
- */
-
- return SharedTextObjCmd(sharedPtr, interp, objc+1, objv-1);
}
/*
@@ -3046,27 +5372,25 @@ static int
CountIndices(
const TkText *textPtr, /* Overall information about text widget. */
const TkTextIndex *indexPtr1,
- /* Index describing location of first
- * character to delete. */
+ /* Index describing location of first character to delete. */
const TkTextIndex *indexPtr2,
- /* Index describing location of last character
- * to delete. NULL means just delete the one
- * character given by indexPtr1. */
+ /* Index describing location of last character to delete. NULL means
+ * just delete the one character given by indexPtr1. */
TkTextCountType type) /* The kind of indices to count. */
{
/*
* Order the starting and stopping indices.
*/
- int compare = TkTextIndexCmp(indexPtr1, indexPtr2);
+ int compare = TkTextIndexCompare(indexPtr1, indexPtr2);
if (compare == 0) {
return 0;
- } else if (compare > 0) {
+ }
+ if (compare > 0) {
return -TkTextIndexCount(textPtr, indexPtr2, indexPtr1, type);
- } else {
- return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type);
}
+ return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type);
}
/*
@@ -3078,7 +5402,7 @@ CountIndices(
* widget command.
*
* Results:
- * Returns a standard Tcl result, currently always TCL_OK.
+ * Returns whether the widget hasn't been destroyed.
*
* Side effects:
* Characters and other entities (windows, images) get deleted from the
@@ -3087,7 +5411,7 @@ CountIndices(
* If 'viewUpdate' is true, we may adjust the window contents'
* y-position, and scrollbar setting.
*
- * If 'viewUpdate' is true we can guarantee that textPtr->topIndex
+ * If 'viewUpdate' is true, true we can guarantee that textPtr->topIndex
* points to a valid TkTextLine after this function returns. However, if
* 'viewUpdate' is false, then there is no such guarantee (since
* topIndex.linePtr can be garbage). The caller is expected to take
@@ -3097,296 +5421,324 @@ CountIndices(
*----------------------------------------------------------------------
*/
-static int
+static bool
+DetectUndoTag(
+ const TkTextTag *tagPtr)
+{
+ for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
+ if (!TkBitTest(tagPtr->sharedTextPtr->dontUndoTags, tagPtr->index)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool
+HaveMarksInRange(
+ const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2,
+ int flags)
+{
+ const TkTextSegment *segPtr1;
+ const TkTextSegment *segPtr2;
+
+ assert(TkTextIndexIsEqual(indexPtr1, indexPtr2));
+
+ segPtr2 = TkTextIndexGetSegment(indexPtr2);
+ if (!segPtr2) {
+ return false;
+ }
+ if (flags & DELETE_INCLUSIVE) {
+ segPtr2 = segPtr2->nextPtr;
+ assert(segPtr2);
+ }
+
+ segPtr1 = TkTextIndexGetSegment(indexPtr1);
+ if (!segPtr1 || !TkTextIsStableMark(segPtr1)) {
+ segPtr1 = TkTextIndexGetFirstSegment(indexPtr1, NULL);
+ } else if (!(flags & DELETE_INCLUSIVE)) {
+ segPtr1 = segPtr1->nextPtr;
+ assert(segPtr1);
+ }
+
+ for ( ; segPtr1 && segPtr1->size == 0; segPtr1 = segPtr1->nextPtr) {
+ if (TkTextIsNormalMark(segPtr1)) {
+ return true;
+ }
+ if (segPtr1 == segPtr2) {
+ return false;
+ }
+ }
+ return false;
+}
+
+static bool
+DeleteMarksOnLastLine(
+ TkText *textPtr,
+ TkTextSegment *segPtr,
+ TkTextSegment *endPtr,
+ int flags)
+{
+ bool rc;
+
+ assert(endPtr);
+
+ if (flags & DELETE_INCLUSIVE) {
+ endPtr = endPtr->nextPtr;
+ assert(endPtr);
+ }
+
+ if (!segPtr) {
+ segPtr = endPtr->sectionPtr->linePtr->segPtr;
+ } else if (!(flags & DELETE_INCLUSIVE)) {
+ segPtr = segPtr->nextPtr;
+ }
+
+ rc = false;
+ while (segPtr != endPtr) {
+ TkTextSegment *nextPtr = segPtr->nextPtr;
+
+ if (TkTextIsNormalMark(segPtr)) {
+ TkTextUnsetMark(textPtr, segPtr);
+ rc = true;
+ }
+
+ segPtr = nextPtr;
+ }
+
+ return rc;
+}
+
+static bool
DeleteIndexRange(
TkSharedText *sharedTextPtr,/* Shared portion of peer widgets. */
TkText *textPtr, /* Overall information about text widget. */
const TkTextIndex *indexPtr1,
- /* Index describing location of first
- * character (or other entity) to delete. */
+ /* Index describing location of first character (or other entity)
+ * to delete. */
const TkTextIndex *indexPtr2,
- /* Index describing location of last
- * character (or other entity) to delete.
- * NULL means just delete the one character
- * given by indexPtr1. */
- int viewUpdate) /* Update vertical view if set. */
+ /* Index describing location of last character (or other entity)
+ * to delete. NULL means just delete the one character given by
+ * indexPtr1. */
+ int flags, /* Flags controlling the deletion. */
+ bool viewUpdate, /* Update vertical view if set. */
+ bool triggerWatchDelete, /* Should we trigger the watch command for deletion? */
+ bool triggerWatchInsert, /* Should we trigger the watch command for insertion? */
+ bool userFlag, /* Trigger user modification? */
+ bool final) /* This is the final call in a sequence of ranges. */
{
- int line1, line2;
- TkTextIndex index1, index2;
- TkText *tPtr;
- int *lineAndByteIndex;
- int resetViewCount;
- int pixels[2*PIXEL_CLIENTS];
-
- if (sharedTextPtr == NULL) {
+ TkTextIndex index1, index2, index3;
+ TkTextPosition *textPosition;
+ TkTextPosition textPosBuf[PIXEL_CLIENTS];
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr;
+ bool deleteOnLastLine;
+ bool altered;
+ int cmp;
+
+ if (!sharedTextPtr) {
sharedTextPtr = textPtr->sharedTextPtr;
}
+ if (triggerWatchInsert) {
+ TkTextIndexToByteIndex((TkTextIndex *) indexPtr1); /* mutable due to concept */
+ }
+
/*
* Prepare the starting and stopping indices.
*/
index1 = *indexPtr1;
- if (indexPtr2 != NULL) {
+
+ if (indexPtr2) {
+ if ((cmp = TkTextIndexCompare(&index1, indexPtr2)) > 0) {
+ return true; /* there is nothing to delete */
+ }
index2 = *indexPtr2;
+ } else if (!TkTextIndexForwChars(textPtr, &index1, 1, &index2, COUNT_INDICES)) {
+ cmp = 0;
} else {
- index2 = index1;
- TkTextIndexForwChars(NULL, &index2, 1, &index2, COUNT_INDICES);
+ cmp = -1;
}
- /*
- * Make sure there's really something to delete.
- */
+ if (cmp == 0) {
+ bool isTagged;
- if (TkTextIndexCmp(&index1, &index2) >= 0) {
- return TCL_OK;
+ deleteOnLastLine = (flags & DELETE_MARKS) && HaveMarksInRange(&index1, &index2, flags);
+ isTagged = TkTextIndexBackChars(textPtr, &index2, 1, &index3, COUNT_INDICES)
+ && TkBTreeCharTagged(&index3, NULL);
+
+ if (!deleteOnLastLine && !isTagged) {
+ return true; /* there is nothing to delete */
+ }
+ } else if (TkTextIndexIsEndOfText(&index1)) {
+ if (!TkTextIndexBackChars(textPtr, &index2, 1, &index3, COUNT_INDICES)
+ || TkTextIndexIsStartOfText(&index3)
+ || !TkBTreeCharTagged(&index3, NULL)) {
+ return true; /* there is nothing to delete */
+ }
+ deleteOnLastLine = (flags & DELETE_MARKS) && HaveMarksInRange(&index1, &index2, flags);
+ } else {
+ deleteOnLastLine = false;
}
/*
- * The code below is ugly, but it's needed to make sure there is always a
- * dummy empty line at the end of the text. If the final newline of the
- * file (just before the dummy line) is being deleted, then back up index
- * to just before the newline. If there is a newline just before the first
- * character being deleted, then back up the first index too. The idea is
- * that a deletion involving a range starting at a line start and
- * including the final \n (i.e. index2 is "end") is an attempt to delete
- * complete lines, so the \n before the deleted block shall become the new
- * final \n. Furthermore, remove any tags that are present on the newline
- * that isn't going to be deleted after all (this simulates deleting the
- * newline and then adding a "clean" one back again). Note that index1 and
- * index2 might now be equal again which means that no text will be
- * deleted but tags might be removed.
+ * Call the "watch" command for deletion. Take into account that the
+ * receiver might change the text content inside the callback, although
+ * he shouldn't do this.
*/
- line1 = TkBTreeLinesTo(textPtr, index1.linePtr);
- line2 = TkBTreeLinesTo(textPtr, index2.linePtr);
- if (line2 == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) {
- TkTextTag **arrayPtr;
- int arraySize, i;
- TkTextIndex oldIndex2;
+ if (triggerWatchDelete) {
+ Tcl_Obj *delObj = TextGetText(textPtr, &index1, &index2, NULL, NULL, UINT_MAX, false, true);
+ char const *deleted = Tcl_GetString(delObj);
+ bool unchanged;
+ bool rc;
- oldIndex2 = index2;
- TkTextIndexBackChars(NULL, &oldIndex2, 1, &index2, COUNT_INDICES);
- line2--;
- if ((index1.byteIndex == 0) && (line1 != 0)) {
- TkTextIndexBackChars(NULL, &index1, 1, &index1, COUNT_INDICES);
- line1--;
- }
- arrayPtr = TkBTreeGetTags(&index2, NULL, &arraySize);
- if (arrayPtr != NULL) {
- for (i = 0; i < arraySize; i++) {
- TkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0);
- }
- ckfree(arrayPtr);
- }
- }
+ TkTextIndexSave(&index1);
+ TkTextIndexSave(&index2);
+ Tcl_IncrRefCount(delObj);
+ rc = TriggerWatchEdit(textPtr, "delete", &index1, &index2, deleted, final);
+ Tcl_DecrRefCount(delObj);
+ unchanged = TkTextIndexRebuild(&index1) && TkTextIndexRebuild(&index2);
- if (line1 < line2) {
- /*
- * We are deleting more than one line. For speed, we remove all tags
- * from the range first. If we don't do this, the code below can (when
- * there are many tags) grow non-linearly in execution time.
- */
+ if (!rc) { return false; } /* the receiver has destroyed this widget */
- Tcl_HashSearch search;
- Tcl_HashEntry *hPtr;
- int i;
+ if (!unchanged && TkTextIndexCompare(&index1, &index2) >= 0) {
+ /* This can only happen if the receiver of the trigger command did any modification. */
+ return true;
+ }
+ }
- for (i=0, hPtr=Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search);
- hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) {
- TkTextTag *tagPtr = Tcl_GetHashValue(hPtr);
+ if (cmp < 0) {
+ TkTextClearSelection(sharedTextPtr, &index1, &index2);
+ }
- TkBTreeTag(&index1, &index2, tagPtr, 0);
- }
+ altered = (cmp < 0);
+ if (deleteOnLastLine) {
/*
- * Special case for the sel tag which is not in the hash table. We
- * need to do this once for each peer text widget.
+ * Some marks on last line have to be deleted. We are doing this separately,
+ * because we won't delete the last line.
+ *
+ * The alternative is to insert a newly last newline instead, so we can remove
+ * the last line, but this is more complicated than doing this separate removal
+ * (consider undo, or the problem with end markers).
*/
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ;
- tPtr = tPtr->next) {
- if (TkBTreeTag(&index1, &index2, tPtr->selTagPtr, 0)) {
- /*
- * Send an event that the selection changed. This is
- * equivalent to:
- * event generate $textWidget <<Selection>>
- */
-
- TkTextSelectionEvent(textPtr);
- tPtr->abortSelections = 1;
- }
+ if (DeleteMarksOnLastLine(textPtr, TkTextIndexGetSegment(&index1),
+ TkTextIndexGetSegment(&index2), flags)) {
+ altered = true;
}
}
- /*
- * Tell the display what's about to happen so it can discard obsolete
- * display information, then do the deletion. Also, if the deletion
- * involves the top line on the screen, then we have to reset the view
- * (the deletion will invalidate textPtr->topIndex). Compute what the new
- * first character will be, then do the deletion, then reset the view.
- */
-
- TkTextChanged(sharedTextPtr, NULL, &index1, &index2);
-
- resetViewCount = 0;
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- lineAndByteIndex = ckalloc(sizeof(int) * 2 * sharedTextPtr->refCount);
- } else {
- lineAndByteIndex = pixels;
- }
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- int line = 0;
- int byteIndex = 0;
- int resetView = 0;
-
- if (TkTextIndexCmp(&index2, &tPtr->topIndex) >= 0) {
- if (TkTextIndexCmp(&index1, &tPtr->topIndex) <= 0) {
- /*
- * Deletion range straddles topIndex: use the beginning of the
- * range as the new topIndex.
- */
+ if (TkTextIndexIsEndOfText(&index2)) {
+ TkTextIndexGetByteIndex(&index2);
+ index3 = index2;
- resetView = 1;
- line = line1;
- byteIndex = index1.byteIndex;
- } else if (index1.linePtr == tPtr->topIndex.linePtr) {
- /*
- * Deletion range starts on top line but after topIndex. Use
- * the current topIndex as the new one.
- */
+ TkTextIndexBackChars(textPtr, &index2, 1, &index3, COUNT_INDICES);
- resetView = 1;
- line = line1;
- byteIndex = tPtr->topIndex.byteIndex;
- } else {
- /*
- * Deletion range starts after the top line. This peers's view
- * will not need to be reset. Nothing to do.
- */
- }
- } else if (index2.linePtr == tPtr->topIndex.linePtr) {
+ if (!textPtr->endMarker->sectionPtr->linePtr->nextPtr) {
/*
- * Deletion range ends on top line but before topIndex. Figure out
- * what will be the new character index for the character
- * currently pointed to by topIndex.
+ * We're about to delete the very last (empty) newline, and this must not
+ * happen. Instead of deleting the newline we will remove all tags from
+ * this newline character (as if we delete this newline, and afterwards
+ * a fresh newline will be appended).
*/
- resetView = 1;
- line = line2;
- byteIndex = tPtr->topIndex.byteIndex;
- if (index1.linePtr != index2.linePtr) {
- byteIndex -= index2.byteIndex;
- } else {
- byteIndex -= (index2.byteIndex - index1.byteIndex);
+ if (DetectUndoTag(TkTextClearTags(sharedTextPtr, textPtr, &index3, &index2, false))) {
+ altered = true;
}
- } else {
- /*
- * Deletion range ends before the top line. This peers's view
- * will not need to be reset. Nothing to do.
- */
- }
- if (resetView) {
- lineAndByteIndex[resetViewCount] = line;
- lineAndByteIndex[resetViewCount+1] = byteIndex;
- } else {
- lineAndByteIndex[resetViewCount] = -1;
+ assert(altered);
}
- resetViewCount += 2;
+ } else {
+ index3 = index2;
}
- /*
- * Push the deletion on the undo stack if something was actually deleted.
- */
-
- if (TkTextIndexCmp(&index1, &index2) < 0) {
- if (sharedTextPtr->undo) {
- Tcl_Obj *get;
-
- if (sharedTextPtr->autoSeparators
- && (sharedTextPtr->lastEditMode != TK_TEXT_EDIT_DELETE)) {
- TkUndoInsertUndoSeparator(sharedTextPtr->undoStack);
- }
+ if (cmp < 0 && !TkTextIndexIsEqual(&index1, &index3)) {
+ /*
+ * Tell the display what's about to happen, so it can discard obsolete
+ * display information, then do the deletion. Also, if the deletion
+ * involves the top line on the screen, then we have to reset the view
+ * (the deletion will invalidate textPtr->topIndex). Compute what the new
+ * first character will be, then do the deletion, then reset the view.
+ */
- sharedTextPtr->lastEditMode = TK_TEXT_EDIT_DELETE;
+ TkTextChanged(sharedTextPtr, NULL, &index1, &index3);
- get = TextGetText(textPtr, &index1, &index2, 0);
- TextPushUndoAction(textPtr, get, 0, &index1, &index2);
+ if (sharedTextPtr->numPeers > sizeof(textPosBuf)/sizeof(textPosBuf[0])) {
+ textPosition = malloc(sizeof(textPosition[0])*sharedTextPtr->numPeers);
+ } else {
+ textPosition = textPosBuf;
}
- sharedTextPtr->stateEpoch++;
-
- TkBTreeDeleteIndexRange(sharedTextPtr->tree, &index1, &index2);
+ InitPosition(sharedTextPtr, textPosition);
+ FindNewTopPosition(sharedTextPtr, textPosition, &index1, &index2, 0);
- UpdateDirtyFlag(sharedTextPtr);
- }
+ undoInfoPtr = TkTextUndoStackIsFull(sharedTextPtr->undoStack) ? NULL : &undoInfo;
+ TkBTreeDeleteIndexRange(sharedTextPtr, &index1, &index3, flags, undoInfoPtr);
- resetViewCount = 0;
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- int line = lineAndByteIndex[resetViewCount];
+ /*
+ * Push the deletion onto the undo stack, and update the modified status of the widget.
+ * Try to join with previously pushed undo token, if possible.
+ */
- if (line != -1) {
- int byteIndex = lineAndByteIndex[resetViewCount+1];
- TkTextIndex indexTmp;
+ if (undoInfoPtr) {
+ const TkTextUndoSubAtom *subAtom;
- if (tPtr == textPtr) {
- if (viewUpdate) {
- /*
- * line cannot be before -startline of textPtr because
- * this line corresponds to an index which is necessarily
- * between "1.0" and "end" relative to textPtr.
- * Therefore no need to clamp line to the -start/-end
- * range.
- */
+ if (sharedTextPtr->autoSeparators && sharedTextPtr->lastEditMode != TK_TEXT_EDIT_DELETE) {
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoPushSeparator(sharedTextPtr->undoStack, true);
+ sharedTextPtr->lastUndoTokenType = -1;
+ }
- TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, line,
- byteIndex, &indexTmp);
- TkTextSetYView(tPtr, &indexTmp, 0);
+ if (TkTextUndoGetMaxSize(sharedTextPtr->undoStack) == 0
+ || TkTextUndoGetCurrentSize(sharedTextPtr->undoStack) + undoInfo.byteSize
+ <= TkTextUndoGetMaxSize(sharedTextPtr->undoStack)) {
+ if (sharedTextPtr->lastUndoTokenType != TK_TEXT_UNDO_DELETE
+ || !((subAtom = TkTextUndoGetLastUndoSubAtom(sharedTextPtr->undoStack))
+ && TkBTreeJoinUndoDelete(subAtom->item, subAtom->size,
+ undoInfo.token, undoInfo.byteSize))) {
+ TkTextPushUndoToken(sharedTextPtr, undoInfo.token, undoInfo.byteSize);
}
+ sharedTextPtr->lastUndoTokenType = TK_TEXT_UNDO_DELETE;
+ sharedTextPtr->prevUndoStartIndex =
+ ((TkTextUndoTokenRange *) undoInfo.token)->startIndex;
+ sharedTextPtr->prevUndoEndIndex = ((TkTextUndoTokenRange *) undoInfo.token)->endIndex;
+ /* stack has changed anyway, but TkBTreeJoinUndoDelete didn't trigger */
+ sharedTextPtr->undoStackEvent = true;
} else {
- TkTextMakeByteIndex(sharedTextPtr->tree, tPtr, line,
- byteIndex, &indexTmp);
- /*
- * line may be before -startline of tPtr and must be
- * clamped to -startline before providing it to
- * TkTextSetYView otherwise lines before -startline
- * would be displayed.
- * There is no need to worry about -endline however,
- * because the view will only be reset if the deletion
- * involves the TOP line of the screen
- */
+ assert(undoInfo.token->undoType->destroyProc);
+ undoInfo.token->undoType->destroyProc(sharedTextPtr, undoInfo.token, false);
+ free(undoInfo.token);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ }
+
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_DELETE;
+ }
- if (tPtr->start != NULL) {
- int start;
- TkTextIndex indexStart;
+ UpdateModifiedFlag(sharedTextPtr, true);
- start = TkBTreeLinesTo(NULL, tPtr->start);
- TkTextMakeByteIndex(sharedTextPtr->tree, NULL, start,
- 0, &indexStart);
- if (TkTextIndexCmp(&indexTmp, &indexStart) < 0) {
- indexTmp = indexStart;
- }
- }
- TkTextSetYView(tPtr, &indexTmp, 0);
- }
+ SetNewTopPosition(sharedTextPtr, textPtr, textPosition, viewUpdate);
+ if (textPosition != textPosBuf) {
+ free(textPosition);
}
- resetViewCount += 2;
}
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- ckfree(lineAndByteIndex);
+
+ if (altered) {
+ TkTextUpdateAlteredFlag(sharedTextPtr);
}
- if (line1 >= line2) {
- /*
- * Invalidate any selection retrievals in progress, assuming we didn't
- * check for this case above.
- */
+ /*
+ * Lastly, trigger the "watch" command for insertion. This must be the last action,
+ * probably the receiver is calling some widget commands inside the callback.
+ */
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- tPtr->abortSelections = 1;
+ if (triggerWatchInsert) {
+ if (!TriggerWatchEdit(textPtr, "insert", indexPtr1, indexPtr1, NULL, final)) {
+ return false; /* widget has been destroyed */
}
}
- return TCL_OK;
+ return true;
}
/*
@@ -3413,18 +5765,15 @@ DeleteIndexRange(
static int
TextFetchSelection(
ClientData clientData, /* Information about text widget. */
- int offset, /* Offset within selection of first character
- * to be returned. */
+ int offset, /* Offset within selection of first character to be returned. */
char *buffer, /* Location in which to place selection. */
- int maxBytes) /* Maximum number of bytes to place at buffer,
- * not including terminating NULL
- * character. */
+ int maxBytes) /* Maximum number of bytes to place at buffer, not including
+ * terminating NULL character. */
{
- register TkText *textPtr = clientData;
- TkTextIndex eof;
- int count, chunkSize, offsetInSeg;
- TkTextSearch search;
- TkTextSegment *segPtr;
+ TkText *textPtr = clientData;
+ TkTextSearch *searchPtr;
+ Tcl_Obj *selTextPtr;
+ int numBytes;
if (!textPtr->exportSelection) {
return -1;
@@ -3438,94 +5787,110 @@ TextFetchSelection(
*/
if (offset == 0) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &textPtr->selIndex);
- textPtr->abortSelections = 0;
+ TkTextIndexSetupToStartOfText(&textPtr->selIndex, textPtr, textPtr->sharedTextPtr->tree);
+ textPtr->abortSelections = false;
} else if (textPtr->abortSelections) {
return 0;
}
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &eof);
- TkBTreeStartSearch(&textPtr->selIndex, &eof, textPtr->selTagPtr, &search);
- if (!TkBTreeCharTagged(&textPtr->selIndex, textPtr->selTagPtr)) {
- if (!TkBTreeNextTag(&search)) {
- if (offset == 0) {
- return -1;
- } else {
- return 0;
- }
- }
- textPtr->selIndex = search.curIndex;
- }
- /*
- * Each iteration through the outer loop below scans one selected range.
- * Each iteration through the inner loop scans one segment in the selected
- * range.
- */
+ searchPtr = &textPtr->selSearch;
+
+ if (offset == 0 || !TkBTreeCharTagged(&textPtr->selIndex, textPtr->selTagPtr)) {
+ TkTextIndex eof;
+
+ TkTextIndexSetupToEndOfText(&eof, textPtr, textPtr->sharedTextPtr->tree);
+ TkBTreeStartSearch(&textPtr->selIndex, &eof, textPtr->selTagPtr, searchPtr, SEARCH_NEXT_TAGON);
+ if (!TkBTreeNextTag(searchPtr)) {
+ return offset == 0 ? -1 : 0;
+ }
+ textPtr->selIndex = searchPtr->curIndex;
- count = 0;
- while (1) {
/*
* Find the end of the current range of selected text.
*/
- if (!TkBTreeNextTag(&search)) {
- Tcl_Panic("TextFetchSelection couldn't find end of range");
+ if (!TkBTreeNextTag(searchPtr)) {
+ assert(!"TextFetchSelection couldn't find end of range");
}
+ } else {
+ /* we are still inside tagged range */
+ }
- /*
- * Copy information from character segments into the buffer until
- * either we run out of space in the buffer or we get to the end of
- * this range of text.
- */
+ /*
+ * Iterate through the the selected ranges and collect the text content.
+ *
+ * NOTE:
+ * The crux with TextFetchSelection is the old interface of this callback function,
+ * it does not fit with the object design (Tcl_Obj), otherwise it would expect an
+ * object as the result. Thus the actual "natural" implementation is a bit
+ * ineffecient, because we are collecting the data with an object (we are using the
+ * "get" mechanism), and afterwards the content of this object will be copied into
+ * the buffer, and the object will be destroyed. Hopefully some day function
+ * TextFetchSelection will be changed to new object design.
+ */
- while (1) {
- if (maxBytes == 0) {
- goto fetchDone;
- }
- segPtr = TkTextIndexToSeg(&textPtr->selIndex, &offsetInSeg);
- chunkSize = segPtr->size - offsetInSeg;
- if (chunkSize > maxBytes) {
- chunkSize = maxBytes;
- }
- if (textPtr->selIndex.linePtr == search.curIndex.linePtr) {
- int leftInRange;
+ Tcl_IncrRefCount(selTextPtr = Tcl_NewObj());
- leftInRange = search.curIndex.byteIndex
- - textPtr->selIndex.byteIndex;
- if (leftInRange < chunkSize) {
- chunkSize = leftInRange;
- if (chunkSize <= 0) {
- break;
- }
- }
- }
- if ((segPtr->typePtr == &tkTextCharType)
- && !TkTextIsElided(textPtr, &textPtr->selIndex, NULL)) {
- memcpy(buffer, segPtr->body.chars + offsetInSeg,
- (size_t) chunkSize);
- buffer += chunkSize;
- maxBytes -= chunkSize;
- count += chunkSize;
- }
- TkTextIndexForwBytes(textPtr, &textPtr->selIndex, chunkSize,
- &textPtr->selIndex);
+ while (true) {
+ TextGetText(textPtr, &textPtr->selIndex, &searchPtr->curIndex, &textPtr->selIndex,
+ selTextPtr, maxBytes - GetByteLength(selTextPtr), true, false);
+
+ if (GetByteLength(selTextPtr) == maxBytes) {
+ break;
}
/*
* Find the beginning of the next range of selected text.
*/
- if (!TkBTreeNextTag(&search)) {
+ if (!TkBTreeNextTag(searchPtr)) {
break;
}
- textPtr->selIndex = search.curIndex;
+
+ textPtr->selIndex = searchPtr->curIndex;
+
+ /*
+ * Find the end of the current range of selected text.
+ */
+
+ if (!TkBTreeNextTag(searchPtr)) {
+ assert(!"TextFetchSelection couldn't find end of range");
+ }
}
- fetchDone:
- *buffer = 0;
- return count;
+ numBytes = GetByteLength(selTextPtr);
+ memcpy(buffer, Tcl_GetString(selTextPtr), numBytes);
+ Tcl_DecrRefCount(selTextPtr);
+ return numBytes;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextSelectionEvent --
+ *
+ * When anything relevant to the "sel" tag has been changed, call this
+ * function to generate a <<Selection>> event.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If <<Selection>> bindings are present, they will trigger.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextSelectionEvent(
+ TkText *textPtr)
+{
+ /*
+ * Send an event that the selection changed. This is equivalent to:
+ * event generate $textWidget <<Selection>>
+ */
+
+ SendVirtualEvent(textPtr->tkwin, "Selection", NULL);
}
/*
@@ -3551,7 +5916,7 @@ void
TkTextLostSelection(
ClientData clientData) /* Information about text widget. */
{
- register TkText *textPtr = clientData;
+ TkText *textPtr = clientData;
if (TkpAlwaysShowSelection(textPtr->tkwin)) {
TkTextIndex start, end;
@@ -3566,13 +5931,10 @@ TkTextLostSelection(
* "sel" tag from everything in the widget.
*/
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- 0, 0, &start);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr),
- 0, &end);
- TkTextRedrawTag(NULL, textPtr, &start, &end, textPtr->selTagPtr, 1);
- TkBTreeTag(&start, &end, textPtr->selTagPtr, 0);
+ TkTextIndexSetupToStartOfText(&start, textPtr, textPtr->sharedTextPtr->tree);
+ TkTextIndexSetupToEndOfText(&end, textPtr, textPtr->sharedTextPtr->tree);
+ TkBTreeTag(textPtr->sharedTextPtr, textPtr, &start, &end, textPtr->selTagPtr,
+ false, NULL, TkTextRedrawTag);
}
/*
@@ -3588,35 +5950,6 @@ TkTextLostSelection(
/*
*----------------------------------------------------------------------
*
- * TkTextSelectionEvent --
- *
- * When anything relevant to the "sel" tag has been changed, call this
- * function to generate a <<Selection>> event.
- *
- * Results:
- * None.
- *
- * Side effects:
- * If <<Selection>> bindings are present, they will trigger.
- *
- *----------------------------------------------------------------------
- */
-
-void
-TkTextSelectionEvent(
- TkText *textPtr)
-{
- /*
- * Send an event that the selection changed. This is equivalent to:
- * event generate $textWidget <<Selection>>
- */
-
- TkSendVirtualEvent(textPtr->tkwin, "Selection", NULL);
-}
-
-/*
- *----------------------------------------------------------------------
- *
* TextBlinkProc --
*
* This function is called as a timer handler to blink the insertion
@@ -3636,53 +5969,54 @@ static void
TextBlinkProc(
ClientData clientData) /* Pointer to record describing text. */
{
- register TkText *textPtr = clientData;
- TkTextIndex index;
- int x, y, w, h, charWidth;
+ TkText *textPtr = clientData;
+ int oldFlags = textPtr->flags;
- if ((textPtr->state == TK_TEXT_STATE_DISABLED) ||
- !(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
- if (!(textPtr->flags & GOT_FOCUS) &&
- (textPtr->insertUnfocussed != TK_TEXT_INSERT_NOFOCUS_NONE)) {
+ if (textPtr->state == TK_TEXT_STATE_DISABLED
+ || !(textPtr->flags & HAVE_FOCUS)
+ || textPtr->insertOffTime == 0) {
+ if (!(textPtr->flags & HAVE_FOCUS) && textPtr->insertUnfocussed != TK_TEXT_INSERT_NOFOCUS_NONE) {
/*
* The widget doesn't have the focus yet it is configured to
* display the cursor when it doesn't have the focus. Act now!
*/
textPtr->flags |= INSERT_ON;
- goto redrawInsert;
- }
- if ((textPtr->insertOffTime == 0) && !(textPtr->flags & INSERT_ON)) {
+ } else if (textPtr->insertOffTime == 0) {
/*
* The widget was configured to have zero offtime while the
* insertion point was not displayed. We have to display it once.
*/
textPtr->flags |= INSERT_ON;
- goto redrawInsert;
}
- return;
- }
- if (textPtr->flags & INSERT_ON) {
- textPtr->flags &= ~INSERT_ON;
- textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
- textPtr->insertOffTime, TextBlinkProc, textPtr);
} else {
- textPtr->flags |= INSERT_ON;
- textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
- textPtr->insertOnTime, TextBlinkProc, textPtr);
- }
- redrawInsert:
- TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- if (TkTextIndexBbox(textPtr, &index, &x, &y, &w, &h, &charWidth) == 0) {
- if (textPtr->insertCursorType) {
- /* Block cursor */
- TkTextRedrawRegion(textPtr, x - textPtr->width / 2, y,
- charWidth + textPtr->insertWidth / 2, h);
+ if (textPtr->flags & INSERT_ON) {
+ textPtr->flags &= ~INSERT_ON;
+ textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
+ textPtr->insertOffTime, TextBlinkProc, textPtr);
} else {
- /* I-beam cursor */
- TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y,
- textPtr->insertWidth, h);
+ textPtr->flags |= INSERT_ON;
+ textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
+ textPtr->insertOnTime, TextBlinkProc, textPtr);
+ }
+ }
+
+ if (oldFlags != textPtr->flags) {
+ TkTextIndex index;
+ int x, y, w, h, charWidth;
+
+ TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
+
+ if (TkTextIndexBbox(textPtr, &index, &x, &y, &w, &h, &charWidth) == 0) {
+ if (textPtr->blockCursorType) { /* Block cursor */
+ x -= textPtr->width/2;
+ w = charWidth + textPtr->insertWidth/2;
+ } else { /* I-beam cursor */
+ x -= textPtr->insertWidth/2;
+ w = textPtr->insertWidth;
+ }
+ TkTextRedrawRegion(textPtr, x, y, w, h);
}
}
}
@@ -3709,65 +6043,140 @@ TextBlinkProc(
static int
TextInsertCmd(
- TkSharedText *sharedTextPtr,/* Shared portion of peer widgets. */
TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[], /* Argument objects. */
const TkTextIndex *indexPtr,/* Index at which to insert. */
- int viewUpdate) /* Update the view if set. */
+ bool viewUpdate, /* Update the view if set. */
+ bool triggerWatchDelete, /* Should we trigger the watch command for deletion? */
+ bool triggerWatchInsert, /* Should we trigger the watch command for insertion? */
+ bool userFlag, /* Trigger user modification? */
+ bool *destroyed, /* Store whether the widget has been destroyed. */
+ bool parseHyphens) /* Should we parse hyphens? (tk_textInsert) */
{
TkTextIndex index1, index2;
+ TkSharedText *sharedTextPtr;
+ TkTextTag *hyphenTagPtr = NULL;
+ int rc = TCL_OK;
int j;
- if (sharedTextPtr == NULL) {
- sharedTextPtr = textPtr->sharedTextPtr;
+ assert(textPtr);
+ assert(destroyed);
+ assert(!TkTextIsDeadPeer(textPtr));
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ *destroyed = false;
+
+ if (parseHyphens && objc > 1 && *Tcl_GetString(objv[0]) == '-') {
+ int argc;
+ Tcl_Obj **argv;
+
+ if (strcmp(Tcl_GetString(objv[0]), "-hyphentags") != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -hyphentags", Tcl_GetString(objv[0])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
+ return TCL_ERROR;
+ }
+ if (Tcl_ListObjGetElements(interp, objv[1], &argc, &argv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ for (j = 0; j < argc; ++j) {
+ TkTextTag *tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(argv[j]), NULL);
+ tagPtr->nextPtr = hyphenTagPtr;
+ hyphenTagPtr = tagPtr;
+ }
+ objc -= 2;
+ objv += 2;
}
+ for (j = 0; j < objc && GetByteLength(objv[j]) == 0; j += 2) {
+ /* empty loop body */
+ }
index1 = *indexPtr;
- for (j = 0; j < objc; j += 2) {
- /*
- * Here we rely on this call to modify index1 if it is outside the
- * acceptable range. In particular, if index1 is "end", it must be set
- * to the last allowable index for insertion, otherwise subsequent tag
- * insertions will fail.
- */
- int length = InsertChars(sharedTextPtr, textPtr, &index1, objv[j],
- viewUpdate);
+ while (j < objc) {
+ Tcl_Obj *stringPtr = objv[j];
+ Tcl_Obj *tagPtr = (j + 1 < objc) ? objv[j + 1] : NULL;
+ char const *string = Tcl_GetString(stringPtr);
+ unsigned length = GetByteLength(stringPtr);
+ int k = j + 2;
+ bool final;
+
+ while (k < objc && GetByteLength(objv[k]) == 0) {
+ k += 2;
+ }
+ final = objc <= k;
- if (objc > (j+1)) {
- Tcl_Obj **tagNamePtrs;
- TkTextTag **oldTagArrayPtr;
- int numTags;
+ if (length > 0) {
+ int numTags = 0;
+ Tcl_Obj **tagNamePtrs = NULL;
+ TkTextTagSet *tagInfoPtr = NULL;
- TkTextIndexForwBytes(textPtr, &index1, length, &index2);
- oldTagArrayPtr = TkBTreeGetTags(&index1, NULL, &numTags);
- if (oldTagArrayPtr != NULL) {
- int i;
+ /*
+ * Call the "watch" command for deletion. Take into account that the
+ * receiver might change the text content, although he shouldn't do this.
+ */
- for (i = 0; i < numTags; i++) {
- TkBTreeTag(&index1, &index2, oldTagArrayPtr[i], 0);
+ if (triggerWatchDelete) {
+ TkTextIndexSave(&index1);
+ if (!TriggerWatchEdit(textPtr, "delete", &index1, &index1, NULL, final)) {
+ *destroyed = true;
+ return rc;
}
- ckfree(oldTagArrayPtr);
+ TkTextIndexRebuild(&index1);
}
- if (Tcl_ListObjGetElements(interp, objv[j+1], &numTags,
- &tagNamePtrs) != TCL_OK) {
- return TCL_ERROR;
- } else {
- int i;
- for (i = 0; i < numTags; i++) {
- const char *strTag = Tcl_GetString(tagNamePtrs[i]);
+ if (tagPtr) {
+ unsigned i;
+
+ if (Tcl_ListObjGetElements(interp, tagPtr, &numTags, &tagNamePtrs) != TCL_OK) {
+ rc = TCL_ERROR;
+ } else if (numTags > 0) {
+ TkTextTag *tagPtr;
+
+ tagInfoPtr = TkTextTagSetResize(NULL, sharedTextPtr->tagInfoSize);
+
+ for (i = 0; i < numTags; ++i) {
+ tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(tagNamePtrs[i]), NULL);
+#if !TK_TEXT_DONT_USE_BITFIELDS
+ if (tagPtr->index >= TkTextTagSetSize(tagInfoPtr)) {
+ tagInfoPtr = TkTextTagSetResize(NULL, sharedTextPtr->tagInfoSize);
+ }
+#endif
+ tagInfoPtr = TkTextTagSetAdd(tagInfoPtr, tagPtr->index);
+ }
+ }
+ }
+
+ InsertChars(textPtr, &index1, &index2, string, length,
+ viewUpdate, tagInfoPtr, hyphenTagPtr, parseHyphens);
+ if (tagInfoPtr) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ }
+
+ /*
+ * Lastly, trigger the "watch" command for insertion. This must be the last action,
+ * probably the receiver is calling some widget commands inside the callback.
+ */
- TkBTreeTag(&index1, &index2,
- TkTextCreateTag(textPtr, strTag, NULL), 1);
+ if (triggerWatchInsert) {
+ if (!TriggerWatchEdit(textPtr, "insert", &index1, &index2, string, final)) {
+ *destroyed = true;
+ return rc;
}
- index1 = index2;
}
+
+ if (rc != TCL_OK) {
+ return rc;
+ }
+ index1 = index2;
}
+
+ j = k;
}
- return TCL_OK;
+
+ return rc;
}
/*
@@ -3799,14 +6208,15 @@ TextSearchCmd(
static const char *const switchStrings[] = {
"-hidden",
- "--", "-all", "-backwards", "-count", "-elide", "-exact", "-forwards",
- "-nocase", "-nolinestop", "-overlap", "-regexp", "-strictlimits", NULL
+ "--", "-all", "-backwards", "-count", "-discardhyphens", "-elide",
+ "-exact", "-forwards", "-nocase", "-nolinestop", "-overlap", "-regexp",
+ "-strictlimits", NULL
};
enum SearchSwitches {
SEARCH_HIDDEN,
- SEARCH_END, SEARCH_ALL, SEARCH_BACK, SEARCH_COUNT, SEARCH_ELIDE,
- SEARCH_EXACT, SEARCH_FWD, SEARCH_NOCASE,
- SEARCH_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP, SEARCH_STRICTLIMITS
+ SEARCH_END, SEARCH_ALL, SEARCH_BACK, SEARCH_COUNT, SEARCH_DISCARDHYPHENS, SEARCH_ELIDE,
+ SEARCH_EXACT, SEARCH_FWD, SEARCH_NOCASE, SEARCH_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP,
+ SEARCH_STRICTLIMITS
};
/*
@@ -3814,19 +6224,20 @@ TextSearchCmd(
* text widget specific.
*/
- searchSpec.exact = 1;
- searchSpec.noCase = 0;
- searchSpec.all = 0;
- searchSpec.backwards = 0;
+ searchSpec.textPtr = textPtr;
+ searchSpec.exact = true;
+ searchSpec.noCase = false;
+ searchSpec.all = false;
+ searchSpec.backwards = false;
searchSpec.varPtr = NULL;
searchSpec.countPtr = NULL;
searchSpec.resPtr = NULL;
- searchSpec.searchElide = 0;
- searchSpec.noLineStop = 0;
- searchSpec.overlap = 0;
- searchSpec.strictLimits = 0;
- searchSpec.numLines =
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
+ searchSpec.searchElide = false;
+ searchSpec.searchHyphens = true;
+ searchSpec.noLineStop = false;
+ searchSpec.overlap = false;
+ searchSpec.strictLimits = false;
+ searchSpec.numLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
searchSpec.clientData = textPtr;
searchSpec.addLineProc = &TextSearchAddNextLine;
searchSpec.foundMatchProc = &TextSearchFoundMatch;
@@ -3836,7 +6247,7 @@ TextSearchCmd(
* Parse switches and other arguments.
*/
- for (i=2 ; i<objc ; i++) {
+ for (i = 2; i < objc; ++i) {
int index;
if (Tcl_GetString(objv[i])[0] != '-') {
@@ -3850,29 +6261,28 @@ TextSearchCmd(
* the side effects of T_GIFO.
*/
- (void) Tcl_GetIndexFromObjStruct(interp, objv[i], switchStrings+1,
+ (void) Tcl_GetIndexFromObjStruct(interp, objv[i], switchStrings + 1,
sizeof(char *), "switch", 0, &index);
return TCL_ERROR;
}
switch ((enum SearchSwitches) index) {
case SEARCH_END:
- i++;
+ i += 1;
goto endOfSwitchProcessing;
case SEARCH_ALL:
- searchSpec.all = 1;
+ searchSpec.all = true;
break;
case SEARCH_BACK:
- searchSpec.backwards = 1;
+ searchSpec.backwards = true;
break;
case SEARCH_COUNT:
- if (i >= objc-1) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "no value given for \"-count\" option", -1));
+ if (i >= objc - 1) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("no value given for \"-count\" option", -1));
Tcl_SetErrorCode(interp, "TK", "TEXT", "VALUE", NULL);
return TCL_ERROR;
}
- i++;
+ i += 1;
/*
* Assumption objv[i] isn't going to disappear on us during this
@@ -3881,56 +6291,56 @@ TextSearchCmd(
searchSpec.varPtr = objv[i];
break;
+ case SEARCH_DISCARDHYPHENS:
+ searchSpec.searchHyphens = false;
+ break;
case SEARCH_ELIDE:
case SEARCH_HIDDEN:
- searchSpec.searchElide = 1;
+ searchSpec.searchElide = true;
break;
case SEARCH_EXACT:
- searchSpec.exact = 1;
+ searchSpec.exact = true;
break;
case SEARCH_FWD:
- searchSpec.backwards = 0;
+ searchSpec.backwards = false;
break;
case SEARCH_NOCASE:
- searchSpec.noCase = 1;
+ searchSpec.noCase = true;
break;
case SEARCH_NOLINESTOP:
- searchSpec.noLineStop = 1;
+ searchSpec.noLineStop = true;
break;
case SEARCH_OVERLAP:
- searchSpec.overlap = 1;
+ searchSpec.overlap = true;
break;
case SEARCH_STRICTLIMITS:
- searchSpec.strictLimits = 1;
+ searchSpec.strictLimits = true;
break;
case SEARCH_REGEXP:
- searchSpec.exact = 0;
+ searchSpec.exact = false;
break;
default:
- Tcl_Panic("unexpected switch fallthrough");
+ assert(!"unexpected switch fallthrough");
}
}
endOfSwitchProcessing:
- argsLeft = objc - (i+2);
- if ((argsLeft != 0) && (argsLeft != 1)) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "?switches? pattern index ?stopIndex?");
+ argsLeft = objc - (i + 2);
+ if (argsLeft != 0 && argsLeft != 1) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?switches? pattern index ?stopIndex?");
return TCL_ERROR;
}
if (searchSpec.noLineStop && searchSpec.exact) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "the \"-nolinestop\" option requires the \"-regexp\" option"
- " to be present", -1));
+ "the \"-nolinestop\" option requires the \"-regexp\" option to be present", -1));
Tcl_SetErrorCode(interp, "TK", "TEXT", "SEARCH_USAGE", NULL);
return TCL_ERROR;
}
if (searchSpec.overlap && !searchSpec.all) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "the \"-overlap\" option requires the \"-all\" option"
- " to be present", -1));
+ "the \"-overlap\" option requires the \"-all\" option to be present", -1));
Tcl_SetErrorCode(interp, "TK", "TEXT", "SEARCH_USAGE", NULL);
return TCL_ERROR;
}
@@ -3941,8 +6351,7 @@ TextSearchCmd(
* regexp pattern depending on the flags set above.
*/
- code = SearchPerform(interp, &searchSpec, objv[i], objv[i+1],
- (argsLeft == 1 ? objv[i+2] : NULL));
+ code = SearchPerform(interp, &searchSpec, objv[i], objv[i + 1], argsLeft == 1 ? objv[i + 2] : NULL);
if (code != TCL_OK) {
goto cleanup;
}
@@ -3951,10 +6360,9 @@ TextSearchCmd(
* Set the '-count' variable, if given.
*/
- if (searchSpec.varPtr != NULL && searchSpec.countPtr != NULL) {
+ if (searchSpec.varPtr && searchSpec.countPtr) {
Tcl_IncrRefCount(searchSpec.countPtr);
- if (Tcl_ObjSetVar2(interp, searchSpec.varPtr, NULL,
- searchSpec.countPtr, TCL_LEAVE_ERR_MSG) == NULL) {
+ if (!Tcl_ObjSetVar2(interp, searchSpec.varPtr, NULL, searchSpec.countPtr, TCL_LEAVE_ERR_MSG)) {
code = TCL_ERROR;
goto cleanup;
}
@@ -3964,16 +6372,16 @@ TextSearchCmd(
* Set the result.
*/
- if (searchSpec.resPtr != NULL) {
+ if (searchSpec.resPtr) {
Tcl_SetObjResult(interp, searchSpec.resPtr);
searchSpec.resPtr = NULL;
}
cleanup:
- if (searchSpec.countPtr != NULL) {
+ if (searchSpec.countPtr) {
Tcl_DecrRefCount(searchSpec.countPtr);
}
- if (searchSpec.resPtr != NULL) {
+ if (searchSpec.resPtr) {
Tcl_DecrRefCount(searchSpec.resPtr);
}
return code;
@@ -3984,7 +6392,7 @@ TextSearchCmd(
*
* TextSearchGetLineIndex --
*
- * Extract a row, text offset index position from an objPtr
+ * Extract a row, text offset index position from an objPtr.
*
* This means we ignore any embedded windows/images and elidden text
* (unless we are searching that).
@@ -4011,40 +6419,35 @@ TextSearchGetLineIndex(
Tcl_Obj *objPtr, /* Contains a textual index like "1.2" */
SearchSpec *searchSpecPtr, /* Contains other search parameters. */
int *linePosPtr, /* For returning the line number. */
- int *offsetPosPtr) /* For returning the text offset in the
- * line. */
+ int *offsetPosPtr) /* For returning the text offset in the line. */
{
- const TkTextIndex *indexPtr;
- int line;
+ TkTextIndex index;
+ int line, byteIndex;
TkText *textPtr = searchSpecPtr->clientData;
+ TkTextLine *linePtr;
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objPtr);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objPtr, &index)) {
return TCL_ERROR;
}
- line = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
+ assert(textPtr);
+ line = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, TkTextIndexGetLine(&index), NULL);
if (line >= searchSpecPtr->numLines) {
- TkTextLine *linePtr;
- int count = 0;
- TkTextSegment *segPtr;
-
- line = searchSpecPtr->numLines-1;
+ line = searchSpecPtr->numLines - 1;
linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, line);
-
- /*
- * Count the number of bytes in this line.
- */
-
- for (segPtr=linePtr->segPtr ; segPtr!=NULL ; segPtr=segPtr->nextPtr) {
- count += segPtr->size;
+ assert(linePtr); /* this may only fail with dead peers */
+ if (textPtr->endMarker == textPtr->sharedTextPtr->endMarker
+ || textPtr->endMarker->sectionPtr->linePtr != TkTextIndexGetLine(&index)) {
+ byteIndex = linePtr->size;
+ } else {
+ byteIndex = TkTextSegToIndex(textPtr->endMarker);
}
- *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, linePtr, count);
} else {
- *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr,
- indexPtr->linePtr, indexPtr->byteIndex);
+ linePtr = TkTextIndexGetLine(&index);
+ byteIndex = TkTextIndexGetByteIndex(&index);
}
+ *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, linePtr, byteIndex);
*linePosPtr = line;
return TCL_OK;
@@ -4071,7 +6474,15 @@ TextSearchGetLineIndex(
*----------------------------------------------------------------------
*/
-static int
+static unsigned
+CountCharsInSeg(
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr->typePtr == &tkTextCharType);
+ return Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
+}
+
+static unsigned
TextSearchIndexInLine(
const SearchSpec *searchSpecPtr,
/* Search parameters. */
@@ -4079,33 +6490,40 @@ TextSearchIndexInLine(
int byteIndex) /* Index into the line. */
{
TkTextSegment *segPtr;
- TkTextIndex curIndex;
- int index, leftToScan;
+ int leftToScan;
+ unsigned index = 0;
TkText *textPtr = searchSpecPtr->clientData;
+ TkTextLine *startLinePtr = textPtr->startMarker->sectionPtr->linePtr;
+ bool isCharSeg;
index = 0;
- curIndex.tree = textPtr->sharedTextPtr->tree;
- curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
- for (segPtr = linePtr->segPtr, leftToScan = byteIndex;
- leftToScan > 0;
- curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) {
- if ((segPtr->typePtr == &tkTextCharType) &&
- (searchSpecPtr->searchElide
- || !TkTextIsElided(textPtr, &curIndex, NULL))) {
- if (leftToScan < segPtr->size) {
- if (searchSpecPtr->exact) {
- index += leftToScan;
+ segPtr = (startLinePtr == linePtr) ? textPtr->startMarker : linePtr->segPtr;
+
+ /*
+ * TODO: Use new elide structure, but this requires a redesign of the whole
+ * search algorithm.
+ */
+
+ for (leftToScan = byteIndex; leftToScan > 0; segPtr = segPtr->nextPtr) {
+ if ((isCharSeg = segPtr->typePtr == &tkTextCharType)
+ || (searchSpecPtr->searchHyphens && segPtr->typePtr == &tkTextHyphenType)) {
+ if (searchSpecPtr->searchElide || !TkTextSegmentIsElided(textPtr, segPtr)) {
+ if (leftToScan < segPtr->size) {
+ if (searchSpecPtr->exact) {
+ index += leftToScan;
+ } else {
+ index += isCharSeg ? Tcl_NumUtfChars(segPtr->body.chars, leftToScan) : 1;
+ }
+ } else if (searchSpecPtr->exact) {
+ index += isCharSeg ? segPtr->size : 2;
} else {
- index += Tcl_NumUtfChars(segPtr->body.chars, leftToScan);
+ index += isCharSeg ? CountCharsInSeg(segPtr) : 1;
}
- } else if (searchSpecPtr->exact) {
- index += segPtr->size;
- } else {
- index += Tcl_NumUtfChars(segPtr->body.chars, -1);
}
}
leftToScan -= segPtr->size;
}
+
return index;
}
@@ -4149,65 +6567,71 @@ TextSearchAddNextLine(
* one by newlines being elided. */
{
TkTextLine *linePtr, *thisLinePtr;
- TkTextIndex curIndex;
- TkTextSegment *segPtr;
+ TkTextSegment *segPtr, *lastPtr;
TkText *textPtr = searchSpecPtr->clientData;
- int nothingYet = 1;
+ TkTextLine *startLinePtr = textPtr->startMarker->sectionPtr->linePtr;
+ TkTextLine *endLinePtr = textPtr->endMarker->sectionPtr->linePtr;
+ bool nothingYet = true;
/*
* Extract the text from the line.
*/
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
- if (linePtr == NULL) {
+ if (!(linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum))) {
return NULL;
}
- curIndex.tree = textPtr->sharedTextPtr->tree;
thisLinePtr = linePtr;
- while (thisLinePtr != NULL) {
- int elideWraps = 0;
+ while (thisLinePtr) {
+ bool elideWraps = false;
- curIndex.linePtr = thisLinePtr;
- curIndex.byteIndex = 0;
- for (segPtr = thisLinePtr->segPtr; segPtr != NULL;
- curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) {
- if (!searchSpecPtr->searchElide
- && TkTextIsElided(textPtr, &curIndex, NULL)) {
- /*
- * If we reach the end of the logical line, and if we have at
- * least one character in the string, then we continue
- * wrapping to the next logical line. If there are no
- * characters yet, then the entire line of characters is
- * elided and there's no need to complicate matters by
- * wrapping - we'll look at the next line in due course.
- */
+ segPtr = (startLinePtr == thisLinePtr) ? textPtr->startMarker : thisLinePtr->segPtr;
+ lastPtr = (endLinePtr == thisLinePtr) ? textPtr->endMarker : NULL;
+
+ /*
+ * TODO: Use new elide structure, but this requires a redesign of the whole
+ * search algorithm.
+ */
+
+ for ( ; segPtr != lastPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr == &tkTextCharType
+ || (searchSpecPtr->searchHyphens && segPtr->typePtr == &tkTextHyphenType)) {
+ if (!searchSpecPtr->searchElide && TkTextSegmentIsElided(textPtr, segPtr)) {
+ /*
+ * If we reach the end of the logical line, and if we have at
+ * least one character in the string, then we continue
+ * wrapping to the next logical line. If there are no
+ * characters yet, then the entire line of characters is
+ * elided and there's no need to complicate matters by
+ * wrapping - we'll look at the next line in due course.
+ */
- if (segPtr->nextPtr == NULL && !nothingYet) {
- elideWraps = 1;
+ if (!segPtr->nextPtr && !nothingYet) {
+ elideWraps = true;
+ }
+ } else if (segPtr->typePtr == &tkTextCharType) {
+ Tcl_AppendToObj(theLine, segPtr->body.chars, segPtr->size);
+ nothingYet = false;
+ } else {
+ Tcl_AppendToObj(theLine, "\xc2\xad", 2); /* U+002D */
+ nothingYet = false;
}
- continue;
}
- if (segPtr->typePtr != &tkTextCharType) {
- continue;
- }
- Tcl_AppendToObj(theLine, segPtr->body.chars, segPtr->size);
- nothingYet = 0;
}
if (!elideWraps) {
break;
}
- lineNum++;
+ lineNum += 1;
if (lineNum >= searchSpecPtr->numLines) {
break;
}
thisLinePtr = TkBTreeNextLine(textPtr, thisLinePtr);
- if (thisLinePtr != NULL && extraLinesPtr != NULL) {
+ if (thisLinePtr && extraLinesPtr) {
/*
* Tell our caller we have an extra line merged in.
*/
- *extraLinesPtr = (*extraLinesPtr) + 1;
+ *extraLinesPtr = *extraLinesPtr + 1;
}
}
@@ -4221,13 +6645,8 @@ TextSearchAddNextLine(
Tcl_SetObjLength(theLine, Tcl_UtfToLower(Tcl_GetString(theLine)));
}
- if (lenPtr != NULL) {
- if (searchSpecPtr->exact) {
- (void)Tcl_GetString(theLine);
- *lenPtr = theLine->length;
- } else {
- *lenPtr = Tcl_GetCharLength(theLine);
- }
+ if (lenPtr) {
+ *lenPtr = searchSpecPtr->exact ? GetByteLength(theLine) : Tcl_GetCharLength(theLine);
}
return linePtr;
}
@@ -4240,9 +6659,9 @@ TextSearchAddNextLine(
* Stores information from a successful search.
*
* Results:
- * 1 if the information was stored, 0 if the position at which the match
- * was found actually falls outside the allowable search region (and
- * therefore the search is actually complete).
+ * 'true' if the information was stored, 'false' if the position at
+ * which the match was found actually falls outside the allowable
+ * search region (and therefore the search is actually complete).
*
* Side effects:
* Memory may be allocated in the 'countPtr' and 'resPtr' fields of
@@ -4252,28 +6671,25 @@ TextSearchAddNextLine(
*----------------------------------------------------------------------
*/
-static int
+static bool
TextSearchFoundMatch(
int lineNum, /* Line on which match was found. */
SearchSpec *searchSpecPtr, /* Search parameters. */
- ClientData clientData, /* Token returned by the 'addNextLineProc',
- * TextSearchAddNextLine. May be NULL, in
- * which we case we must generate it (from
- * lineNum). */
- Tcl_Obj *theLine, /* Text from current line, only accessed for
- * exact searches, and is allowed to be NULL
- * for regexp searches. */
- int matchOffset, /* Offset of found item in utf-8 bytes for
- * exact search, Unicode chars for regexp. */
- int matchLength) /* Length also in bytes/chars as per search
- * type. */
+ ClientData clientData, /* Token returned by the 'addNextLineProc', TextSearchAddNextLine.
+ * May be NULL, in which we case we must generate it (from lineNum). */
+ Tcl_Obj *theLine, /* Text from current line, only accessed for exact searches, and
+ * is allowed to be NULL for regexp searches. */
+ int matchOffset, /* Offset of found item in utf-8 bytes for exact search, Unicode
+ * chars for regexp. */
+ int matchLength) /* Length also in bytes/chars as per search type. */
{
int numChars;
int leftToScan;
- TkTextIndex curIndex, foundIndex;
+ TkTextIndex foundIndex;
TkTextSegment *segPtr;
- TkTextLine *linePtr;
+ TkTextLine *linePtr, *startLinePtr;
TkText *textPtr = searchSpecPtr->clientData;
+ int byteIndex;
if (lineNum == searchSpecPtr->stopLine) {
/*
@@ -4282,9 +6698,8 @@ TextSearchFoundMatch(
* and the search is over.
*/
- if (searchSpecPtr->backwards ^
- (matchOffset >= searchSpecPtr->stopOffset)) {
- return 0;
+ if (searchSpecPtr->backwards ^ (matchOffset >= searchSpecPtr->stopOffset)) {
+ return false;
}
}
@@ -4294,9 +6709,7 @@ TextSearchFoundMatch(
*/
if (searchSpecPtr->exact) {
- const char *startOfLine = Tcl_GetString(theLine);
-
- numChars = Tcl_NumUtfChars(startOfLine + matchOffset, matchLength);
+ numChars = Tcl_NumUtfChars(Tcl_GetString(theLine) + matchOffset, matchLength);
} else {
numChars = matchLength;
}
@@ -4307,9 +6720,8 @@ TextSearchFoundMatch(
*/
if (searchSpecPtr->strictLimits && lineNum == searchSpecPtr->stopLine) {
- if (searchSpecPtr->backwards ^
- ((matchOffset + numChars) > searchSpecPtr->stopOffset)) {
- return 0;
+ if (searchSpecPtr->backwards ^ (matchOffset + numChars > searchSpecPtr->stopOffset)) {
+ return false;
}
}
@@ -4325,76 +6737,70 @@ TextSearchFoundMatch(
*/
linePtr = clientData;
- if (linePtr == NULL) {
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
- lineNum);
+ if (!linePtr) {
+ linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
}
-
- curIndex.tree = textPtr->sharedTextPtr->tree;
+ startLinePtr = textPtr->startMarker->sectionPtr->linePtr;
/*
* Find the starting point.
*/
leftToScan = matchOffset;
- while (1) {
- curIndex.linePtr = linePtr;
- curIndex.byteIndex = 0;
-
+ while (true) {
/*
* Note that we allow leftToScan to be zero because we want to skip
* over any preceding non-textual items.
*/
- for (segPtr = linePtr->segPtr; leftToScan >= 0 && segPtr;
- segPtr = segPtr->nextPtr) {
- if (segPtr->typePtr != &tkTextCharType) {
- matchOffset += segPtr->size;
- } else if (!searchSpecPtr->searchElide
- && TkTextIsElided(textPtr, &curIndex, NULL)) {
- if (searchSpecPtr->exact) {
- matchOffset += segPtr->size;
- } else {
- matchOffset += Tcl_NumUtfChars(segPtr->body.chars, -1);
- }
- } else {
- if (searchSpecPtr->exact) {
- leftToScan -= segPtr->size;
+ segPtr = (linePtr == startLinePtr) ? textPtr->startMarker : linePtr->segPtr;
+ byteIndex = TkTextSegToIndex(segPtr);
+
+ /*
+ * TODO: Use new elide structure, but this requires a redesign of the whole
+ * search algorithm.
+ */
+
+ for ( ; leftToScan >= 0 && segPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ int size = searchSpecPtr->exact ? segPtr->size : CountCharsInSeg(segPtr);
+
+ if (!searchSpecPtr->searchElide && TkTextSegmentIsElided(textPtr, segPtr)) {
+ matchOffset += size;
} else {
- leftToScan -= Tcl_NumUtfChars(segPtr->body.chars, -1);
+ leftToScan -= size;
}
- }
- curIndex.byteIndex += segPtr->size;
- }
- if (segPtr == NULL && leftToScan >= 0) {
- /*
- * This will only happen if we are eliding newlines.
- */
-
- linePtr = TkBTreeNextLine(textPtr, linePtr);
- if (linePtr == NULL) {
- /*
- * If we reach the end of the text, we have a serious problem,
- * unless there's actually nothing left to look for.
- */
+ } else if (searchSpecPtr->searchHyphens && segPtr->typePtr == &tkTextHyphenType) {
+ int size = searchSpecPtr->exact ? 2 : 1;
- if (leftToScan == 0) {
- break;
+ if (!searchSpecPtr->searchElide && TkTextSegmentIsElided(textPtr, segPtr)) {
+ matchOffset += size;
} else {
- Tcl_Panic("Reached end of text in a match");
+ leftToScan -= size;
}
+ } else {
+ assert(segPtr->size <= 1);
+ matchOffset += segPtr->size;
}
+ byteIndex += segPtr->size;
+ }
- /*
- * We've wrapped to the beginning of the next logical line, which
- * has been merged with the previous one whose newline was elided.
- */
+ assert(!segPtr || leftToScan < 0 || TkBTreeNextLine(textPtr, linePtr));
- lineNum++;
- matchOffset = 0;
- } else {
+ if (segPtr || leftToScan < 0) {
break;
}
+
+ /*
+ * This will only happen if we are eliding newlines.
+ *
+ * We've wrapped to the beginning of the next logical line, which
+ * has been merged with the previous one whose newline was elided.
+ */
+
+ linePtr = linePtr->nextPtr;
+ lineNum += 1;
+ matchOffset = 0;
}
/*
@@ -4402,21 +6808,18 @@ TextSearchFoundMatch(
*/
if (searchSpecPtr->exact) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum,
- matchOffset, &foundIndex);
+ TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, matchOffset, &foundIndex);
} else {
- TkTextMakeCharIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum,
- matchOffset, &foundIndex);
+ TkTextMakeCharIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, matchOffset, &foundIndex);
}
if (searchSpecPtr->all) {
- if (searchSpecPtr->resPtr == NULL) {
+ if (!searchSpecPtr->resPtr) {
searchSpecPtr->resPtr = Tcl_NewObj();
}
- Tcl_ListObjAppendElement(NULL, searchSpecPtr->resPtr,
- TkTextNewIndexObj(textPtr, &foundIndex));
+ Tcl_ListObjAppendElement(NULL, searchSpecPtr->resPtr, TkTextNewIndexObj(&foundIndex));
} else {
- searchSpecPtr->resPtr = TkTextNewIndexObj(textPtr, &foundIndex);
+ searchSpecPtr->resPtr = TkTextNewIndexObj(&foundIndex);
}
/*
@@ -4425,34 +6828,41 @@ TextSearchFoundMatch(
* the string. When we add matchLength it will become non-negative.
*/
- for (leftToScan += matchLength; leftToScan > 0;
- curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) {
- if (segPtr == NULL) {
+ /*
+ * TODO: Use new elide structure, but this requires a redesign of the whole
+ * search algorithm.
+ */
+
+ for (leftToScan += matchLength; leftToScan > 0; segPtr = segPtr->nextPtr) {
+ bool isCharSeg;
+
+ if (!segPtr) {
/*
* We are on the next line - this of course should only ever
* happen with searches which have matched across multiple lines.
*/
- linePtr = TkBTreeNextLine(textPtr, linePtr);
+ assert(TkBTreeNextLine(textPtr, linePtr));
+ linePtr = linePtr->nextPtr;
segPtr = linePtr->segPtr;
- curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
+ byteIndex = 0;
}
- if (segPtr->typePtr != &tkTextCharType) {
+
+ isCharSeg = (segPtr->typePtr == &tkTextCharType);
+
+ if (!isCharSeg && (!searchSpecPtr->searchHyphens || segPtr->typePtr != &tkTextHyphenType)) {
/*
* Anything we didn't count in the search needs adding.
*/
+ assert(segPtr->size <= 1);
numChars += segPtr->size;
- continue;
- } else if (!searchSpecPtr->searchElide
- && TkTextIsElided(textPtr, &curIndex, NULL)) {
- numChars += Tcl_NumUtfChars(segPtr->body.chars, -1);
- continue;
- }
- if (searchSpecPtr->exact) {
- leftToScan -= segPtr->size;
+ } else if (!searchSpecPtr->searchElide && TkTextSegmentIsElided(textPtr, segPtr)) {
+ numChars += isCharSeg ? CountCharsInSeg(segPtr) : 1;
+ } else if (searchSpecPtr->exact) {
+ leftToScan -= isCharSeg ? segPtr->size : 2;
} else {
- leftToScan -= Tcl_NumUtfChars(segPtr->body.chars, -1);
+ leftToScan -= isCharSeg ? CountCharsInSeg(segPtr) : 1;
}
}
@@ -4460,10 +6870,10 @@ TextSearchFoundMatch(
* Now store the count result, if it is wanted.
*/
- if (searchSpecPtr->varPtr != NULL) {
+ if (searchSpecPtr->varPtr) {
Tcl_Obj *tmpPtr = Tcl_NewIntObj(numChars);
if (searchSpecPtr->all) {
- if (searchSpecPtr->countPtr == NULL) {
+ if (!searchSpecPtr->countPtr) {
searchSpecPtr->countPtr = Tcl_NewObj();
}
Tcl_ListObjAppendElement(NULL, searchSpecPtr->countPtr, tmpPtr);
@@ -4471,7 +6881,8 @@ TextSearchFoundMatch(
searchSpecPtr->countPtr = tmpPtr;
}
}
- return 1;
+
+ return true;
}
/*
@@ -4525,8 +6936,8 @@ TkTextGetTabs(
for (i = 0; i < objc; i++) {
char c = Tcl_GetString(objv[i])[0];
- if ((c != 'l') && (c != 'r') && (c != 'c') && (c != 'n')) {
- count++;
+ if (c != 'l' && c != 'r' && c != 'c' && c != 'n') {
+ count += 1;
}
}
@@ -4534,8 +6945,7 @@ TkTextGetTabs(
* Parse the elements of the list one at a time to fill in the array.
*/
- tabArrayPtr = ckalloc(sizeof(TkTextTabArray)
- + (count - 1) * sizeof(TkTextTab));
+ tabArrayPtr = malloc(sizeof(TkTextTabArray) + (count - 1)*sizeof(TkTextTab));
tabArrayPtr->numTabs = 0;
prevStop = 0.0;
lastStop = 0.0;
@@ -4547,26 +6957,23 @@ TkTextGetTabs(
* downwards, to find the right integer pixel position.
*/
- if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[i],
- &tabPtr->location) != TCL_OK) {
+ if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[i], &tabPtr->location) != TCL_OK) {
goto error;
}
if (tabPtr->location <= 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "tab stop \"%s\" is not at a positive distance",
- Tcl_GetString(objv[i])));
+ "tab stop \"%s\" is not at a positive distance", Tcl_GetString(objv[i])));
Tcl_SetErrorCode(interp, "TK", "VALUE", "TAB_STOP", NULL);
goto error;
}
prevStop = lastStop;
- if (Tk_GetDoublePixelsFromObj(interp, textPtr->tkwin, objv[i],
- &lastStop) != TCL_OK) {
+ if (Tk_GetDoublePixelsFromObj(interp, textPtr->tkwin, objv[i], &lastStop) != TCL_OK) {
goto error;
}
- if (i > 0 && (tabPtr->location <= (tabPtr-1)->location)) {
+ if (i > 0 && tabPtr->location <= (tabPtr - 1)->location) {
/*
* This tab is actually to the left of the previous one, which is
* illegal.
@@ -4579,11 +6986,8 @@ TkTextGetTabs(
* position.
*/
- if (textPtr->charWidth > 0) {
- tabPtr->location = (tabPtr-1)->location + textPtr->charWidth;
- } else {
- tabPtr->location = (tabPtr-1)->location + 8;
- }
+ tabPtr->location = (tabPtr - 1)->location;
+ tabPtr->location += (textPtr->charWidth > 0 ? textPtr->charWidth : 8);
lastStop = tabPtr->location;
#else
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
@@ -4595,7 +6999,7 @@ TkTextGetTabs(
#endif /* _TK_ALLOW_DECREASING_TABS */
}
- tabArrayPtr->numTabs++;
+ tabArrayPtr->numTabs += 1;
/*
* See if there is an explicit alignment in the next list element.
@@ -4603,7 +7007,7 @@ TkTextGetTabs(
*/
tabPtr->alignment = LEFT;
- if ((i+1) == objc) {
+ if (i + 1 == objc) {
continue;
}
@@ -4611,7 +7015,7 @@ TkTextGetTabs(
* There may be a more efficient way of getting this.
*/
- TkUtfToUniChar(Tcl_GetString(objv[i+1]), &ch);
+ TkUtfToUniChar(Tcl_GetString(objv[i + 1]), &ch);
if (!Tcl_UniCharIsAlpha(ch)) {
continue;
}
@@ -4636,7 +7040,7 @@ TkTextGetTabs(
return tabArrayPtr;
error:
- ckfree(tabArrayPtr);
+ free(tabArrayPtr);
return NULL;
}
@@ -4659,175 +7063,298 @@ TkTextGetTabs(
*----------------------------------------------------------------------
*/
+static void
+AppendOption(
+ char *result,
+ const char *str,
+ const char *delim)
+{
+ unsigned len = strlen(result);
+
+ if (delim && len > 0 && result[len - 1] != ' ' && result[len - 1] != '?') {
+ strcpy(result + len, delim);
+ len += strlen(delim);
+ }
+ strcpy(result + len, str);
+}
+
static int
-TextDumpCmd(
- register TkText *textPtr, /* Information about text widget. */
+GetDumpFlags(
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "dump". */
+ Tcl_Obj *const objv[], /* Argument objects. */
+ unsigned allowed, /* Which options are allowed? */
+ unsigned dflt, /* Default options (-all) */
+ unsigned *what, /* Store flags here. */
+ int *lastArg, /* Store index of last used argument, can be NULL. */
+ TkTextIndex *index1, /* Store first index here. */
+ TkTextIndex *index2, /* Store second index here. */
+ Tcl_Obj **command) /* Store command here, can be NULL. */
{
- TkTextIndex index1, index2;
- int arg;
- int lineno; /* Current line number. */
- int what = 0; /* bitfield to select segment types. */
- int atEnd; /* True if dumping up to logical end. */
- TkTextLine *linePtr;
- Tcl_Obj *command = NULL; /* Script callback to apply to segments. */
-#define TK_DUMP_TEXT 0x1
-#define TK_DUMP_MARK 0x2
-#define TK_DUMP_TAG 0x4
-#define TK_DUMP_WIN 0x8
-#define TK_DUMP_IMG 0x10
-#define TK_DUMP_ALL (TK_DUMP_TEXT|TK_DUMP_MARK|TK_DUMP_TAG| \
- TK_DUMP_WIN|TK_DUMP_IMG)
static const char *const optStrings[] = {
- "-all", "-command", "-image", "-mark", "-tag", "-text", "-window",
- NULL
+ "-all", "-bindings", "-chars", "-command", "-configurations", "-discardselection",
+ "-displaychars", "-displaytext", "-dontresolve", "-elide", "-image",
+ "-insertmark", "-mark", "-nested", "-node", "-setup",
+ "-tag", "-text", "-window", NULL
};
enum opts {
- DUMP_ALL, DUMP_CMD, DUMP_IMG, DUMP_MARK, DUMP_TAG, DUMP_TXT, DUMP_WIN
+ DUMP_ALL, DUMP_TAG_BINDINGS, DUMP_CHARS, DUMP_CMD, DUMP_TAG_CONFIGS, DUMP_DISCARD_SEL,
+ DUMP_DISPLAY_CHARS, DUMP_DISPLAY_TEXT, DUMP_DONT_RESOLVE, DUMP_ELIDE, DUMP_IMG,
+ DUMP_INSERT_MARK, DUMP_MARK, DUMP_NESTED, DUMP_NODE, DUMP_TEXT_CONFIGS,
+ DUMP_TAG, DUMP_TEXT, DUMP_WIN
};
+ static const int dumpFlags[] = {
+ 0, TK_DUMP_TAG_BINDINGS, TK_DUMP_CHARS, 0, TK_DUMP_TAG_CONFIGS, TK_DUMP_DISCARD_SEL,
+ TK_DUMP_DISPLAY_CHARS, TK_DUMP_DISPLAY_TEXT, TK_DUMP_DONT_RESOLVE, TK_DUMP_ELIDE, TK_DUMP_IMG,
+ TK_DUMP_INSERT_MARK, TK_DUMP_MARK, TK_DUMP_NESTED, TK_DUMP_NODE, TK_DUMP_TEXT_CONFIGS,
+ TK_DUMP_TAG, TK_DUMP_TEXT, TK_DUMP_WIN
+ };
+
+ int arg, i;
+ unsigned flags = 0;
+ const char *myOptStrings[sizeof(optStrings)/sizeof(optStrings[0])];
+ int myOptIndices[sizeof(optStrings)/sizeof(optStrings[0])];
+ int myOptCount;
+
+ assert(what);
+ assert(!index1 == !index2);
+ assert(DUMP_ALL == 0); /* otherwise next loop is wrong */
- for (arg=2 ; arg < objc ; arg++) {
+ /* We know that option -all is allowed in any case. */
+ myOptStrings[0] = optStrings[DUMP_ALL];
+ myOptIndices[0] = DUMP_ALL;
+ myOptCount = 1;
+
+ for (i = 1; i < sizeof(optStrings)/sizeof(optStrings[0]) - 1; ++i) {
+ if (i == DUMP_CMD ? !!command : (allowed & dumpFlags[i]) == dumpFlags[i]) {
+ myOptStrings[myOptCount] = optStrings[i];
+ myOptIndices[myOptCount] = i;
+ myOptCount += 1;
+ }
+ }
+ myOptStrings[myOptCount] = NULL;
+
+ if (lastArg) {
+ *lastArg = 0;
+ }
+ *what = 0;
+
+ for (arg = 2; arg < objc && Tcl_GetString(objv[arg])[0] == '-'; ++arg) {
int index;
- if (Tcl_GetString(objv[arg])[0] != '-') {
- break;
+
+ if (Tcl_GetString(objv[arg])[1] == '-'
+ && Tcl_GetString(objv[arg])[2] == '\0'
+ && (arg < objc - 1 || Tcl_GetString(objv[arg + 1])[0] != '-')) {
+ continue;
}
- if (Tcl_GetIndexFromObjStruct(interp, objv[arg], optStrings,
+
+ if (Tcl_GetIndexFromObjStruct(interp, objv[arg], myOptStrings,
sizeof(char *), "option", 0, &index) != TCL_OK) {
return TCL_ERROR;
}
- switch ((enum opts) index) {
+
+ switch ((enum opts) myOptIndices[index]) {
+#define CASE(Flag) case DUMP_##Flag: *what |= TK_DUMP_##Flag; flags |= TK_DUMP_##Flag; break;
+ CASE(CHARS);
+ CASE(TEXT);
+ CASE(DISPLAY_CHARS);
+ CASE(DISPLAY_TEXT);
+ CASE(TAG);
+ CASE(MARK);
+ CASE(ELIDE);
+ CASE(NESTED);
+ CASE(NODE);
+ CASE(DISCARD_SEL);
+ CASE(INSERT_MARK);
+ CASE(TEXT_CONFIGS);
+ CASE(TAG_BINDINGS);
+ CASE(TAG_CONFIGS);
+ CASE(DONT_RESOLVE);
+ CASE(IMG);
+ CASE(WIN);
+#undef CASE
case DUMP_ALL:
- what = TK_DUMP_ALL;
- break;
- case DUMP_TXT:
- what |= TK_DUMP_TEXT;
- break;
- case DUMP_TAG:
- what |= TK_DUMP_TAG;
- break;
- case DUMP_MARK:
- what |= TK_DUMP_MARK;
- break;
- case DUMP_IMG:
- what |= TK_DUMP_IMG;
- break;
- case DUMP_WIN:
- what |= TK_DUMP_WIN;
+ *what = dflt;
break;
case DUMP_CMD:
- arg++;
- if (arg >= objc) {
+ arg += 1;
+ if (!command || arg >= objc) {
goto wrongArgs;
}
- command = objv[arg];
+ *command = objv[arg];
break;
- default:
- Tcl_Panic("unexpected switch fallthrough");
+ }
+ if (~allowed & flags) {
+ goto wrongArgs;
}
}
- if (arg >= objc || arg+2 < objc) {
- wrongArgs:
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "Usage: %s dump ?-all -image -text -mark -tag -window? "
- "?-command script? index ?index2?", Tcl_GetString(objv[0])));
- Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL);
- return TCL_ERROR;
+ if (!(*what & dflt)) {
+ *what |= dflt;
}
- if (what == 0) {
- what = TK_DUMP_ALL;
+ if (!index1) {
+ if (arg < objc) {
+ goto wrongArgs;
+ }
+ return TCL_OK;
+ }
+ if (arg >= objc || arg + 2 < objc) {
+ goto wrongArgs;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index1) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[arg], index1)) {
return TCL_ERROR;
}
- arg++;
- atEnd = 0;
+ arg += 1;
+ if (lastArg) {
+ *lastArg = arg;
+ }
if (objc == arg) {
- TkTextIndexForwChars(NULL, &index1, 1, &index2, COUNT_INDICES);
- } else {
- int length;
- const char *str;
+ TkTextIndexForwChars(textPtr, index1, 1, index2, COUNT_INDICES);
+ } else if (!TkTextGetIndexFromObj(interp, textPtr, objv[arg], index2)) {
+ return TCL_ERROR;
+ }
+ return TCL_OK;
- if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) {
- return TCL_ERROR;
- }
- str = Tcl_GetString(objv[arg]);
- length = objv[arg]->length;
- if (strncmp(str, "end", (unsigned) length) == 0) {
- atEnd = 1;
+wrongArgs:
+ {
+ char result[500];
+ unsigned i;
+
+ result[0] = 0;
+ AppendOption(result, "?", NULL);
+
+ for (i = 0; myOptStrings[i]; ++i) {
+ if (myOptIndices[i] != DUMP_CMD) {
+ AppendOption(result, myOptStrings[i], " ");
+ }
}
+ AppendOption(result, "? ?", NULL);
+ if (command) { AppendOption(result, "-command script", NULL); }
+ AppendOption(result, "?", NULL);
+ if (index1) { AppendOption(result, " index ?index2?", NULL); }
+
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("Usage: %s %s %s",
+ Tcl_GetString(objv[0]), Tcl_GetString(objv[1]), result));
+ Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL);
+ }
+ return TCL_ERROR;
+}
+
+static int
+TextDumpCmd(
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "dump". */
+{
+ TkTextIndex index1, index2;
+ TkTextBTree tree;
+ TkTextTag *tagPtr, *tPtr;
+ int lineno; /* Current line number. */
+ unsigned what; /* bitfield to select segment types. */
+ int lastArg; /* Index of last argument. */
+ TkTextLine *linePtr;
+ TkTextIndex prevByteIndex;
+ Tcl_Obj *command = NULL; /* Script callback to apply to segments. */
+ TkTextTag *prevTagPtr = NULL;
+ int result;
+
+ assert(textPtr);
+
+ result = GetDumpFlags(textPtr, interp, objc, objv, TK_DUMP_DUMP_ALL|TK_DUMP_NODE, TK_DUMP_DUMP_ALL,
+ &what, &lastArg, &index1, &index2, &command);
+ if (result != TCL_OK) {
+ return result;
}
- if (TkTextIndexCmp(&index1, &index2) >= 0) {
+ if (TkTextIndexCompare(&index1, &index2) >= 0) {
return TCL_OK;
}
- lineno = TkBTreeLinesTo(textPtr, index1.linePtr);
- if (index1.linePtr == index2.linePtr) {
- DumpLine(interp, textPtr, what, index1.linePtr,
- index1.byteIndex, index2.byteIndex, lineno, command);
+ tree = textPtr->sharedTextPtr->tree;
+ textPtr->sharedTextPtr->inspectEpoch += 1;
+ lineno = TkBTreeLinesTo(tree, textPtr, TkTextIndexGetLine(&index1), NULL);
+ prevByteIndex = index1;
+ if (TkTextIndexBackBytes(textPtr, &index1, 1, &prevByteIndex) == 0) {
+ unsigned epoch = textPtr->sharedTextPtr->inspectEpoch + 1;
+ tagPtr = TkBTreeGetTags(&prevByteIndex);
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) { tPtr->epoch = epoch; }
} else {
- int textChanged;
- int lineend = TkBTreeLinesTo(textPtr, index2.linePtr);
- int endByteIndex = index2.byteIndex;
+ tagPtr = NULL;
+ }
+ if (TkTextIndexGetLine(&index1) == TkTextIndexGetLine(&index2)) {
+ /* we are at the end, so we can ignore the return code of DumpLine */
+ DumpLine(interp, textPtr, what, TkTextIndexGetLine(&index1),
+ TkTextIndexGetByteIndex(&index1), TkTextIndexGetByteIndex(&index2),
+ lineno, command, &prevTagPtr);
+ } else {
+ int lineend = TkBTreeLinesTo(tree, textPtr, TkTextIndexGetLine(&index2), NULL);
+ int endByteIndex = TkTextIndexGetByteIndex(&index2);
- textChanged = DumpLine(interp, textPtr, what, index1.linePtr,
- index1.byteIndex, 32000000, lineno, command);
- if (textChanged) {
+ if (!DumpLine(interp, textPtr, what, TkTextIndexGetLine(&index1),
+ TkTextIndexGetByteIndex(&index1), INT_MAX, lineno, command, &prevTagPtr)) {
if (textPtr->flags & DESTROYED) {
return TCL_OK;
}
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineno);
- textChanged = 0;
+ if (!(linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineno))) {
+ goto textChanged;
+ }
} else {
- linePtr = index1.linePtr;
+ linePtr = TkTextIndexGetLine(&index1);
}
- while ((linePtr = TkBTreeNextLine(textPtr, linePtr)) != NULL) {
- lineno++;
- if (lineno == lineend) {
+ while ((linePtr = TkBTreeNextLine(textPtr, linePtr))) {
+ if (++lineno == lineend) {
break;
}
- textChanged = DumpLine(interp, textPtr, what, linePtr, 0,
- 32000000, lineno, command);
- if (textChanged) {
+ if (!DumpLine(interp, textPtr, what, linePtr, 0, INT_MAX, lineno, command, &prevTagPtr)) {
if (textPtr->flags & DESTROYED) {
return TCL_OK;
}
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineno);
- textChanged = 0;
+ if (!(linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineno))) {
+ goto textChanged;
+ }
}
}
- if (linePtr != NULL) {
- DumpLine(interp, textPtr, what, linePtr, 0, endByteIndex, lineno,
- command);
- if (textPtr->flags & DESTROYED) {
- return TCL_OK;
- }
+ if (linePtr) {
+ /* we are at the end, so we can ignore the return code of DumpLine */
+ DumpLine(interp, textPtr, what, linePtr, 0, endByteIndex, lineno, command, &prevTagPtr);
}
}
+ textChanged:
+
/*
* Special case to get the leftovers hiding at the end mark.
*/
- if (atEnd) {
- if (textPtr->flags & DESTROYED) {
- return TCL_OK;
+ if (!(textPtr->flags & DESTROYED)) {
+ if (lastArg < objc
+ && strncmp(Tcl_GetString(objv[lastArg]), "end", GetByteLength(objv[lastArg])) == 0) {
+ /*
+ * Re-get the end index, in case it has changed.
+ */
+
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[lastArg], &index2)) {
+ return TCL_ERROR;
+ }
+ if (!DumpLine(interp, textPtr, what & ~TK_DUMP_TEXT, TkTextIndexGetLine(&index2), 0, 1,
+ lineno, command, &prevTagPtr)) {
+ prevTagPtr = NULL; /* the tags are no longer valid */
+ }
}
- /*
- * Re-get the end index, in case it has changed.
- */
+ if (prevTagPtr && TkTextIndexIsEndOfText(&index2)) {
+ /*
+ * Finally print "tagoff" information, if at end of text.
+ */
- if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) {
- return TCL_ERROR;
+ for ( ; prevTagPtr; prevTagPtr = prevTagPtr->succPtr) {
+ if (!DumpSegment(textPtr, interp, "tagoff", prevTagPtr->name, command, &index2, what)) {
+ break;
+ }
+ }
}
- DumpLine(interp, textPtr, what & ~TK_DUMP_TEXT, index2.linePtr,
- 0, 1, lineno, command);
}
+
return TCL_OK;
}
@@ -4840,10 +7367,10 @@ TextDumpCmd(
* "start" up to, but not including, "end".
*
* Results:
- * Returns 1 if the command callback made any changes to the text widget
+ * Returns false if the command callback made any changes to the text widget
* which will have invalidated internal structures such as TkTextSegment,
* TkTextIndex, pointers. Our caller can then take action to recompute
- * such entities. Returns 0 otherwise.
+ * such entities, or he aborts with an error. Returns true otherwise.
*
* Side effects:
* None, but see DumpSegment which can have arbitrary side-effects
@@ -4851,7 +7378,7 @@ TextDumpCmd(
*----------------------------------------------------------------------
*/
-static int
+static bool
DumpLine(
Tcl_Interp *interp,
TkText *textPtr,
@@ -4859,172 +7386,446 @@ DumpLine(
TkTextLine *linePtr, /* The current line. */
int startByte, int endByte, /* Byte range to dump. */
int lineno, /* Line number for indices dump. */
- Tcl_Obj *command) /* Script to apply to the segment. */
+ Tcl_Obj *command, /* Script to apply to the segment. */
+ TkTextTag **prevTagPtr) /* Tag information from previous segment. */
{
+ TkSharedText *sharedTextPtr;
+ TkTextSegment *sPtr;
TkTextSegment *segPtr;
+ TkTextSegment *endPtr;
+ TkTextSegment *newSegPtr;
TkTextIndex index;
- int offset = 0, textChanged = 0;
+ int offset = 0;
+ int currentSize = 0;
+ int bufSize = 0;
+ bool textChanged = false;
+ char *buffer = NULL;
+ bool eol;
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+
+ if (!*prevTagPtr && (startByte > 0 || linePtr != TkBTreeGetStartLine(textPtr))) {
+ /*
+ * If this is the first line to dump, and we are not at start of line,
+ * then we need the preceding tag information.
+ */
+
+ TkTextIndex index;
+ TkTextTag *tagPtr, *tPtr;
+ unsigned epoch = sharedTextPtr->inspectEpoch;
+
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetByteIndex2(&index, linePtr, startByte);
+ TkBTreeMoveBackward(&index, 1);
+ segPtr = TkTextIndexGetContentSegment(&index, NULL);
+ assert(segPtr);
+ tagPtr = TkBTreeGetSegmentTags(textPtr->sharedTextPtr, segPtr, textPtr);
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ tPtr->flag = epoch; /* mark as open */
+ }
+ }
/*
- * Must loop through line looking at its segments.
- * character
- * toggleOn, toggleOff
- * mark
- * image
- * window
+ * Must loop through line looking at its segments: character, hyphen, mark, image, window.
*/
segPtr = linePtr->segPtr;
- while ((offset < endByte) && (segPtr != NULL)) {
- int lineChanged = 0;
- int currentSize = segPtr->size;
+ endPtr = textPtr->endMarker;
+ eol = !segPtr->nextPtr;
- if ((what & TK_DUMP_TEXT) && (segPtr->typePtr == &tkTextCharType) &&
- (offset + currentSize > startByte)) {
- int last = currentSize; /* Index of last char in seg. */
- int first = 0; /* Index of first char in seg. */
+ if ((what & TK_DUMP_NODE)
+ && startByte == 0
+ && (!linePtr->prevPtr || linePtr->prevPtr->parentPtr != linePtr->parentPtr)) {
+ char buf[20];
+ unsigned depth, number;
- if (offset + currentSize > endByte) {
- last = endByte - offset;
- }
- if (startByte > offset) {
- first = startByte - offset;
- }
- if (last != currentSize) {
- /*
- * To avoid modifying the string in place we copy over just
- * the segment that we want. Since DumpSegment can modify the
- * text, we could not confidently revert the modification
- * here.
- */
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetToStartOfLine2(&index, linePtr);
+ number = TkBTreeChildNumber(sharedTextPtr->tree, linePtr, &depth);
+ snprintf(buf, sizeof(buf), "%d:%d", number, depth);
- int length = last - first;
- char *range = ckalloc(length + 1);
+ if (!DumpSegment(textPtr, interp, "node", buf, command, &index, what)) {
+ goto textChanged;
+ }
+ }
- memcpy(range, segPtr->body.chars + first, length);
- range[length] = '\0';
+ while (segPtr && offset < endByte) {
+ currentSize = segPtr->size;
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset + first, &index);
- lineChanged = DumpSegment(textPtr, interp, "text", range,
- command, &index, what);
- ckfree(range);
- } else {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset + first, &index);
- lineChanged = DumpSegment(textPtr, interp, "text",
- segPtr->body.chars + first, command, &index, what);
- }
- } else if ((offset >= startByte)) {
- if ((what & TK_DUMP_MARK)
- && (segPtr->typePtr == &tkTextLeftMarkType
- || segPtr->typePtr == &tkTextRightMarkType)) {
- const char *name;
- TkTextMark *markPtr = &segPtr->body.mark;
-
- if (segPtr == textPtr->insertMarkPtr) {
- name = "insert";
- } else if (segPtr == textPtr->currentMarkPtr) {
- name = "current";
- } else if (markPtr->hPtr == NULL) {
- name = NULL;
- lineChanged = 0;
- } else {
- name = Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable,
- markPtr->hPtr);
+ if (offset + MAX(1, currentSize) > startByte) {
+ if ((what & TK_DUMP_TAG) && segPtr->tagInfoPtr) {
+ TkTextTag *tagPtr = TkBTreeGetSegmentTags(sharedTextPtr, segPtr, textPtr);
+ unsigned epoch = sharedTextPtr->inspectEpoch;
+ unsigned nextEpoch = epoch + 1;
+ TkTextTag *tPtr;
+
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (tPtr->flag == epoch) {
+ tPtr->flag = nextEpoch; /* mark as still open */
+ }
}
- if (name != NULL) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "mark", name,
- command, &index, what);
+
+ if (*prevTagPtr) {
+ /*
+ * Print "tagoff" information.
+ */
+
+ for (tPtr = *prevTagPtr; tPtr; tPtr = tPtr->succPtr) {
+ if (tPtr->flag == epoch) { /* should be closed? */
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno, offset, &index);
+ if (!DumpSegment(textPtr, interp, "tagoff",
+ tPtr->name, command, &index, what)) {
+ goto textChanged;
+ }
+ tPtr->flag = 0; /* mark as closed */
+ }
+ }
}
- } else if ((what & TK_DUMP_TAG) &&
- (segPtr->typePtr == &tkTextToggleOnType)) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "tagon",
- segPtr->body.toggle.tagPtr->name, command, &index,
- what);
- } else if ((what & TK_DUMP_TAG) &&
- (segPtr->typePtr == &tkTextToggleOffType)) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "tagoff",
- segPtr->body.toggle.tagPtr->name, command, &index,
- what);
- } else if ((what & TK_DUMP_IMG) &&
- (segPtr->typePtr == &tkTextEmbImageType)) {
- TkTextEmbImage *eiPtr = &segPtr->body.ei;
- const char *name = (eiPtr->name == NULL) ? "" : eiPtr->name;
-
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "image", name,
- command, &index, what);
- } else if ((what & TK_DUMP_WIN) &&
- (segPtr->typePtr == &tkTextEmbWindowType)) {
- TkTextEmbWindow *ewPtr = &segPtr->body.ew;
- const char *pathname;
-
- if (ewPtr->tkwin == (Tk_Window) NULL) {
- pathname = "";
- } else {
- pathname = Tk_PathName(ewPtr->tkwin);
+
+ /*
+ * Print "tagon" information.
+ */
+
+ sharedTextPtr->inspectEpoch = ++epoch;
+
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (tPtr->flag != epoch) {
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno, offset, &index);
+ if (!DumpSegment(textPtr, interp, "tagon", tPtr->name, command, &index, what)) {
+ goto textChanged;
+ }
+ tPtr->flag = epoch; /* mark as open */
+ }
+ tPtr->succPtr = tPtr->nextPtr;
}
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "window", pathname,
- command, &index, what);
+
+ *prevTagPtr = tagPtr;
}
- }
- offset += currentSize;
- if (lineChanged) {
- TkTextSegment *newSegPtr;
- int newOffset = 0;
+ if (what & segPtr->typePtr->group) {
+ assert(segPtr->typePtr->group != SEG_GROUP_BRANCH);
- textChanged = 1;
+ if (segPtr->typePtr->group == SEG_GROUP_CHAR) {
+ int last = currentSize; /* Index of last char in seg. */
+ int first = 0; /* Index of first char in seg. */
- /*
- * Our indices are no longer valid.
- */
+ if (offset + currentSize > endByte) {
+ last = endByte - offset;
+ }
+ if (startByte > offset) {
+ first = startByte - offset;
+ }
+ if (last != currentSize) {
+ /*
+ * To avoid modifying the string in place we copy over just
+ * the segment that we want. Since DumpSegment can modify the
+ * text, we could not confidently revert the modification here.
+ */
- if (textPtr->flags & DESTROYED) {
- return textChanged;
- }
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineno);
- newSegPtr = linePtr->segPtr;
- if (segPtr != newSegPtr) {
- while ((newOffset < endByte) && (newOffset < offset)
- && (newSegPtr != NULL)) {
- newOffset += currentSize;
- newSegPtr = newSegPtr->nextPtr;
- if (segPtr == newSegPtr) {
- break;
+ int length = last - first;
+
+ if (length >= bufSize) {
+ bufSize = MAX(length + 1, 2*length);
+ buffer = realloc(buffer, bufSize);
+ }
+
+ memcpy(buffer, segPtr->body.chars + first, length);
+ buffer[length] = '\0';
+
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno,
+ offset + first, &index);
+ if (!DumpSegment(textPtr, interp, "text", buffer, command, &index, what)) {
+ goto textChanged;
+ }
+ } else {
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno,
+ offset + first, &index);
+ if (!DumpSegment(textPtr, interp, "text",
+ segPtr->body.chars + first, command, &index, what)) {
+ goto textChanged;
+ }
}
- }
- if (segPtr != newSegPtr && newOffset == offset
- && currentSize == 0) {
- TkTextSegment *searchPtr = newSegPtr;
+ } else if (segPtr == endPtr) {
+ if (linePtr == TkBTreeGetLastLine(textPtr)) {
+ break; /* finished */
+ }
+ /* print final newline in next iteration */
+ currentSize = linePtr->size - offset - 1;
+ startByte = offset + currentSize + linePtr->lastPtr->size - 1;
+ segPtr = linePtr->lastPtr->prevPtr;
+ } else {
+ char const *value = NULL;
- while (searchPtr != NULL && searchPtr->size == 0) {
- if (searchPtr == segPtr) {
- newSegPtr = searchPtr;
- break;
+ switch ((int) segPtr->typePtr->group) {
+ case SEG_GROUP_MARK:
+ value = TkTextMarkName(sharedTextPtr, textPtr, segPtr);
+ break;
+ case SEG_GROUP_IMAGE: {
+ TkTextEmbImage *eiPtr = &segPtr->body.ei;
+ value = eiPtr->name ? eiPtr->name : "";
+ break;
+ }
+ case SEG_GROUP_WINDOW: {
+ TkTextEmbWindow *ewPtr = &segPtr->body.ew;
+ value = ewPtr->tkwin ? Tk_PathName(ewPtr->tkwin) : "";
+ break;
+ }
+ case SEG_GROUP_HYPHEN:
+ value = "";
+ break;
+ }
+ if (value) {
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno, offset, &index);
+ if (!DumpSegment(textPtr, interp, segPtr->typePtr->name, value, command,
+ &index, what)) {
+ goto textChanged;
}
- searchPtr = searchPtr->nextPtr;
}
}
- segPtr = newSegPtr;
}
}
- if (segPtr != NULL) {
+
+ offset += currentSize;
+ segPtr = segPtr->nextPtr;
+ continue;
+
+ textChanged:
+
+ /*
+ * Our indices, segments, and tag chains are no longer valid. It's a bad
+ * idea to do changes while the dump is running, it's impossible to
+ * synchronize in any case, but we will try the best.
+ */
+
+ *prevTagPtr = NULL;
+ textChanged = true;
+
+ if (eol || (textPtr->flags & DESTROYED)) {
+ break;
+ }
+
+ offset += currentSize;
+ if (!(linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineno))) {
+ break;
+ }
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetByteIndex2(&index, linePtr, MIN(offset, linePtr->size - 1));
+
+ sPtr = newSegPtr = TkTextIndexGetFirstSegment(&index, NULL);
+ while (sPtr && sPtr != segPtr) {
+ sPtr = sPtr->nextPtr;
+ }
+ if (sPtr != segPtr) {
+ segPtr = newSegPtr;
+ } else if (offset >= segPtr->size) {
segPtr = segPtr->nextPtr;
}
}
- return textChanged;
+
+ free(buffer);
+ return !textChanged;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextChecksumCmd --
+ *
+ * Return the checksum over the whole content.
+ * About the format see documentation.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * Memory is allocated for the result, if needed (standard Tcl result
+ * side effects).
+ *
+ *----------------------------------------------------------------------
+ */
+
+static uint32_t
+ComputeChecksum(
+ uint32_t crc,
+ const char *buf,
+ unsigned len)
+{
+ static const uint32_t crcTable[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+ };
+
+ assert(buf);
+
+ /* basic algorithm stolen from zlib/crc32.c (public domain) */
+
+#define DO1(buf) crc = crcTable[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8);
+#define DO2(buf) DO1(buf); DO1(buf);
+#define DO4(buf) DO2(buf); DO2(buf);
+#define DO8(buf) DO4(buf); DO4(buf);
+
+ crc = crc ^ 0xffffffff;
+
+ if (len == 0) {
+ while (*buf) {
+ DO1(buf);
+ }
+ } else {
+ while (len >= 8) {
+ DO8(buf);
+ len -= 8;
+ }
+ while (len--) {
+ DO1(buf);
+ }
+ }
+ return crc ^ 0xffffffff;
+}
+
+static int
+TextChecksumCmd(
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "checksum". */
+{
+ const TkSharedText *sharedTextPtr;
+ const TkTextSegment *segPtr;
+ const TkTextSegment *endPtr;
+ const TkTextLine *linePtr;
+ TkTextTag **tagArrPtr = NULL; /* avoid compiler warning */
+ unsigned what;
+ unsigned crc;
+ int result;
+
+ assert(textPtr);
+
+ result = GetDumpFlags(textPtr, interp, objc, objv, TK_DUMP_CRC_ALL, TK_DUMP_CRC_DFLT,
+ &what, NULL, NULL, NULL, NULL);
+
+ if (result != TCL_OK) {
+ return result;
+ }
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ segPtr = sharedTextPtr->startMarker;
+ endPtr = sharedTextPtr->endMarker;
+ linePtr = segPtr->sectionPtr->linePtr;
+ if (endPtr->sectionPtr->linePtr != linePtr) {
+ endPtr = NULL;
+ }
+ crc = 0;
+
+ if ((what & SEG_GROUP_TAG)) {
+ tagArrPtr = malloc(sizeof(tagArrPtr[0])*sharedTextPtr->numTags);
+ }
+
+ /*
+ * Note that 0xff cannot occur in UTF-8 strings, so we can use this value as a separator.
+ */
+
+ while (segPtr != endPtr) {
+ if (segPtr->tagInfoPtr
+ && (what & SEG_GROUP_TAG)
+ && segPtr->tagInfoPtr != sharedTextPtr->emptyTagInfoPtr) {
+ unsigned i = TkTextTagSetFindFirst(segPtr->tagInfoPtr);
+ unsigned n = 0;
+
+ for ( ; i != TK_TEXT_TAG_SET_NPOS; i = TkTextTagSetFindNext(segPtr->tagInfoPtr, i)) {
+ assert(sharedTextPtr->tagLookup[i]);
+ tagArrPtr[n++] = sharedTextPtr->tagLookup[i];
+ }
+
+ TkTextSortTags(n, tagArrPtr);
+
+ for (i = 0; i < n; ++i) {
+ crc = ComputeChecksum(crc, "\xff\x00", 2);
+ crc = ComputeChecksum(crc, tagArrPtr[i]->name, 0);
+ }
+ }
+ switch ((int) segPtr->typePtr->group) {
+ case SEG_GROUP_CHAR:
+ if (what & SEG_GROUP_CHAR) {
+ crc = ComputeChecksum(crc, "\xff\x01", 0);
+ crc = ComputeChecksum(crc, segPtr->body.chars, segPtr->size);
+ }
+ break;
+ case SEG_GROUP_HYPHEN:
+ if (what & SEG_GROUP_HYPHEN) {
+ crc = ComputeChecksum(crc, "\xff\x02", 0);
+ }
+ break;
+ case SEG_GROUP_WINDOW:
+ if ((what & SEG_GROUP_WINDOW)) {
+ crc = ComputeChecksum(crc, "\xff\x03", 0);
+ crc = ComputeChecksum(crc, Tk_PathName(segPtr->body.ew.tkwin), 0);
+ }
+ break;
+ case SEG_GROUP_IMAGE:
+ if ((what & SEG_GROUP_IMAGE) && segPtr->body.ei.name) {
+ crc = ComputeChecksum(crc, "\xff\x04", 0);
+ crc = ComputeChecksum(crc, segPtr->body.ei.name, 0);
+ }
+ break;
+ case SEG_GROUP_MARK:
+ if ((what & SEG_GROUP_MARK) && TkTextIsNormalMark(segPtr)) {
+ const char *name;
+ const char *signature;
+
+ name = TkTextMarkName(sharedTextPtr, NULL, segPtr);
+ signature = (segPtr->typePtr == &tkTextRightMarkType) ? "\xff\x05" : "\xff\x06";
+ crc = ComputeChecksum(crc, signature, 0);
+ crc = ComputeChecksum(crc, name, 0);
+ }
+ break;
+ case SEG_GROUP_BRANCH:
+ if (segPtr->typePtr == &tkTextBranchType && (what & TK_DUMP_DISPLAY)) {
+ segPtr = segPtr->body.branch.nextPtr;
+ }
+ break;
+ }
+ if (!(segPtr = segPtr->nextPtr)) {
+ linePtr = linePtr->nextPtr;
+ segPtr = linePtr->segPtr;
+ }
+ }
+
+ if ((what & SEG_GROUP_TAG)) {
+ free(tagArrPtr);
+ }
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(crc));
+ return TCL_OK;
}
/*
@@ -5036,10 +7837,10 @@ DumpLine(
* make a script callback with that information as arguments.
*
* Results:
- * Returns 1 if the command callback made any changes to the text widget
+ * Returns 'false' if the command callback made any changes to the text widget
* which will have invalidated internal structures such as TkTextSegment,
* TkTextIndex, pointers. Our caller can then take action to recompute
- * such entities. Returns 0 otherwise.
+ * such entities, or he aborts with an error. Returns 'true' otherwise.
*
* Side effects:
* Either evals the callback or appends elements to the result string.
@@ -5048,7 +7849,7 @@ DumpLine(
*----------------------------------------------------------------------
*/
-static int
+static bool
DumpSegment(
TkText *textPtr,
Tcl_Interp *interp,
@@ -5066,10 +7867,10 @@ DumpSegment(
values[1] = Tcl_NewStringObj(value, -1);
values[2] = Tcl_NewStringObj(buffer, -1);
tuple = Tcl_NewListObj(3, values);
- if (command == NULL) {
+ if (!command) {
Tcl_ListObjAppendList(NULL, Tcl_GetObjResult(interp), tuple);
Tcl_DecrRefCount(tuple);
- return 0;
+ return true;
} else {
int oldStateEpoch = TkBTreeEpoch(textPtr->sharedTextPtr->tree);
Tcl_DString buf;
@@ -5082,146 +7883,700 @@ DumpSegment(
code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0);
Tcl_DStringFree(&buf);
if (code != TCL_OK) {
- Tcl_AddErrorInfo(interp,
- "\n (segment dumping command executed by text)");
+ Tcl_AddErrorInfo(interp, "\n (segment dumping command executed by text)");
Tcl_BackgroundException(interp, code);
}
Tcl_DecrRefCount(tuple);
- return ((textPtr->flags & DESTROYED) ||
- TkBTreeEpoch(textPtr->sharedTextPtr->tree) != oldStateEpoch);
+ return !(textPtr->flags & DESTROYED)
+ && TkBTreeEpoch(textPtr->sharedTextPtr->tree) == oldStateEpoch;
}
}
/*
*----------------------------------------------------------------------
*
- * TextEditUndo --
+ * TkTextInspectOptions --
*
- * Undo the last change.
+ * Build information from option table for "inspect".
*
* Results:
* None.
*
* Side effects:
- * Apart from manipulating the undo and redo stacks, the state of the
- * rest of the widget may also change (due to whatever is being undone).
+ * Memory is allocated for the result, if needed (standard Tcl result
+ * side effects).
*
*----------------------------------------------------------------------
*/
-static int
-TextEditUndo(
- TkText *textPtr) /* Overall information about text widget. */
+static bool
+ObjIsEqual(
+ Tcl_Obj *obj1,
+ Tcl_Obj *obj2)
{
- int status;
- Tcl_Obj *cmdObj;
- int code;
+ char const *b1, *b2;
+ unsigned i, length;
- if (!textPtr->sharedTextPtr->undo) {
- return TCL_OK;
- }
+ assert(obj1);
+ assert(obj2);
- /*
- * Turn off the undo feature while we revert a compound action, setting
- * the dirty handling mode to undo for the duration (unless it is
- * 'fixed').
- */
+ b1 = Tcl_GetString(obj1);
+ b2 = Tcl_GetString(obj2);
- textPtr->sharedTextPtr->undo = 0;
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_UNDO;
+ if (strcmp(b1, "#ffffff") == 0) {
+ return strcmp(b2, "#ffffff") == 0 || strcmp(b2, "white") == 0;
}
- status = TkUndoRevert(textPtr->sharedTextPtr->undoStack);
+ if (strcmp(b1, "#000000") == 0) {
+ return strcmp(b2, "#000000") == 0 || strcmp(b2, "black") == 0;
+ }
+
+ length = GetByteLength(obj1);
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
+ if (length != GetByteLength(obj2)) {
+ return false;
}
- textPtr->sharedTextPtr->undo = 1;
- /*
- * Convert undo/redo temporary marks set by TkUndoRevert() into
- * indices left in the interp result.
- */
+ for (i = 0; i < length; ++i) {
+ if (b1[i] != b2[i]) {
+ return false;
+ }
+ }
- cmdObj = Tcl_ObjPrintf("::tk::TextUndoRedoProcessMarks %s",
- Tk_PathName(textPtr->tkwin));
- Tcl_IncrRefCount(cmdObj);
- code = Tcl_EvalObjEx(textPtr->interp, cmdObj, TCL_EVAL_GLOBAL);
- if (code != TCL_OK) {
- Tcl_AddErrorInfo(textPtr->interp,
- "\n (on undoing)");
- Tcl_BackgroundException(textPtr->interp, code);
+ return true;
+}
+
+/*
+ * NOTE: This function should be moved to tkFont.c. I will not do this,
+ * because I won't touch any file not belonging to text widget implementation.
+ * So the Tk Team has to do this.
+ */
+static Tcl_Obj *
+GetFontAttrs(
+ TkText *textPtr,
+ int argc,
+ Tcl_Obj **args)
+{
+ Tcl_Interp *interp = textPtr->interp;
+ Tcl_Obj *objPtr = NULL;
+
+ if (Tk_FontObjCmd(textPtr->tkwin, interp, argc, args) == TCL_OK) {
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ Tcl_Obj *family = NULL;
+ Tcl_Obj *size = NULL;
+ Tcl_Obj *slant = NULL;
+ Tcl_Obj *weight = NULL;
+ Tcl_Obj *underline = NULL;
+ Tcl_Obj *overstrike = NULL;
+ Tcl_Obj **objv;
+ int objc, i;
+
+ if (Tcl_ListObjGetElements(interp, result, &objc, &objv) == TCL_OK) {
+ for (i = 0; i < objc - 1; ++i) {
+ if (Tcl_GetString(objv[i])[0] == '-') {
+ switch (Tcl_GetString(objv[i])[1]) {
+ case 'f': /* -family */
+ family = objv[i + 1];
+ break;
+ case 'o': /* -overstrike */
+ overstrike = objv[i + 1];
+ break;
+ case 's':
+ switch (Tcl_GetString(objv[i])[2]) {
+ case 'i': /* -size */
+ size = objv[i + 1];
+ break;
+ case 'l': /* -slant */
+ slant = objv[i + 1];
+ break;
+ }
+ break;
+ case 'u': /* -underline */
+ underline = objv[i + 1];
+ break;
+ case 'w': /* -weight */
+ weight = objv[i + 1];
+ break;
+ }
+ }
+ }
+ }
+
+ if (family && size) {
+ Tcl_DString str;
+ int boolean;
+
+ Tcl_DStringInit(&str);
+ Tcl_DStringAppendElement(&str, Tcl_GetString(family));
+ Tcl_DStringAppendElement(&str, Tcl_GetString(size));
+ if (weight && strcmp(Tcl_GetString(weight), "normal") != 0) {
+ Tcl_DStringAppendElement(&str, Tcl_GetString(weight));
+ }
+ if (slant && strcmp(Tcl_GetString(slant), "roman") != 0) {
+ Tcl_DStringAppendElement(&str, Tcl_GetString(slant));
+ }
+ if (underline && Tcl_GetBooleanFromObj(NULL, underline, &boolean) == TCL_OK && boolean) {
+ Tcl_DStringAppendElement(&str, "underline");
+ }
+ if (overstrike && Tcl_GetBooleanFromObj(NULL, overstrike, &boolean) == TCL_OK && boolean) {
+ Tcl_DStringAppendElement(&str, "overstrike");
+ }
+
+ objPtr = Tcl_NewStringObj(Tcl_DStringValue(&str), Tcl_DStringLength(&str));
+ Tcl_DStringFree(&str);
+ }
+
+ Tcl_ResetResult(interp);
}
- Tcl_DecrRefCount(cmdObj);
- return status;
+ return objPtr;
+}
+
+void
+TkTextInspectOptions(
+ TkText *textPtr,
+ const void *recordPtr,
+ Tk_OptionTable optionTable,
+ Tcl_DString *result, /* should be already initialized */
+ bool resolveFontNames,
+ bool discardDefaultValues)
+{
+ Tcl_Obj *objPtr;
+ Tcl_Interp *interp = textPtr->interp;
+
+ Tcl_DStringTrunc(result, 0);
+
+ if ((objPtr = Tk_GetOptionInfo(interp, (char *) recordPtr, optionTable, NULL, textPtr->tkwin))) {
+ Tcl_Obj **objv;
+ Tcl_Obj *font = NULL; /* shut up compiler */
+ Tcl_Obj *actual = NULL; /* shut up compiler */
+ int objc = 0;
+ int i;
+
+ Tcl_ListObjGetElements(interp, objPtr, &objc, &objv);
+
+ if (resolveFontNames) {
+ Tcl_IncrRefCount(font = Tcl_NewStringObj("font", -1));
+ Tcl_IncrRefCount(actual = Tcl_NewStringObj("actual", -1));
+ }
+
+ for (i = 0; i < objc; ++i) {
+ Tcl_Obj **argv;
+ int argc = 0;
+
+ Tcl_ListObjGetElements(interp, objv[i], &argc, &argv);
+
+ if (argc >= 5) { /* only if this option has a non-default value */
+ Tcl_Obj *val = argv[4];
+
+ if (GetByteLength(val) > 0) {
+ Tcl_Obj *value = val;
+ Tcl_Obj *name;
+ int len;
+
+ if (discardDefaultValues) {
+ Tcl_Obj *dflt = argv[3];
+
+ if (ObjIsEqual(dflt, val)) {
+ continue;
+ }
+ }
+
+ name = argv[0];
+ if (Tcl_DStringLength(result) > 0) {
+ Tcl_DStringAppend(result, " ", 1);
+ }
+ Tcl_DStringAppend(result, Tcl_GetString(name), GetByteLength(name));
+ Tcl_DStringAppend(result, " ", 1);
+
+ if (resolveFontNames
+ && strcmp(Tcl_GetString(name), "-font") == 0
+ && (Tcl_ListObjLength(interp, val, &len) != TCL_OK || len == 1)) {
+ const char *s = Tcl_GetString(val);
+ unsigned len = GetByteLength(val);
+
+ /*
+ * Don't resolve font names like TkFixedFont, TkTextFont, etc.
+ */
+
+ if (len < 7
+ || strncmp(s, "Tk", 2) != 0
+ || strncmp(s + len - 4, "Font", 4) != 0) {
+ Tcl_Obj *args[3];
+ Tcl_Obj *result;
+
+ /*
+ * Try to resolve the font name to the actual font attributes.
+ */
+
+ args[0] = font;
+ args[1] = actual;
+ args[2] = val;
+
+ if ((result = GetFontAttrs(textPtr, 3, args))) {
+ value = result;
+ }
+ }
+ }
+
+ Tcl_DStringAppendElement(result, Tcl_GetString(value));
+
+ if (value != val) {
+ Tcl_DecrRefCount(value);
+ }
+ }
+ }
+ }
+
+ if (resolveFontNames) {
+ Tcl_DecrRefCount(actual);
+ Tcl_DecrRefCount(font);
+ }
+ }
}
/*
*----------------------------------------------------------------------
*
- * TextEditRedo --
+ * TextInspectCmd --
*
- * Redo the last undone change.
+ * Return information about text and the associated tags.
+ * About the format see documentation.
*
* Results:
- * None.
+ * A standard Tcl result.
*
* Side effects:
- * Apart from manipulating the undo and redo stacks, the state of the
- * rest of the widget may also change (due to whatever is being redone).
+ * Memory is allocated for the result, if needed (standard Tcl result
+ * side effects).
*
*----------------------------------------------------------------------
*/
-static int
-TextEditRedo(
- TkText *textPtr) /* Overall information about text widget. */
+static void
+GetBindings(
+ TkText *textPtr,
+ const char *name,
+ Tk_BindingTable bindingTable,
+ Tcl_DString *str)
{
- int status;
- Tcl_Obj *cmdObj;
- int code;
+ Tcl_Interp *interp = textPtr->interp;
+ Tcl_DString str2;
+ Tcl_Obj **argv;
+ int argc, i;
- if (!textPtr->sharedTextPtr->undo) {
- return TCL_OK;
+ Tk_GetAllBindings(interp, bindingTable, (ClientData) name);
+ Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp), &argc, &argv);
+ Tcl_DStringInit(&str2);
+
+ for (i = 0; i < argc; ++i) {
+ const char *event = Tcl_GetString(argv[i]);
+ const char *binding = Tk_GetBinding(interp, bindingTable, (ClientData) name, event);
+ char *p;
+
+ Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp), &argc, &argv);
+
+ Tcl_DStringStartSublist(str);
+ Tcl_DStringAppendElement(str, "bind");
+ Tcl_DStringAppendElement(str, name);
+ Tcl_DStringAppendElement(str, event);
+
+ Tcl_DStringTrunc(&str2, 0);
+ p = strchr(binding, '\n');
+ while (p) {
+ Tcl_DStringAppend(&str2, binding, p - binding);
+ Tcl_DStringAppend(&str2, "; ", 2);
+ binding = p + 1;
+ p = strchr(binding, '\n');
+ }
+ Tcl_DStringAppend(&str2, binding, -1);
+
+ Tcl_DStringAppendElement(str, Tcl_DStringValue(&str2));
+ Tcl_DStringEndSublist(str);
}
- /*
- * Turn off the undo feature temporarily while we revert a previously
- * undone compound action, setting the dirty handling mode to redo for the
- * duration (unless it is 'fixed').
- */
+ Tcl_DStringFree(&str2);
+ Tcl_ResetResult(interp);
+}
- textPtr->sharedTextPtr->undo = 0;
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_REDO;
+static int
+TextInspectCmd(
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. */
+{
+ TkSharedText *sharedTextPtr;
+ TkTextTag *prevTagPtr;
+ TkTextSegment *nextPtr;
+ TkTextSegment *prevPtr;
+ Tcl_DString buf[2];
+ Tcl_DString *str = &buf[0];
+ Tcl_DString *opts = &buf[1];
+ TkTextTag **tagArray;
+ TkTextTag *tagPtr;
+ TkTextTag *tPtr;
+ unsigned tagArrSize;
+ unsigned epoch;
+ unsigned what;
+ bool closeSubList;
+ int result;
+
+ result = GetDumpFlags(textPtr, interp, objc, objv, TK_DUMP_INSPECT_ALL, TK_DUMP_INSPECT_DFLT,
+ &what, NULL, NULL, NULL, NULL);
+ if (result != TCL_OK) {
+ return result;
+ }
+
+ Tcl_DStringInit(str);
+ Tcl_DStringInit(opts);
+ sharedTextPtr = textPtr->sharedTextPtr;
+ epoch = sharedTextPtr->inspectEpoch;
+ tagPtr = textPtr->selTagPtr; /* any non-null value */
+ nextPtr = textPtr->startMarker;
+ closeSubList = false;
+ prevTagPtr = NULL;
+ prevPtr = NULL;
+ tagArrSize = 128;
+ tagArray = malloc(tagArrSize * sizeof(tagArray[0]));
+
+ assert(textPtr->selTagPtr->textPtr == textPtr);
+ if (what & TK_DUMP_DISCARD_SEL) {
+ /* this little trick is discarding the "sel" tag */
+ textPtr->selTagPtr->textPtr = (TkText *) textPtr->selTagPtr;
+ }
+
+ if (what & TK_DUMP_TEXT_CONFIGS) {
+ TkTextInspectOptions(textPtr, textPtr, textPtr->optionTable, opts,
+ !(what & TK_DUMP_DONT_RESOLVE), false);
+ Tcl_DStringStartSublist(str);
+ Tcl_DStringAppendElement(str, "setup");
+ Tcl_DStringAppendElement(str, Tk_PathName(textPtr->tkwin));
+ Tcl_DStringAppendElement(str, Tcl_DStringValue(opts));
+ Tcl_DStringEndSublist(str);
+ }
+
+ if (what & TK_DUMP_TAG_CONFIGS) {
+ TkTextTag **tags = textPtr->sharedTextPtr->tagLookup;
+ unsigned n = textPtr->sharedTextPtr->numTags;
+ unsigned i;
+
+ for (i = 0; i < n; ++i) {
+ TkTextTag *tagPtr = tags[i];
+
+ if (tagPtr && (!(what & TK_DUMP_DISCARD_SEL) || tagPtr != textPtr->selTagPtr)) {
+ TkTextInspectOptions(textPtr, tagPtr, tagPtr->optionTable, opts,
+ !(what & TK_DUMP_DONT_RESOLVE), true);
+ Tcl_DStringStartSublist(str);
+ Tcl_DStringAppendElement(str, "configure");
+ Tcl_DStringAppendElement(str, tagPtr->name);
+ if (Tcl_DStringLength(opts) > 2) {
+ Tcl_DStringAppendElement(str, Tcl_DStringValue(opts));
+ }
+ Tcl_DStringEndSublist(str);
+ }
+ }
}
- status = TkUndoApply(textPtr->sharedTextPtr->undoStack);
+ if (what & TK_DUMP_TAG_BINDINGS) {
+ TkTextTag **tags = textPtr->sharedTextPtr->tagLookup;
+ unsigned n = textPtr->sharedTextPtr->numTags;
+ unsigned i;
+
+ for (i = 0; i < n; ++i) {
+ TkTextTag *tagPtr = tags[i];
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
+ if (tagPtr && (!(what & TK_DUMP_DISCARD_SEL) || tagPtr != textPtr->selTagPtr)) {
+ GetBindings(textPtr, tagPtr->name, sharedTextPtr->tagBindingTable, str);
+ }
+ }
}
- textPtr->sharedTextPtr->undo = 1;
- /*
- * Convert undo/redo temporary marks set by TkUndoApply() into
- * indices left in the interp result.
- */
+ do {
+ TkTextSegment *segPtr = nextPtr;
+ unsigned group = segPtr->typePtr->group;
+ const char *value = NULL;
+ const char *type = NULL;
+ bool printTags = false;
- cmdObj = Tcl_ObjPrintf("::tk::TextUndoRedoProcessMarks %s",
- Tk_PathName(textPtr->tkwin));
- Tcl_IncrRefCount(cmdObj);
- code = Tcl_EvalObjEx(textPtr->interp, cmdObj, TCL_EVAL_GLOBAL);
- if (code != TCL_OK) {
- Tcl_AddErrorInfo(textPtr->interp,
- "\n (on undoing)");
- Tcl_BackgroundException(textPtr->interp, code);
+ nextPtr = segPtr->nextPtr;
+
+ switch (group) {
+ case SEG_GROUP_BRANCH:
+ if (segPtr->typePtr == &tkTextBranchType && (what & TK_DUMP_DISPLAY)) {
+ segPtr = segPtr->body.branch.nextPtr;
+ nextPtr = segPtr->nextPtr;
+ }
+ if (!(what & SEG_GROUP_BRANCH)) {
+ continue;
+ }
+ type = "elide";
+ value = (segPtr->typePtr == &tkTextBranchType) ? "on" : "off";
+ break;
+ case SEG_GROUP_IMAGE:
+ if (!(what & SEG_GROUP_IMAGE) || !segPtr->body.ei.name) {
+ continue;
+ }
+ type = "image";
+ TkTextInspectOptions(textPtr, &segPtr->body.ei, segPtr->body.ei.optionTable, opts,
+ false, false);
+ value = Tcl_DStringValue(opts);
+ printTags = !!(what & TK_DUMP_TAG);
+ break;
+ case SEG_GROUP_WINDOW:
+ if (!(what & SEG_GROUP_WINDOW) || !segPtr->body.ew.tkwin) {
+ continue;
+ }
+ type = "window";
+ TkTextInspectOptions(textPtr, &segPtr->body.ew, segPtr->body.ew.optionTable, opts,
+ false, false);
+ value = Tcl_DStringValue(opts);
+ printTags = !!(what & TK_DUMP_TAG);
+ break;
+ case SEG_GROUP_MARK:
+ if (segPtr == textPtr->endMarker) {
+ if (prevPtr != segPtr
+ && (what & SEG_GROUP_CHAR)
+ && segPtr->sectionPtr->linePtr != TkBTreeGetLastLine(textPtr)) {
+ /* print newline before finishing */
+ type = "break";
+ printTags = !!(what & TK_DUMP_TAG);
+ tagPtr = TkBTreeGetSegmentTags(sharedTextPtr,
+ segPtr->sectionPtr->linePtr->lastPtr, textPtr);
+ nextPtr = segPtr; /* repeat this mark */
+ } else {
+ nextPtr = NULL; /* finished */
+ }
+ } else if (!(what & SEG_GROUP_MARK)) {
+ continue;
+ } else if (!TkTextIsNormalMark(segPtr)
+ && (!(what & TK_DUMP_INSERT_MARK) || segPtr != textPtr->insertMarkPtr)) {
+ continue;
+ } else {
+ type = (segPtr->typePtr == &tkTextLeftMarkType ? "left" : "right");
+ value = TkTextMarkName(sharedTextPtr, textPtr, segPtr);
+ }
+ break;
+ case SEG_GROUP_HYPHEN:
+ if (!(what & SEG_GROUP_HYPHEN)) {
+ continue;
+ }
+ printTags = !!(what & TK_DUMP_TAG);
+ type = "hyphen";
+ break;
+ case SEG_GROUP_CHAR:
+ if (what & SEG_GROUP_CHAR) {
+ printTags = !!(what & TK_DUMP_TAG);
+ if (prevPtr == segPtr || *segPtr->body.chars == '\n') {
+ type = "break";
+ nextPtr = segPtr->sectionPtr->linePtr->nextPtr->segPtr;
+ if (prevPtr == segPtr) {
+ tagPtr = prevTagPtr;
+ segPtr->body.chars[segPtr->size - 1] = '\n';
+ } else if (type && printTags) {
+ tagPtr = TkBTreeGetSegmentTags(sharedTextPtr, segPtr, textPtr);
+ }
+ } else {
+ type = "text";
+ if (segPtr->size > 1 && segPtr->body.chars[segPtr->size - 1] == '\n') {
+ nextPtr = segPtr; /* repeat this char segment */
+ segPtr->body.chars[segPtr->size - 1] = '\0';
+ }
+ value = segPtr->body.chars;
+ if (printTags) {
+ tagPtr = TkBTreeGetSegmentTags(sharedTextPtr, segPtr, textPtr);
+ }
+ }
+ } else if (!nextPtr) {
+ nextPtr = segPtr->sectionPtr->linePtr->nextPtr->segPtr;
+ }
+ break;
+ default:
+ continue;
+ }
+
+ if (closeSubList) {
+ if (what & TK_DUMP_NESTED) {
+ unsigned nextEpoch = epoch + 1;
+ unsigned numTags = 0;
+ unsigned i;
+
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (tPtr->flag == epoch) {
+ tPtr->flag = nextEpoch; /* mark as still open */
+ }
+ }
+
+ for ( ; prevTagPtr; prevTagPtr = prevTagPtr->succPtr) {
+ if (prevTagPtr->flag == epoch) { /* should be closed? */
+ if (numTags == tagArrSize) {
+ tagArrSize *= 2;
+ tagArray = realloc(tagArray, tagArrSize * sizeof(tagArray[0]));
+ }
+ tagArray[numTags++] = prevTagPtr;
+ prevTagPtr->flag = 0; /* mark as closed */
+ }
+ }
+
+ Tcl_DStringStartSublist(str);
+ TkTextSortTags(numTags, tagArray);
+ for (i = 0; i < numTags; ++i) {
+ Tcl_DStringAppendElement(str, tagArray[i]->name);
+ }
+ Tcl_DStringEndSublist(str);
+ }
+
+ prevTagPtr = NULL;
+ closeSubList = false;
+ Tcl_DStringEndSublist(str);
+ }
+
+ if (type) {
+ Tcl_DStringStartSublist(str);
+ Tcl_DStringAppendElement(str, type);
+ if (value) {
+ Tcl_DStringAppendElement(str, value);
+ }
+ closeSubList = true;
+
+ if (printTags) {
+ unsigned numTags = 0;
+ unsigned i;
+
+ prevTagPtr = tagPtr;
+
+ if (what & TK_DUMP_NESTED) {
+ epoch += 1;
+
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (tPtr->flag != epoch) { /* should be opened? */
+ if (numTags == tagArrSize) {
+ tagArrSize *= 2;
+ tagArray = realloc(tagArray, tagArrSize * sizeof(tagArray[0]));
+ }
+ tagArray[numTags++] = tPtr;
+ tPtr->flag = epoch; /* mark as open */
+ }
+ tPtr->succPtr = tPtr->nextPtr;
+ }
+ } else {
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (numTags == tagArrSize) {
+ tagArrSize *= 2;
+ tagArray = realloc(tagArray, tagArrSize * sizeof(tagArray[0]));
+ }
+ tagArray[numTags++] = tPtr;
+ }
+ }
+
+ Tcl_DStringStartSublist(str);
+ TkTextSortTags(numTags, tagArray);
+ for (i = 0; i < numTags; ++i) {
+ Tcl_DStringAppendElement(str, tagArray[i]->name);
+ }
+ Tcl_DStringEndSublist(str);
+ }
+ }
+
+ prevPtr = segPtr;
+ } while (nextPtr);
+
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_DStringValue(str), Tcl_DStringLength(str)));
+ Tcl_DStringFree(str);
+ Tcl_DStringFree(opts);
+
+ textPtr->selTagPtr->textPtr = textPtr; /* restore */
+ sharedTextPtr->inspectEpoch = epoch;
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InspectRetainedUndoItems --
+ *
+ * Return information about content of retained undo items, these
+ * items are not yet pushed onto undo stack.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory is allocated for the result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+InspectRetainedUndoItems(
+ const TkSharedText *sharedTextPtr,
+ Tcl_Obj *objPtr)
+{
+ if (sharedTextPtr->undoTagListCount > 0 || sharedTextPtr->undoMarkListCount > 0) {
+ Tcl_Obj *resultPtr = Tcl_NewObj();
+ int i;
+
+ for (i = 0; i < sharedTextPtr->undoTagListCount; ++i) {
+ TkTextInspectUndoTagItem(sharedTextPtr, sharedTextPtr->undoTagList[i], resultPtr);
+ }
+
+ for (i = 0; i < sharedTextPtr->undoMarkListCount; ++i) {
+ TkTextInspectUndoMarkItem(sharedTextPtr, &sharedTextPtr->undoMarkList[i], resultPtr);
+ }
+
+ Tcl_ListObjLength(NULL, resultPtr, &i);
+ if (i == 0) {
+ Tcl_IncrRefCount(resultPtr);
+ Tcl_DecrRefCount(resultPtr);
+ } else {
+ Tcl_ListObjAppendElement(NULL, objPtr, resultPtr);
+ }
}
- Tcl_DecrRefCount(cmdObj);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InspectUndoStack --
+ *
+ * Return information about content of undo/redo stack.
+ *
+ * Results:
+ * A Tcl object.
+ *
+ * Side effects:
+ * Memory is allocated for the result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+InspectUndoStack(
+ const TkSharedText *sharedTextPtr,
+ InspectUndoStackProc firstAtomProc,
+ InspectUndoStackProc nextAtomProc,
+ Tcl_Obj *objPtr)
+{
+ TkTextUndoStack undoStack;
+ const TkTextUndoAtom *atom;
+ Tcl_Obj *atomPtr;
+ unsigned i;
- return status;
+ assert(sharedTextPtr->undoStack);
+
+ undoStack = sharedTextPtr->undoStack;
+
+ for (atom = firstAtomProc(undoStack); atom; atom = nextAtomProc(undoStack)) {
+ atomPtr = Tcl_NewObj();
+
+ for (i = 0; i < atom->arraySize; ++i) {
+ const TkTextUndoToken *token = (const TkTextUndoToken *) atom->array[i].item;
+ Tcl_Obj *subAtomPtr = token->undoType->inspectProc(sharedTextPtr, token);
+ Tcl_ListObjAppendElement(NULL, atomPtr, subAtomPtr);
+ }
+
+ Tcl_ListObjAppendElement(NULL, objPtr, atomPtr);
+ }
}
/*
@@ -5241,6 +8596,15 @@ TextEditRedo(
*----------------------------------------------------------------------
*/
+static Tcl_Obj *
+GetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *token)
+{
+ assert(token->undoType->commandProc);
+ return token->undoType->commandProc(sharedTextPtr, token);
+}
+
static int
TextEditCmd(
TkText *textPtr, /* Information about text widget. */
@@ -5248,75 +8612,165 @@ TextEditCmd(
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
- int index, setModified, oldModified;
- int canRedo = 0;
- int canUndo = 0;
-
+ int index;
+ bool setModified, oldModified;
+ TkSharedText *sharedTextPtr;
static const char *const editOptionStrings[] = {
- "canundo", "canredo", "modified", "redo", "reset", "separator",
- "undo", NULL
+ "altered",
+#if SUPPORT_DEPRECATED_CANUNDO_REDO
+ "canredo", "canundo",
+#endif /* SUPPORT_DEPRECATED_CANUNDO_REDO */
+ "info", "inspect", "irreversible", "modified", "recover", "redo", "reset",
+ "separator", "undo", NULL
};
enum editOptions {
- EDIT_CANUNDO, EDIT_CANREDO, EDIT_MODIFIED, EDIT_REDO, EDIT_RESET,
+ EDIT_ALTERED,
+#if SUPPORT_DEPRECATED_CANUNDO_REDO
+ EDIT_CANREDO, EDIT_CANUNDO,
+#endif /* SUPPORT_DEPRECATED_CANUNDO_REDO */
+ EDIT_INFO, EDIT_INSPECT, EDIT_IRREVERSIBLE, EDIT_MODIFIED, EDIT_RECOVER, EDIT_REDO, EDIT_RESET,
EDIT_SEPARATOR, EDIT_UNDO
};
+ sharedTextPtr = textPtr->sharedTextPtr;
+
if (objc < 3) {
Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
return TCL_ERROR;
}
-
if (Tcl_GetIndexFromObjStruct(interp, objv[2], editOptionStrings,
sizeof(char *), "edit option", 0, &index) != TCL_OK) {
return TCL_ERROR;
}
switch ((enum editOptions) index) {
- case EDIT_CANREDO:
+ case EDIT_ALTERED:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?boolean?");
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(sharedTextPtr->isAltered));
+ return TCL_OK;
+ break;
+#if SUPPORT_DEPRECATED_CANUNDO_REDO
+ case EDIT_CANREDO: {
+ static bool warnDeprecated = true;
+ bool canRedo = false;
+
+ if (warnDeprecated) {
+ warnDeprecated = false;
+ fprintf(stderr, "Command \"edit canredo\" is deprecated, please use \"edit info\"\n");
+ }
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- if (textPtr->sharedTextPtr->undo) {
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
+ if (textPtr->sharedTextPtr->undoStack) {
+ canRedo = TkTextUndoGetCurrentRedoStackDepth(textPtr->sharedTextPtr->undoStack) > 0;
}
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(canRedo));
break;
- case EDIT_CANUNDO:
+ }
+ case EDIT_CANUNDO: {
+ static bool warnDeprecated = true;
+ bool canUndo = false;
+
+ if (warnDeprecated) {
+ warnDeprecated = false;
+ fprintf(stderr, "Command \"edit canundo\" is deprecated, please use \"edit info\"\n");
+ }
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
if (textPtr->sharedTextPtr->undo) {
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
+ canUndo = TkTextUndoGetCurrentUndoStackDepth(textPtr->sharedTextPtr->undoStack) > 0;
}
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(canUndo));
break;
+ }
+#endif /* SUPPORT_DEPRECATED_CANUNDO_REDO */
+ case EDIT_INFO:
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?array?");
+ return TCL_ERROR;
+ } else {
+ Tcl_SetObjResult(textPtr->interp, MakeEditInfo(interp, textPtr, objc == 4 ? objv[3] : NULL));
+ }
+ break;
+ case EDIT_INSPECT:
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?stack?");
+ return TCL_ERROR;
+ } else {
+ char const *stack = (objc == 4) ? Tcl_GetString(objv[3]) : NULL;
+
+ if (stack && strcmp(stack, "undo") != 0 && strcmp(stack, "redo") != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad stack argument \"%s\": must be \"undo\" or \"redo\"", stack));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "STACK_VALUE", NULL);
+ return TCL_ERROR;
+ }
+ if (sharedTextPtr->undoStack) {
+ Tcl_Obj *undoResultPtr = NULL;
+ Tcl_Obj *redoResultPtr = NULL;
+
+ if (!stack || stack[0] == 'u') {
+ undoResultPtr = Tcl_NewObj();
+ InspectRetainedUndoItems(sharedTextPtr, undoResultPtr);
+ InspectUndoStack(sharedTextPtr, TkTextUndoFirstUndoAtom,
+ TkTextUndoNextUndoAtom, undoResultPtr);
+ }
+ if (!stack || stack[0] == 'r') {
+ redoResultPtr = Tcl_NewObj();
+ InspectUndoStack(sharedTextPtr, TkTextUndoFirstRedoAtom,
+ TkTextUndoNextRedoAtom, redoResultPtr);
+ }
+ if (!stack) {
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, undoResultPtr);
+ Tcl_ListObjAppendElement(NULL, objPtr, redoResultPtr);
+ Tcl_SetObjResult(interp, objPtr);
+ } else if (stack[0] == 'u') {
+ Tcl_SetObjResult(interp, undoResultPtr);
+ } else {
+ Tcl_SetObjResult(interp, redoResultPtr);
+ }
+ }
+ }
+ break;
+ case EDIT_IRREVERSIBLE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?boolean?");
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(sharedTextPtr->isIrreversible));
+ break;
case EDIT_MODIFIED:
if (objc == 3) {
- Tcl_SetObjResult(interp,
- Tcl_NewBooleanObj(textPtr->sharedTextPtr->isDirty));
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(sharedTextPtr->isModified));
return TCL_OK;
} else if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "?boolean?");
return TCL_ERROR;
- } else if (Tcl_GetBooleanFromObj(interp, objv[3],
- &setModified) != TCL_OK) {
+ } else if (Tcl_GetBooleanFromObj(interp, objv[3], (int *) &setModified) != TCL_OK) {
return TCL_ERROR;
}
/*
- * Set or reset the dirty info, and trigger a Modified event.
+ * Set or reset the modified status, and trigger a <<Modified>> event.
*/
- setModified = setModified ? 1 : 0;
+ oldModified = sharedTextPtr->isModified;
+ sharedTextPtr->isModified = setModified;
- oldModified = textPtr->sharedTextPtr->isDirty;
- textPtr->sharedTextPtr->isDirty = setModified;
- if (setModified) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_FIXED;
- } else {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
+ /*
+ * Setting the flag to 'false' is clearing the user's decision.
+ */
+
+ sharedTextPtr->userHasSetModifiedFlag = setModified;
+ if (sharedTextPtr->undoStack) {
+ sharedTextPtr->undoLevel = TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack);
}
/*
@@ -5324,59 +8778,167 @@ TextEditCmd(
* However, degree of modified-ness doesn't matter. [Bug 1799782]
*/
- if ((!oldModified) != (!setModified)) {
- GenerateModifiedEvent(textPtr);
+ assert(setModified == true || setModified == false);
+
+ if (oldModified != setModified) {
+ GenerateEvent(textPtr->sharedTextPtr, "Modified");
}
break;
- case EDIT_REDO:
+ case EDIT_RECOVER:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- if (TextEditRedo(textPtr)) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to redo", -1));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_REDO", NULL);
+ if (sharedTextPtr->undoStack) {
+ int redoDepth;
+
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ ErrorNotAllowed(interp, "cannot recover inside undo/redo operation");
+ return TCL_ERROR;
+ }
+
+ redoDepth = TkTextUndoGetMaxRedoDepth(sharedTextPtr->undoStack);
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoSetMaxStackDepth(sharedTextPtr->undoStack, textPtr->maxUndoDepth, 0);
+
+ while (TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack) > 0) {
+ TkTextUndoDoUndo(sharedTextPtr->undoStack);
+ }
+
+ TkTextUndoSetMaxStackDepth(sharedTextPtr->undoStack, textPtr->maxUndoDepth, redoDepth);
+ }
+ break;
+ case EDIT_REDO:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
- if (!canUndo || !canRedo) {
- GenerateUndoStackEvent(textPtr);
+ if (sharedTextPtr->undoStack) {
+ /*
+ * It's possible that this command command will be invoked inside the "watch" callback,
+ * but this is not allowed when performing undo/redo.
+ */
+
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ ErrorNotAllowed(interp, "cannot redo inside undo/redo operation");
+ return TCL_ERROR;
+ }
+
+ if (TkTextUndoGetCurrentRedoStackDepth(sharedTextPtr->undoStack) == 0) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to redo", -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_REDO", NULL);
+ return TCL_ERROR;
+ }
+
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoDoRedo(sharedTextPtr->undoStack);
}
break;
case EDIT_RESET:
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 3, objv, NULL);
+ if (objc == 3) {
+ if (sharedTextPtr->undoStack) {
+ /*
+ * It's possible that this command command will be invoked inside the "watch" callback,
+ * but this is not allowed when performing undo/redo.
+ */
+
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ ErrorNotAllowed(interp, "cannot reset stack inside undo/redo operation");
+ return TCL_ERROR;
+ }
+
+ TkTextUndoClearStack(sharedTextPtr->undoStack);
+ sharedTextPtr->undoLevel = 0;
+ sharedTextPtr->isAltered = false;
+ sharedTextPtr->isIrreversible = false;
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ }
+ return TCL_OK;
+ } else if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?stack?");
+ return TCL_ERROR;
+ } else {
+ char const *stack = Tcl_GetString(objv[3]);
+
+ if (strcmp(stack, "undo") != 0 && strcmp(stack, "redo") != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad stack argument \"%s\": must be \"undo\" or \"redo\"", stack));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "STACK_VALUE", NULL);
+ return TCL_ERROR;
+ }
+ if (sharedTextPtr->undoStack) {
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ /*
+ * It's possible that this command command will be invoked inside
+ * the "watch" callback, but this is not allowed when performing
+ * undo/redo.
+ */
+
+ ErrorNotAllowed(interp, "cannot reset stack inside undo/redo operation");
+ return TCL_ERROR;
+ }
+ if (stack[0] == 'u') {
+ TkTextUndoClearUndoStack(sharedTextPtr->undoStack);
+ sharedTextPtr->undoLevel = 0;
+ sharedTextPtr->isAltered = false;
+ sharedTextPtr->isIrreversible = false;
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ } else {
+ TkTextUndoClearRedoStack(sharedTextPtr->undoStack);
+ }
+ }
return TCL_ERROR;
- }
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
- TkUndoClearStacks(textPtr->sharedTextPtr->undoStack);
- if (canUndo || canRedo) {
- GenerateUndoStackEvent(textPtr);
}
break;
- case EDIT_SEPARATOR:
- if (objc != 3) {
+ case EDIT_SEPARATOR: {
+ bool immediately = false;
+
+ if (objc == 4) {
+ if (strcmp(Tcl_GetString(objv[3]), "-immediately")) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -immediately", Tcl_GetString(objv[3])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
+ return TCL_ERROR;
+ }
+ immediately = true;
+ } else if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack);
+ if (sharedTextPtr->undoStack) {
+ if (immediately) {
+ PushRetainedUndoTokens(sharedTextPtr);
+ }
+ TkTextUndoPushSeparator(sharedTextPtr->undoStack, immediately);
+ sharedTextPtr->lastUndoTokenType = -1;
+ }
break;
+ }
case EDIT_UNDO:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
- if (TextEditUndo(textPtr)) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to undo", -1));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_UNDO", NULL);
- return TCL_ERROR;
- }
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- if (!canRedo || !canUndo) {
- GenerateUndoStackEvent(textPtr);
+ if (sharedTextPtr->undoStack) {
+ /*
+ * It's possible that this command command will be invoked inside the "watch" callback,
+ * but this is not allowed when performing undo/redo.
+ */
+
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ ErrorNotAllowed(interp, "cannot undo inside undo/redo operation");
+ return TCL_ERROR;
+ }
+
+ PushRetainedUndoTokens(sharedTextPtr);
+
+ if (TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack) == 0) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to undo", -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_UNDO", NULL);
+ return TCL_ERROR;
+ }
+
+ TkTextUndoDoUndo(sharedTextPtr->undoStack);
}
break;
}
@@ -5386,6 +8948,161 @@ TextEditCmd(
/*
*----------------------------------------------------------------------
*
+ * MakeEditInfo --
+ *
+ * Returns the array containing the "edit info" information.
+ *
+ * Results:
+ * Tcl_Obj of list type containing the required information.
+ *
+ * Side effects:
+ * Some memory will be allocated:
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+MakeEditInfo(
+ Tcl_Interp *interp, /* Current interpreter. */
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Obj *arrayPtr) /* Name of array, may be NULL. */
+{
+ enum {
+ INFO_UNDOSTACKSIZE, INFO_REDOSTACKSIZE, INFO_UNDODEPTH, INFO_REDODEPTH,
+ INFO_UNDOBYTESIZE, INFO_REDOBYTESIZE, INFO_UNDOCOMMANDS, INFO_REDOCOMMANDS,
+ INFO_BYTESIZE, INFO_TOTALBYTESIZE, INFO_LINES, INFO_TOTALLINES, INFO_IMAGES,
+ INFO_WINDOWS, INFO_DISPIMAGES, INFO_DISPWINDOWS, INFO_TAGS, INFO_USEDTAGS,
+ INFO_MARKS, INFO_GENERATEDMARKS, INFO_LINESPERNODE,
+ INFO_LAST /* must be last item */
+ };
+
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextUndoStack st = sharedTextPtr->undoStack;
+ Tcl_Obj *var = arrayPtr ? arrayPtr : Tcl_NewStringObj("", 0);
+ Tcl_Obj *name[INFO_LAST];
+ Tcl_Obj *value[INFO_LAST];
+ int usedTags, i;
+
+ name[INFO_UNDOSTACKSIZE ] = Tcl_NewStringObj("undostacksize", -1);
+ name[INFO_REDOSTACKSIZE ] = Tcl_NewStringObj("redostacksize", -1);
+ name[INFO_UNDODEPTH ] = Tcl_NewStringObj("undodepth", -1);
+ name[INFO_REDODEPTH ] = Tcl_NewStringObj("redodepth", -1);
+ name[INFO_UNDOBYTESIZE ] = Tcl_NewStringObj("undobytesize", -1);
+ name[INFO_REDOBYTESIZE ] = Tcl_NewStringObj("redobytesize", -1);
+ name[INFO_UNDOCOMMANDS ] = Tcl_NewStringObj("undocommands", -1);
+ name[INFO_REDOCOMMANDS ] = Tcl_NewStringObj("redocommands", -1);
+ name[INFO_BYTESIZE ] = Tcl_NewStringObj("bytesize", -1);
+ name[INFO_TOTALBYTESIZE ] = Tcl_NewStringObj("totalbytesize", -1);
+ name[INFO_LINES ] = Tcl_NewStringObj("lines", -1);
+ name[INFO_TOTALLINES ] = Tcl_NewStringObj("totallines", -1);
+ name[INFO_IMAGES ] = Tcl_NewStringObj("images", -1);
+ name[INFO_WINDOWS ] = Tcl_NewStringObj("windows", -1);
+ name[INFO_DISPIMAGES ] = Tcl_NewStringObj("visibleimages", -1);
+ name[INFO_DISPWINDOWS ] = Tcl_NewStringObj("visiblewindows", -1);
+ name[INFO_TAGS ] = Tcl_NewStringObj("tags", -1);
+ name[INFO_USEDTAGS ] = Tcl_NewStringObj("usedtags", -1);
+ name[INFO_MARKS ] = Tcl_NewStringObj("marks", -1);
+ name[INFO_GENERATEDMARKS] = Tcl_NewStringObj("generatedmarks", -1);
+ name[INFO_LINESPERNODE ] = Tcl_NewStringObj("linespernode", -1);
+
+ if (st) {
+ const TkTextUndoAtom *atom;
+ Tcl_Obj *listPtr;
+
+ value[INFO_UNDOSTACKSIZE] = Tcl_NewIntObj(TkTextUndoCountUndoItems(st));
+ value[INFO_REDOSTACKSIZE] = Tcl_NewIntObj(TkTextUndoCountRedoItems(st));
+ value[INFO_UNDODEPTH ] = Tcl_NewIntObj(TkTextUndoGetCurrentUndoStackDepth(st));
+ value[INFO_REDODEPTH ] = Tcl_NewIntObj(TkTextUndoGetCurrentRedoStackDepth(st));
+ value[INFO_UNDOBYTESIZE ] = Tcl_NewIntObj(TkTextUndoGetCurrentUndoSize(st));
+ value[INFO_REDOBYTESIZE ] = Tcl_NewIntObj(TkTextUndoGetCurrentRedoSize(st));
+ value[INFO_UNDOCOMMANDS ] = Tcl_NewObj();
+ value[INFO_REDOCOMMANDS ] = Tcl_NewObj();
+
+ listPtr = value[TkTextUndoIsPerformingUndo(st) ? INFO_REDOCOMMANDS : INFO_UNDOCOMMANDS];
+
+ for (i = sharedTextPtr->undoTagListCount - 1; i >= 0; --i) {
+ const TkTextTag *tagPtr = sharedTextPtr->undoTagList[i];
+
+ if (tagPtr->recentTagAddRemoveToken && !tagPtr->recentTagAddRemoveTokenIsNull) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, tagPtr->recentTagAddRemoveToken));
+ }
+ if (tagPtr->recentChangePriorityToken && tagPtr->savedPriority != tagPtr->priority) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, tagPtr->recentTagAddRemoveToken));
+ }
+ }
+
+ for (i = sharedTextPtr->undoMarkListCount - 1; i >= 0; --i) {
+ const TkTextMarkChange *changePtr = &sharedTextPtr->undoMarkList[i];
+
+ if (changePtr->setMark) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, changePtr->setMark));
+ }
+ if (changePtr->moveMark) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, changePtr->moveMark));
+ }
+ if (changePtr->toggleGravity) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, changePtr->toggleGravity));
+ }
+ }
+
+ atom = TkTextUndoIsPerformingUndo(st) ?
+ TkTextUndoCurrentRedoAtom(st) : TkTextUndoCurrentUndoAtom(st);
+
+ if (atom) {
+ for (i = atom->arraySize - 1; i >= 0; --i) {
+ const TkTextUndoSubAtom *subAtom = atom->array + i;
+ TkTextUndoToken *token = subAtom->item;
+
+ Tcl_ListObjAppendElement(interp, listPtr, GetCommand(sharedTextPtr, token));
+ }
+ }
+ } else {
+ value[INFO_UNDOSTACKSIZE] =
+ value[INFO_REDOSTACKSIZE] =
+ value[INFO_UNDODEPTH] =
+ value[INFO_REDODEPTH] =
+ value[INFO_UNDOBYTESIZE] =
+ value[INFO_REDOBYTESIZE] = Tcl_NewIntObj(0);
+ value[INFO_UNDOCOMMANDS] = value[INFO_REDOCOMMANDS] = Tcl_NewObj();
+ }
+
+ usedTags = TkTextTagSetCount(TkBTreeRootTagInfo(sharedTextPtr->tree));
+
+ /* Related to this widget */
+
+ value[INFO_BYTESIZE ] = Tcl_NewIntObj(TkBTreeSize(sharedTextPtr->tree, textPtr));
+ value[INFO_LINES ] = Tcl_NewIntObj(TkBTreeNumLines(sharedTextPtr->tree, textPtr));
+ value[INFO_DISPIMAGES ] = Tcl_NewIntObj(TkTextCountVisibleImages(textPtr));
+ value[INFO_DISPWINDOWS ] = Tcl_NewIntObj(TkTextCountVisibleWindows(textPtr));
+
+ /* Related to shared resource */
+
+ value[INFO_TOTALBYTESIZE ] = Tcl_NewIntObj(TkBTreeSize(sharedTextPtr->tree, NULL));
+ value[INFO_TOTALLINES ] = Tcl_NewIntObj(TkBTreeNumLines(sharedTextPtr->tree, NULL));
+ value[INFO_IMAGES ] = Tcl_NewIntObj(sharedTextPtr->numImages);
+ value[INFO_WINDOWS ] = Tcl_NewIntObj(sharedTextPtr->numWindows);
+ value[INFO_TAGS ] = Tcl_NewIntObj(sharedTextPtr->numTags);
+ value[INFO_USEDTAGS ] = Tcl_NewIntObj(usedTags);
+ value[INFO_MARKS ] = Tcl_NewIntObj(sharedTextPtr->numMarks);
+ value[INFO_GENERATEDMARKS] = Tcl_NewIntObj(sharedTextPtr->numPrivateMarks);
+ value[INFO_LINESPERNODE ] = Tcl_NewIntObj(TkBTreeLinesPerNode(sharedTextPtr->tree));
+
+ Tcl_UnsetVar(interp, Tcl_GetString(var), 0);
+ for (i = 0; i < sizeof(name)/sizeof(name[0]); ++i) {
+ Tcl_ObjSetVar2(interp, var, name[i], value[i], 0);
+ }
+
+ return var;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TextGetText --
*
* Returns the text from indexPtr1 to indexPtr2, placing that text in a
@@ -5405,8 +9122,8 @@ TextEditCmd(
*
* Results:
* Tcl_Obj of string type containing the specified text. If the
- * visibleOnly flag is set to 1, then only those characters which are not
- * elided will be returned. Otherwise (flag is 0) all characters in the
+ * visibleOnly flag is set to true, then only those characters which are not
+ * elided will be returned. Otherwise (flag is false) all characters in the
* given range are returned.
*
* Side effects:
@@ -5418,61 +9135,345 @@ TextEditCmd(
static Tcl_Obj *
TextGetText(
- const TkText *textPtr, /* Information about text widget. */
+ TkText *textPtr, /* Information about text widget. */
const TkTextIndex *indexPtr1,
/* Get text from this index... */
const TkTextIndex *indexPtr2,
/* ...to this index. */
- int visibleOnly) /* If non-zero, then only return non-elided
- * characters. */
+ TkTextIndex *lastIndexPtr, /* Position before last character of the result, can be NULL. */
+ Tcl_Obj *resultPtr, /* Append text to this object, can be NULL. */
+ unsigned maxBytes, /* Maximal number of bytes. */
+ bool visibleOnly, /* If true, then only return non-elided characters. */
+ bool includeHyphens) /* If true, then also include soft hyphens. */
{
- TkTextIndex tmpIndex;
- Tcl_Obj *resultPtr = Tcl_NewObj();
+ TkTextSegment *segPtr, *lastPtr;
+ TkTextIndex index;
+ int offset1, offset2;
- TkTextMakeByteIndex(indexPtr1->tree, textPtr,
- TkBTreeLinesTo(textPtr, indexPtr1->linePtr),
- indexPtr1->byteIndex, &tmpIndex);
+ assert(textPtr);
+ assert(TkTextIndexCompare(indexPtr1, indexPtr2) <= 0);
- if (TkTextIndexCmp(indexPtr1, indexPtr2) < 0) {
- while (1) {
- int offset;
- TkTextSegment *segPtr = TkTextIndexToSeg(&tmpIndex, &offset);
- int last = segPtr->size, last2;
+ if (!resultPtr) {
+ resultPtr = Tcl_NewObj();
+ }
- if (tmpIndex.linePtr == indexPtr2->linePtr) {
- /*
- * The last line that was requested must be handled carefully,
- * because we may need to break out of this loop in the middle
- * of the line.
- */
+ segPtr = TkTextIndexGetContentSegment(indexPtr1, &offset1);
+ if (lastIndexPtr) {
+ *lastIndexPtr = *indexPtr2;
+ }
- if (indexPtr2->byteIndex == tmpIndex.byteIndex) {
- break;
+ if (visibleOnly && TkTextSegmentIsElided(textPtr, segPtr)) {
+ index = *indexPtr1;
+ if (!TkTextSkipElidedRegion(&index) || TkTextIndexCompare(&index, indexPtr2) >= 0) {
+ return resultPtr; /* end of text reached */
+ }
+ segPtr = TkTextIndexGetContentSegment(&index, &offset1);
+ }
+
+ lastPtr = TkTextIndexGetContentSegment(indexPtr2, &offset2);
+
+ if (segPtr == lastPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset1, MIN(maxBytes, offset2 - offset1));
+ }
+ } else {
+ TkTextLine *linePtr = segPtr->sectionPtr->linePtr;
+
+ TkTextIndexClear(&index, textPtr);
+
+ if (segPtr->typePtr == &tkTextCharType) {
+ unsigned nbytes = MIN(maxBytes, segPtr->size - offset1);
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset1, nbytes);
+ if ((maxBytes -= nbytes) == 0) {
+ return resultPtr;
+ }
+ } else if (segPtr->typePtr == &tkTextHyphenType) {
+ if (includeHyphens) {
+ Tcl_AppendToObj(resultPtr, "\xc2\xad", 2); /* U+002D */
+ if ((maxBytes -= MIN(maxBytes, 2)) == 0) {
+ return resultPtr;
}
- last2 = indexPtr2->byteIndex - tmpIndex.byteIndex + offset;
- if (last2 < last) {
- last = last2;
+ }
+ } else if (segPtr->typePtr == &tkTextBranchType) {
+ if (visibleOnly) {
+ TkTextIndexSetSegment(&index, segPtr = segPtr->body.branch.nextPtr);
+ if (TkTextIndexRestrictToEndRange(&index) >= 0) {
+ return resultPtr; /* end of text reached */
+ }
+ linePtr = segPtr->sectionPtr->linePtr;
+ }
+ }
+ if (!(segPtr = segPtr->nextPtr)) {
+ assert(linePtr->nextPtr);
+ linePtr = linePtr->nextPtr;
+ segPtr = linePtr->segPtr;
+ }
+ while (segPtr != lastPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ unsigned nbytes = MIN(maxBytes, segPtr->size);
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars, nbytes);
+ if ((maxBytes -= nbytes) == 0) {
+ if (lastIndexPtr) {
+ TkTextIndexSetSegment(lastIndexPtr, segPtr);
+ TkTextIndexAddToByteIndex(lastIndexPtr, nbytes);
+ }
+ return resultPtr; /* end of text reached */
+ }
+ } else if (segPtr->typePtr == &tkTextHyphenType) {
+ if (includeHyphens) {
+ Tcl_AppendToObj(resultPtr, "\xc2\xad", 2); /* U+002D */
+ if ((maxBytes -= MIN(maxBytes, 2)) == 0) {
+ return resultPtr;
+ }
+ }
+ } else if (segPtr->typePtr == &tkTextBranchType) {
+ if (visibleOnly) {
+ TkTextIndexSetSegment(&index, segPtr = segPtr->body.branch.nextPtr);
+ if (TkTextIndexRestrictToEndRange(&index) >= 0) {
+ return resultPtr; /* end of text reached */
+ }
+ linePtr = segPtr->sectionPtr->linePtr;
}
}
- if (segPtr->typePtr == &tkTextCharType &&
- !(visibleOnly && TkTextIsElided(textPtr,&tmpIndex,NULL))){
- Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset,
- last - offset);
+ if (!(segPtr = segPtr->nextPtr)) {
+ assert(linePtr->nextPtr);
+ linePtr = linePtr->nextPtr;
+ segPtr = linePtr->segPtr;
}
- TkTextIndexForwBytes(textPtr, &tmpIndex, last-offset, &tmpIndex);
+ }
+ if (offset2 > 0) {
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars, MIN(maxBytes, offset2));
}
}
+
return resultPtr;
}
/*
*----------------------------------------------------------------------
*
- * GenerateModifiedEvent --
+ * TriggerWatchEdit --
+ *
+ * Trigger the watch command for delete/insert operations, see the
+ * documentation for details on what it does.
+ *
+ * Results:
+ * Returns 'false' if the referenced widget has been destroyed, otherwise
+ * 'true' will be returned.
+ *
+ * Side effects:
+ * It might happen that the receiver of the "watch" command is destroying the widget.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+AppendTags(
+ Tcl_DString *buf,
+ TkTextTag *tagPtr)
+{
+ Tcl_DStringStartSublist(buf);
+ for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
+ Tcl_DStringAppendElement(buf, tagPtr->name);
+ }
+ Tcl_DStringEndSublist(buf);
+}
+
+static bool
+TriggerWatchEdit(
+ TkText *textPtr, /* Information about text widget. */
+ const char *operation, /* The triggering operation. */
+ const TkTextIndex *indexPtr1, /* Start index for deletion / insert. */
+ const TkTextIndex *indexPtr2, /* End index after insert / before deletion. */
+ const char *string, /* Deleted/inserted chars. */
+ bool final) /* Flag indicating whether this is a final part. */
+{
+ TkSharedText *sharedTextPtr;
+ TkText *peerArr[20];
+ TkText **peers = peerArr;
+ TkText *tPtr;
+ unsigned i, n = 0;
+ bool rc = true;
+
+ assert(textPtr->sharedTextPtr->triggerWatchCmd);
+ assert(!indexPtr1 == !indexPtr2);
+ assert(strcmp(operation, "insert") == 0 || strcmp(operation, "delete") == 0);
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ sharedTextPtr->triggerWatchCmd = false;
+
+ if (sharedTextPtr->numPeers > sizeof(peerArr) / sizeof(peerArr[0])) {
+ peers = malloc(sharedTextPtr->numPeers * sizeof(peerArr[0]));
+ }
+
+ /*
+ * Firstly save all peers, we have to take into account that the list of
+ * peers is changing when executing the "watch" command.
+ */
+
+ peers[n++] = textPtr;
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr != textPtr) {
+ peers[n++] = tPtr;
+ }
+ tPtr->refCount += 1;
+ }
+
+ for (i = 0; i < sharedTextPtr->numPeers; ++i) {
+ TkText *tPtr = peers[i];
+
+ if (!(tPtr->flags & DESTROYED) && tPtr->watchCmd) {
+ TkTextIndex index[4];
+
+ if (indexPtr1) {
+ TkTextSegment *startMarker;
+ TkTextSegment *endMarker;
+ int cmp;
+
+ index[0] = *indexPtr1;
+ index[1] = *indexPtr2;
+
+ startMarker = tPtr->startMarker;
+ endMarker = tPtr->endMarker;
+
+ if (startMarker != sharedTextPtr->startMarker) {
+ TkTextIndex start;
+ TkTextIndexClear(&start, tPtr);
+ TkTextIndexSetSegment(&start, startMarker);
+ if (TkTextIndexCompare(&start, &index[0]) > 0) {
+ index[0] = start;
+ }
+ }
+ if (endMarker != sharedTextPtr->endMarker) {
+ TkTextIndex end;
+ TkTextIndexClear(&end, tPtr);
+ TkTextIndexSetSegment(&end, endMarker);
+ if (TkTextIndexCompare(&end, &index[1]) < 0) {
+ index[1] = end;
+ }
+ }
+
+ if ((cmp = TkTextIndexCompare(&index[0], &index[1])) <= 0) {
+ TkTextTag *tagPtr;
+ TkTextIndex myIndex;
+ Tcl_DString buf;
+ char idx[2][TK_POS_CHARS];
+ char const *arg;
+
+ TkTextPrintIndex(tPtr, &index[0], idx[0]);
+ TkTextPrintIndex(tPtr, &index[1], idx[1]);
+
+ Tcl_DStringInit(&buf);
+ Tcl_DStringAppendElement(&buf, string);
+
+ tagPtr = NULL;
+ if (TkTextIndexBackChars(tPtr, &index[0], 1, &myIndex, COUNT_CHARS)) {
+ tagPtr = TkBTreeGetTags(&myIndex);
+ }
+ AppendTags(&buf, tagPtr);
+ AppendTags(&buf, TkBTreeGetTags(&index[1]));
+ AppendTags(&buf, cmp == 0 ? NULL : TkBTreeGetTags(&index[0]));
+ if (*operation == 'd') {
+ tagPtr = NULL;
+ if (cmp && TkTextIndexBackChars(tPtr, &index[1], 1, &myIndex, COUNT_CHARS)) {
+ tagPtr = TkBTreeGetTags(&myIndex);
+ }
+ AppendTags(&buf, tagPtr);
+ }
+ Tcl_DStringAppendElement(&buf, final ? "yes" : "no");
+ arg = Tcl_DStringValue(&buf);
+
+ if (!TkTextTriggerWatchCmd(tPtr, operation, idx[0], idx[1], arg, true)
+ && tPtr == textPtr) {
+ rc = false; /* this widget has been destroyed */
+ }
+
+ Tcl_DStringFree(&buf);
+ }
+ } else {
+ if (!TkTextTriggerWatchCmd(textPtr, operation, NULL, NULL, NULL, true)
+ && tPtr == textPtr) {
+ rc = false; /* this widget has been destroyed */
+ }
+ }
+ }
+
+ if (--tPtr->refCount == 0) {
+ free(tPtr);
+ }
+ }
+
+ if (peers != peerArr) {
+ free(peers);
+ }
+
+ sharedTextPtr->triggerWatchCmd = true;
+ return rc;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextTriggerWatchCmd --
+ *
+ * Trigger the watch command, see the documentation for details on
+ * what it does.
+ *
+ * Results:
+ * Returns 'false' if this peer has been destroyed, otherwise 'true'
+ * will be returned.
+ *
+ * Side effects:
+ * It might happen that the receiver of the "watch" command is destroying the widget.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextTriggerWatchCmd(
+ TkText *textPtr, /* Information about text widget. */
+ const char *operation, /* The trigger operation. */
+ const char *index1, /* 1st argument for watch command. */
+ const char *index2, /* 2nd argument for watch command. */
+ const char *arg, /* 3rd argument for watch command. */
+ bool userFlag) /* 4rd argument for watch command. */
+{
+ Tcl_DString cmd;
+
+ assert(textPtr);
+ assert(textPtr->watchCmd);
+ assert(operation);
+
+ Tcl_DStringInit(&cmd);
+ Tcl_DStringAppend(&cmd, Tcl_GetString(textPtr->watchCmd), -1);
+ Tcl_DStringAppendElement(&cmd, Tk_PathName(textPtr->tkwin));
+ Tcl_DStringAppendElement(&cmd, operation);
+ Tcl_DStringAppendElement(&cmd, index1 ? index1 : "");
+ Tcl_DStringAppendElement(&cmd, index2 ? index2 : "");
+ Tcl_DStringAppendElement(&cmd, arg ? arg : "");
+ Tcl_DStringAppendElement(&cmd, userFlag ? "1" : "0");
+
+ textPtr->refCount += 1;
+
+ Tcl_Preserve((ClientData) textPtr->interp);
+ if (Tcl_EvalEx(textPtr->interp, Tcl_DStringValue(&cmd), Tcl_DStringLength(&cmd), 0) != TCL_OK) {
+ Tcl_AddErrorInfo(textPtr->interp, "\n (triggering the \"watch\" command failed)");
+ Tcl_BackgroundException(textPtr->interp, TCL_ERROR);
+ }
+ Tcl_Release((ClientData) textPtr->interp);
+
+ Tcl_DStringFree(&cmd);
+ return !TkTextDecrRefCountAndTestIfDestroyed(textPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GenerateEvent --
*
- * Send an event that the text was modified. This is equivalent to:
- * event generate $textWidget <<Modified>>
- * for all peers of $textWidget.
+ * Send an event about a new state. This is equivalent to:
+ * event generate $textWidget <<TYPE>>
+ * for all peers of this text widget.
*
* Results:
* None
@@ -5484,52 +9485,64 @@ TextGetText(
*/
static void
-GenerateModifiedEvent(
- TkText *textPtr) /* Information about text widget. */
+GenerateEvent(
+ TkSharedText *sharedTextPtr,
+ const char *type)
{
- for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
- textPtr = textPtr->next) {
+ TkText *textPtr;
+
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
Tk_MakeWindowExist(textPtr->tkwin);
- TkSendVirtualEvent(textPtr->tkwin, "Modified", NULL);
+ SendVirtualEvent(textPtr->tkwin, type, NULL);
}
}
/*
*----------------------------------------------------------------------
*
- * GenerateUndoStackEvent --
+ * UpdateModifiedFlag --
*
- * Send an event that the undo or redo stack became empty or unempty.
- * This is equivalent to:
- * event generate $textWidget <<UndoStack>>
- * for all peers of $textWidget.
+ * Updates the modified flag of the text widget.
*
* Results:
* None
*
* Side effects:
- * May force the text window (and all peers) into existence.
+ * None.
*
*----------------------------------------------------------------------
*/
static void
-GenerateUndoStackEvent(
- TkText *textPtr) /* Information about text widget. */
+UpdateModifiedFlag(
+ TkSharedText *sharedTextPtr,
+ bool flag)
{
- for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
- textPtr = textPtr->next) {
- Tk_MakeWindowExist(textPtr->tkwin);
- TkSendVirtualEvent(textPtr->tkwin, "UndoStack", NULL);
+ bool oldModifiedFlag = sharedTextPtr->isModified;
+
+ if (flag) {
+ sharedTextPtr->isModified = true;
+ } else if (sharedTextPtr->undoStack && !sharedTextPtr->userHasSetModifiedFlag) {
+ if (sharedTextPtr->insertDeleteUndoTokenCount > 0) {
+ sharedTextPtr->isModified = true;
+ } else {
+ unsigned undoDepth = TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack);
+ sharedTextPtr->isModified = (undoDepth > 0 && undoDepth == sharedTextPtr->undoLevel);
+ }
+ }
+
+ if (oldModifiedFlag != sharedTextPtr->isModified) {
+ sharedTextPtr->userHasSetModifiedFlag = false;
+ GenerateEvent(sharedTextPtr, "Modified");
}
}
/*
*----------------------------------------------------------------------
*
- * UpdateDirtyFlag --
+ * TkTextUpdateAlteredFlag --
*
- * Updates the dirtyness of the text widget
+ * Updates the "altered" flag of the text widget.
*
* Results:
* None
@@ -5540,43 +9553,89 @@ GenerateUndoStackEvent(
*----------------------------------------------------------------------
*/
-static void
-UpdateDirtyFlag(
+void
+TkTextUpdateAlteredFlag(
TkSharedText *sharedTextPtr)/* Information about text widget. */
{
- int oldDirtyFlag;
+ bool oldIsAlteredFlag = sharedTextPtr->isAltered;
+ bool oldIsIrreversibleFlag = sharedTextPtr->isIrreversible;
- /*
- * If we've been forced to be dirty, we stay dirty (until explicitly
- * reset, of course).
- */
-
- if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_FIXED) {
- return;
+ if (sharedTextPtr->undoStack) {
+ if (TkTextUndoContentIsIrreversible(sharedTextPtr->undoStack)) {
+ sharedTextPtr->isIrreversible = true;
+ }
+ if (!sharedTextPtr->isIrreversible) {
+ sharedTextPtr->isAltered = sharedTextPtr->undoTagListCount > 0
+ || sharedTextPtr->undoMarkListCount > 0
+ || TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack) > 0;
+ }
+ } else {
+ sharedTextPtr->isIrreversible = true;
+ }
+ if (sharedTextPtr->isIrreversible) {
+ sharedTextPtr->isAltered = true;
+ }
+ if (oldIsAlteredFlag != sharedTextPtr->isAltered) {
+ GenerateEvent(sharedTextPtr, "Altered");
}
+ if (oldIsIrreversibleFlag != sharedTextPtr->isIrreversible) {
+ GenerateEvent(sharedTextPtr, "Irreversible");
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextRunAfterSyncCmd --
+ *
+ * This function executes the command scheduled by
+ * [.text sync -command $cmd], if any.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Anything may happen, depending on $cmd contents.
+ *
+ *----------------------------------------------------------------------
+ */
- if (sharedTextPtr->isDirty < 0
- && sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_NORMAL) {
- /*
- * If dirty flag is negative, only redo operations can make it zero
- * again. If we do a normal operation, it can never become zero any
- * more (other than by explicit reset).
- */
+void
+TkTextRunAfterSyncCmd(
+ TkText *textPtr) /* Information about text widget. */
+{
+ int code;
+ bool error = false;
+ Tcl_Obj *afterSyncCmd;
- sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_FIXED;
+ assert(!TkTextPendingSync(textPtr));
+
+ textPtr->pendingAfterSync = false;
+ afterSyncCmd = textPtr->afterSyncCmd;
+
+ if (!afterSyncCmd) {
return;
}
- oldDirtyFlag = sharedTextPtr->isDirty;
- if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_UNDO) {
- sharedTextPtr->isDirty--;
- } else {
- sharedTextPtr->isDirty++;
- }
+ /*
+ * We have to expect nested calls, futhermore the receiver might destroy the widget.
+ */
- if (sharedTextPtr->isDirty == 0 || oldDirtyFlag == 0) {
- GenerateModifiedEvent(sharedTextPtr->peers);
+ textPtr->afterSyncCmd = NULL;
+ textPtr->refCount += 1;
+
+ Tcl_Preserve((ClientData) textPtr->interp);
+ if (!(textPtr->flags & DESTROYED)) {
+ code = Tcl_EvalObjEx(textPtr->interp, afterSyncCmd, TCL_EVAL_GLOBAL);
+ if (code == TCL_ERROR && !error) {
+ Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)");
+ Tcl_BackgroundError(textPtr->interp);
+ error = true;
+ }
}
+ Tcl_DecrRefCount(afterSyncCmd);
+ Tcl_Release((ClientData) textPtr->interp);
+ TkTextReleaseIfDestroyed(textPtr);
}
/*
@@ -5598,31 +9657,143 @@ UpdateDirtyFlag(
static void
RunAfterSyncCmd(
- ClientData clientData) /* Information about text widget. */
+ ClientData clientData) /* Information about text widget. */
{
- register TkText *textPtr = (TkText *) clientData;
- int code;
+ TkText *textPtr = (TkText *) clientData;
+
+ if (!(textPtr->flags & DESTROYED)) {
+ if (TkTextPendingSync(textPtr)) {
+ /* Too late here, the widget is not in sync, so we have to wait. */
+ } else {
+ TkTextRunAfterSyncCmd(textPtr);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextGenerateWidgetViewSyncEvent --
+ *
+ * Send the <<WidgetViewSync>> event related to the text widget
+ * line metrics asynchronous update.
+ * This is equivalent to:
+ * event generate $textWidget <<WidgetViewSync>> -detail $s
+ * where $s is the sync status: true (when the widget view is in
+ * sync with its internal data) or false (when it is not).
+ *
+ * Note that this has to be done in the idle loop, otherwise vwait
+ * will not return.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If corresponding bindings are present, they will trigger.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FireWidgetViewSyncEvent(
+ ClientData clientData) /* Information about text widget. */
+{
+ TkText *textPtr = (TkText *) clientData;
+ Tcl_Interp *interp;
+ bool syncState;
+
+ textPtr->pendingFireEvent = false;
- if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
+ if (textPtr->flags & DESTROYED) {
+ return;
+ }
+
+ syncState = !TkTextPendingSync(textPtr);
+
+ if (textPtr->sendSyncEvent && syncState) {
/*
- * The widget has been deleted. Don't do anything.
- */
+ * The user is waiting for sync state 'true', so we must send it.
+ */
+
+ textPtr->prevSyncState = false;
+ }
+
+ if (textPtr->prevSyncState == syncState) {
+ /*
+ * Do not send "WidgetViewSync" with same sync state as before
+ * (except if we must send it because the user is waiting for it).
+ */
- if (textPtr->refCount-- <= 1) {
- ckfree((char *) textPtr);
- }
return;
}
- Tcl_Preserve((ClientData) textPtr->interp);
- code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd, TCL_EVAL_GLOBAL);
- if (code == TCL_ERROR) {
- Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)");
- Tcl_BackgroundError(textPtr->interp);
+ if ((textPtr->sendSyncEvent || textPtr->pendingAfterSync) && !syncState) {
+ /*
+ * Do not send "WidgetViewSync" with sync state "false" as long as
+ * we have a pending sync command.
+ */
+
+ return;
}
- Tcl_Release((ClientData) textPtr->interp);
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- textPtr->afterSyncCmd = NULL;
+
+ if (syncState) {
+ textPtr->sendSyncEvent = false;
+ }
+ textPtr->prevSyncState = syncState;
+
+ interp = textPtr->interp;
+ Tcl_Preserve((ClientData) interp);
+ SendVirtualEvent(textPtr->tkwin, "WidgetViewSync", Tcl_NewBooleanObj(syncState));
+ Tcl_Release((ClientData) interp);
+}
+
+void
+TkTextGenerateWidgetViewSyncEvent(
+ TkText *textPtr, /* Information about text widget. */
+ bool sendImmediately)
+{
+ if (!textPtr->pendingFireEvent) {
+ textPtr->pendingFireEvent = true;
+ if (sendImmediately) {
+ FireWidgetViewSyncEvent((ClientData) textPtr);
+ } else {
+ Tcl_DoWhenIdle(FireWidgetViewSyncEvent, (ClientData) textPtr);
+ }
+ }
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TkTextPrintIndex --
+ *
+ * This function generates a string description of an index, suitable for
+ * reading in again later.
+ *
+ * Results:
+ * The characters pointed to by string are modified. Returns the number
+ * of characters in the string.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+/*
+ * NOTE: this function has external linkage (declared in a common header file)
+ * and cannot be inlined.
+ */
+
+int
+TkTextPrintIndex(
+ const TkText *textPtr,
+ const TkTextIndex *indexPtr,/* Pointer to index. */
+ char *string) /* Place to store the position. Must have at least TK_POS_CHARS
+ * characters. */
+{
+ assert(textPtr);
+ return TkTextIndexPrint(textPtr->sharedTextPtr, textPtr, indexPtr, string);
}
/*
@@ -5652,19 +9823,22 @@ SearchPerform(
SearchSpec *searchSpecPtr, /* Search parameters. */
Tcl_Obj *patObj, /* Contains an exact string or a regexp
* pattern. Must have a refCount > 0. */
- Tcl_Obj *fromPtr, /* Contains information describing the first
- * index. */
- Tcl_Obj *toPtr) /* NULL or information describing the last
- * index. */
+ Tcl_Obj *fromPtr, /* Contains information describing the first index. */
+ Tcl_Obj *toPtr) /* NULL or information describing the last index. */
{
+ TkText *textPtr = searchSpecPtr->clientData;
+
+ if (TkTextIsDeadPeer(textPtr)) {
+ return TCL_OK;
+ }
+
/*
* Find the starting line and starting offset (measured in Unicode chars
* for regexp search, utf-8 bytes for exact search).
*/
if (searchSpecPtr->lineIndexProc(interp, fromPtr, searchSpecPtr,
- &searchSpecPtr->startLine,
- &searchSpecPtr->startOffset) != TCL_OK) {
+ &searchSpecPtr->startLine, &searchSpecPtr->startOffset) != TCL_OK) {
return TCL_ERROR;
}
@@ -5672,15 +9846,13 @@ SearchPerform(
* Find the optional end location, similarly.
*/
- if (toPtr != NULL) {
- const TkTextIndex *indexToPtr, *indexFromPtr;
- TkText *textPtr = searchSpecPtr->clientData;
+ if (toPtr) {
+ TkTextIndex indexTo, indexFrom;
- indexToPtr = TkTextGetIndexFromObj(interp, textPtr, toPtr);
- if (indexToPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, toPtr, &indexTo)
+ || !TkTextGetIndexFromObj(interp, textPtr, fromPtr, &indexFrom)) {
return TCL_ERROR;
}
- indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, fromPtr);
/*
* Check for any empty search range here. It might be better in the
@@ -5688,14 +9860,12 @@ SearchPerform(
* wrap when given a negative search range).
*/
- if (TkTextIndexCmp(indexFromPtr, indexToPtr) ==
- (searchSpecPtr->backwards ? -1 : 1)) {
+ if (TkTextIndexCompare(&indexFrom, &indexTo) == (searchSpecPtr->backwards ? -1 : 1)) {
return TCL_OK;
}
if (searchSpecPtr->lineIndexProc(interp, toPtr, searchSpecPtr,
- &searchSpecPtr->stopLine,
- &searchSpecPtr->stopOffset) != TCL_OK) {
+ &searchSpecPtr->stopLine, &searchSpecPtr->stopOffset) != TCL_OK) {
return TCL_ERROR;
}
} else {
@@ -5776,9 +9946,9 @@ SearchCore(
#define LOTS_OF_MATCHES 20
int matchNum = LOTS_OF_MATCHES;
- int smArray[2 * LOTS_OF_MATCHES];
- int *storeMatch = smArray;
- int *storeLength = smArray + LOTS_OF_MATCHES;
+ int32_t smArray[2 * LOTS_OF_MATCHES];
+ int32_t *storeMatch = smArray;
+ int32_t *storeLength = smArray + LOTS_OF_MATCHES;
int lastBackwardsLineMatch = -1;
int lastBackwardsMatchOffset = -1;
@@ -5807,7 +9977,7 @@ SearchCore(
(searchSpecPtr->noCase ? TCL_REG_NOCASE : 0)
| (searchSpecPtr->noLineStop ? 0 : TCL_REG_NLSTOP)
| TCL_REG_ADVANCED | TCL_REG_CANMATCH | TCL_REG_NLANCH);
- if (regexp == NULL) {
+ if (!regexp) {
return TCL_ERROR;
}
}
@@ -5829,7 +9999,7 @@ SearchCore(
*/
pattern = Tcl_GetString(patObj);
- matchLength = patObj->length;
+ matchLength = GetByteLength(patObj);
nl = strchr(pattern, '\n');
/*
@@ -5838,7 +10008,7 @@ SearchCore(
* will work fine.
*/
- if (nl != NULL && nl[1] != '\0') {
+ if (nl && nl[1] != '\0') {
firstNewLine = (nl - pattern);
}
} else {
@@ -5883,7 +10053,7 @@ SearchCore(
lineInfo = searchSpecPtr->addLineProc(lineNum, searchSpecPtr, theLine,
&lastOffset, &linesSearched);
- if (lineInfo == NULL) {
+ if (!lineInfo) {
/*
* This should not happen, since 'lineNum' should be valid in the
* call above. However, let's try to be flexible and not cause a
@@ -5919,7 +10089,7 @@ SearchCore(
* other part of the line.
*/
- passes++;
+ passes += 1;
if ((passes == 1) ^ searchSpecPtr->backwards) {
/*
* Forward search and first pass, or backward search and
@@ -5931,8 +10101,7 @@ SearchCore(
if (searchSpecPtr->startOffset > firstOffset) {
firstOffset = searchSpecPtr->startOffset;
}
- if ((firstOffset >= lastOffset)
- && ((lastOffset != 0) || searchSpecPtr->exact)) {
+ if (firstOffset >= lastOffset && (lastOffset != 0 || searchSpecPtr->exact)) {
goto nextLine;
}
} else {
@@ -5967,8 +10136,7 @@ SearchCore(
int lastFullLine = lastOffset;
if (firstNewLine == -1) {
- if (searchSpecPtr->strictLimits
- && (firstOffset + matchLength > lastOffset)) {
+ if (searchSpecPtr->strictLimits && (firstOffset + matchLength > lastOffset)) {
/*
* Not enough characters to match.
*/
@@ -5990,31 +10158,31 @@ SearchCore(
const char c = pattern[0];
+ p = startOfLine;
if (alreadySearchOffset != -1) {
- p = startOfLine + alreadySearchOffset;
+ p += alreadySearchOffset;
alreadySearchOffset = -1;
} else {
- p = startOfLine + lastOffset -1;
+ p += lastOffset - 1;
}
while (p >= startOfLine + firstOffset) {
- if (p[0] == c && !strncmp(p, pattern,
- (unsigned) matchLength)) {
+ if (p[0] == c && strncmp(p, pattern, matchLength) == 0) {
goto backwardsMatch;
}
- p--;
+ p -= 1;
}
break;
} else {
p = strstr(startOfLine + firstOffset, pattern);
}
- if (p == NULL) {
+ if (!p) {
/*
* Single line match failed.
*/
break;
}
- } else if (firstNewLine >= (lastOffset - firstOffset)) {
+ } else if (firstNewLine >= lastOffset - firstOffset) {
/*
* Multi-line match, but not enough characters to match.
*/
@@ -6027,7 +10195,7 @@ SearchCore(
*/
p = startOfLine + lastOffset - firstNewLine - 1;
- if (strncmp(p, pattern, (unsigned) firstNewLine + 1)) {
+ if (strncmp(p, pattern, firstNewLine + 1) != 0) {
/*
* No match.
*/
@@ -6044,7 +10212,7 @@ SearchCore(
*/
int lastTotal = lastOffset;
- int skipFirst = lastOffset - firstNewLine -1;
+ int skipFirst = lastOffset - firstNewLine - 1;
/*
* We may be able to match if given more text. The
@@ -6055,7 +10223,7 @@ SearchCore(
while (1) {
lastFullLine = lastTotal;
- if (lineNum+extraLines>=searchSpecPtr->numLines) {
+ if (lineNum + extraLines >= searchSpecPtr->numLines) {
p = NULL;
break;
}
@@ -6066,9 +10234,8 @@ SearchCore(
*/
if (extraLines > maxExtraLines) {
- if (searchSpecPtr->addLineProc(lineNum
- + extraLines, searchSpecPtr, theLine,
- &lastTotal, &extraLines) == NULL) {
+ if (!searchSpecPtr->addLineProc(lineNum + extraLines, searchSpecPtr,
+ theLine, &lastTotal, &extraLines)) {
p = NULL;
if (!searchSpecPtr->backwards) {
extraLinesSearched = extraLines;
@@ -6086,14 +10253,14 @@ SearchCore(
* exact searches.
*/
- if ((lastTotal - skipFirst) >= matchLength) {
+ if (lastTotal - skipFirst >= matchLength) {
/*
* We now have enough text to match, so we
* make a final test and break whatever the
* result.
*/
- if (strncmp(p,pattern,(unsigned)matchLength)) {
+ if (strncmp(p, pattern, matchLength) != 0) {
p = NULL;
}
break;
@@ -6102,8 +10269,7 @@ SearchCore(
* Not enough text yet, but check the prefix.
*/
- if (strncmp(p, pattern,
- (unsigned)(lastTotal - skipFirst))) {
+ if (strncmp(p, pattern, lastTotal - skipFirst) != 0) {
p = NULL;
break;
}
@@ -6112,7 +10278,7 @@ SearchCore(
* The prefix matches, so keep looking.
*/
}
- extraLines++;
+ extraLines += 1;
}
/*
* If we reach here, with p != NULL, we've found a
@@ -6120,7 +10286,7 @@ SearchCore(
* didn't finish it off, so we go to the next line.
*/
- if (p == NULL) {
+ if (!p) {
break;
}
@@ -6135,7 +10301,7 @@ SearchCore(
}
backwardsMatch:
- if ((p - startOfLine) >= lastOffset) {
+ if (p - startOfLine >= lastOffset) {
break;
}
@@ -6178,8 +10344,7 @@ SearchCore(
*/
if (!searchSpecPtr->backwards) {
- alreadySearchOffset =
- firstOffset - lastFullLine;
+ alreadySearchOffset = firstOffset - lastFullLine;
break;
}
}
@@ -6191,8 +10356,7 @@ SearchCore(
break;
}
} else {
- firstOffset = p - startOfLine +
- TkUtfToUniChar(startOfLine+matchOffset,&ch);
+ firstOffset = p - startOfLine + TkUtfToUniChar(startOfLine + matchOffset, &ch);
}
}
} while (searchSpecPtr->all);
@@ -6207,7 +10371,7 @@ SearchCore(
int lastFullLine = lastOffset;
match = Tcl_RegExpExecObj(interp, regexp, theLine,
- firstOffset, 1, (firstOffset>0 ? TCL_REG_NOTBOL : 0));
+ firstOffset, 1, firstOffset > 0 ? TCL_REG_NOTBOL : 0);
if (match < 0) {
code = TCL_ERROR;
goto searchDone;
@@ -6220,9 +10384,9 @@ SearchCore(
* full greedy match.
*/
- if (!match ||
- ((info.extendStart == info.matches[0].start)
- && (info.matches[0].end == lastOffset-firstOffset))) {
+ if (!match
+ || (info.extendStart == info.matches[0].start
+ && info.matches[0].end == lastOffset - firstOffset)) {
int extraLines = 0;
int prevFullLine;
@@ -6235,8 +10399,7 @@ SearchCore(
int lastTotal = lastOffset;
- if ((lastBackwardsLineMatch != -1)
- && (lastBackwardsLineMatch == (lineNum + 1))) {
+ if (lastBackwardsLineMatch != -1 && lastBackwardsLineMatch == lineNum + 1) {
lastNonOverlap = lastTotal;
}
@@ -6272,8 +10435,7 @@ SearchCore(
* when we look at that line.
*/
- if (!match && !searchSpecPtr->backwards
- && (firstOffset == 0)) {
+ if (!match && !searchSpecPtr->backwards && firstOffset == 0) {
extraLinesSearched = extraLines;
}
break;
@@ -6288,9 +10450,8 @@ SearchCore(
*/
if (extraLines > maxExtraLines) {
- if (searchSpecPtr->addLineProc(lineNum
- + extraLines, searchSpecPtr, theLine,
- &lastTotal, &extraLines) == NULL) {
+ if (!searchSpecPtr->addLineProc(lineNum + extraLines, searchSpecPtr,
+ theLine, &lastTotal, &extraLines)) {
/*
* There are no more acceptable lines, so we
* can say we have searched all of these.
@@ -6303,16 +10464,14 @@ SearchCore(
}
maxExtraLines = extraLines;
- if ((lastBackwardsLineMatch != -1)
- && (lastBackwardsLineMatch
- == (lineNum + extraLines + 1))) {
+ if (lastBackwardsLineMatch != -1
+ && lastBackwardsLineMatch == lineNum + extraLines + 1) {
lastNonOverlap = lastTotal;
}
}
match = Tcl_RegExpExecObj(interp, regexp, theLine,
- firstOffset, 1,
- ((firstOffset > 0) ? TCL_REG_NOTBOL : 0));
+ firstOffset, 1, firstOffset > 0 ? TCL_REG_NOTBOL : 0);
if (match < 0) {
code = TCL_ERROR;
goto searchDone;
@@ -6336,9 +10495,8 @@ SearchCore(
* circumstances.
*/
- if ((match &&
- firstOffset+info.matches[0].end != lastTotal &&
- firstOffset+info.matches[0].end < prevFullLine)
+ if ((match && firstOffset + info.matches[0].end != lastTotal
+ && firstOffset + info.matches[0].end < prevFullLine)
|| info.extendStart < 0) {
break;
}
@@ -6350,11 +10508,10 @@ SearchCore(
* that line.
*/
- if (match && (info.matches[0].start >= lastOffset)) {
+ if (match && info.matches[0].start >= lastOffset) {
break;
}
- if (match && ((firstOffset + info.matches[0].end)
- >= prevFullLine)) {
+ if (match && firstOffset + info.matches[0].end >= prevFullLine) {
if (extraLines > 0) {
extraLinesSearched = extraLines - 1;
}
@@ -6365,7 +10522,7 @@ SearchCore(
* The prefix matches, so keep looking.
*/
- extraLines++;
+ extraLines += 1;
}
/*
@@ -6393,30 +10550,27 @@ SearchCore(
}
if (lastBackwardsLineMatch != -1) {
- if ((lineNum + linesSearched + extraLinesSearched)
- == lastBackwardsLineMatch) {
+ if (lineNum + linesSearched + extraLinesSearched == lastBackwardsLineMatch) {
/*
* Possible overlap or inclusion.
*/
- int thisOffset = firstOffset + info.matches[0].end
- - info.matches[0].start;
+ int thisOffset = firstOffset + info.matches[0].end - info.matches[0].start;
if (lastNonOverlap != -1) {
/*
* Possible overlap or enclosure.
*/
- if (thisOffset-lastNonOverlap >=
- lastBackwardsMatchOffset+matchLength){
+ if (thisOffset - lastNonOverlap >=
+ lastBackwardsMatchOffset + matchLength) {
/*
* Totally encloses previous match, so
* forget the previous match.
*/
lastBackwardsLineMatch = -1;
- } else if ((thisOffset - lastNonOverlap)
- > lastBackwardsMatchOffset) {
+ } else if (thisOffset - lastNonOverlap > lastBackwardsMatchOffset) {
/*
* Overlap. Previous match is ok, and the
* current match is only ok if we are
@@ -6444,7 +10598,7 @@ SearchCore(
goto recordBackwardsMatch;
}
- } else if (lineNum+linesSearched+extraLinesSearched
+ } else if (lineNum + linesSearched + extraLinesSearched
< lastBackwardsLineMatch) {
/*
* No overlap.
@@ -6468,8 +10622,7 @@ SearchCore(
if (lastBackwardsLineMatch != -1) {
recordBackwardsMatch:
searchSpecPtr->foundMatchProc(lastBackwardsLineMatch,
- searchSpecPtr, NULL, NULL,
- lastBackwardsMatchOffset, matchLength);
+ searchSpecPtr, NULL, NULL, lastBackwardsMatchOffset, matchLength);
lastBackwardsLineMatch = -1;
if (!searchSpecPtr->all) {
goto searchDone;
@@ -6491,10 +10644,9 @@ SearchCore(
if (matchOffset == -1 ||
((searchSpecPtr->all || searchSpecPtr->backwards)
- && ((firstOffset < matchOffset)
- || ((firstOffset + info.matches[0].end
- - info.matches[0].start)
- > (matchOffset + matchLength))))) {
+ && (firstOffset < matchOffset
+ || firstOffset + info.matches[0].end - info.matches[0].start
+ > matchOffset + matchLength))) {
matchOffset = firstOffset;
matchLength = info.matches[0].end - info.matches[0].start;
@@ -6512,13 +10664,12 @@ SearchCore(
* matches on the heap.
*/
- int *newArray =
- ckalloc(4 * matchNum * sizeof(int));
- memcpy(newArray, storeMatch, matchNum*sizeof(int));
- memcpy(newArray + 2*matchNum, storeLength,
- matchNum * sizeof(int));
+ int matchNumSize = matchNum * sizeof(int32_t);
+ int32_t *newArray = malloc(4*matchNumSize);
+ memcpy(newArray, storeMatch, matchNumSize);
+ memcpy(newArray + 2*matchNum, storeLength, matchNumSize);
if (storeMatch != smArray) {
- ckfree(storeMatch);
+ free((char *) storeMatch);
}
matchNum *= 2;
storeMatch = newArray;
@@ -6526,7 +10677,7 @@ SearchCore(
}
storeMatch[matches] = matchOffset;
storeLength[matches] = matchLength;
- matches++;
+ matches += 1;
} else {
/*
* Now actually record the match, but only if we are
@@ -6535,8 +10686,7 @@ SearchCore(
if (searchSpecPtr->all &&
!searchSpecPtr->foundMatchProc(lineNum,
- searchSpecPtr, lineInfo, theLine, matchOffset,
- matchLength)) {
+ searchSpecPtr, lineInfo, theLine, matchOffset, matchLength)) {
/*
* We reached the end of the search.
*/
@@ -6551,8 +10701,7 @@ SearchCore(
* explicitly disallow overlapping matches.
*/
- if (matchLength > 0 && !searchSpecPtr->overlap
- && !searchSpecPtr->backwards) {
+ if (matchLength > 0 && !searchSpecPtr->overlap && !searchSpecPtr->backwards) {
firstOffset += matchLength;
if (firstOffset >= lastOffset) {
/*
@@ -6574,7 +10723,7 @@ SearchCore(
* We'll add this on again just below.
*/
- firstOffset --;
+ firstOffset -= 1;
}
}
@@ -6584,7 +10733,7 @@ SearchCore(
* repeated forward searches).
*/
- firstOffset++;
+ firstOffset += 1;
} while (searchSpecPtr->backwards || searchSpecPtr->all);
if (matches > 0) {
@@ -6593,7 +10742,7 @@ SearchCore(
* with 'foundMatchProc' yet.
*/
- matches--;
+ matches -= 1;
matchOffset = storeMatch[matches];
matchLength = storeLength[matches];
while (--matches >= 0) {
@@ -6608,8 +10757,7 @@ SearchCore(
* found which would exercise such a problem.
*/
}
- if (storeMatch[matches] + storeLength[matches]
- >= matchOffset + matchLength) {
+ if (storeMatch[matches] + storeLength[matches] >= matchOffset + matchLength) {
/*
* The new match totally encloses the previous one, so
* we overwrite the previous one.
@@ -6620,8 +10768,7 @@ SearchCore(
continue;
}
if (!searchSpecPtr->overlap) {
- if (storeMatch[matches] + storeLength[matches]
- > matchOffset) {
+ if (storeMatch[matches] + storeLength[matches] > matchOffset) {
continue;
}
}
@@ -6657,8 +10804,7 @@ SearchCore(
* we are done.
*/
- if ((lastBackwardsLineMatch == -1) && (matchOffset >= 0)
- && !searchSpecPtr->all) {
+ if (lastBackwardsLineMatch == -1 && matchOffset >= 0 && !searchSpecPtr->all) {
searchSpecPtr->foundMatchProc(lineNum, searchSpecPtr, lineInfo,
theLine, matchOffset, matchLength);
goto searchDone;
@@ -6681,14 +10827,12 @@ SearchCore(
}
if (searchSpecPtr->backwards) {
- lineNum--;
+ lineNum -= 1;
if (lastBackwardsLineMatch != -1
- && ((lineNum < 0)
- || (lineNum + 2 < lastBackwardsLineMatch))) {
+ && (lineNum < 0 || lineNum + 2 < lastBackwardsLineMatch)) {
searchSpecPtr->foundMatchProc(lastBackwardsLineMatch,
- searchSpecPtr, NULL, NULL,
- lastBackwardsMatchOffset, matchLength);
+ searchSpecPtr, NULL, NULL, lastBackwardsMatchOffset, matchLength);
lastBackwardsLineMatch = -1;
if (!searchSpecPtr->all) {
goto searchDone;
@@ -6696,7 +10840,7 @@ SearchCore(
}
if (lineNum < 0) {
- lineNum = searchSpecPtr->numLines-1;
+ lineNum = searchSpecPtr->numLines - 1;
}
if (!searchSpecPtr->exact) {
/*
@@ -6711,7 +10855,7 @@ SearchCore(
break;
}
} else {
- lineNum++;
+ lineNum += 1;
if (lineNum >= searchSpecPtr->numLines) {
lineNum = 0;
}
@@ -6748,7 +10892,7 @@ SearchCore(
*/
if (storeMatch != smArray) {
- ckfree(storeMatch);
+ free((char *) storeMatch);
}
return code;
@@ -6757,6 +10901,175 @@ SearchCore(
/*
*----------------------------------------------------------------------
*
+ * GetTextStartEnd -
+ *
+ * Converts an internal TkTextSegment ptr into a Tcl string obj containing
+ * the representation of the index. (Handler for the 'startEndMark' configuration
+ * option type.)
+ *
+ * Results:
+ * Tcl_Obj containing the string representation of the index position.
+ *
+ * Side effects:
+ * Creates a new Tcl_Obj.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+GetTextStartEnd(
+ ClientData clientData,
+ Tk_Window tkwin,
+ char *recordPtr, /* Pointer to widget record. */
+ int internalOffset) /* Offset within *recordPtr containing the start object. */
+{
+ TkTextIndex index;
+ char buf[TK_POS_CHARS] = { '\0' };
+ const TkText *textPtr = (const TkText *) recordPtr;
+ const TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ Tcl_Obj **objPtr = (Tcl_Obj **) (recordPtr + internalOffset);
+ const TkTextSegment *sharedMarker;
+ TkTextSegment *marker;
+
+ if (objPtr == &textPtr->newStartIndex) {
+ marker = textPtr->startMarker;
+ sharedMarker = sharedTextPtr->startMarker;
+ } else {
+ marker = textPtr->endMarker;
+ sharedMarker = sharedTextPtr->endMarker;
+ }
+ if (marker != sharedMarker) {
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&index, marker);
+ TkTextIndexPrint(sharedTextPtr, NULL, &index, buf);
+ }
+ return Tcl_NewStringObj(buf, -1);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SetTextStartEnd --
+ *
+ * Converts a Tcl_Obj representing a widget's (start or end) index into a
+ * TkTextSegment* value. (Handler for the 'startEndMark' configuration option type.)
+ *
+ * Results:
+ * Standard Tcl result.
+ *
+ * Side effects:
+ * May store the TkTextSegment* value into the internal representation
+ * pointer. May change the pointer to the Tcl_Obj to NULL to indicate
+ * that the specified string was empty and that is acceptable.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ObjectIsEmpty(
+ Tcl_Obj *objPtr) /* Object to test. May be NULL. */
+{
+ return objPtr ? GetByteLength(objPtr) == 0 : true;
+}
+
+static int
+SetTextStartEnd(
+ ClientData clientData,
+ Tcl_Interp *interp, /* Current interp; may be used for errors. */
+ Tk_Window tkwin, /* Window for which option is being set. */
+ Tcl_Obj **value, /* Pointer to the pointer to the value object.
+ * We use a pointer to the pointer because we
+ * may need to return a value (NULL). */
+ char *recordPtr, /* Pointer to storage for the widget record. */
+ int internalOffset, /* Offset within *recordPtr at which the
+ * internal value is to be stored. */
+ char *oldInternalPtr, /* Pointer to storage for the old value. */
+ int flags) /* Flags for the option, set Tk_SetOptions. */
+{
+ Tcl_Obj **objPtr = (Tcl_Obj **) (recordPtr + internalOffset);
+ Tcl_Obj **oldObjPtr = (Tcl_Obj **) oldInternalPtr;
+ const TkText *textPtr = (const TkText *) recordPtr;
+
+ assert(!*objPtr);
+ *oldObjPtr = NULL;
+
+ if ((flags & TK_OPTION_NULL_OK) && ObjectIsEmpty(*value)) {
+ *value = NULL;
+ *objPtr = Tcl_NewStringObj((objPtr == &textPtr->newStartIndex) ? "begin" : "end", -1);
+ } else {
+ *objPtr = *value;
+ }
+ Tcl_IncrRefCount(*objPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RestoreTextStartEnd --
+ *
+ * Restore an index option value from a saved value. (Handler for the
+ * 'index' configuration option type.)
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Restores the old value.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RestoreTextStartEnd(
+ ClientData clientData,
+ Tk_Window tkwin,
+ char *internalPtr, /* Pointer to storage for value. */
+ char *oldInternalPtr) /* Pointer to old value. */
+{
+ Tcl_Obj **newValue = (Tcl_Obj **) internalPtr;
+ Tcl_Obj **oldValue = (Tcl_Obj **) oldInternalPtr;
+
+ if (*oldValue) {
+ Tcl_IncrRefCount(*oldValue);
+ }
+ *newValue = *oldValue;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeTextStartEnd --
+ *
+ * Free an index option value from a saved value. (Handler for the
+ * 'index' configuration option type.)
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Releases some memory.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeTextStartEnd(
+ ClientData clientData,
+ Tk_Window tkwin,
+ char *internalPtr)
+{
+ Tcl_Obj *objPtr = *(Tcl_Obj **) internalPtr;
+
+ if (objPtr) {
+ Tcl_DecrRefCount(objPtr);
+ }
+}
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+/*
+ *----------------------------------------------------------------------
+ *
* GetLineStartEnd -
*
* Converts an internal TkTextLine ptr into a Tcl string obj containing
@@ -6776,15 +11089,16 @@ GetLineStartEnd(
ClientData clientData,
Tk_Window tkwin,
char *recordPtr, /* Pointer to widget record. */
- int internalOffset) /* Offset within *recordPtr containing the
- * line value. */
+ int internalOffset) /* Offset within *recordPtr containing the line value. */
{
+ TkText *textPtr;
TkTextLine *linePtr = *(TkTextLine **)(recordPtr + internalOffset);
- if (linePtr == NULL) {
+ if (!linePtr) {
return Tcl_NewObj();
}
- return Tcl_NewIntObj(1 + TkBTreeLinesTo(NULL, linePtr));
+ textPtr = (TkText *) recordPtr;
+ return Tcl_NewIntObj(1 + TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, linePtr, NULL));
}
/*
@@ -6824,13 +11138,9 @@ SetLineStartEnd(
char *internalPtr;
TkText *textPtr = (TkText *) recordPtr;
- if (internalOffset >= 0) {
- internalPtr = recordPtr + internalOffset;
- } else {
- internalPtr = NULL;
- }
+ internalPtr = internalOffset >= 0 ? recordPtr + internalOffset : NULL;
- if (flags & TK_OPTION_NULL_OK && ObjectIsEmpty(*value)) {
+ if ((flags & TK_OPTION_NULL_OK) && ObjectIsEmpty(*value)) {
*value = NULL;
} else {
int line;
@@ -6838,10 +11148,10 @@ SetLineStartEnd(
if (Tcl_GetIntFromObj(interp, *value, &line) != TCL_OK) {
return TCL_ERROR;
}
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, NULL, line-1);
+ linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, NULL, line - 1);
}
- if (internalPtr != NULL) {
+ if (internalPtr) {
*((TkTextLine **) oldInternalPtr) = *((TkTextLine **) internalPtr);
*((TkTextLine **) internalPtr) = linePtr;
}
@@ -6872,40 +11182,10 @@ RestoreLineStartEnd(
char *internalPtr, /* Pointer to storage for value. */
char *oldInternalPtr) /* Pointer to old value. */
{
- *(TkTextLine **)internalPtr = *(TkTextLine **)oldInternalPtr;
+ *(TkTextLine **) internalPtr = *(TkTextLine **) oldInternalPtr;
}
-
-/*
- *----------------------------------------------------------------------
- *
- * ObjectIsEmpty --
- *
- * This function tests whether the string value of an object is empty.
- *
- * Results:
- * The return value is 1 if the string value of objPtr has length zero,
- * and 0 otherwise.
- *
- * Side effects:
- * May cause object shimmering, since this function can force a
- * conversion to a string object.
- *
- *----------------------------------------------------------------------
- */
-static int
-ObjectIsEmpty(
- Tcl_Obj *objPtr) /* Object to test. May be NULL. */
-{
- if (objPtr == NULL) {
- return 1;
- }
- if (objPtr->bytes != NULL) {
- return (objPtr->length == 0);
- }
- (void)Tcl_GetString(objPtr);
- return (objPtr->length == 0);
-}
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
/*
*----------------------------------------------------------------------
@@ -6925,19 +11205,22 @@ ObjectIsEmpty(
*----------------------------------------------------------------------
*/
+#if TK_MAJOR_VERSION > 8 || (TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION > 5)
+
int
TkpTesttextCmd(
ClientData clientData, /* Main window for application. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument strings. */
+ Tcl_Obj *const objv[]) /* Argument strings. */
{
TkText *textPtr;
size_t len;
int lineIndex, byteIndex, byteOffset;
TkTextIndex index;
- char buf[64];
+ char buf[TK_POS_CHARS];
Tcl_CmdInfo info;
+ Tcl_Obj *watchCmd;
if (objc < 3) {
return TCL_ERROR;
@@ -6955,8 +11238,7 @@ TkpTesttextCmd(
lineIndex = atoi(Tcl_GetString(objv[3])) - 1;
byteIndex = atoi(Tcl_GetString(objv[4]));
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineIndex,
- byteIndex, &index);
+ TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineIndex, byteIndex, &index);
} else if (strncmp(Tcl_GetString(objv[2]), "forwbytes", len) == 0) {
if (objc != 5) {
return TCL_ERROR;
@@ -6979,16 +11261,287 @@ TkpTesttextCmd(
return TCL_ERROR;
}
+ /*
+ * Avoid triggering of the "watch" command.
+ */
+
+ watchCmd = textPtr->watchCmd;
+ textPtr->watchCmd = NULL;
TkTextSetMark(textPtr, "insert", &index);
+ textPtr->watchCmd = watchCmd;
+
TkTextPrintIndex(textPtr, &index, buf);
- Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s %d", buf, index.byteIndex));
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s %d", buf, TkTextIndexGetByteIndex(&index)));
return TCL_OK;
}
+
+#else /* backport to Tk 8.5 */
+
+int
+TkpTesttextCmd(
+ ClientData clientData, /* Main window for application. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int argc, /* Number of arguments. */
+ const char **argv) /* Argument strings. */
+{
+ TkText *textPtr;
+ size_t len;
+ int lineIndex, byteIndex, byteOffset;
+ TkTextIndex index;
+ char buf[64];
+ unsigned offs;
+ Tcl_CmdInfo info;
+
+ if (argc < 3) {
+ return TCL_ERROR;
+ }
+
+ if (Tcl_GetCommandInfo(interp, argv[1], &info) == 0) {
+ return TCL_ERROR;
+ }
+ if (info.isNativeObjectProc) {
+ textPtr = (TkText *) info.objClientData;
+ } else {
+ textPtr = (TkText *) info.clientData;
+ }
+ len = strlen(argv[2]);
+ if (strncmp(argv[2], "byteindex", len) == 0) {
+ if (argc != 5) {
+ return TCL_ERROR;
+ }
+ lineIndex = atoi(argv[3]) - 1;
+ byteIndex = atoi(argv[4]);
+
+ TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineIndex, byteIndex, &index);
+ } else if (strncmp(argv[2], "forwbytes", len) == 0) {
+ if (argc != 5) {
+ return TCL_ERROR;
+ }
+ if (TkTextGetIndex(interp, textPtr, argv[3], &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ byteOffset = atoi(argv[4]);
+ TkTextIndexForwBytes(textPtr, &index, byteOffset, &index);
+ } else if (strncmp(argv[2], "backbytes", len) == 0) {
+ if (argc != 5) {
+ return TCL_ERROR;
+ }
+ if (TkTextGetIndex(interp, textPtr, argv[3], &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ byteOffset = atoi(argv[4]);
+ TkTextIndexBackBytes(textPtr, &index, byteOffset, &index);
+ } else {
+ return TCL_ERROR;
+ }
+
+ TkTextSetMark(textPtr, "insert", &index);
+ TkTextPrintIndex(textPtr, &index, buf);
+ offs = strlen(buf);
+ snprintf(buf + offs, sizeof(buf) - offs, " %d", TkTextIndexGetByteIndex(&index));
+ Tcl_AppendResult(interp, buf, NULL);
+
+ return TCL_OK;
+}
+
+#endif /* TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 5 */
+
+#if !NDEBUG
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkpTextInspect --
+ *
+ * This function is for debugging only, printing the text content
+ * on stdout.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkpTextInspect(
+ TkText *textPtr)
+{
+ Tcl_Obj *resultPtr;
+ Tcl_Obj *objv[9];
+ Tcl_Obj **argv;
+ int argc, i;
+
+ Tcl_IncrRefCount(resultPtr = Tcl_GetObjResult(textPtr->interp));
+ Tcl_ResetResult(textPtr->interp);
+ Tcl_IncrRefCount(objv[0] = Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
+ Tcl_IncrRefCount(objv[1] = Tcl_NewStringObj("inspect", -1));
+ Tcl_IncrRefCount(objv[2] = Tcl_NewStringObj("-discardselection", -1));
+ Tcl_IncrRefCount(objv[3] = Tcl_NewStringObj("-elide", -1));
+ Tcl_IncrRefCount(objv[4] = Tcl_NewStringObj("-chars", -1));
+ Tcl_IncrRefCount(objv[5] = Tcl_NewStringObj("-image", -1));
+ Tcl_IncrRefCount(objv[6] = Tcl_NewStringObj("-window", -1));
+ Tcl_IncrRefCount(objv[7] = Tcl_NewStringObj("-mark", -1));
+ Tcl_IncrRefCount(objv[8] = Tcl_NewStringObj("-tag", -1));
+ TextInspectCmd(textPtr, textPtr->interp, sizeof(objv)/sizeof(objv[0]), objv);
+ for (i = 0; i < sizeof(objv)/sizeof(objv[0]); ++i) {
+ Tcl_DecrRefCount(objv[i]);
+ }
+ Tcl_ListObjGetElements(textPtr->interp, Tcl_GetObjResult(textPtr->interp), &argc, &argv);
+ for (i = 0; i < argc; ++i) {
+ printf("%s\n", Tcl_GetString(argv[i]));
+ }
+ Tcl_SetObjResult(textPtr->interp, resultPtr);
+ Tcl_DecrRefCount(resultPtr);
+}
+
+#endif /* !NDEBUG */
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkpTextDump --
+ *
+ * This function is for debugging only, printing the text content
+ * on stdout.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+#if !NDEBUG
+
+void
+TkpTextDump(
+ TkText *textPtr)
+{
+ Tcl_Obj *resultPtr;
+ Tcl_Obj *objv[4];
+ Tcl_Obj **argv;
+ int argc, i;
+
+ Tcl_IncrRefCount(resultPtr = Tcl_GetObjResult(textPtr->interp));
+ Tcl_ResetResult(textPtr->interp);
+
+ Tcl_IncrRefCount(objv[0] = Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
+ Tcl_IncrRefCount(objv[1] = Tcl_NewStringObj("dump", -1));
+ Tcl_IncrRefCount(objv[2] = Tcl_NewStringObj("begin", -1));
+ Tcl_IncrRefCount(objv[3] = Tcl_NewStringObj("end", -1));
+ TextDumpCmd(textPtr, textPtr->interp, sizeof(objv)/sizeof(objv[0]), objv);
+ for (i = 0; i < sizeof(objv)/sizeof(objv[0]); ++i) {
+ Tcl_DecrRefCount(objv[i]);
+ }
+
+ Tcl_ListObjGetElements(textPtr->interp, Tcl_GetObjResult(textPtr->interp), &argc, &argv);
+ for (i = 0; i < argc; i += 3) {
+ char const *type = Tcl_GetString(argv[i]);
+ char const *text = Tcl_GetString(argv[i + 1]);
+ char const *indx = Tcl_GetString(argv[i + 2]);
+
+ printf("%s ", indx);
+ printf("%s ", type);
+
+ if (strcmp(type, "text") == 0) {
+ int len = strlen(text), i;
+
+ printf("\"");
+ for (i = 0; i < len; ++i) {
+ char c = text[i];
+
+ switch (c) {
+ case '\t': printf("\\t"); break;
+ case '\n': printf("\\n"); break;
+ case '\v': printf("\\v"); break;
+ case '\f': printf("\\f"); break;
+ case '\r': printf("\\r"); break;
+
+ default:
+ if (UCHAR(c) < 0x80 && isprint(c)) {
+ printf("%c", c);
+ } else {
+ printf("\\x%02u", (unsigned) UCHAR(c));
+ }
+ break;
+ }
+ }
+ printf("\"\n");
+ } else if (strcmp(type, "mark") == 0) {
+ Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, text);
+ const TkTextSegment *markPtr = NULL;
+
+ if (hPtr) {
+ markPtr = Tcl_GetHashValue(hPtr);
+ } else {
+ if (strcmp(text, "insert") == 0) { markPtr = textPtr->insertMarkPtr; }
+ if (strcmp(text, "current") == 0) { markPtr = textPtr->currentMarkPtr; }
+ }
+ if (markPtr) {
+ printf("%s (%s)\n", text, markPtr->typePtr == &tkTextLeftMarkType ? "left" : "right");
+ }
+ } else {
+ printf("%s\n", text);
+ }
+ }
+
+ Tcl_SetObjResult(textPtr->interp, resultPtr);
+ Tcl_DecrRefCount(resultPtr);
+}
+
+#endif /* !NDEBUG */
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+/* Additionally we need stand-alone object code. */
+#define inline extern
+inline TkSharedText * TkBTreeGetShared(TkTextBTree tree);
+inline int TkBTreeGetNumberOfDisplayLines(const TkTextPixelInfo *pixelInfo);
+inline TkTextPixelInfo *TkBTreeLinePixelInfo(const TkText *textPtr, TkTextLine *linePtr);
+inline unsigned TkBTreeEpoch(TkTextBTree tree);
+inline unsigned TkBTreeIncrEpoch(TkTextBTree tree);
+inline struct Node *TkBTreeGetRoot(TkTextBTree tree);
+inline TkTextLine * TkBTreePrevLogicalLine(const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, TkTextLine *linePtr);
+inline TkTextTag * TkBTreeGetTags(const TkTextIndex *indexPtr);
+inline TkTextLine * TkBTreeGetStartLine(const TkText *textPtr);
+inline TkTextLine * TkBTreeGetLastLine(const TkText *textPtr);
+inline TkTextLine * TkBTreeNextLine(const TkText *textPtr, TkTextLine *linePtr);
+inline TkTextLine * TkBTreePrevLine(const TkText *textPtr, TkTextLine *linePtr);
+inline unsigned TkBTreeCountLines(const TkTextBTree tree, const TkTextLine *linePtr1,
+ const TkTextLine *linePtr2);
+inline bool TkTextIsDeadPeer(const TkText *textPtr);
+inline bool TkTextIsStartEndMarker(const TkTextSegment *segPtr);
+inline bool TkTextIsSpecialMark(const TkTextSegment *segPtr);
+inline bool TkTextIsPrivateMark(const TkTextSegment *segPtr);
+inline bool TkTextIsSpecialOrPrivateMark(const TkTextSegment *segPtr);
+inline bool TkTextIsNormalOrSpecialMark(const TkTextSegment *segPtr);
+inline bool TkTextIsNormalMark(const TkTextSegment *segPtr);
+inline bool TkTextIsStableMark(const TkTextSegment *segPtr);
+inline void TkTextIndexSetEpoch(TkTextIndex *indexPtr, unsigned epoch);
+inline void TkTextIndexUpdateEpoch(TkTextIndex *indexPtr, unsigned epoch);
+inline void TkTextIndexSetPeer(TkTextIndex *indexPtr, TkText *textPtr);
+inline void TkTextIndexSetToLastChar2(TkTextIndex *indexPtr, TkTextLine *linePtr);
+inline void TkTextIndexInvalidate(TkTextIndex *indexPtr);
+inline TkTextLine * TkTextIndexGetLine(const TkTextIndex *indexPtr);
+inline TkTextSegment * TkTextIndexGetSegment(const TkTextIndex *indexPtr);
+inline TkSharedText * TkTextIndexGetShared(const TkTextIndex *indexPtr);
+inline bool TkTextIndexSameLines(const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2);
+inline void TkTextIndexSave(TkTextIndex *indexPtr);
+# if TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION < 7 && TCL_UTF_MAX <= 4
+inline int TkUtfToUniChar(const char *src, int *chPtr);
+# endif
+#endif /* __STDC_VERSION__ >= 199901L */
+
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
- * fill-column: 78
+ * fill-column: 105
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/generic/tkText.h b/generic/tkText.h
index 430c96b..34e9832 100644
--- a/generic/tkText.h
+++ b/generic/tkText.h
@@ -5,6 +5,7 @@
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1995 Sun Microsystems, Inc.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -14,70 +15,343 @@
#define _TKTEXT
#ifndef _TK
-#include "tk.h"
+# include "tk.h"
#endif
-#ifndef _TKUNDO
-#include "tkUndo.h"
+#ifndef _TKINT
+# include "tkInt.h"
#endif
+#include "tkTextUndo.h"
+#include "tkQTree.h"
+#include "tkBool.h"
+#include "tkAlloc.h"
+#include <stdint.h>
+
+#ifdef MAC_OSX_TK
+/* required for TK_LAYOUT_WITH_BASE_CHUNKS */
+# include "tkMacOSXInt.h"
+#endif
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+#ifdef BUILD_tk
+# undef TCL_STORAGE_CLASS
+# define TCL_STORAGE_CLASS DLLEXPORT
+#endif
+
+#if TK_CHECK_ALLOCS
+# define DEBUG_ALLOC(expr) expr
+#else
+# define DEBUG_ALLOC(expr)
+#endif
+
+#if TK_MAJOR_VERSION < 9
+
+/* We are still supporting the deprecated -startline/-endline options. */
+# define SUPPORT_DEPRECATED_STARTLINE_ENDLINE 1
+
+/* We are still supporting invalid changes in readonly/disabled widgets. */
+# define SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET 1
+
+/*
+ * The special index identifier "begin" currently has the lowest precedence,
+ * because of portability reasons. But in a future Tk version it should have
+ * the same precedence as the special index identifier "end".
+ */
+# define BEGIN_DOES_NOT_BELONG_TO_BASE 1
+
+/* We are still supporting deprecated tag options. */
+# define SUPPORT_DEPRECATED_TAG_OPTIONS 1
+
+/* We are still supporting the deprecated commands "edit canundo/redo". */
+# define SUPPORT_DEPRECATED_CANUNDO_REDO 1
+
+#endif /* TK_MAJOR_VERSION < 9 */
+
+#if TK_TEXT_DONT_USE_BITFIELDS
+# define TkTextTagSet TkIntSet
+# define STRUCT struct
+#else
+# define STRUCT union
+#endif /* TK_TEXT_DONT_USE_BITFIELDS < 9 */
+
+/*
+ * Forward declarations.
+ */
+
+struct TkTextUndoStack;
+struct TkBitField;
+struct TkTextUndoToken;
+STRUCT TkTextTagSet;
+
+/* We need a backport to version 8.5 */
+#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5
+typedef struct TkTextBTree_ *TkTextBTree;
+#endif
+
+/*
+ * The data structure below defines the pixel information for a single line of text.
+ */
+
+typedef struct TkTextDispLineEntry {
+ uint32_t height; /* Height of display line in pixels. */
+ uint32_t pixels; /* Accumulated height of display lines. In last entry this attribute
+ * will contain the old number of display lines. */
+ uint32_t byteOffset:24; /* Byte offet relative to logical line. */
+ uint32_t hyphenRule:8; /* Hyphenation rule applied to last char chunk of this display line. */
+} TkTextDispLineEntry;
+
+typedef struct TkTextDispLineInfo {
+ uint32_t numDispLines; /* Number of display lines (so far). */
+ TkTextDispLineEntry entry[1];
+ /* Cached information about the display line, this is required for
+ * long lines to avoid repeated display line height calculations
+ * when scrolling. If numDispLines <= 1 then this information is
+ * NULL, in this case attribute height of TkTextPixelInfo is
+ * identical to the pixel height of the (single) display line. */
+} TkTextDispLineInfo;
+
+typedef struct TkTextPixelInfo {
+ uint32_t height; /* Number of vertical pixels taken up by this line, whether
+ * currently displayed or not. This number is only updated
+ * asychronously. Note that this number is the sum of
+ * dispLineInfo, but only when dispLineInfo != NULL. */
+ uint32_t epoch; /* Last epoch at which the pixel height was recalculated. */
+ TkTextDispLineInfo *dispLineInfo;
+ /* Pixel information for each display line, available only
+ * if more than one display line exists, otherwise it is NULL. */
+} TkTextPixelInfo;
+
+/*
+ * Macro that determines how much space to allocate:
+ */
+
+#define TEXT_DISPLINEINFO_SIZE(numDispLines) (Tk_Offset(TkTextDispLineInfo, entry) + \
+ (numDispLines)*sizeof(((TkTextDispLineInfo *) 0)->entry[0]))
+
/*
* The data structure below defines a single logical line of text (from
* newline to newline, not necessarily what appears on one display line of the
* screen).
*/
+struct TkTextSegment;
+
typedef struct TkTextLine {
struct Node *parentPtr; /* Pointer to parent node containing line. */
- struct TkTextLine *nextPtr; /* Next in linked list of lines with same
- * parent node in B-tree. NULL means end of
- * list. */
+ struct TkTextLine *nextPtr; /* Next in linked list of lines with same parent node in B-tree.
+ * NULL means end of list. */
+ struct TkTextLine *prevPtr; /* Previous in linked list of lines. NULL means no predecessor. */
struct TkTextSegment *segPtr;
- /* First in ordered list of segments that make
- * up the line. */
- int *pixels; /* Array containing two integers for each
- * referring text widget. The first of these
- * is the number of vertical pixels taken up
- * by this line, whether currently displayed
- * or not. This number is only updated
- * asychronously. The second of these is the
- * last epoch at which the pixel height was
- * recalculated. */
+ /* First in ordered list of segments that make up the line. */
+ struct TkTextSegment *lastPtr;
+ /* Last in ordered list of segments that make up the line. */
+ STRUCT TkTextTagSet *tagonPtr;
+ /* This set contains all tags used in this line. */
+ STRUCT TkTextTagSet *tagoffPtr;
+ /* This set contains tag t if and only if at least one segment in
+ * this line does not use tag t, provided that tag t is also included
+ * in tagonPtr. */
+ TkTextPixelInfo *pixelInfo; /* Array containing the pixel information for each referring
+ * text widget. */
+ int32_t size; /* Sum of sizes over all segments belonging to this line. */
+ uint32_t numBranches; /* Counting the number of branches on this line. Only count branches
+ * connected with links, do not count branches pointing to a mark. */
+ uint32_t numLinks:30; /* Counting the number of links on this line. */
+ uint32_t changed:1; /* Will be set when the content of this logical line has changed. The
+ * display stuff will use (and reset) this flag, but only for logical
+ * lines. The purpose of this flag is the acceleration of the line
+ * break information. */
+ uint32_t logicalLine:1; /* Flag whether this is the start of a logical line. */
} TkTextLine;
/*
* -----------------------------------------------------------------------
+ * Index structure containing line and byte index.
+ * -----------------------------------------------------------------------
+ */
+
+typedef struct TkTextPosition {
+ int32_t lineIndex;
+ int32_t byteIndex;
+} TkTextPosition;
+
+/*
+ * -----------------------------------------------------------------------
+ * Structures for undo/redo mechanism.
+ *
+ * Note that TkTextUndoToken is a generic type, used as a base struct for
+ * inheritance. Inheritance in C is portable due to C99 section 6.7.2.1
+ * bullet point 13:
+ *
+ * Within a structure object, the non-bit-field members and the units
+ * in which bit-fields reside have addresses that increase in the order
+ * in which they are declared. A pointer to a structure object, suitably
+ * converted, points to its initial member (or if that member is a
+ * bit-field, then to the unit in which it resides), and vice versa.
+ * There may be unnamed padding within a structure object, but not at
+ * beginning.
+ *
+ * This inheritance concept is also used in the portable GTK library.
+ * -----------------------------------------------------------------------
+ */
+
+/* we need some forward declarations */
+struct TkSharedText;
+struct TkTextIndex;
+struct TkTextUndoInfo;
+struct TkTextUndoIndex;
+
+typedef enum {
+ TK_TEXT_UNDO_INSERT, TK_TEXT_REDO_INSERT,
+ TK_TEXT_UNDO_DELETE, TK_TEXT_REDO_DELETE,
+ TK_TEXT_UNDO_IMAGE, TK_TEXT_REDO_IMAGE,
+ TK_TEXT_UNDO_WINDOW, TK_TEXT_REDO_WINDOW,
+ TK_TEXT_UNDO_TAG, TK_TEXT_REDO_TAG,
+ TK_TEXT_UNDO_TAG_CLEAR, TK_TEXT_REDO_TAG_CLEAR,
+ TK_TEXT_UNDO_TAG_PRIORITY, TK_TEXT_REDO_TAG_PRIORITY,
+ TK_TEXT_UNDO_MARK_SET, TK_TEXT_REDO_MARK_SET,
+ TK_TEXT_UNDO_MARK_MOVE, TK_TEXT_REDO_MARK_MOVE,
+ TK_TEXT_UNDO_MARK_GRAVITY, TK_TEXT_REDO_MARK_GRAVITY
+} TkTextUndoAction;
+
+typedef Tcl_Obj *(*TkTextGetUndoCommandProc)(
+ const struct TkSharedText *sharedTextPtr,
+ const struct TkTextUndoToken *item);
+
+typedef void (*TkTextUndoProc)(
+ struct TkSharedText *sharedTextPtr,
+ struct TkTextUndoInfo *undoInfo,
+ struct TkTextUndoInfo *redoInfo,
+ bool isRedo);
+
+typedef void (*TkTextDestroyUndoItemProc)(
+ struct TkSharedText *sharedTextPtr,
+ struct TkTextUndoToken *item,
+ bool reused);
+
+typedef void (*TkTextGetUndoRangeProc)(
+ const struct TkSharedText *sharedTextPtr,
+ const struct TkTextUndoToken *item,
+ struct TkTextIndex *startIndex,
+ struct TkTextIndex *endIndex);
+
+typedef Tcl_Obj *(*TkTextInspectProc)(
+ const struct TkSharedText *sharedTextPtr,
+ const struct TkTextUndoToken *item);
+
+typedef struct Tk_UndoType {
+ TkTextUndoAction action;
+ TkTextGetUndoCommandProc commandProc; /* mandatory */
+ TkTextUndoProc undoProc; /* mandatory */
+ TkTextDestroyUndoItemProc destroyProc; /* optional */
+ TkTextGetUndoRangeProc rangeProc; /* mandatory */
+ TkTextInspectProc inspectProc; /* mandatory */
+} Tk_UndoType;
+
+/*
+ * The struct below either contains a mark segment or a line/byte index pair.
+ * This struct is portable due to C99 7.18.1.4.
+ */
+
+typedef struct TkTextUndoIndex {
+ union {
+ struct TkTextSegment *markPtr; /* Predecessor/successor segment. */
+ uintptr_t byteIndex; /* Byte index in this line. */
+ } u;
+ int32_t lineIndex; /* Line index, if -1 this struct contains a mark segment, otherwise
+ * (if >= 0) this struct contains a line/byte index pair. */
+} TkTextUndoIndex;
+
+/*
+ * This is a generic type, any "derived" struct must contain
+ * 'const Tk_UndoType *' as the first member (see note above).
+ */
+typedef struct TkTextUndoToken {
+ const Tk_UndoType *undoType;
+} TkTextUndoToken;
+
+/*
+ * This is a generic type, any "derived" struct must also contain
+ * these members. Especially the tokens for insert/delete must be
+ * derived from this struct.
+ */
+typedef struct TkTextUndoTokenRange {
+ const Tk_UndoType *undoType;
+ TkTextUndoIndex startIndex;
+ TkTextUndoIndex endIndex;
+} TkTextUndoTokenRange;
+
+typedef struct TkTextUndoInfo {
+ TkTextUndoToken *token; /* The data of this undo/redo item. */
+ uint32_t byteSize; /* Byte size of this item. */
+} TkTextUndoInfo;
+
+/*
+ * -----------------------------------------------------------------------
* Segments: each line is divided into one or more segments, where each
- * segment is one of several things, such as a group of characters, a tag
- * toggle, a mark, or an embedded widget. Each segment starts with a standard
+ * segment is one of several things, such as a group of characters, a hyphen,
+ * a mark, or an embedded widget. Each segment starts with a standard
* header followed by a body that varies from type to type.
* -----------------------------------------------------------------------
*/
/*
- * The data structure below defines the body of a segment that represents a
- * tag toggle. There is one of these structures at both the beginning and end
- * of each tagged range.
+ * The data structure below defines the body of a segment that represents
+ * a branch. A branch is adjusting the chain of segments, depending whether
+ * elidden text will be processed or not.
*/
-typedef struct TkTextToggle {
- struct TkTextTag *tagPtr; /* Tag that starts or ends here. */
- int inNodeCounts; /* 1 means this toggle has been accounted for
- * in node toggle counts; 0 means it hasn't,
- * yet. */
-} TkTextToggle;
+typedef struct TkTextBranch {
+ struct TkTextSegment *nextPtr; /* The next in list of segments for the alternative branch. */
+} TkTextBranch;
+
+/*
+ * The data structure below defines the body of a segment that represents
+ * a link. A link is the connection point for a segment chain of elidden
+ * text.
+ */
+
+typedef struct TkTextLink {
+ struct TkTextSegment *prevPtr; /* Previous in list of segments for the alternative branch. */
+} TkTextLink;
/*
* The data structure below defines line segments that represent marks. There
* is one of these for each mark in the text.
*/
+typedef struct TkTextMarkChange {
+ struct TkTextSegment *markPtr;
+ /* Pointer to mark segment which contains this item. */
+ struct TkTextUndoToken *toggleGravity;
+ /* Undo token for "mark gravity". */
+ struct TkTextUndoToken *moveMark;
+ /* Undo token for "mark move". */
+ struct TkTextUndoToken *setMark;
+ /* Undo token for "mark set". */
+ const struct Tk_SegType *savedMarkType;
+ /* Type of mark (left or right gravity) before "mark gravity". We
+ * need this information for optimizing succeeding calls of
+ * "mark gravity" with same mark. */
+} TkTextMarkChange;
+
typedef struct TkTextMark {
struct TkText *textPtr; /* Overall information about text widget. */
- TkTextLine *linePtr; /* Line structure that contains the
- * segment. */
- Tcl_HashEntry *hPtr; /* Pointer to hash table entry for mark (in
- * sharedTextPtr->markTable). */
+ uintptr_t ptr; /* [Address is even - real type is Tcl_HashEntry *]
+ * Pointer to hash table entry for mark (in sharedTextPtr->markTable).
+ * [Address is odd - real type is const char *]
+ * Name of mark, this is used iff the segment is preserved for undo. */
+ TkTextMarkChange *changePtr;/* Pointer to retained undo tokens. */
} TkTextMark;
/*
@@ -87,42 +361,35 @@ typedef struct TkTextMark {
*/
typedef struct TkTextEmbWindowClient {
- struct TkText *textPtr; /* Information about the overall text
- * widget. */
- Tk_Window tkwin; /* Window for this segment. NULL means that
- * the window hasn't been created yet. */
- int chunkCount; /* Number of display chunks that refer to this
- * window. */
- int displayed; /* Non-zero means that the window has been
- * displayed on the screen recently. */
+ struct TkText *textPtr; /* Information about the overall text widget. */
+ Tk_Window tkwin; /* Window for this segment. NULL means that the window
+ * hasn't been created yet. */
+ Tcl_HashEntry *hPtr; /* Pointer to hash table entry for mark
+ * (in sharedTextPtr->windowTable). */
+ unsigned chunkCount; /* Number of display chunks that refer to this window. */
+ bool displayed; /* Non-zero means that the window has been displayed on
+ * the screen recently. */
struct TkTextSegment *parent;
struct TkTextEmbWindowClient *next;
} TkTextEmbWindowClient;
typedef struct TkTextEmbWindow {
struct TkSharedText *sharedTextPtr;
- /* Information about the shared portion of the
- * text widget. */
- Tk_Window tkwin; /* Window for this segment. This is just a
- * temporary value, copied from 'clients', to
- * make option table updating easier. NULL
- * means that the window hasn't been created
- * yet. */
- TkTextLine *linePtr; /* Line structure that contains this
- * window. */
- char *create; /* Script to create window on-demand. NULL
- * means no such script. Malloc-ed. */
- int align; /* How to align window in vertical space. See
- * definitions in tkTextWind.c. */
- int padX, padY; /* Padding to leave around each side of
- * window, in pixels. */
- int stretch; /* Should window stretch to fill vertical
- * space of line (except for pady)? 0 or 1. */
- Tk_OptionTable optionTable; /* Token representing the configuration
- * specifications. */
+ /* Information about the shared portion of the text widget. */
+ Tk_Window tkwin; /* Window for this segment. This is just a temporary value,
+ * copied from 'clients', to make option table updating easier.
+ * NULL means that the window hasn't been created yet. */
+ char *create; /* Script to create window on-demand. NULL means no such script.
+ * Malloc-ed. */
+ int align; /* How to align window in vertical space. See definitions in
+ * tkTextWind.c. */
+ int padX, padY; /* Padding to leave around each side of window, in pixels. */
+ bool stretch; /* Should window stretch to fill vertical space of line
+ * (except for pady)? */
+ Tk_OptionTable optionTable; /* Token representing the configuration specifications. */
TkTextEmbWindowClient *clients;
- /* Linked list of peer-widget specific
- * information for this embedded window. */
+ /* Linked list of peer-widget specific information for this
+ * embedded window. */
} TkTextEmbWindow;
/*
@@ -132,53 +399,111 @@ typedef struct TkTextEmbWindow {
typedef struct TkTextEmbImage {
struct TkSharedText *sharedTextPtr;
- /* Information about the shared portion of the
- * text widget. This is used when the image
- * changes or is deleted. */
- TkTextLine *linePtr; /* Line structure that contains this image. */
+ /* Information about the shared portion of the text widget.
+ * This is used when the image changes or is deleted. */
char *imageString; /* Name of the image for this segment. */
- char *imageName; /* Name used by text widget to identify this
- * image. May be unique-ified. */
- char *name; /* Name used in the hash table. Used by
- * "image names" to identify this instance of
- * the image. */
- Tk_Image image; /* Image for this segment. NULL means that the
- * image hasn't been created yet. */
- int align; /* How to align image in vertical space. See
- * definitions in tkTextImage.c. */
- int padX, padY; /* Padding to leave around each side of image,
- * in pixels. */
- int chunkCount; /* Number of display chunks that refer to this
- * image. */
- Tk_OptionTable optionTable; /* Token representing the configuration
- * specifications. */
+ char *imageName; /* Name used by text widget to identify this image.
+ * May be unique-ified. */
+ char *name; /* Name used in the hash table. Used by "image names" to
+ * identify this instance of the image. */
+ struct TkTextEmbImage *nextPtr;
+ /* Will be used in TkTextPickCurrent. */
+ bool hovered; /* Will be used in TkTextPickCurrent. */
+ bool haveBindings; /* Flag whether this image has bindings. */
+ uint32_t numClients; /* Size of bbox array. */
+ TkQTreeRect *bbox; /* Bounding box of this image, one bbox for every peer. */
+ Tk_Image image; /* Image for this segment. NULL means that the image hasn't
+ * been created yet. */
+ int imgHeight; /* Height of displayed image. */
+ Tcl_HashEntry *hPtr; /* Pointer to hash table entry for image
+ * (in sharedTextPtr->imageTable).*/
+ int align; /* How to align image in vertical space. See definitions in
+ * tkTextImage.c. */
+ int padX, padY; /* Padding to leave around each side of image, in pixels. */
+ Tk_OptionTable optionTable; /* Token representing the configuration specifications. */
} TkTextEmbImage;
/*
+ * A structure of the following type is for the definition of the hyphenation
+ * segments. Note that this structure is a derivation of a char segment. This
+ * is portable due to C99 section 6.7.2.1 (see above - undo/redo).
+ */
+
+typedef struct TkTextHyphen {
+ char chars[6]; /* Characters that make up character info. Actual length varies. */
+ int8_t textSize; /* Actual length of text, but not greater than 5. */
+ int8_t rules; /* Set of rules for this hyphen, will be set by tk_textInsert. */
+} TkTextHyphen;
+
+/*
* The data structure below defines line segments.
*/
typedef struct TkTextSegment {
const struct Tk_SegType *typePtr;
- /* Pointer to record describing segment's
- * type. */
+ /* Pointer to record describing segment's type. */
struct TkTextSegment *nextPtr;
- /* Next in list of segments for this line, or
- * NULL for end of list. */
- int size; /* Size of this segment (# of bytes of index
- * space it occupies). */
+ /* Next in list of segments for this line, or NULL for end of list. */
+ struct TkTextSegment *prevPtr;
+ /* Previous in list of segments for this line, or NULL for start
+ * of list. */
+ struct TkTextSection *sectionPtr;
+ /* The section where this segment belongs. */
+ STRUCT TkTextTagSet *tagInfoPtr;
+ /* Tag information for this segment, needed for testing whether
+ * this content is tagged with a specific tag. Used only if size > 0.
+ * (In case of segments with size == 0 memory will be wasted - these
+ * segments do not need this attribute - but the alternative would be
+ * a quite complex structure with some nested structs and unions, and
+ * this is quite inconvenient. Only marks and branches/links do not
+ * use this information, so the waste of memory is relatively low.) */
+ int32_t size; /* Size of this segment (# of bytes of index space it occupies). */
+ uint32_t refCount:26; /* Reference counter, don't delete until counter is zero, or
+ * tree is gone. */
+ uint32_t protectionFlag:1; /* This (char) segment is protected, join is not allowed. */
+ uint32_t insertMarkFlag:1; /* This segment is the special "insert" mark? */
+ uint32_t currentMarkFlag:1; /* This segment is the special "current" mark? */
+ uint32_t privateMarkFlag:1; /* This mark segment is private (generated)? */
+ uint32_t normalMarkFlag:1; /* This mark segment is neither protected, nor special, nor private,
+ * nor start or end marker. */
+ uint32_t startEndMarkFlag:1;/* This segment is a start marker or an end marker? */
+
union {
- char chars[2]; /* Characters that make up character info.
- * Actual length varies to hold as many
- * characters as needed.*/
- TkTextToggle toggle; /* Information about tag toggle. */
- TkTextMark mark; /* Information about mark. */
+ char chars[1]; /* Characters that make up character info. Actual length varies
+ * to hold as many characters as needed. */
+ TkTextHyphen hyphen; /* Information about hyphen. */
TkTextEmbWindow ew; /* Information about embedded window. */
TkTextEmbImage ei; /* Information about embedded image. */
+ TkTextMark mark; /* Information about mark. */
+ TkTextBranch branch; /* Information about branch. */
+ TkTextLink link; /* Information about link. */
} body;
} TkTextSegment;
/*
+ * Macro that determines how much space to allocate for a specific segment:
+ */
+
+#define SEG_SIZE(bodyType) ((unsigned) (Tk_Offset(TkTextSegment, body) + sizeof(bodyType)))
+
+/*
+ * The data structure below defines sections of text segments. Each section
+ * contains 40 line segments in average. In this way fast search for segments
+ * is possible.
+ */
+typedef struct TkTextSection {
+ struct TkTextLine *linePtr; /* The line where this section belongs. */
+ struct TkTextSection *nextPtr;
+ /* Next in list of sections, or NULL if last. */
+ struct TkTextSection *prevPtr;
+ /* Previous in list of sections, or NULL if first. */
+ struct TkTextSegment *segPtr;
+ /* First segment belonging to this section. */
+ int32_t size:24; /* Sum of size over all segments belonging to this section. */
+ uint32_t length:8; /* Number of segments belonging to this section. */
+} TkTextSection;
+
+/*
* Data structures of the type defined below are used during the execution of
* Tcl commands to keep track of various interesting places in a text. An
* index is only valid up until the next modification to the character
@@ -188,14 +513,29 @@ typedef struct TkTextSegment {
typedef struct TkTextIndex {
TkTextBTree tree; /* Tree containing desired position. */
- TkTextLine *linePtr; /* Pointer to line containing position of
- * interest. */
- int byteIndex; /* Index within line of desired character (0
- * means first one). */
- struct TkText *textPtr; /* May be NULL, but otherwise the text widget
- * with which this index is associated. If not
- * NULL, then we have a refCount on the
- * widget. */
+ struct TkText *textPtr; /* The associated text widget (required). */
+ uint32_t stateEpoch; /* The epoch of the segment pointer. */
+
+ /*
+ * The following attribtes should not be accessed directly, use the TkTextIndex*
+ * functions if you want to set or get attributes.
+ */
+
+ struct {
+ TkTextLine *linePtr; /* Pointer to line containing position of interest. */
+ TkTextSegment *segPtr; /* Pointer to segment containing position
+ * of interest (NULL means not yet computed). */
+ bool isCharSegment; /* Whether 'segPtr' is a char segment (if not NULL). */
+ int32_t byteIndex; /* Index within line of desired character (0 means first one,
+ * -1 means not yet computed). */
+ int32_t lineNo; /* The line number of the line pointer. */
+ int32_t lineNoRel; /* The line number of the line pointer in associated text widget. */
+ } priv;
+
+ bool discardConsistencyCheck;
+ /* This flag is for debugging only: in certain situations consistency
+ * checks should not be done (for example when inserting or deleting
+ * text). */
} TkTextIndex;
/*
@@ -204,17 +544,14 @@ typedef struct TkTextIndex {
typedef struct TkTextDispChunk TkTextDispChunk;
-typedef void Tk_ChunkDisplayProc(struct TkText *textPtr,
- TkTextDispChunk *chunkPtr, int x, int y,
- int height, int baseline, Display *display,
- Drawable dst, int screenY);
-typedef void Tk_ChunkUndisplayProc(struct TkText *textPtr,
- TkTextDispChunk *chunkPtr);
-typedef int Tk_ChunkMeasureProc(TkTextDispChunk *chunkPtr, int x);
-typedef void Tk_ChunkBboxProc(struct TkText *textPtr,
- TkTextDispChunk *chunkPtr, int index, int y,
- int lineHeight, int baseline, int *xPtr,
- int *yPtr, int *widthPtr, int *heightPtr);
+typedef void Tk_ChunkDisplayProc(struct TkText *textPtr, TkTextDispChunk *chunkPtr,
+ int x, int y, int height, int baseline, Display *display, Drawable dst,
+ int screenY);
+typedef void Tk_ChunkUndisplayProc(struct TkText *textPtr, TkTextDispChunk *chunkPtr);
+typedef int Tk_ChunkMeasureProc(TkTextDispChunk *chunkPtr, int x);
+typedef void Tk_ChunkBboxProc(struct TkText *textPtr, TkTextDispChunk *chunkPtr,
+ int index, int y, int lineHeight, int baseline, int *xPtr, int *yPtr,
+ int *widthPtr, int *heightPtr);
/*
* The structure below represents a chunk of stuff that is displayed together
@@ -222,6 +559,35 @@ typedef void Tk_ChunkBboxProc(struct TkText *textPtr,
* code but most of its fields are filled in by segment-type-specific code.
*/
+typedef enum {
+ TEXT_DISP_CHAR = 1 << 0, /* Character layout */
+ TEXT_DISP_HYPHEN = 1 << 1, /* Hyphen layout */
+ TEXT_DISP_ELIDED = 1 << 2, /* Elided content layout */
+ TEXT_DISP_WINDOW = 1 << 3, /* Embedded window layout */
+ TEXT_DISP_IMAGE = 1 << 4, /* Embedded image layout */
+ TEXT_DISP_CURSOR = 1 << 5, /* Insert cursor layout */
+} TkTextDispType;
+
+/* This constant can be used for a test whether the chunk has any content. */
+#define TEXT_DISP_CONTENT (TEXT_DISP_CHAR|TEXT_DISP_HYPHEN|TEXT_DISP_WINDOW|TEXT_DISP_IMAGE)
+/* This constant can be used for a test whether the chunk contains text. */
+#define TEXT_DISP_TEXT (TEXT_DISP_CHAR|TEXT_DISP_HYPHEN)
+
+typedef struct TkTextDispChunkProcs {
+ TkTextDispType type; /* Layout type. */
+ Tk_ChunkDisplayProc *displayProc;
+ /* Procedure to invoke to draw this chunk on the display
+ * or an off-screen pixmap. */
+ Tk_ChunkUndisplayProc *undisplayProc;
+ /* Procedure to invoke when segment ceases to be displayed
+ * on screen anymore. */
+ Tk_ChunkMeasureProc *measureProc;
+ /* Procedure to find character under a given x-location. */
+ Tk_ChunkBboxProc *bboxProc; /* Procedure to find bounding box of character in chunk. */
+} TkTextDispChunkProcs;
+
+struct TkTextDispChunkSection;
+
struct TkTextDispChunk {
/*
* The fields below are set by the type-independent code before calling
@@ -229,50 +595,77 @@ struct TkTextDispChunk {
* segment-type-specific code.
*/
- int x; /* X position of chunk, in pixels. This
- * position is measured from the left edge of
- * the logical line, not from the left edge of
- * the window (i.e. it doesn't change under
- * horizontal scrolling). */
struct TkTextDispChunk *nextPtr;
- /* Next chunk in the display line or NULL for
- * the end of the list. */
- struct TextStyle *stylePtr; /* Display information, known only to
- * tkTextDisp.c. */
+ /* Next chunk in the display line or NULL for the end of the list. */
+ struct TkTextDispChunk *prevPtr;
+ /* Previous chunk in the display line or NULL for the start of the
+ * list. */
+ struct TkTextDispChunk *prevCharChunkPtr;
+ /* Previous char chunk in the display line, or NULL. */
+ struct TkTextDispChunkSection *sectionPtr;
+ /* The section of this chunk. The section structure allows fast search
+ * for x positions, and character positions. */
+ struct TextStyle *stylePtr; /* Display information, known only to tkTextDisp.c. */
/*
* The fields below are set by the layoutProc that creates the chunk.
*/
- Tk_ChunkDisplayProc *displayProc;
- /* Procedure to invoke to draw this chunk on
- * the display or an off-screen pixmap. */
- Tk_ChunkUndisplayProc *undisplayProc;
- /* Procedure to invoke when segment ceases to
- * be displayed on screen anymore. */
- Tk_ChunkMeasureProc *measureProc;
- /* Procedure to find character under a given
- * x-location. */
- Tk_ChunkBboxProc *bboxProc; /* Procedure to find bounding box of character
- * in chunk. */
- int numBytes; /* Number of bytes that will be displayed in
- * the chunk. */
- int minAscent; /* Minimum space above the baseline needed by
- * this chunk. */
- int minDescent; /* Minimum space below the baseline needed by
- * this chunk. */
- int minHeight; /* Minimum total line height needed by this
- * chunk. */
- int width; /* Width of this chunk, in pixels. Initially
- * set by chunk-specific code, but may be
- * increased to include tab or extra space at
- * end of line. */
- int breakIndex; /* Index within chunk of last acceptable
- * position for a line (break just before this
- * byte index). <= 0 means don't break during
- * or immediately after this chunk. */
- ClientData clientData; /* Additional information for use of
- * displayProc and undisplayProc. */
+ const TkTextDispChunkProcs *layoutProcs;
+ const char *brks; /* Line break information of this chunk for TEXT_WRAPMODE_CODEPOINT. */
+ ClientData clientData; /* Additional information for use of displayProc and undisplayProc. */
+
+ /*
+ * The fields below are set by the type-independent code before calling
+ * the segment-type-specific layoutProc. They should not be modified by
+ * segment-type-specific code.
+ */
+
+ int32_t x; /* X position of chunk, in pixels. This position is measured
+ * from the left edge of the logical line, not from the left
+ * edge of the window (i.e. it doesn't change under horizontal
+ * scrolling). */
+
+ /*
+ * The fields below are set by the layoutProc that creates the chunk.
+ */
+
+ uint32_t byteOffset; /* Byte offset relative to display line start. */
+ uint32_t numBytes; /* Number of bytes that will be used in the chunk. */
+ uint32_t numSpaces; /* Number of expandable spaces. */
+ uint32_t segByteOffset; /* Starting offset in corresponding char segment. */
+ int32_t minAscent; /* Minimum space above the baseline needed by this chunk. */
+ int32_t minDescent; /* Minimum space below the baseline needed by this chunk. */
+ int32_t minHeight; /* Minimum total line height needed by this chunk. */
+ int32_t width; /* Width of this chunk, in pixels. Initially set by
+ * chunk-specific code, but may be increased to include tab
+ * or extra space at end of line. */
+ int32_t additionalWidth; /* Additional width when expanding spaces for full justification. */
+ int32_t hyphenRules; /* Allowed hyphenation rules for this (hyphen) chunk. */
+ int32_t breakIndex:29; /* Index within chunk of last acceptable position for a line
+ * (break just before this byte index). <= 0 means don't break
+ * during or immediately after this chunk. */
+ uint32_t wrappedAtSpace:1; /* This flag will be set when the a chunk has been wrapped while
+ * gobbling a trailing space. */
+ uint32_t endsWithSyllable:1;/* This flag will be set when the corresponding sgement for
+ * this chunk will be followed by a hyphen segment. */
+ uint32_t skipFirstChar:1; /* This flag will be set if the first byte has to be skipped due
+ * to a spelling change. */
+
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+
+ /*
+ * Support of context drawing (Mac):
+ */
+
+ Tcl_DString baseChars; /* Actual characters for the stretch of text, only defined in
+ * base chunk. */
+ int32_t baseWidth; /* Width in pixels of the whole string, if known, else 0. */
+ int32_t xAdjustment; /* Adjustment of x-coord for next chunk. */
+ struct TkTextDispChunk *baseChunkPtr;
+ /* Points to base chunk. */
+
+#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
};
/*
@@ -282,31 +675,125 @@ struct TkTextDispChunk {
*/
typedef enum {
- TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, TEXT_WRAPMODE_WORD,
+ TEXT_WRAPMODE_CHAR,
+ TEXT_WRAPMODE_NONE,
+ TEXT_WRAPMODE_WORD,
+ TEXT_WRAPMODE_CODEPOINT,
TEXT_WRAPMODE_NULL
} TkWrapMode;
+/*
+ * The spacing mode of the text widget:
+ */
+
+typedef enum {
+ TEXT_SPACEMODE_NONE,
+ TEXT_SPACEMODE_EXACT,
+ TEXT_SPACEMODE_TRIM,
+ TEXT_SPACEMODE_NULL,
+} TkTextSpaceMode;
+
+/*
+ * The justification modes:
+ */
+
+typedef enum {
+ TK_TEXT_JUSTIFY_LEFT, /* The text is aligned along the left margin. This is the default. */
+ TK_TEXT_JUSTIFY_RIGHT, /* The text is aligned along the right margin. */
+ TK_TEXT_JUSTIFY_FULL, /* The text is aligned along the left margin, and word-spacing is
+ * adjusted so that the text flush with both margins. */
+ TK_TEXT_JUSTIFY_CENTER /* The text is aligned to neither the left nor the right margin,
+ * there is an even gap on each side of each line. */
+} TkTextJustify;
+
+/*
+ * If the soft hyphen is the right neighbor of character "c", and the right neighbor is character
+ * "k", then the ck hyphenation rule will be applied.
+ */
+#define TK_TEXT_HYPHEN_CK 1 /* de */
+/*
+ * Hungarian has an unusual hyphenation case which involves reinsertion of a root-letter, as in the
+ * following example: "vissza" becomes "visz-sza". These special cases, occurring when the characters
+ * are in the middle of a word are: "ccs" becomes "cs-cs", "ggy" becomes "gy-gy", "lly" becomes "ly-ly",
+ * "nny" becomes "ny-ny", "tty" becomes "ty-ty", "zzs" becomes "zs-zs", "ssz" becomes "sz-sz".
+ */
+#define TK_TEXT_HYPHEN_DOUBLE_DIGRAPH 2 /* hu */
+/*
+ * If the soft hyphen is the right neighbor of any vowel, and the right neighbor is the same vowel,
+ * then the doublevowel hyphenation rule will be applied.
+ */
+#define TK_TEXT_HYPHEN_DOUBLE_VOWEL 3 /* nl */
+/*
+ * In Catalan, a geminated consonant can be splitted: the word "paral·lel" hyphenates
+ * into "paral-lel".
+ */
+#define TK_TEXT_HYPHEN_GEMINATION 4 /* ca */
+/*
+ * In Polish the hyphen will be repeated after line break, this means for example that "kong-fu"
+ * becomes "kong- -fu".
+ */
+#define TK_TEXT_HYPHEN_REPEAT 5 /* pl */
+/*
+ * If the soft hyphen is the right neighbor of any vocal, and the right neighbor of any vocal with
+ * trema (umlaut), then the trema hyphenation rule will be applied.
+ */
+#define TK_TEXT_HYPHEN_TREMA 6 /* nl */
+/*
+ * If the soft hyphen is the right neighbor of any consonant, and the right neighbor is the same
+ * consonant, and the right consonant is followed by a vowel, then the tripleconsonant hyphenation
+ * rule will be applied when not hyphenating.
+ */
+#define TK_TEXT_HYPHEN_TRIPLE_CONSONANT 7 /* de, nb, nn, no, sv */
+/*
+ * Mask of all defined hyphen rules.
+ */
+#define TK_TEXT_HYPHEN_MASK ((1 << (TK_TEXT_HYPHEN_TRIPLE_CONSONANT + 1)) - 1)
+
typedef struct TkTextTag {
- const char *name; /* Name of this tag. This field is actually a
- * pointer to the key from the entry in
- * sharedTextPtr->tagTable, so it needn't be
- * freed explicitly. For 'sel' tags this is
- * just a static string, so again need not be
- * freed. */
- const struct TkText *textPtr;
- /* If non-NULL, then this tag only applies to
- * the given text widget (when there are peer
- * widgets). */
- int priority; /* Priority of this tag within widget. 0 means
- * lowest priority. Exactly one tag has each
- * integer value between 0 and numTags-1. */
- struct Node *tagRootPtr; /* Pointer into the B-Tree at the lowest node
- * that completely dominates the ranges of
- * text occupied by the tag. At this node
- * there is no information about the tag. One
- * or more children of the node do contain
- * information about the tag. */
- int toggleCount; /* Total number of tag toggles. */
+ const char *name; /* Name of this tag. This field is actually a pointer to the key
+ * from the entry in 'sharedTextPtr->tagTable', so it needn't be
+ * freed explicitly. For "sel" tags this is just a static string,
+ * so again need not be freed. */
+ const struct TkSharedText *sharedTextPtr;
+ /* Shared section of all peers. */
+ struct TkText *textPtr;
+ /* If non-NULL, then this tag only applies to the given text widget
+ * (when there are peer widgets). */
+ struct Node *rootPtr; /* Pointer into the B-Tree at the lowest node that completely
+ * dominates the ranges of text occupied by the tag. At this node
+ * there is no information about the tag. One or more children of
+ * the node do contain information about the tag. */
+ int32_t priority; /* Priority of this tag within widget. 0 means lowest priority.
+ * Exactly one tag has each integer value between 0 and numTags-1. */
+ uint32_t index; /* Unique index for fast tag lookup. It is guaranteed that the index
+ * number is less or equal than 'TkBitSize(sharedTextPtr->usedTags)'.*/
+ unsigned refCount; /* Number of objects referring to us. */
+ bool isDisabled; /* This tag is disabled? */
+
+ /*
+ * Information for tag collection [TkBTreeGetTags, TextInspectCmd, TkTextPickCurrent].
+ */
+
+ struct TkTextTag *nextPtr; /* Will be set by TkBTreeGetTags, TkBTreeClearTags, and TextInsertCmd. */
+ struct TkTextTag *succPtr; /* Only TextInspectCmd will use this attribute. */
+ uint32_t flag; /* Only for temporary usage (currently only TextInspectCmd and
+ * TkTextPickCurrent will use this attribute). */
+ uint32_t epoch; /* Only TkBTreeGetTags, TkBTreeGetSegmentTags, TkBTreeClearTags,
+ * and TkTextPickCurrent will use this attribute. */
+
+ /*
+ * Information for undo/redo.
+ */
+
+ TkTextUndoToken *recentTagAddRemoveToken;
+ /* Holds the undo information of last tag add/remove operation. */
+ TkTextUndoToken *recentChangePriorityToken;
+ /* Holds the undo information of last tag lower/raise operation. */
+ bool recentTagAddRemoveTokenIsNull;
+ /* 'recentTagAddRemoveToken' is null, this means the pointer still
+ * is valid, but should not be saved onto undo stack. */
+ int32_t savedPriority; /* Contains the priority before recentChangePriorityToken will be set. */
+ int32_t undoTagListIndex; /* Index to entry in 'undoTagList', is -1 if not in 'undoTagList'. */
/*
* Information for displaying text with this tag. The information belows
@@ -316,116 +803,116 @@ typedef struct TkTextTag {
* specifies an override.
*/
- Tk_3DBorder border; /* Used for drawing background. NULL means no
- * value specified here. */
+ Tk_3DBorder border; /* Used for drawing background. NULL means no value specified here. */
int borderWidth; /* Width of 3-D border for background. */
Tcl_Obj *borderWidthPtr; /* Width of 3-D border for background. */
- char *reliefString; /* -relief option string (malloc-ed). NULL
- * means option not specified. */
+ char *reliefString; /* -relief option string (malloc-ed). NULL means option not specified. */
int relief; /* 3-D relief for background. */
- Pixmap bgStipple; /* Stipple bitmap for background. None means
- * no value specified here. */
- XColor *fgColor; /* Foreground color for text. NULL means no
- * value specified here. */
- Tk_Font tkfont; /* Font for displaying text. NULL means no
- * value specified here. */
- Pixmap fgStipple; /* Stipple bitmap for text and other
- * foreground stuff. None means no value
- * specified here.*/
- char *justifyString; /* -justify option string (malloc-ed). NULL
- * means option not specified. */
- Tk_Justify justify; /* How to justify text: TK_JUSTIFY_LEFT,
- * TK_JUSTIFY_RIGHT, or TK_JUSTIFY_CENTER.
- * Only valid if justifyString is non-NULL. */
- char *lMargin1String; /* -lmargin1 option string (malloc-ed). NULL
- * means option not specified. */
- int lMargin1; /* Left margin for first display line of each
- * text line, in pixels. Only valid if
- * lMargin1String is non-NULL. */
- char *lMargin2String; /* -lmargin2 option string (malloc-ed). NULL
- * means option not specified. */
- int lMargin2; /* Left margin for second and later display
- * lines of each text line, in pixels. Only
- * valid if lMargin2String is non-NULL. */
- Tk_3DBorder lMarginColor; /* Used for drawing background in left margins.
- * This is used for both lmargin1 and lmargin2.
- * NULL means no value specified here. */
- char *offsetString; /* -offset option string (malloc-ed). NULL
- * means option not specified. */
- int offset; /* Vertical offset of text's baseline from
- * baseline of line. Used for superscripts and
- * subscripts. Only valid if offsetString is
+ Pixmap bgStipple; /* Stipple bitmap for background. None means no value specified here. */
+ char *indentBgString; /* Background will be indented accordingly to the -lmargin1, and
+ * -lmargin2 options. */
+ bool indentBg; /* Background will be indented accordingly to the -lmargin1, and
+ * -lmargin2 options. */
+ XColor *fgColor; /* Foreground color for text. NULL means no value specified here. */
+ Tk_Font tkfont; /* Font for displaying text. NULL means no value specified here. */
+ Pixmap fgStipple; /* Stipple bitmap for text and other foreground stuff. None means
+ * no value specified here.*/
+ char *justifyString; /* -justify option string (malloc-ed). NULL means option not
+ * specified. */
+ TkTextJustify justify; /* How to justify text: TK_TEXT_JUSTIFY_LEFT, TK_TEXT_JUSTIFY_RIGHT,
+ * TK_TEXT_JUSTIFY_CENTER, or TK_TEXT_JUSTIFY_FULL. Only valid if
+ * justifyString is non-NULL. */
+ char *lMargin1String; /* -lmargin1 option string (malloc-ed). NULL means option not
+ * specified. */
+ int lMargin1; /* Left margin for first display line of each text line, in pixels.
+ * Only valid if lMargin1String is non-NULL. */
+ char *lMargin2String; /* -lmargin2 option string (malloc-ed). NULL means option not
+ * specified. */
+ int lMargin2; /* Left margin for second and later display lines of each text line,
+ * in pixels. Only valid if lMargin2String is non-NULL. */
+ Tk_3DBorder lMarginColor; /* Used for drawing background in left margins. This is used for both
+ * lmargin1 and lmargin2. NULL means no value specified here. */
+ char *offsetString; /* -offset option string (malloc-ed). NULL means option not specified. */
+ int offset; /* Vertical offset of text's baseline from baseline of line. Used
+ * for superscripts and subscripts. Only valid if offsetString is
* non-NULL. */
- char *overstrikeString; /* -overstrike option string (malloc-ed). NULL
- * means option not specified. */
- int overstrike; /* Non-zero means draw horizontal line through
- * middle of text. Only valid if
- * overstrikeString is non-NULL. */
- XColor *overstrikeColor; /* Color for the overstrike. NULL means same
- * color as foreground. */
- char *rMarginString; /* -rmargin option string (malloc-ed). NULL
- * means option not specified. */
- int rMargin; /* Right margin for text, in pixels. Only
- * valid if rMarginString is non-NULL. */
- Tk_3DBorder rMarginColor; /* Used for drawing background in right margin.
- * NULL means no value specified here. */
+ char *overstrikeString; /* -overstrike option string (malloc-ed). NULL means option not
+ * specified. */
+ bool overstrike; /* True means draw horizontal line through middle of text. Only
+ * valid if overstrikeString is non-NULL. */
+ XColor *overstrikeColor; /* Color for the overstrike. NULL means same color as foreground. */
+ char *rMarginString; /* -rmargin option string (malloc-ed). NULL means option not
+ * specified. */
+ int rMargin; /* Right margin for text, in pixels. Only valid if rMarginString
+ * is non-NULL. */
+ Tk_3DBorder rMarginColor; /* Used for drawing background in right margin. NULL means no value
+ * specified here. */
Tk_3DBorder selBorder; /* Used for drawing background for selected text.
* NULL means no value specified here. */
- XColor *selFgColor; /* Foreground color for selected text. NULL means
- * no value specified here. */
- char *spacing1String; /* -spacing1 option string (malloc-ed). NULL
- * means option not specified. */
- int spacing1; /* Extra spacing above first display line for
- * text line. Only valid if spacing1String is
- * non-NULL. */
- char *spacing2String; /* -spacing2 option string (malloc-ed). NULL
- * means option not specified. */
- int spacing2; /* Extra spacing between display lines for the
- * same text line. Only valid if
- * spacing2String is non-NULL. */
- char *spacing3String; /* -spacing2 option string (malloc-ed). NULL
- * means option not specified. */
- int spacing3; /* Extra spacing below last display line for
- * text line. Only valid if spacing3String is
- * non-NULL. */
- Tcl_Obj *tabStringPtr; /* -tabs option string. NULL means option not
- * specified. */
+ XColor *selFgColor; /* Foreground color for selected text. NULL means no value specified
+ * here. */
+ char *spacing1String; /* -spacing1 option string (malloc-ed). NULL means option not
+ * specified. */
+ int spacing1; /* Extra spacing above first display line for text line. Only valid
+ * if spacing1String is non-NULL. */
+ char *spacing2String; /* -spacing2 option string (malloc-ed). NULL means option not
+ * specified. */
+ int spacing2; /* Extra spacing between display lines for the same text line. Only
+ * valid if spacing2String is non-NULL. */
+ char *spacing3String; /* -spacing2 option string (malloc-ed). NULL means option not
+ * specified. */
+ int spacing3; /* Extra spacing below last display line for text line. Only valid
+ * if spacing3String is non-NULL. */
+ Tcl_Obj *tabStringPtr; /* -tabs option string. NULL means option not specified. */
struct TkTextTabArray *tabArrayPtr;
- /* Info about tabs for tag (malloc-ed) or
- * NULL. Corresponds to tabString. */
- int tabStyle; /* One of TABULAR or WORDPROCESSOR or NONE (if
- * not specified). */
- char *underlineString; /* -underline option string (malloc-ed). NULL
- * means option not specified. */
- int underline; /* Non-zero means draw underline underneath
- * text. Only valid if underlineString is
- * non-NULL. */
- XColor *underlineColor; /* Color for the underline. NULL means same
- * color as foreground. */
- TkWrapMode wrapMode; /* How to handle wrap-around for this tag.
- * Must be TEXT_WRAPMODE_CHAR,
- * TEXT_WRAPMODE_NONE, TEXT_WRAPMODE_WORD, or
- * TEXT_WRAPMODE_NULL to use wrapmode for
- * whole widget. */
- char *elideString; /* -elide option string (malloc-ed). NULL
- * means option not specified. */
- int elide; /* Non-zero means that data under this tag
- * should not be displayed. */
- int affectsDisplay; /* Non-zero means that this tag affects the
- * way information is displayed on the screen
- * (so need to redisplay if tag changes). */
- Tk_OptionTable optionTable; /* Token representing the configuration
- * specifications. */
- int affectsDisplayGeometry; /* Non-zero means that this tag affects the
- * size with which information is displayed on
- * the screen (so need to recalculate line
- * dimensions if tag changes). */
-} TkTextTag;
-
-#define TK_TAG_AFFECTS_DISPLAY 0x1
-#define TK_TAG_UNDERLINE 0x2
-#define TK_TAG_JUSTIFY 0x4
-#define TK_TAG_OFFSET 0x10
+ /* Info about tabs for tag (malloc-ed) or NULL. Corresponds to
+ * tabString. */
+ int tabStyle; /* One of TABULAR or WORDPROCESSOR or NONE (if not specified). */
+ char *underlineString; /* -underline option string (malloc-ed). NULL means option not
+ * specified. */
+ bool underline; /* True means draw underline underneath text. Only valid if
+ * underlineString is non-NULL. */
+ XColor *underlineColor; /* Color for the underline. NULL means same color as foreground. */
+ XColor *eolColor; /* Color for the end of line symbol. NULL means same color as
+ * foreground. */
+ XColor *hyphenColor; /* Color for the soft hyphen character. NULL means same color as
+ * foreground. */
+ TkWrapMode wrapMode; /* How to handle wrap-around for this tag. Must be TEXT_WRAPMODE_CHAR,
+ * TEXT_WRAPMODE_NONE, TEXT_WRAPMODE_WORD, TEXT_WRAPMODE_CODEPOINT, or
+ * TEXT_WRAPMODE_NULL to use wrapmode for whole widget. */
+ TkTextSpaceMode spaceMode; /* How to handle displaying spaces. Must be TEXT_SPACEMODE_NULL,
+ * TEXT_SPACEMODE_NONE, TEXT_SPACEMODE_EXACT, or TEXT_SPACEMODE_TRIM. */
+ Tcl_Obj *hyphenRulesPtr; /* The hyphen rules string. */
+ int hyphenRules; /* The hyphen rules, only useful for soft hyphen segments. */
+ Tcl_Obj *langPtr; /* -lang option string. NULL means option not specified. */
+ char lang[3]; /* The specified language for the text content, only enabled if not
+ * NUL. */
+ char *elideString; /* -elide option string (malloc-ed). NULL means option not specified. */
+ bool elide; /* True means that data under this tag should not be displayed. */
+ bool undo; /* True means that any change of tagging with this tag will be pushed
+ * on the undo stack (if undo stack is enabled), otherwise this tag
+ * will not regarded in the undo/redo process. */
+
+ /*
+ * Derived values, and the container for all the options.
+ */
+
+ bool affectsDisplay; /* True means that this tag affects the way information is
+ * displayed on the screen (so need to redisplay if tag changes). */
+ bool affectsDisplayGeometry;/* True means that this tag affects the size with which
+ * information is displayed on the screen (so need to recalculate
+ * line dimensions if tag changes). */
+ Tk_OptionTable optionTable; /* Token representing the configuration specifications. */
+ } TkTextTag;
+
+/*
+ * Some definitions for tag search, used by TkBTreeStartSearch, TkBTreeStartSearchBack:
+ */
+
+typedef enum {
+ SEARCH_NEXT_TAGON, /* Search for next range, this will skip the current range. */
+ SEARCH_EITHER_TAGON_TAGOFF, /* Search for next tagon/tagoff change. */
+} TkTextSearchMode;
/*
* The data structure below is used for searching a B-tree for transitions on
@@ -435,26 +922,23 @@ typedef struct TkTextTag {
*/
typedef struct TkTextSearch {
- TkTextIndex curIndex; /* Position of last tag transition returned by
- * TkBTreeNextTag, or index of start of
- * segment containing starting position for
- * search if TkBTreeNextTag hasn't been called
- * yet, or same as stopIndex if search is
- * over. */
- TkTextSegment *segPtr; /* Actual tag segment returned by last call to
- * TkBTreeNextTag, or NULL if TkBTreeNextTag
- * hasn't returned anything yet. */
- TkTextSegment *nextPtr; /* Where to resume search in next call to
- * TkBTreeNextTag. */
- TkTextSegment *lastPtr; /* Stop search before just before considering
- * this segment. */
- TkTextTag *tagPtr; /* Tag to search for (or tag found, if allTags
- * is non-zero). */
- int linesLeft; /* Lines left to search (including curIndex
- * and stopIndex). When this becomes <= 0 the
- * search is over. */
- int allTags; /* Non-zero means ignore tag check: search for
- * transitions on all tags. */
+ TkTextIndex curIndex; /* Position of last tag transition returned by TkBTreeNextTag,
+ * or index of start of segment containing starting position
+ * for search if TkBTreeNextTag hasn't been called yet, or
+ * same as stopIndex if search is over. */
+ TkTextSegment *segPtr; /* Actual tag segment returned by last call to TkBTreeNextTag,
+ * or NULL if TkBTreeNextTag hasn't returned anything yet. */
+ TkTextSegment *resultPtr; /* Actual result of last search. */
+ TkTextSegment *lastPtr; /* Stop search before just before considering this segment. */
+ TkTextLine *lastLinePtr; /* The last line of the search range. */
+ const TkTextTag *tagPtr; /* Tag to search for. */
+ struct TkText *textPtr; /* Where we are searching. */
+ TkTextSearchMode mode; /* Search mode. */
+ bool tagon; /* We have to search for toggle on? */
+ bool endOfText; /* Search is ending at end of text? */
+ int linesLeft; /* Lines left to search (including curIndex and stopIndex).
+ * When this becomes <= 0 the search is over. */
+ int linesToEndOfText; /* Add this to linesLeft when searching to end of text. */
} TkTextSearch;
/*
@@ -462,7 +946,7 @@ typedef struct TkTextSearch {
* in sync with the 'tabOptionStrings' array in the function 'TkTextGetTabs'
*/
-typedef enum {LEFT, RIGHT, CENTER, NUMERIC} TkTextTabAlign;
+typedef enum { LEFT, RIGHT, CENTER, NUMERIC } TkTextTabAlign;
/*
* The following are the supported styles of tabbing, used for the -tabstyle
@@ -476,22 +960,18 @@ typedef enum {
} TkTextTabStyle;
typedef struct TkTextTab {
- int location; /* Offset in pixels of this tab stop from the
- * left margin (lmargin2) of the text. */
- TkTextTabAlign alignment; /* Where the tab stop appears relative to the
- * text. */
+ int location; /* Offset in pixels of this tab stop from the left margin
+ * (lmargin2) of the text. */
+ TkTextTabAlign alignment; /* Where the tab stop appears relative to the text. */
} TkTextTab;
typedef struct TkTextTabArray {
int numTabs; /* Number of tab stops. */
- double lastTab; /* The accurate fractional pixel position of
- * the last tab. */
- double tabIncrement; /* The accurate fractional pixel increment
- * between interpolated tabs we have to create
- * when we exceed numTabs. */
- TkTextTab tabs[1]; /* Array of tabs. The actual size will be
- * numTabs. THIS FIELD MUST BE THE LAST IN THE
- * STRUCTURE. */
+ double lastTab; /* The accurate fractional pixel position of the last tab. */
+ double tabIncrement; /* The accurate fractional pixel increment between interpolated
+ * tabs we have to create when we exceed numTabs. */
+ TkTextTab tabs[1]; /* Array of tabs. The actual size will be numTabs. THIS FIELD
+ * MUST BE THE LAST IN THE STRUCTURE. */
} TkTextTabArray;
/*
@@ -506,96 +986,183 @@ typedef enum {
} TkTextEditMode;
/*
- * Enumeration defining the ways in which a text widget may be modified (for
- * undo/redo handling).
+ * The following enum is used to define a type for the -state option of the Text widget.
*/
typedef enum {
- TK_TEXT_DIRTY_NORMAL, /* Normal behavior. */
- TK_TEXT_DIRTY_UNDO, /* Reverting a compound action. */
- TK_TEXT_DIRTY_REDO, /* Reapplying a compound action. */
- TK_TEXT_DIRTY_FIXED /* Forced to be dirty; can't be undone/redone
- * by normal activity. */
-} TkTextDirtyMode;
-
-/*
- * The following enum is used to define a type for the -state option of the
- * Text widget.
- */
-
-typedef enum {
- TK_TEXT_STATE_DISABLED, TK_TEXT_STATE_NORMAL
+ TK_TEXT_STATE_DISABLED, /* Don't receive any text. */
+ TK_TEXT_STATE_NORMAL, /* Allows all operations. */
+ TK_TEXT_STATE_READONLY /* Do not allow user operations. */
} TkTextState;
/*
- * A data structure of the following type is shared between each text widget
- * that are peers.
+ * A data structure of the following type is shared between each text widget that are peers.
*/
+struct TkRangeList;
+struct TkText;
+
typedef struct TkSharedText {
- int refCount; /* Reference count this shared object. */
- TkTextBTree tree; /* B-tree representation of text and tags for
- * widget. */
- Tcl_HashTable tagTable; /* Hash table that maps from tag names to
- * pointers to TkTextTag structures. The "sel"
- * tag does not feature in this table, since
+ unsigned refCount; /* Reference count this shared object. */
+ TkTextBTree tree; /* B-tree representation of text and tags for widget. */
+ Tcl_HashTable tagTable; /* Hash table that maps from tag names to pointers to TkTextTag
+ * structures. The "sel" tag does not feature in this table, since
* there's one of those for each text peer. */
- int numTags; /* Number of tags currently defined for
- * widget; needed to keep track of
- * priorities. */
- Tcl_HashTable markTable; /* Hash table that maps from mark names to
- * pointers to mark segments. The special
- * "insert" and "current" marks are not stored
- * in this table, but directly accessed as
- * fields of textPtr. */
- Tcl_HashTable windowTable; /* Hash table that maps from window names to
- * pointers to window segments. If a window
- * segment doesn't yet have an associated
+ unsigned numEnabledTags; /* Number of tags currently enabled; needed to keep track of
+ * priorities. */
+ unsigned numTags; /* Number of tags currently defined for widget. */
+ unsigned numMarks; /* Number of marks, not including private or special marks. */
+ unsigned numPrivateMarks; /* Number of private marks. */
+ unsigned numImages; /* Number of embedded images; for information only. */
+ unsigned numWindows; /* Number of embedded windows; for information only. */
+ unsigned tagInfoSize; /* The required index size for tag info sets. */
+ struct TkBitField *usedTags;
+ /* Bit set of used tag indices. */
+ struct TkBitField *elisionTags;
+ /* Bit set of tags with elide information. */
+ struct TkBitField *selectionTags;
+ /* Bit set of all selection tags. */
+ struct TkBitField *dontUndoTags;
+ /* Bit set of all tags with -undo=no. */
+ struct TkBitField *affectDisplayTags;
+ /* Bit set of tags which are affecting the display. */
+ struct TkBitField *notAffectDisplayTags;
+ /* Bit set of tags which are *not* affecting the display. */
+ struct TkBitField *affectDisplayNonSelTags;
+ /* Bit set of tags which are affecting the display, but exclusive
+ * the special selection tags. */
+ struct TkBitField *affectGeometryTags;
+ /* Bit set of tags which are affecting the display geometry. */
+ struct TkBitField *affectGeometryNonSelTags;
+ /* Bit set of tags which are affecting the display geometry, but
+ * exclusive the special selection tags. */
+ struct TkBitField *affectLineHeightTags;
+ /* Bit set of tags which are affecting the line heigth. */
+ TkTextTag **tagLookup; /* Lookup vector for tags. */
+ Tcl_HashTable markTable; /* Hash table that maps from mark names to pointers to mark
+ * segments. The special "insert" and "current" marks are not
+ * stored in this table, but directly accessed as fields of
+ * textPtr. */
+ Tcl_HashTable windowTable; /* Hash table that maps from window names to pointers to window
+ * segments. If a window segment doesn't yet have an associated
* window, there is no entry for it here. */
- Tcl_HashTable imageTable; /* Hash table that maps from image names to
- * pointers to image segments. If an image
- * segment doesn't yet have an associated
+ Tcl_HashTable imageTable; /* Hash table that maps from image names to pointers to image
+ * segments. If an image segment doesn't yet have an associated
* image, there is no entry for it here. */
- Tk_BindingTable bindingTable;
- /* Table of all bindings currently defined for
- * this widget. NULL means that no bindings
- * exist, so the table hasn't been created.
- * Each "object" used for this table is the
- * name of a tag. */
- int stateEpoch; /* This is incremented each time the B-tree's
- * contents change structurally, or when the
- * start/end limits change, and means that any
- * cached TkTextIndex objects are no longer
- * valid. */
+ Tk_BindingTable tagBindingTable;
+ /* Table of all tag bindings currently defined for this widget.
+ * NULL means that no bindings exist, so the table hasn't been
+ * created. Each "object" used for this table is the name of a
+ * tag. */
+ Tk_BindingTable imageBindingTable;
+ /* Table of all image bindings currently defined for this widget.
+ * NULL means that no bindings exist, so the table hasn't been
+ * created. Each "object" used for this table is the name of an
+ * image. */
+ TkTextSegment *startMarker; /* The start marker, the content of this widget starts after this
+ * merker. */
+ TkTextSegment *endMarker; /* If the end marker is at byte index zero, then the next newline
+ * does not belong to this widget, otherwise the next newline
+ * also belongs to this widget. */
+ STRUCT TkTextTagSet *emptyTagInfoPtr;
+ /* Empty tag information. */
+ unsigned numMotionEventBindings;
+ /* Number of tags with bindings to motion events. */
+ unsigned numElisionTags; /* Number of tags with elideString. */
+ bool allowUpdateLineMetrics;
+ /* We don't allow line height computations before first Configure
+ * event has been accepted. */
+
+ /*
+ * Miscellanous information.
+ */
+
+ bool steadyMarks; /* This option causes that any mark now simultaneous behaves like
+ * an invisible character, this means that the relative order of
+ * marks will not change. */
+ unsigned imageCount; /* Used for creating unique image names. */
+ unsigned countEmbWindows; /* Used for counting embedded windows. */
+ bool triggerWatchCmd; /* Whether we should trigger the watch command for any peer. */
+ bool haveToSetCurrentMark; /* Flag whether a position change of the "current" mark has
+ * been postponed in any peer. */
+
+ /*
+ * Miscellaneous mutual data.
+ */
+
+ unsigned inspectEpoch; /* Only used in TextInspectCmd. */
+ unsigned pickEpoch; /* Only used in TkTextPickCurrent. */
+ TkTextSegment *protectionMark[2];
+ /* Protection markers for segments .*/
+ struct TkText *mainPeer; /* Needed for unrelated index lookup. */
+
+ /*
+ * Information for displaying.
+ */
+
+ Tcl_HashTable breakInfoTable;
+ /* Hash table that maps from logical line pointers to BreakInfos for
+ * this widget. Note that this table is used in display stuff, but
+ * for technical reasons we have to keep this table in shared
+ * resource, because it's a shared table. */
+ bool breakInfoTableIsInitialized;
+ /* Flag whether breakInfoTable is initialized. */
/*
* Information related to the undo/redo functionality.
*/
- TkUndoRedoStack *undoStack; /* The undo/redo stack. */
- int undo; /* Non-zero means the undo/redo behaviour is
- * enabled. */
- int maxUndo; /* The maximum depth of the undo stack
- * expressed as the maximum number of compound
- * statements. */
- int autoSeparators; /* Non-zero means the separators will be
- * inserted automatically. */
- int undoMarkId; /* Counts undo marks temporarily used during
- undo and redo operations. */
- int isDirty; /* Flag indicating the 'dirtyness' of the
- * text widget. If the flag is not zero,
- * unsaved modifications have been applied to
- * the text widget. */
- TkTextDirtyMode dirtyMode; /* The nature of the dirtyness characterized
- * by the isDirty flag. */
- TkTextEditMode lastEditMode;/* Keeps track of what the last edit mode
- * was. */
+ struct TkTextUndoStack *undoStack;
+ /* The undo/redo stack. */
+ int maxUndoDepth; /* The maximum depth of the undo stack expressed as the
+ * maximum number of compound statements. */
+ int maxRedoDepth; /* The maximum depth of the redo stack expressed as the
+ * maximum number of compound statements. */
+ int maxUndoSize; /* The maximum number of bytes kept on the undo stack. */
+ bool undo; /* Non-zero means the undo/redo behaviour is enabled. */
+ bool autoSeparators; /* Non-zero means the separators will be inserted automatically. */
+ bool isModified; /* Flag indicating the computed 'modified' state of the text widget. */
+ bool isAltered; /* Flag indicating the computed 'altered' state of the text widget. */
+ bool isIrreversible; /* Flag indicating the computed 'irreversible' flag. Value
+ * 'true' can never change to 'false', except the widget will
+ * be cleared, or the user is clearing. */
+ bool userHasSetModifiedFlag;/* Flag indicating if the user has set the 'modified' flag.
+ * Value 'true' is superseding the computed value, but value
+ * 'false' is only clearing to the initial state of this flag. */
+ bool undoStackEvent; /* Flag indicating whether <<UndoStack>> is already triggered. */
+ unsigned undoLevel; /* The undo level which corresponds to the unmodified state. */
+ TkTextEditMode lastEditMode;/* Keeps track of what the last edit mode was. */
+ int lastUndoTokenType; /* Type of newest undo token on stack. */
+ TkTextTag **undoTagList; /* Array of tags, prepared for undo stack. */
+ TkTextMarkChange *undoMarkList;
+ /* Array of mark changes, prepared for undo stack. */
+ uint32_t undoTagListCount; /* Number of entries in array 'undoTagList'. */
+ uint32_t undoTagListSize; /* Size of array 'undoTagList'. */
+ /* Array of undo entries for mark operations. */
+ uint32_t undoMarkListCount; /* Number of entries in array 'undoMarkList'. */
+ uint32_t undoMarkListSize; /* Size of array 'undoMarkList'. */
+ uint32_t insertDeleteUndoTokenCount;
+ /* Count number of tokens on undo stack for insert/delete actions. */
+ TkTextUndoIndex prevUndoStartIndex;
+ /* Start index (left position) of previous undo operation; only for
+ * 'insert' and 'delete'. */
+ TkTextUndoIndex prevUndoEndIndex;
+ /* End index (right position) of previous undo operation; only for
+ * 'insert' and 'delete'. */
/*
- * Keep track of all the peers
+ * Keep track of all the peers.
*/
struct TkText *peers;
+ unsigned numPeers;
+
+ /*
+ * Hook for watching the existence of this struct. Do never use dynamic memory,
+ * only stack pointers shall be hooked.
+ */
+
+ bool *stillExisting;
} TkSharedText;
/*
@@ -610,126 +1177,156 @@ typedef enum {
} TkTextInsertUnfocussed;
/*
+ * The tagging modes:
+ */
+
+typedef enum {
+ TK_TEXT_TAGGING_WITHIN, /* The new text will receive any tags that are present on both
+ * the character before and the character after the insertion point.
+ * This is the default. */
+ TK_TEXT_TAGGING_GRAVITY, /* The new text will receive any tags that are present at one side
+ * of the insertion point: if insert cursor has gravity right then
+ * receive the tags of the character after the insertion point,
+ * otherwise it will receive the tags of the character before the
+ * insertion point (supports Arabian, and the like). */
+ TK_TEXT_TAGGING_NONE /* The new text will not receive any tags from adjacent characters. */
+} TkTextTagging;
+
+/*
* A data structure of the following type is kept for each text widget that
* currently exists for this process:
*/
+struct TkTextStringList;
+
typedef struct TkText {
/*
- * Information related to and accessed by widget peers and the
- * TkSharedText handling routines.
+ * Information related to and accessed by widget peers and the TkSharedText handling routines.
*/
TkSharedText *sharedTextPtr;/* Shared section of all peers. */
struct TkText *next; /* Next in list of linked peers. */
- TkTextLine *start; /* First B-tree line to show, or NULL to start
- * at the beginning. */
- TkTextLine *end; /* Last B-tree line to show, or NULL for up to
- * the end. */
- int pixelReference; /* Counter into the current tree reference
- * index corresponding to this widget. */
- int abortSelections; /* Set to 1 whenever the text is modified in a
- * way that interferes with selection
- * retrieval: used to abort incremental
- * selection retrievals. */
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+ TkTextLine *startLine; /* First B-tree line to show, or NULL to start at the beginning.
+ * Note that this feature is deprecated and should be removed some day.
+ */
+ TkTextLine *endLine; /* Last B-tree line to show, or NULL for up to the end.
+ * Note that this feature is deprecated and should be removed some day.
+ */
+#endif
+ TkTextSegment *startMarker; /* First B-Tree segment (mark) belonging to this widget. */
+ TkTextSegment *endMarker; /* Last B-Tree segment (mark) belonging to this widget */
+ Tcl_Obj *newStartIndex; /* New position for start marker. */
+ Tcl_Obj *newEndIndex; /* New position for end marker. */
+ int pixelReference; /* Counter into the current tree reference index corresponding
+ * to this widget. */
+ bool abortSelections; /* Set to true whenever the text is modified in a way that interferes
+ * with selection retrieval: used to abort incremental selection
+ * retrievals. */
+ bool pendingAfterSync; /* RunAfterSyncCmd is in event queue. */
+ bool pendingFireEvent; /* FireWidgetViewSyncEvent is in event queue. */
+ bool sendSyncEvent; /* Send <<WidgetViewSync>> event as soon as the line metric is
+ * up-to-date, even if we have no sync state change. */
+ bool prevSyncState; /* Previous sync state of the line-height calculation. */
+ bool dontRepick; /* Set to 'true' during scroll operation, but only when -responsiveness
+ * is greater than zero. */
/*
- * Standard Tk widget information and text-widget specific items
+ * Standard Tk widget information and text-widget specific items.
*/
- Tk_Window tkwin; /* Window that embodies the text. NULL means
- * that the window has been destroyed but the
- * data structures haven't yet been cleaned
- * up.*/
- Display *display; /* Display for widget. Needed, among other
- * things, to allow resources to be freed even
- * after tkwin has gone away. */
- Tcl_Interp *interp; /* Interpreter associated with widget. Used to
- * delete widget command. */
+ Tk_Window tkwin; /* Window that embodies the text. NULL means that the window has been
+ * destroyed but the data structures haven't yet been cleaned up.*/
+ Display *display; /* Display for widget. Needed, among other things, to allow resources
+ * to be freed even after tkwin has gone away. */
+ Tcl_Interp *interp; /* Interpreter associated with widget. Used to delete widget command. */
Tcl_Command widgetCmd; /* Token for text's widget command. */
- int state; /* Either STATE_NORMAL or STATE_DISABLED. A
- * text widget is read-only when disabled. */
+ TkTextState state; /* Either TK_TEXT_STATE_NORMAL, TK_TEXT_STATE_READONLY, or
+ * TK_TEXT_STATE_DISABLED. A text widget is also read-only when
+ * disabled. */
/*
* Default information for displaying (may be overridden by tags applied
* to ranges of characters).
*/
- Tk_3DBorder border; /* Structure used to draw 3-D border and
- * default background. */
- int borderWidth; /* Width of 3-D border to draw around entire
- * widget. */
+ Tk_3DBorder border; /* Structure used to draw 3-D border and default background. */
+ int borderWidth; /* Width of 3-D border to draw around entire widget. */
int padX, padY; /* Padding between text and window border. */
- int relief; /* 3-d effect for border around entire widget:
- * TK_RELIEF_RAISED etc. */
- int highlightWidth; /* Width in pixels of highlight to draw around
- * widget when it has the focus. <= 0 means
- * don't draw a highlight. */
- XColor *highlightBgColorPtr;
- /* Color for drawing traversal highlight area
- * when highlight is off. */
+ int relief; /* 3-d effect for border around entire widget: TK_RELIEF_RAISED etc. */
+ int highlightWidth; /* Width in pixels of highlight to draw around widget when it
+ * has the focus. <= 0 means don't draw a highlight. */
+ XColor *highlightBgColorPtr;/* Color for drawing traversal highlight area when highlight is off. */
XColor *highlightColorPtr; /* Color for drawing traversal highlight. */
Tk_Cursor cursor; /* Current cursor for window, or None. */
XColor *fgColor; /* Default foreground color for text. */
+ XColor *eolColor; /* Foreground color for end of line symbol, can be NULL. */
+ Tcl_Obj *eolCharPtr; /* Use this character for displaying end of line. Can be NULL or empty,
+ * in this case the default char U+00B6 (pilcrow) will be used. */
+ XColor *hyphenColor; /* Foreground color for soft hyphens, can be NULL. */
Tk_Font tkfont; /* Default font for displaying text. */
- int charWidth; /* Width of average character in default
- * font. */
- int charHeight; /* Height of average character in default
- * font, including line spacing. */
- int spacing1; /* Default extra spacing above first display
- * line for each text line. */
- int spacing2; /* Default extra spacing between display lines
- * for the same text line. */
- int spacing3; /* Default extra spacing below last display
- * line for each text line. */
+ int charWidth; /* Width of average character in default font. */
+ int spaceWidth; /* Width of space character in default font. */
+ int lineHeight; /* Height of line in default font, including line spacing. */
+ int spacing1; /* Default extra spacing above first display line for each text line. */
+ int spacing2; /* Default extra spacing between display lines for the same text line. */
+ int spacing3; /* Default extra spacing below last display line for each text line. */
Tcl_Obj *tabOptionPtr; /* Value of -tabs option string. */
TkTextTabArray *tabArrayPtr;
- /* Information about tab stops (malloc'ed).
- * NULL means perform default tabbing
- * behavior. */
+ /* Information about tab stops (malloc'ed). NULL means perform
+ * default tabbing behavior. */
int tabStyle; /* One of TABULAR or WORDPROCESSOR. */
+ TkTextJustify justify; /* How to justify text: TK_TEXT_JUSTIFY_LEFT, TK_TEXT_JUSTIFY_RIGHT,
+ * TK_TEXT_JUSTIFY_CENTER, or TK_TEXT_JUSTIFY_FULL. */
+ Tcl_Obj *hyphenRulesPtr; /* The hyphen rules string. */
+ int hyphenRules; /* The hyphen rules, only useful for soft hyphen segments. */
+ Tcl_Obj *langPtr; /* -lang option string. NULL means option not specified. */
+ char lang[3]; /* The specified language for the text content, only enabled if not
+ * NUL. */
/*
* Additional information used for displaying:
*/
- TkWrapMode wrapMode; /* How to handle wrap-around. Must be
- * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or
- * TEXT_WRAPMODE_WORD. */
- int width, height; /* Desired dimensions for window, measured in
- * characters. */
- int setGrid; /* Non-zero means pass gridding information to
- * window manager. */
- int prevWidth, prevHeight; /* Last known dimensions of window; used to
- * detect changes in size. */
- TkTextIndex topIndex; /* Identifies first character in top display
- * line of window. */
+ TkWrapMode wrapMode; /* How to handle wrap-around. Must be TEXT_WRAPMODE_CHAR,
+ * TEXT_WRAPMODE_WORD, TEXT_WRAPMODE_CODEPOINT, or TEXT_WRAPMODE_NONE. */
+ TkTextSpaceMode spaceMode; /* How to handle displaying spaces. Must be TEXT_SPACEMODE_NONE,
+ * TEXT_SPACEMODE_EXACT, or TEXT_SPACEMODE_TRIM. */
+ bool hyphens; /* Indicating the hypenation support. */
+ bool hyphenate; /* Indicating whether the soft hyphens will be used for line breaks
+ * (if not in state TK_TEXT_STATE_NORMAL). */
+ bool useUniBreak; /* Use library libunibreak for line break computation, otherwise the
+ * internal algorithm will be used. */
+ int width, height; /* Desired dimensions for window, measured in characters. */
+ bool setGrid; /* Non-zero means pass gridding information to window manager. */
+ int prevWidth, prevHeight; /* Last known dimensions of window; used to detect changes in size. */
+ TkTextIndex topIndex; /* Identifies first character in top display line of window. */
struct TextDInfo *dInfoPtr; /* Information maintained by tkTextDisp.c. */
+ bool showEndOfLine; /* Flag whether the end of line symbol will be shown at end of
+ * each logical line. */
+ bool syncTime; /* Synchronization timeout, used for line metric calculation, default is
+ * 200. */
/*
* Information related to selection.
*/
- TkTextTag *selTagPtr; /* Pointer to "sel" tag. Used to tell when a
- * new selection has been made. */
- Tk_3DBorder selBorder; /* Border and background for selected
- * characters. This is a copy of information
- * in *selTagPtr, so it shouldn't be
+ TkTextTag *selTagPtr; /* Pointer to "sel" tag. Used to tell when a new selection
+ * has been made. */
+ Tk_3DBorder selBorder; /* Border and background for selected characters. This is
+ * a copy of information in *selTagPtr, so it shouldn't be
* explicitly freed. */
Tk_3DBorder inactiveSelBorder;
- /* Border and background for selected
- * characters when they don't have the
- * focus. */
+ /* Border and background for selected characters when they
+ * don't have the focus. */
int selBorderWidth; /* Width of border around selection. */
Tcl_Obj *selBorderWidthPtr; /* Width of border around selection. */
- XColor *selFgColorPtr; /* Foreground color for selected text. This is
- * a copy of information in *selTagPtr, so it
- * shouldn't be explicitly freed. */
- int exportSelection; /* Non-zero means tie "sel" tag to X
- * selection. */
- TkTextIndex selIndex; /* Used during multi-pass selection
- * retrievals. This index identifies the next
- * character to be returned from the
+ XColor *selFgColorPtr; /* Foreground color for selected text. This is a copy of
+ * information in *selTagPtr, so it shouldn't be explicitly freed. */
+ bool exportSelection; /* Non-zero means tie "sel" tag to X selection. */
+ TkTextSearch selSearch; /* Used during multi-pass selection retrievals. */
+ TkTextIndex selIndex; /* Used during multi-pass selection retrievals. This index
+ * identifies the next character to be returned from the
* selection. */
/*
@@ -738,8 +1335,10 @@ typedef struct TkText {
TkTextSegment *insertMarkPtr;
/* Points to segment for "insert" mark. */
- Tk_3DBorder insertBorder; /* Used to draw vertical bar for insertion
- * cursor. */
+ Tk_3DBorder insertBorder; /* Used to draw vertical bar for insertion cursor. */
+ XColor *insertFgColorPtr; /* Foreground color for text behind a block cursor.
+ * NULL means no value specified here. */
+ bool showInsertFgColor; /* Flag whether insertFgColorPtr is relevant. */
int insertWidth; /* Total width of insert cursor. */
int insertBorderWidth; /* Width of 3-D border around insert cursor */
TkTextInsertUnfocussed insertUnfocussed;
@@ -750,182 +1349,237 @@ typedef struct TkText {
int insertOffTime; /* Number of milliseconds cursor should spend
* in "off" state for each blink. */
Tcl_TimerToken insertBlinkHandler;
- /* Timer handler used to blink cursor on and
- * off. */
+ /* Timer handler used to blink cursor on and off. */
+ TkTextTagging tagging; /* Tagging mode, used when inserting chars; the mode how to extend
+ * tagged ranges of characters. */
+
+ /*
+ * Information used for the watch of changes:
+ */
+
+ Tcl_Obj *watchCmd; /* The command prefix for the "watch" command. */
+ bool triggerAlways; /* Whether we should trigger for any modification. */
+ TkTextIndex insertIndex; /* Saved position of insertion cursor. */
+
+ /*
+ * Information related to the language support functionality.
+ */
+
+ char *brksBuffer; /* Buffer for line break information, will be filled by
+ * TkTextComputeBreakLocations (for TEXT_WRAPMODE_CODEPOINT). */
+ unsigned brksBufferSize; /* Size of line break buffer. */
/*
* Information used for event bindings associated with tags:
*/
TkTextSegment *currentMarkPtr;
- /* Pointer to segment for "current" mark, or
- * NULL if none. */
- XEvent pickEvent; /* The event from which the current character
- * was chosen. Must be saved so that we can
- * repick after modifications to the text. */
- int numCurTags; /* Number of tags associated with character at
- * current mark. */
- TkTextTag **curTagArrayPtr; /* Pointer to array of tags for current mark,
- * or NULL if none. */
+ /* Pointer to segment for "current" mark, or NULL if none. */
+ TkTextIndex currentMarkIndex;
+ /* The index of the "current" mark, needed for postponing the
+ * insertion of the "current" mark segment.
+ */
+ bool haveToSetCurrentMark; /* Flag whether a position change of the "current" mark has
+ * been postponed. */
+ XEvent pickEvent; /* The event from which the current character was chosen.
+ * Must be saved so that we can repick after modifications
+ * to the text. */
+ unsigned numCurTags; /* Number of tags associated with character at current mark. */
+ TkTextTag **curTagArrayPtr;
+ /* Pointer to array of tags for current mark, or NULL if none. */
+ bool currNearbyFlag; /* The 'nearby' flag of last pick event. */
+
+ /*
+ * Information used for event bindings associated with images:
+ */
+
+ bool configureBboxTree; /* Flag whether we have to resize the image bounding box tree. */
+ TkQTree imageBboxTree; /* Lookup of points in a set of rectangles, for fast mouse
+ * hovering lookup. */
+ TkTextEmbImage **hoveredImageArr;
+ /* This is the array of currently hovered image. */
+ unsigned hoveredImageArrSize;
+ /* Number of entries in 'hoveredImageArrSize'. */
+ unsigned hoveredImageArrCapacity;
+ /* Capacity of 'hoveredImageArr'. */
/*
* Miscellaneous additional information:
*/
- char *takeFocus; /* Value of -takeFocus option; not used in the
- * C code, but used by keyboard traversal
- * scripts. Malloc'ed, but may be NULL. */
- char *xScrollCmd; /* Prefix of command to issue to update
- * horizontal scrollbar when view changes. */
- char *yScrollCmd; /* Prefix of command to issue to update
- * vertical scrollbar when view changes. */
- int flags; /* Miscellaneous flags; see below for
- * definitions. */
- Tk_OptionTable optionTable; /* Token representing the configuration
- * specifications. */
- int refCount; /* Number of cached TkTextIndex objects
- * refering to us. */
- int insertCursorType; /* 0 = standard insertion cursor, 1 = block
- * cursor. */
+ char *takeFocus; /* Value of -takeFocus option; not used in the C code, but
+ * used by keyboard traversal scripts. Malloc'ed, but may be NULL. */
+ char *xScrollCmd; /* Prefix of command to issue to update horizontal scrollbar
+ * when view changes. */
+ char *yScrollCmd; /* Prefix of command to issue to update vertical scrollbar when
+ * view changes. */
+ unsigned flags; /* Miscellaneous flags; see below for definitions. */
+ Tk_OptionTable optionTable; /* Token representing the configuration specifications. */
+ unsigned refCount; /* Number of objects referring to us. */
+ bool blockCursorType; /* false = standard insertion cursor, true = block cursor. */
+ bool accelerateTagSearch; /* Update B-Tree tag information for search? */
+ int responsiveness; /* The delay in ms before repick the mouse position (behavior when
+ * scrolling the widget). */
+ unsigned uniqueIdCounter; /* Used for the generation of unique mark names. */
+ struct TkTextStringList *varBindingList;
+ /* Linked list of variables which should be unset when the widget
+ * will be destroyed. */
+ bool sharedIsReleased; /* Boolean value whether shared resource have been released. */
+
+ /*
+ * Copies of information from the shared section relating to the editor control mode:
+ */
+
+ bool steadyMarks; /* false = behavior of original implementation,
+ * true = new editor control mode. */
+
+ /*
+ * Copies of information from the shared section relating to the undo/redo functonality:
+ */
+
+ bool undo; /* Non-zero means the undo/redo behaviour is enabled. */
+ int maxUndoDepth; /* The maximum depth of the undo stack expressed as the
+ * maximum number of compound statements. */
+ int maxRedoDepth; /* The maximum depth of the redo stack expressed as the
+ * maximum number of compound statements. */
+ int maxUndoSize; /* The maximum number of bytes kept on the undo stack. */
+ bool autoSeparators; /* Non-zero means the separators will be inserted automatically. */
/*
- * Copies of information from the shared section relating to the undo/redo
- * functonality
+ * Support of sync command:
*/
- int undo; /* Non-zero means the undo/redo behaviour is
- * enabled. */
- int maxUndo; /* The maximum depth of the undo stack
- * expressed as the maximum number of compound
- * statements. */
- int autoSeparators; /* Non-zero means the separators will be
- * inserted automatically. */
- Tcl_Obj *afterSyncCmd; /* Command to be executed when lines are up to
- * date */
+ Tcl_Obj *afterSyncCmd; /* Commands to be executed when lines are up to date */
+
+#if TK_CHECK_ALLOCS
+ unsigned widgetNumber;
+#endif
} TkText;
/*
* Flag values for TkText records:
*
- * GOT_SELECTION: Non-zero means we've already claimed the
- * selection.
- * INSERT_ON: Non-zero means insertion cursor should be
- * displayed on screen.
- * GOT_FOCUS: Non-zero means this window has the input
- * focus.
+ * GOT_SELECTION: Non-zero means we've already claimed the selection.
+ * INSERT_ON: Non-zero means insertion cursor should be displayed on screen.
+ * HAVE_FOCUS: Non-zero means this window has the input focus.
* BUTTON_DOWN: 1 means that a mouse button is currently down;
- * this is used to implement grabs for the
- * duration of button presses.
+ * this is used to implement grabs for the duration of button presses.
* UPDATE_SCROLLBARS: Non-zero means scrollbar(s) should be updated
* during next redisplay operation.
- * NEED_REPICK This appears unused and should probably be
- * ignored.
+ * NEED_REPICK This appears unused and should probably be ignored.
* OPTIONS_FREED The widget's options have been freed.
* DESTROYED The widget is going away.
*/
-#define GOT_SELECTION 1
-#define INSERT_ON 2
-#define GOT_FOCUS 4
-#define BUTTON_DOWN 8
-#define UPDATE_SCROLLBARS 0x10
-#define NEED_REPICK 0x20
-#define OPTIONS_FREED 0x40
-#define DESTROYED 0x80
+#define GOT_SELECTION (1 << 0)
+#define INSERT_ON (1 << 1)
+#define HAVE_FOCUS (1 << 2)
+#define BUTTON_DOWN (1 << 3)
+#define UPDATE_SCROLLBARS (1 << 4)
+#define NEED_REPICK (1 << 5)
+#define OPTIONS_FREED (1 << 6)
+#define DESTROYED (1 << 7)
+
+/*
+ * The categories of segment types:
+ */
+
+typedef enum {
+ SEG_GROUP_CHAR = 1 << 0, /* tkTextCharType */
+ SEG_GROUP_MARK = 1 << 1, /* tkTextLeftMarkType, tkTextRightMarkType */
+ SEG_GROUP_HYPHEN = 1 << 2, /* tkTextHyphenType */
+ SEG_GROUP_BRANCH = 1 << 3, /* tkTextBranchType, tkTextLinkType */
+ SEG_GROUP_IMAGE = 1 << 4, /* tkTextEmbImageType */
+ SEG_GROUP_WINDOW = 1 << 5, /* tkTextEmbWindowType */
+ SEG_GROUP_PROTECT = 1 << 6, /* tkTextProtectionMarkType */
+ SEG_GROUP_TAG = 1 << 7, /* this is only needed for convenience */
+} TkSegGroupType;
/*
* Records of the following type define segment types in terms of a collection
* of procedures that may be called to manipulate segments of that type.
*/
-typedef TkTextSegment * Tk_SegSplitProc(struct TkTextSegment *segPtr,
- int index);
-typedef int Tk_SegDeleteProc(struct TkTextSegment *segPtr,
- TkTextLine *linePtr, int treeGone);
-typedef TkTextSegment * Tk_SegCleanupProc(struct TkTextSegment *segPtr,
- TkTextLine *linePtr);
-typedef void Tk_SegLineChangeProc(struct TkTextSegment *segPtr,
- TkTextLine *linePtr);
-typedef int Tk_SegLayoutProc(struct TkText *textPtr,
- struct TkTextIndex *indexPtr,
- TkTextSegment *segPtr, int offset, int maxX,
- int maxChars, int noCharsYet, TkWrapMode wrapMode,
- struct TkTextDispChunk *chunkPtr);
-typedef void Tk_SegCheckProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
+typedef bool Tk_SegDeleteProc(TkTextBTree tree, struct TkTextSegment *segPtr, int flags);
+typedef void Tk_SegReuseProc(struct TkTextSegment *segPtr);
+typedef int Tk_SegLayoutProc(const struct TkTextIndex *indexPtr, TkTextSegment *segPtr,
+ int offset, int maxX, int maxChars, bool noCharsYet, TkWrapMode wrapMode,
+ TkTextSpaceMode spaceMode, struct TkTextDispChunk *chunkPtr);
+typedef void Tk_SegCheckProc(const struct TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+typedef Tcl_Obj *Tk_SegInspectProc(const TkSharedText *textPtr, const TkTextSegment *segPtr);
typedef struct Tk_SegType {
const char *name; /* Name of this kind of segment. */
- int leftGravity; /* If a segment has zero size (e.g. a mark or
- * tag toggle), does it attach to character to
- * its left or right? 1 means left, 0 means
- * right. */
- Tk_SegSplitProc *splitProc; /* Procedure to split large segment into two
- * smaller ones. */
+ TkSegGroupType group; /* Group information. */
+ int gravity; /* The gravity of this segment, one of GRAVITY_LEFT, GRAVITY_NEUTRAL,
+ * GRAVITY_RIGHT. */
Tk_SegDeleteProc *deleteProc;
/* Procedure to call to delete segment. */
- Tk_SegCleanupProc *cleanupProc;
- /* After any change to a line, this procedure
- * is invoked for all segments left in the
- * line to perform any cleanup they wish
- * (e.g. joining neighboring segments). */
- Tk_SegLineChangeProc *lineChangeProc;
- /* Invoked when a segment is about to be moved
- * from its current line to an earlier line
- * because of a deletion. The linePtr is that
- * for the segment's old line. CleanupProc
- * will be invoked after the deletion is
- * finished. */
+ Tk_SegReuseProc *restoreProc;
+ /* Restore a preserved segment. This will be done when performing
+ * an undo. */
Tk_SegLayoutProc *layoutProc;
- /* Returns size information when figuring out
- * what to display in window. */
- Tk_SegCheckProc *checkProc; /* Called during consistency checks to check
- * internal consistency of segment. */
+ /* Returns size information when figuring out what to display
+ * in window. */
+ Tk_SegCheckProc *checkProc; /* Called during consistency checks to check internal consistency
+ * of segment. */
+ Tk_SegInspectProc *inspectProc;
+ /* Called when creating the information for "inspect". */
} Tk_SegType;
/*
+ * These items are the gravity values:
+ */
+
+enum { GRAVITY_LEFT, GRAVITY_NEUTRAL, GRAVITY_RIGHT };
+
+/*
* The following type and items describe different flags for text widget items
* to count. They are used in both tkText.c and tkTextIndex.c, in
* 'CountIndices', 'TkTextIndexBackChars', 'TkTextIndexForwChars', and
* 'TkTextIndexCount'.
*/
-typedef int TkTextCountType;
-
-#define COUNT_CHARS 0
-#define COUNT_INDICES 1
-#define COUNT_DISPLAY 2
-#define COUNT_DISPLAY_CHARS (COUNT_CHARS | COUNT_DISPLAY)
-#define COUNT_DISPLAY_INDICES (COUNT_INDICES | COUNT_DISPLAY)
+typedef enum {
+ COUNT_HYPHENS = 1 << 1,
+ COUNT_TEXT = 1 << 2,
+ COUNT_CHARS = COUNT_HYPHENS | COUNT_TEXT,
+ COUNT_INDICES = 1 << 3,
+ COUNT_DISPLAY = 1 << 4,
+ COUNT_DISPLAY_CHARS = COUNT_CHARS | COUNT_DISPLAY,
+ COUNT_DISPLAY_HYPHENS = COUNT_HYPHENS | COUNT_DISPLAY,
+ COUNT_DISPLAY_TEXT = COUNT_TEXT | COUNT_DISPLAY,
+ COUNT_DISPLAY_INDICES = COUNT_INDICES | COUNT_DISPLAY
+} TkTextCountType;
/*
- * The following structure is used to keep track of elided text taking account
- * of different tag priorities, it is need for quick calculations of whether a
- * single index is elided, and to start at a given index and maintain a
- * correct elide state as we move or count forwards or backwards.
+ * Some definitions for line break support, must coincide with the defintions
+ * in /usr/include/linebreak.h:
*/
-#define LOTSA_TAGS 1000
-typedef struct TkTextElideInfo {
- int numTags; /* Total tags in widget. */
- int elide; /* Is the state currently elided. */
- int elidePriority; /* Tag priority controlling elide state. */
- TkTextSegment *segPtr; /* Segment to look at next. */
- int segOffset; /* Offset of segment within line. */
- int deftagCnts[LOTSA_TAGS];
- TkTextTag *deftagPtrs[LOTSA_TAGS];
- int *tagCnts; /* 0 or 1 depending if the tag with that
- * priority is on or off. */
- TkTextTag **tagPtrs; /* Only filled with a tagPtr if the
- * corresponding tagCnt is 1. */
-} TkTextElideInfo;
+#define LINEBREAK_MUSTBREAK 0 /* Break is mandatory */
+#define LINEBREAK_ALLOWBREAK 1 /* Break is allowed */
+#define LINEBREAK_NOBREAK 2 /* No break is possible */
+#define LINEBREAK_INSIDEACHAR 3 /* Inside UTF-8 sequence */
/*
- * The constant below is used to specify a line when what is really wanted is
- * the entire text. For now, just use a very big number.
+ * Flags for the delete function (Tk_SegDeleteProc):
+ *
+ * TREE_GONE The entire tree is being deleted, so everything must get cleaned up.
+ * DELETE_BRANCHES The branches and links will be deleted.
+ * DELETE_MARKS The marks will be deleted.
+ * DELETE_INCLUSIVE The deletion of the marks includes also the marks given as arguments
+ * for the range.
+ * DELETE_CLEANUP We have to delete anyway, due to a cleanup.
+ * DELETE_PRESERVE We have to preserve this segment.
*/
-#define TK_END_OF_TEXT 1000000
+#define TREE_GONE (1 << 0)
+#define DELETE_BRANCHES (1 << 1)
+#define DELETE_MARKS (1 << 2)
+#define DELETE_INCLUSIVE (1 << 3)
+#define DELETE_CLEANUP (1 << 4)
+#define DELETE_PRESERVE (1 << 5)
/*
* The following definition specifies the maximum number of characters needed
@@ -935,26 +1589,54 @@ typedef struct TkTextElideInfo {
#define TK_POS_CHARS 30
/*
+ * Mask used for those options which may impact the text content
+ * of individual lines displayed in the widget.
+ */
+
+#define TK_TEXT_LINE_REDRAW (1 << 0)
+#define TK_TEXT_LINE_REDRAW_BOTTOM_LINE (1 << 1)
+
+/*
* Mask used for those options which may impact the pixel height calculations
* of individual lines displayed in the widget.
*/
-#define TK_TEXT_LINE_GEOMETRY 1
+#define TK_TEXT_LINE_GEOMETRY (1 << 2)
/*
- * Mask used for those options which may impact the start and end lines used
- * in the widget.
+ * Mask used for those options which should invoke the line metric update
+ * immediately.
*/
-#define TK_TEXT_LINE_RANGE 2
+#define TK_TEXT_SYNCHRONIZE (1 << 3)
+
+/*
+ * Mask used for those options which may impact the start and end lines/index
+ * used in the widget.
+ */
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+# define TK_TEXT_LINE_RANGE (1 << 4)
+# define TK_TEXT_INDEX_RANGE ((1 << 5)|TK_TEXT_LINE_RANGE)
+#else
+# define TK_TEXT_INDEX_RANGE (1 << 4)
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+#if SUPPORT_DEPRECATED_TAG_OPTIONS
+# define TK_TEXT_DEPRECATED_OVERSTRIKE_FG (1 << 6)
+# define TK_TEXT_DEPRECATED_UNDERLINE_FG (1 << 7)
+#endif /* SUPPORT_DEPRECATED_TAG_OPTIONS */
/*
* Used as 'action' values in calls to TkTextInvalidateLineMetrics
*/
-#define TK_TEXT_INVALIDATE_ONLY 0
-#define TK_TEXT_INVALIDATE_INSERT 1
-#define TK_TEXT_INVALIDATE_DELETE 2
+typedef enum {
+ TK_TEXT_INVALIDATE_ONLY,
+ TK_TEXT_INVALIDATE_INSERT,
+ TK_TEXT_INVALIDATE_DELETE,
+ TK_TEXT_INVALIDATE_ELIDE
+} TkTextInvalidateAction;
/*
* Used as special 'pickPlace' values in calls to TkTextSetYView. Zero or
@@ -968,206 +1650,476 @@ typedef struct TkTextElideInfo {
* Declarations for variables shared among the text-related files:
*/
-MODULE_SCOPE int tkBTreeDebug;
-MODULE_SCOPE int tkTextDebug;
+MODULE_SCOPE bool tkBTreeDebug;
+MODULE_SCOPE bool tkTextDebug;
MODULE_SCOPE const Tk_SegType tkTextCharType;
+MODULE_SCOPE const Tk_SegType tkTextBranchType;
+MODULE_SCOPE const Tk_SegType tkTextLinkType;
MODULE_SCOPE const Tk_SegType tkTextLeftMarkType;
MODULE_SCOPE const Tk_SegType tkTextRightMarkType;
-MODULE_SCOPE const Tk_SegType tkTextToggleOnType;
-MODULE_SCOPE const Tk_SegType tkTextToggleOffType;
-MODULE_SCOPE const Tk_SegType tkTextEmbWindowType;
+MODULE_SCOPE const Tk_SegType tkTextHyphenType;
MODULE_SCOPE const Tk_SegType tkTextEmbImageType;
+MODULE_SCOPE const Tk_SegType tkTextEmbWindowType;
+MODULE_SCOPE const Tk_SegType tkTextProtectionMarkType;
/*
- * Convenience macros for use by B-tree clients which want to access pixel
- * information on each line. Currently only used by TkTextDisp.c
+ * Convenience constants for a better readability of TkTextFindDisplayLineStartEnd call:
*/
-#define TkBTreeLinePixelCount(text, line) \
- (line)->pixels[2*(text)->pixelReference]
-#define TkBTreeLinePixelEpoch(text, line) \
- (line)->pixels[1+2*(text)->pixelReference]
+enum { DISP_LINE_START = false, DISP_LINE_END = true };
+
+/*
+ * Helper for guarded deallocation.
+ */
+
+#define FREE_SEGMENT(ptr) { \
+ assert(ptr->typePtr); \
+ assert(!(ptr->typePtr = NULL)); \
+ free(ptr); }
+
+/*
+ * We need a callback function for tag changes. The return value informs whether
+ * this operation is undoable.
+ */
+
+typedef bool TkTextTagChangedProc(
+ const TkSharedText *sharedTextPtr,
+ TkText *textPtr,
+ const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2,
+ const TkTextTag *tagPtr,
+ bool affectsDisplayGeometry);
/*
* Declarations for procedures that are used by the text-related files but
* shouldn't be used anywhere else in Tk (or by Tk clients):
*/
-MODULE_SCOPE int TkBTreeAdjustPixelHeight(const TkText *textPtr,
- TkTextLine *linePtr, int newPixelHeight,
- int mergedLogicalLines);
-MODULE_SCOPE int TkBTreeCharTagged(const TkTextIndex *indexPtr,
- TkTextTag *tagPtr);
+inline TkSharedText * TkBTreeGetShared(TkTextBTree tree);
+inline int TkBTreeGetNumberOfDisplayLines(const TkTextPixelInfo *pixelInfo);
+MODULE_SCOPE void TkBTreeAdjustPixelHeight(const TkText *textPtr,
+ TkTextLine *linePtr, int newPixelHeight, unsigned mergedLogicalLines,
+ unsigned oldNumDispLines);
+MODULE_SCOPE void TkBTreeUpdatePixelHeights(const TkText *textPtr, TkTextLine *linePtr,
+ int numLines, unsigned epoch);
+MODULE_SCOPE void TkBTreeResetDisplayLineCounts(TkText *textPtr, TkTextLine *linePtr,
+ unsigned numLines);
+MODULE_SCOPE bool TkBTreeHaveElidedSegments(const TkSharedText *sharedTextPtr);
+inline TkTextPixelInfo * TkBTreeLinePixelInfo(const TkText *textPtr, TkTextLine *linePtr);
+MODULE_SCOPE bool TkBTreeCharTagged(const TkTextIndex *indexPtr, const TkTextTag *tagPtr);
MODULE_SCOPE void TkBTreeCheck(TkTextBTree tree);
-MODULE_SCOPE TkTextBTree TkBTreeCreate(TkSharedText *sharedTextPtr);
-MODULE_SCOPE void TkBTreeAddClient(TkTextBTree tree, TkText *textPtr,
- int defaultHeight);
-MODULE_SCOPE void TkBTreeClientRangeChanged(TkText *textPtr,
- int defaultHeight);
-MODULE_SCOPE void TkBTreeRemoveClient(TkTextBTree tree,
- TkText *textPtr);
+MODULE_SCOPE TkTextBTree TkBTreeCreate(TkSharedText *sharedTextPtr, unsigned epoch);
+MODULE_SCOPE void TkBTreeAddClient(TkTextBTree tree, TkText *textPtr, int defaultHeight);
+MODULE_SCOPE void TkBTreeClientRangeChanged(TkText *textPtr, unsigned defaultHeight);
+MODULE_SCOPE void TkBTreeRemoveClient(TkTextBTree tree, TkText *textPtr);
MODULE_SCOPE void TkBTreeDestroy(TkTextBTree tree);
-MODULE_SCOPE void TkBTreeDeleteIndexRange(TkTextBTree tree,
- TkTextIndex *index1Ptr, TkTextIndex *index2Ptr);
-MODULE_SCOPE int TkBTreeEpoch(TkTextBTree tree);
-MODULE_SCOPE TkTextLine *TkBTreeFindLine(TkTextBTree tree,
- const TkText *textPtr, int line);
-MODULE_SCOPE TkTextLine *TkBTreeFindPixelLine(TkTextBTree tree,
- const TkText *textPtr, int pixels,
- int *pixelOffset);
-MODULE_SCOPE TkTextTag **TkBTreeGetTags(const TkTextIndex *indexPtr,
- const TkText *textPtr, int *numTagsPtr);
-MODULE_SCOPE void TkBTreeInsertChars(TkTextBTree tree,
- TkTextIndex *indexPtr, const char *string);
-MODULE_SCOPE int TkBTreeLinesTo(const TkText *textPtr,
- TkTextLine *linePtr);
-MODULE_SCOPE int TkBTreePixelsTo(const TkText *textPtr,
- TkTextLine *linePtr);
-MODULE_SCOPE void TkBTreeLinkSegment(TkTextSegment *segPtr,
- TkTextIndex *indexPtr);
-MODULE_SCOPE TkTextLine *TkBTreeNextLine(const TkText *textPtr,
- TkTextLine *linePtr);
-MODULE_SCOPE int TkBTreeNextTag(TkTextSearch *searchPtr);
-MODULE_SCOPE int TkBTreeNumPixels(TkTextBTree tree,
- const TkText *textPtr);
-MODULE_SCOPE TkTextLine *TkBTreePreviousLine(TkText *textPtr,
- TkTextLine *linePtr);
-MODULE_SCOPE int TkBTreePrevTag(TkTextSearch *searchPtr);
-MODULE_SCOPE void TkBTreeStartSearch(TkTextIndex *index1Ptr,
- TkTextIndex *index2Ptr, TkTextTag *tagPtr,
- TkTextSearch *searchPtr);
-MODULE_SCOPE void TkBTreeStartSearchBack(TkTextIndex *index1Ptr,
- TkTextIndex *index2Ptr, TkTextTag *tagPtr,
- TkTextSearch *searchPtr);
-MODULE_SCOPE int TkBTreeTag(TkTextIndex *index1Ptr,
- TkTextIndex *index2Ptr, TkTextTag *tagPtr,
- int add);
-MODULE_SCOPE void TkBTreeUnlinkSegment(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-MODULE_SCOPE void TkTextBindProc(ClientData clientData,
- XEvent *eventPtr);
+MODULE_SCOPE int TkBTreeLoad(TkText *textPtr, Tcl_Obj *content);
+MODULE_SCOPE void TkBTreeDeleteIndexRange(TkSharedText *sharedTextPtr,
+ TkTextIndex *index1Ptr, TkTextIndex *index2Ptr,
+ int flags, TkTextUndoInfo *undoInfo);
+inline unsigned TkBTreeEpoch(TkTextBTree tree);
+inline unsigned TkBTreeIncrEpoch(TkTextBTree tree);
+inline struct Node * TkBTreeGetRoot(TkTextBTree tree);
+MODULE_SCOPE TkTextLine * TkBTreeFindLine(TkTextBTree tree, const TkText *textPtr, int line);
+MODULE_SCOPE TkTextLine * TkBTreeFindPixelLine(TkTextBTree tree,
+ const TkText *textPtr, int pixels, int32_t *pixelOffset);
+MODULE_SCOPE TkTextLine * TkBTreeGetLogicalLine(const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, TkTextLine *linePtr);
+MODULE_SCOPE TkTextLine * TkBTreeNextLogicalLine(const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, TkTextLine *linePtr);
+inline TkTextLine * TkBTreePrevLogicalLine(const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, TkTextLine *linePtr);
+MODULE_SCOPE TkTextLine * TkBTreeNextDisplayLine(TkText *textPtr, TkTextLine *linePtr,
+ int *displayLineNo, unsigned offset);
+MODULE_SCOPE TkTextLine * TkBTreePrevDisplayLine(TkText *textPtr, TkTextLine *linePtr,
+ int *displayLineNo, unsigned offset);
+MODULE_SCOPE TkTextSegment * TkBTreeFindStartOfElidedRange(const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, const TkTextSegment *segPtr);
+MODULE_SCOPE TkTextSegment * TkBTreeFindEndOfElidedRange(const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, const TkTextSegment *segPtr);
+inline TkTextTag * TkBTreeGetTags(const TkTextIndex *indexPtr);
+MODULE_SCOPE TkTextTag * TkBTreeGetSegmentTags(const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr, const TkText *textPtr);
+MODULE_SCOPE const char * TkBTreeGetLang(const TkText *textPtr, const TkTextSegment *segPtr);
+MODULE_SCOPE void TkBTreeInsertChars(TkTextBTree tree, TkTextIndex *indexPtr, const char *string,
+ STRUCT TkTextTagSet *tagInfoPtr, TkTextTag *hyphenTagPtr,
+ TkTextUndoInfo *undoInfo);
+MODULE_SCOPE TkTextSegment *TkBTreeMakeCharSegment(const char *string, unsigned length,
+ STRUCT TkTextTagSet *tagInfoPtr);
+MODULE_SCOPE void TkBTreeMakeUndoIndex(const TkSharedText *sharedTextPtr,
+ TkTextSegment *segPtr, TkTextUndoIndex *indexPtr);
+MODULE_SCOPE void TkBTreeUndoIndexToIndex(const TkSharedText *sharedTextPtr,
+ const TkTextUndoIndex *srcPtr, TkTextIndex *dstPtr);
+MODULE_SCOPE Tcl_Obj * TkBTreeUndoTagInspect(const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item);
+MODULE_SCOPE bool TkBTreeJoinUndoInsert(TkTextUndoToken *token1, unsigned byteSize1,
+ TkTextUndoToken *token2, unsigned byteSize2);
+MODULE_SCOPE bool TkBTreeJoinUndoDelete(TkTextUndoToken *token1, unsigned byteSize1,
+ TkTextUndoToken *token2, unsigned byteSize2);
+MODULE_SCOPE void TkBTreeReInsertSegment(const TkSharedText *sharedTextPtr,
+ const TkTextUndoIndex *indexPtr, TkTextSegment *segPtr);
+MODULE_SCOPE unsigned TkBTreeLinesTo(TkTextBTree tree, const TkText *textPtr,
+ const TkTextLine *linePtr, int *deviation);
+MODULE_SCOPE unsigned TkBTreePixelsTo(const TkText *textPtr, const TkTextLine *linePtr);
+MODULE_SCOPE void TkBTreeLinkSegment(const TkSharedText *sharedTextPtr,
+ TkTextSegment *segPtr, TkTextIndex *indexPtr);
+inline TkTextLine * TkBTreeGetStartLine(const TkText *textPtr);
+inline TkTextLine * TkBTreeGetLastLine(const TkText *textPtr);
+inline TkTextLine * TkBTreeNextLine(const TkText *textPtr, TkTextLine *linePtr);
+inline TkTextLine * TkBTreePrevLine(const TkText *textPtr, TkTextLine *linePtr);
+MODULE_SCOPE bool TkBTreeMoveForward(TkTextIndex *indexPtr, unsigned byteCount);
+MODULE_SCOPE bool TkBTreeMoveBackward(TkTextIndex *indexPtr, unsigned byteCount);
+MODULE_SCOPE bool TkBTreeNextTag(TkTextSearch *searchPtr);
+MODULE_SCOPE bool TkBTreePrevTag(TkTextSearch *searchPtr);
+MODULE_SCOPE TkTextSegment * TkBTreeFindNextTagged(const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2, const struct TkBitField *discardTags);
+MODULE_SCOPE TkTextSegment * TkBTreeFindPrevTagged(const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2, bool discardSelection);
+MODULE_SCOPE TkTextSegment * TkBTreeFindNextUntagged(const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2, const struct TkBitField *discardTags);
+MODULE_SCOPE unsigned TkBTreeNumPixels(const TkText *textPtr);
+MODULE_SCOPE unsigned TkBTreeSize(const TkTextBTree tree, const TkText *textPtr);
+MODULE_SCOPE unsigned TkBTreeCountSize(const TkTextBTree tree, const TkText *textPtr,
+ const TkTextLine *linePtr1, const TkTextLine *linePtr2);
+inline unsigned TkBTreeCountLines(const TkTextBTree tree, const TkTextLine *linePtr1,
+ const TkTextLine *linePtr2);
+MODULE_SCOPE void TkBTreeStartSearch(const TkTextIndex *index1Ptr,
+ const TkTextIndex *index2Ptr, const TkTextTag *tagPtr,
+ TkTextSearch *searchPtr, TkTextSearchMode mode);
+MODULE_SCOPE void TkBTreeStartSearchBack(const TkTextIndex *index1Ptr,
+ const TkTextIndex *index2Ptr, const TkTextTag *tagPtr,
+ TkTextSearch *searchPtr, TkTextSearchMode mode);
+MODULE_SCOPE void TkBTreeLiftSearch(TkTextSearch *searchPtr);
+MODULE_SCOPE bool TkBTreeTag(TkSharedText *sharedTextPtr, TkText *textPtr,
+ const TkTextIndex *index1Ptr, const TkTextIndex *index2Ptr,
+ TkTextTag *tagPtr, bool add, TkTextUndoInfo *undoInfo,
+ TkTextTagChangedProc changedProc);
+MODULE_SCOPE TkTextTag * TkBTreeClearTags(TkSharedText *sharedTextPtr, TkText *textPtr,
+ const TkTextIndex *index1Ptr, const TkTextIndex *index2Ptr,
+ TkTextUndoInfo *undoInfo, bool discardSelection,
+ TkTextTagChangedProc changedProc);
+MODULE_SCOPE void TkBTreeUpdateElideInfo(TkText *textPtr, TkTextTag *tagPtr);
+MODULE_SCOPE void TkBTreeUnlinkSegment(const TkSharedText *sharedTextPtr, TkTextSegment *segPtr);
+MODULE_SCOPE void TkBTreeFreeSegment(TkTextSegment *segPtr);
+MODULE_SCOPE unsigned TkBTreeChildNumber(const TkTextBTree tree, const TkTextLine *linePtr,
+ unsigned *depth);
+MODULE_SCOPE unsigned TkBTreeLinesPerNode(const TkTextBTree tree);
+MODULE_SCOPE const STRUCT TkTextTagSet * TkBTreeRootTagInfo(const TkTextBTree tree);
+MODULE_SCOPE void TkTextBindProc(ClientData clientData, XEvent *eventPtr);
MODULE_SCOPE void TkTextSelectionEvent(TkText *textPtr);
+MODULE_SCOPE void TkTextAllocStatistic();
+MODULE_SCOPE int TkConfigureText(Tcl_Interp *interp, TkText *textPtr, int objc,
+ Tcl_Obj *const objv[]);
+MODULE_SCOPE bool TkTextTriggerWatchCmd(TkText *textPtr, const char *operation,
+ const char *index1, const char *index2, const char *arg, bool userFlag);
+MODULE_SCOPE void TkTextUpdateAlteredFlag(TkSharedText *sharedTextPtr);
MODULE_SCOPE int TkTextIndexBbox(TkText *textPtr,
const TkTextIndex *indexPtr, int *xPtr, int *yPtr,
int *widthPtr, int *heightPtr, int *charWidthPtr);
-MODULE_SCOPE int TkTextCharLayoutProc(TkText *textPtr,
- TkTextIndex *indexPtr, TkTextSegment *segPtr,
- int offset, int maxX, int maxChars, int noBreakYet,
- TkWrapMode wrapMode, TkTextDispChunk *chunkPtr);
+MODULE_SCOPE int TkTextCharLayoutProc(const TkTextIndex *indexPtr, TkTextSegment *segPtr,
+ int byteOffset, int maxX, int maxBytes, bool noCharsYet,
+ TkWrapMode wrapMode, TkTextSpaceMode spaceMode, TkTextDispChunk *chunkPtr);
MODULE_SCOPE void TkTextCreateDInfo(TkText *textPtr);
-MODULE_SCOPE int TkTextDLineInfo(TkText *textPtr,
- const TkTextIndex *indexPtr, int *xPtr, int *yPtr,
- int *widthPtr, int *heightPtr, int *basePtr);
-MODULE_SCOPE void TkTextEmbWinDisplayProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr, int x, int y,
- int lineHeight, int baseline, Display *display,
- Drawable dst, int screenY);
-MODULE_SCOPE TkTextTag *TkTextCreateTag(TkText *textPtr,
- const char *tagName, int *newTag);
+MODULE_SCOPE bool TkTextGetDLineInfo(TkText *textPtr, const TkTextIndex *indexPtr, int *xPtr,
+ int *yPtr, int *widthPtr, int *heightPtr, int *basePtr);
+MODULE_SCOPE int TkTextBindEvent(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[],
+ TkSharedText *sharedTextPtr, Tk_BindingTable *bindingTablePtr,
+ const char *name);
+MODULE_SCOPE TkTextTag * TkTextClearTags(TkSharedText *sharedTextPtr, TkText *textPtr,
+ const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2,
+ bool discardSelection);
+MODULE_SCOPE void TkTextClearSelection(TkSharedText *sharedTextPtr,
+ const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2);
+MODULE_SCOPE void TkTextUpdateTagDisplayFlags(TkTextTag *tagPtr);
+MODULE_SCOPE TkTextTag * TkTextCreateTag(TkText *textPtr, const char *tagName, bool *newTag);
+MODULE_SCOPE TkTextTag * TkTextFindTag(const TkText *textPtr, const char *tagName);
+MODULE_SCOPE int TkConfigureTag(Tcl_Interp *interp, TkText *textPtr, const char *tagName,
+ int objc, Tcl_Obj *const objv[]);
+MODULE_SCOPE void TkTextEnableTag(TkSharedText *sharedTextPtr, TkTextTag *tagPtr);
+MODULE_SCOPE void TkTextSortTags(unsigned numTags, TkTextTag **tagArrayPtr);
MODULE_SCOPE void TkTextFreeDInfo(TkText *textPtr);
-MODULE_SCOPE void TkTextDeleteTag(TkText *textPtr, TkTextTag *tagPtr);
-MODULE_SCOPE void TkTextFreeTag(TkText *textPtr, TkTextTag *tagPtr);
-MODULE_SCOPE int TkTextGetObjIndex(Tcl_Interp *interp, TkText *textPtr,
- Tcl_Obj *idxPtr, TkTextIndex *indexPtr);
-MODULE_SCOPE int TkTextSharedGetObjIndex(Tcl_Interp *interp,
- TkSharedText *sharedTextPtr, Tcl_Obj *idxPtr,
+MODULE_SCOPE void TkTextResetDInfo(TkText *textPtr);
+MODULE_SCOPE void TkTextDeleteBreakInfoTableEntries(Tcl_HashTable *breakInfoTable);
+MODULE_SCOPE void TkTextPushTagPriorityUndo(TkSharedText *sharedTextPtr, TkTextTag *tagPtr,
+ unsigned priority);
+MODULE_SCOPE void TkTextPushTagPriorityRedo(TkSharedText *sharedTextPtr, TkTextTag *tagPtr,
+ unsigned priority);
+MODULE_SCOPE void TkTextInspectUndoTagItem(const TkSharedText *sharedTextPtr,
+ const TkTextTag *tagPtr, Tcl_Obj* objPtr);
+MODULE_SCOPE void TkTextTagAddRetainedUndo(TkSharedText *sharedTextPtr, TkTextTag *tagPtr);
+MODULE_SCOPE void TkTextPushUndoTagTokens(TkSharedText *sharedTextPtr, TkTextTag *tagPtr);
+MODULE_SCOPE void TkTextReleaseUndoTagToken(TkSharedText *sharedTextPtr, TkTextTag *tagPtr);
+MODULE_SCOPE void TkTextPushUndoMarkTokens(TkSharedText *sharedTextPtr,
+ TkTextMarkChange *changePtr);
+MODULE_SCOPE void TkTextReleaseUndoMarkTokens(TkSharedText *sharedTextPtr,
+ TkTextMarkChange *changePtr);
+MODULE_SCOPE void TkTextInspectUndoMarkItem(const TkSharedText *sharedTextPtr,
+ const TkTextMarkChange *changePtr, Tcl_Obj* objPtr);
+MODULE_SCOPE bool TkTextTagChangedUndoRedo(const TkSharedText *sharedTextPtr, TkText *textPtr,
+ const TkTextIndex *index1Ptr, const TkTextIndex *index2Ptr,
+ const TkTextTag *tagPtr, bool affectsDisplayGeometry);
+MODULE_SCOPE bool TkTextDeleteTag(TkText *textPtr, TkTextTag *tagPtr, Tcl_HashEntry *hPtr);
+MODULE_SCOPE void TkTextReleaseTag(TkSharedText *sharedTextPtr, TkTextTag *tagPtr,
+ Tcl_HashEntry *hPtr);
+MODULE_SCOPE void TkTextFontHeightChanged(TkText *textPtr);
+MODULE_SCOPE int TkTextTestRelation(Tcl_Interp *interp, int relation, const char *op);
+MODULE_SCOPE bool TkTextReleaseIfDestroyed(TkText *textPtr);
+MODULE_SCOPE bool TkTextDecrRefCountAndTestIfDestroyed(TkText *textPtr);
+MODULE_SCOPE void TkTextFreeAllTags(TkText *textPtr);
+MODULE_SCOPE bool TkTextGetIndexFromObj(Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *objPtr,
TkTextIndex *indexPtr);
-MODULE_SCOPE const TkTextIndex *TkTextGetIndexFromObj(Tcl_Interp *interp,
- TkText *textPtr, Tcl_Obj *objPtr);
-MODULE_SCOPE TkTextTabArray *TkTextGetTabs(Tcl_Interp *interp,
- TkText *textPtr, Tcl_Obj *stringPtr);
-MODULE_SCOPE void TkTextFindDisplayLineEnd(TkText *textPtr,
- TkTextIndex *indexPtr, int end, int *xOffset);
-MODULE_SCOPE void TkTextIndexBackChars(const TkText *textPtr,
- const TkTextIndex *srcPtr, int count,
- TkTextIndex *dstPtr, TkTextCountType type);
-MODULE_SCOPE int TkTextIndexCmp(const TkTextIndex *index1Ptr,
+MODULE_SCOPE TkTextTabArray * TkTextGetTabs(Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *stringPtr);
+MODULE_SCOPE void TkTextInspectOptions(TkText *textPtr, const void *recordPtr,
+ Tk_OptionTable optionTable, Tcl_DString *result, bool resolveFontNames,
+ bool discardDefaultValues);
+MODULE_SCOPE void TkTextFindDisplayLineStartEnd(TkText *textPtr, TkTextIndex *indexPtr, bool end);
+MODULE_SCOPE unsigned TkTextCountDisplayLines(TkText *textPtr, const TkTextIndex *indexFrom,
+ const TkTextIndex *indexTo);
+MODULE_SCOPE void TkTextFindDisplayIndex(TkText *textPtr, TkTextIndex *indexPtr,
+ int displayLineOffset, int *xOffset);
+MODULE_SCOPE bool TkTextIndexBackChars(const TkText *textPtr, const TkTextIndex *srcPtr,
+ int count, TkTextIndex *dstPtr, TkTextCountType type);
+MODULE_SCOPE Tcl_UniChar TkTextIndexGetChar(const TkTextIndex *indexPtr);
+MODULE_SCOPE unsigned TkTextIndexCountBytes(const TkTextIndex *index1Ptr,
const TkTextIndex *index2Ptr);
-MODULE_SCOPE int TkTextIndexCountBytes(const TkText *textPtr,
- const TkTextIndex *index1Ptr,
- const TkTextIndex *index2Ptr);
-MODULE_SCOPE int TkTextIndexCount(const TkText *textPtr,
- const TkTextIndex *index1Ptr,
- const TkTextIndex *index2Ptr,
+MODULE_SCOPE unsigned TkTextIndexCount(const TkText *textPtr,
+ const TkTextIndex *index1Ptr, const TkTextIndex *index2Ptr,
TkTextCountType type);
-MODULE_SCOPE void TkTextIndexForwChars(const TkText *textPtr,
- const TkTextIndex *srcPtr, int count,
- TkTextIndex *dstPtr, TkTextCountType type);
-MODULE_SCOPE void TkTextIndexOfX(TkText *textPtr, int x,
- TkTextIndex *indexPtr);
-MODULE_SCOPE int TkTextIndexYPixels(TkText *textPtr,
- const TkTextIndex *indexPtr);
-MODULE_SCOPE TkTextSegment *TkTextIndexToSeg(const TkTextIndex *indexPtr,
- int *offsetPtr);
+MODULE_SCOPE bool TkTextIndexForwChars(const TkText *textPtr, const TkTextIndex *srcPtr,
+ int count, TkTextIndex *dstPtr, TkTextCountType type);
+MODULE_SCOPE void TkTextIndexOfX(TkText *textPtr, int x, TkTextIndex *indexPtr);
+MODULE_SCOPE int TkTextIndexYPixels(TkText *textPtr, const TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextComputeBreakLocations(Tcl_Interp *interp, const char *text, unsigned len,
+ const char *lang, char *brks);
+MODULE_SCOPE bool TkTextTestLangCode(Tcl_Interp *interp, Tcl_Obj *langCodePtr);
+MODULE_SCOPE int TkTextParseHyphenRules(TkText *textPtr, Tcl_Obj *objPtr, int *rulesPtr);
MODULE_SCOPE void TkTextLostSelection(ClientData clientData);
-MODULE_SCOPE TkTextIndex *TkTextMakeCharIndex(TkTextBTree tree, TkText *textPtr,
- int lineIndex, int charIndex,
- TkTextIndex *indexPtr);
-MODULE_SCOPE int TkTextMeasureDown(TkText *textPtr,
- TkTextIndex *srcPtr, int distance);
-MODULE_SCOPE void TkTextFreeElideInfo(TkTextElideInfo *infoPtr);
-MODULE_SCOPE int TkTextIsElided(const TkText *textPtr,
- const TkTextIndex *indexPtr,
- TkTextElideInfo *infoPtr);
-MODULE_SCOPE int TkTextMakePixelIndex(TkText *textPtr,
- int pixelIndex, TkTextIndex *indexPtr);
-MODULE_SCOPE void TkTextInvalidateLineMetrics(
- TkSharedText *sharedTextPtr, TkText *textPtr,
- TkTextLine *linePtr, int lineCount, int action);
-MODULE_SCOPE int TkTextUpdateLineMetrics(TkText *textPtr, int lineNum,
- int endLine, int doThisMuch);
-MODULE_SCOPE int TkTextUpdateOneLine(TkText *textPtr,
- TkTextLine *linePtr, int pixelHeight,
- TkTextIndex *indexPtr, int partialCalc);
+MODULE_SCOPE void TkTextConfigureUndoStack(TkSharedText *sharedTextPtr, int maxUndoDepth,
+ int maxByteSize);
+MODULE_SCOPE void TkTextConfigureRedoStack(TkSharedText *sharedTextPtr, int maxRedoDepth);
+MODULE_SCOPE void TkTextPushUndoToken(TkSharedText *sharedTextPtr, void *token,
+ unsigned byteSize);
+MODULE_SCOPE void TkTextPushRedoToken(TkSharedText *sharedTextPtr, void *token,
+ unsigned byteSize);
+MODULE_SCOPE void TkTextUndoAddMoveSegmentItem(TkSharedText *sharedTextPtr,
+ TkTextSegment *oldPos, TkTextSegment *newPos);
+MODULE_SCOPE TkTextIndex * TkTextMakeCharIndex(TkTextBTree tree, TkText *textPtr,
+ int lineIndex, int charIndex, TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextSegmentIsElided(const TkText *textPtr, const TkTextSegment *segPtr);
+MODULE_SCOPE void TkTextDispAllocStatistic();
+MODULE_SCOPE bool TkTextLineIsElided(const TkSharedText *sharedTextPtr, const TkTextLine *linePtr,
+ const TkText *textPtr);
+MODULE_SCOPE bool TkTextIsElided(const TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextTestTag(const TkTextIndex *indexPtr, const TkTextTag *tagPtr);
+inline bool TkTextIsDeadPeer(const TkText *textPtr);
+MODULE_SCOPE void TkTextGenerateWidgetViewSyncEvent(TkText *textPtr, bool sendImmediately);
+MODULE_SCOPE void TkTextRunAfterSyncCmd(TkText *textPtr);
+MODULE_SCOPE void TkTextInvalidateLineMetrics(TkSharedText *sharedTextPtr, TkText *textPtr,
+ TkTextLine *linePtr, unsigned lineCount, TkTextInvalidateAction action);
+MODULE_SCOPE void TkTextUpdateLineMetrics(TkText *textPtr, unsigned lineNum, unsigned endLine);
+MODULE_SCOPE int TkTextUpdateOneLine(TkText *textPtr, TkTextLine *linePtr, TkTextIndex *indexPtr,
+ unsigned maxDispLines);
MODULE_SCOPE int TkTextMarkCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-MODULE_SCOPE int TkTextMarkNameToIndex(TkText *textPtr,
- const char *name, TkTextIndex *indexPtr);
+MODULE_SCOPE TkTextSegment * TkTextFindMark(const TkText *textPtr, const char *name);
+MODULE_SCOPE TkTextSegment * TkTextFreeMarks(TkSharedText *sharedTextPtr, bool retainPrivateMarks);
+MODULE_SCOPE bool TkTextMarkNameToIndex(TkText *textPtr, const char *name, TkTextIndex *indexPtr);
MODULE_SCOPE void TkTextMarkSegToIndex(TkText *textPtr,
TkTextSegment *markPtr, TkTextIndex *indexPtr);
+MODULE_SCOPE TkTextSegment * TkTextMakeStartEndMark(TkText *textPtr, Tk_SegType const *typePtr);
+MODULE_SCOPE TkTextSegment * TkTextMakeMark(TkText *textPtr, const char *name);
+MODULE_SCOPE TkTextSegment * TkTextMakeNewMark(TkText *textPtr, const char *name);
+MODULE_SCOPE void TkTextUnsetMark(TkText *textPtr, TkTextSegment *markPtr);
+inline bool TkTextIsStartEndMarker(const TkTextSegment *segPtr);
+inline bool TkTextIsSpecialMark(const TkTextSegment *segPtr);
+inline bool TkTextIsPrivateMark(const TkTextSegment *segPtr);
+inline bool TkTextIsSpecialOrPrivateMark(const TkTextSegment *segPtr);
+inline bool TkTextIsNormalOrSpecialMark(const TkTextSegment *segPtr);
+inline bool TkTextIsNormalMark(const TkTextSegment *segPtr);
+inline bool TkTextIsStableMark(const TkTextSegment *segPtr);
+MODULE_SCOPE const char * TkTextMarkName(const TkSharedText *sharedTextPtr, const TkText *textPtr,
+ const TkTextSegment *markPtr);
+MODULE_SCOPE void TkTextUpdateCurrentMark(TkSharedText *sharedTextPtr);
+MODULE_SCOPE void TkTextSaveCursorIndex(TkText *textPtr);
+MODULE_SCOPE bool TkTextTriggerWatchCursor(TkText *textPtr);
+MODULE_SCOPE void TkTextInsertGetBBox(TkText *textPtr, int x, int y, int height, XRectangle *bbox);
+MODULE_SCOPE bool TkTextDrawBlockCursor(TkText *textPtr);
+MODULE_SCOPE unsigned TkTextGetCursorWidth(TkText *textPtr, int *x, int *offs);
MODULE_SCOPE void TkTextEventuallyRepick(TkText *textPtr);
-MODULE_SCOPE Bool TkTextPendingsync(TkText *textPtr);
+MODULE_SCOPE bool TkTextPendingSync(const TkText *textPtr);
MODULE_SCOPE void TkTextPickCurrent(TkText *textPtr, XEvent *eventPtr);
-MODULE_SCOPE void TkTextPixelIndex(TkText *textPtr, int x, int y,
- TkTextIndex *indexPtr, int *nearest);
-MODULE_SCOPE Tcl_Obj * TkTextNewIndexObj(TkText *textPtr,
- const TkTextIndex *indexPtr);
-MODULE_SCOPE void TkTextRedrawRegion(TkText *textPtr, int x, int y,
- int width, int height);
-MODULE_SCOPE void TkTextRedrawTag(TkSharedText *sharedTextPtr,
- TkText *textPtr, TkTextIndex *index1Ptr,
- TkTextIndex *index2Ptr, TkTextTag *tagPtr,
- int withTag);
+MODULE_SCOPE int TkTextGetFirstXPixel(const TkText *textPtr);
+MODULE_SCOPE int TkTextGetFirstYPixel(const TkText *textPtr);
+MODULE_SCOPE int TkTextGetLastXPixel(const TkText *textPtr);
+MODULE_SCOPE int TkTextGetLastYPixel(const TkText *textPtr);
+MODULE_SCOPE unsigned TkTextCountVisibleImages(const TkText *textPtr);
+MODULE_SCOPE unsigned TkTextCountVisibleWindows(const TkText *textPtr);
+MODULE_SCOPE bool TkTextPixelIndex(TkText *textPtr, int x, int y,
+ TkTextIndex *indexPtr, bool *nearest);
+MODULE_SCOPE Tcl_Obj * TkTextNewIndexObj(const TkTextIndex *indexPtr);
+MODULE_SCOPE void TkTextRedrawRegion(TkText *textPtr, int x, int y, int width, int height);
+MODULE_SCOPE bool TkTextRedrawTag(const TkSharedText *sharedTextPtr, TkText *textPtr,
+ const TkTextIndex *index1Ptr, const TkTextIndex *index2Ptr,
+ const TkTextTag *tagPtr, bool affectsDisplayGeometry);
MODULE_SCOPE void TkTextRelayoutWindow(TkText *textPtr, int mask);
+MODULE_SCOPE void TkTextCheckLineMetricUpdate(const TkText *textPtr);
+MODULE_SCOPE void TkTextCheckDisplayLineConsistency(const TkText *textPtr);
MODULE_SCOPE int TkTextScanCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
MODULE_SCOPE int TkTextSeeCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-MODULE_SCOPE int TkTextSegToOffset(const TkTextSegment *segPtr,
- const TkTextLine *linePtr);
-MODULE_SCOPE void TkTextSetYView(TkText *textPtr,
- TkTextIndex *indexPtr, int pickPlace);
+MODULE_SCOPE void TkTextSetYView(TkText *textPtr, TkTextIndex *indexPtr, int pickPlace);
MODULE_SCOPE int TkTextTagCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
MODULE_SCOPE int TkTextImageCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-MODULE_SCOPE int TkTextImageIndex(TkText *textPtr,
- const char *name, TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextImageIndex(TkText *textPtr, const char *name, TkTextIndex *indexPtr);
+MODULE_SCOPE void TkTextImageAddClient(TkSharedText *sharedTextPtr, TkText *textPtr);
+MODULE_SCOPE TkTextSegment * TkTextMakeImage(TkText *textPtr, Tcl_Obj *options);
MODULE_SCOPE int TkTextWindowCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-MODULE_SCOPE int TkTextWindowIndex(TkText *textPtr, const char *name,
- TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextWindowIndex(TkText *textPtr, const char *name, TkTextIndex *indexPtr);
+MODULE_SCOPE TkTextSegment * TkTextMakeWindow(TkText *textPtr, Tcl_Obj *options);
MODULE_SCOPE int TkTextYviewCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-MODULE_SCOPE void TkTextWinFreeClient(Tcl_HashEntry *hPtr,
- TkTextEmbWindowClient *client);
+MODULE_SCOPE void TkTextGetViewOffset(TkText *textPtr, int *x, int *y);
+MODULE_SCOPE void TkTextWinFreeClient(Tcl_HashEntry *hPtr, TkTextEmbWindowClient *client);
+MODULE_SCOPE void TkTextIndexSetPosition(TkTextIndex *indexPtr,
+ int byteIndex, TkTextSegment *segPtr);
+MODULE_SCOPE int TkTextSegToIndex(const TkTextSegment *segPtr);
+MODULE_SCOPE int TkTextIndexPrint(const TkSharedText *sharedTextPtr, const TkText *textPtr,
+ const struct TkTextIndex *indexPtr, char *string);
+MODULE_SCOPE void TkTextIndexSetByteIndex(TkTextIndex *indexPtr, int byteIndex);
+MODULE_SCOPE void TkTextIndexSetByteIndex2(TkTextIndex *indexPtr,
+ TkTextLine *linePtr, int byteIndex);
+inline void TkTextIndexSetEpoch(TkTextIndex *indexPtr, unsigned epoch);
+inline void TkTextIndexUpdateEpoch(TkTextIndex *indexPtr, unsigned epoch);
+MODULE_SCOPE void TkTextIndexSetSegment(TkTextIndex *indexPtr, TkTextSegment *segPtr);
+inline void TkTextIndexSetPeer(TkTextIndex *indexPtr, TkText *textPtr);
+MODULE_SCOPE bool TkTextIndexIsEmpty(const TkTextIndex *indexPtr);
+MODULE_SCOPE void TkTextIndexSetLine(TkTextIndex *indexPtr, TkTextLine *linePtr);
+MODULE_SCOPE void TkTextIndexSetToStartOfLine(TkTextIndex *indexPtr);
+MODULE_SCOPE void TkTextIndexSetToStartOfLine2(TkTextIndex *indexPtr, TkTextLine *linePtr);
+MODULE_SCOPE void TkTextIndexSetToEndOfLine2(TkTextIndex *indexPtr, TkTextLine *linePtr);
+MODULE_SCOPE void TkTextIndexSetToLastChar(TkTextIndex *indexPtr);
+inline void TkTextIndexSetToLastChar2(TkTextIndex *indexPtr, TkTextLine *linePtr);
+MODULE_SCOPE void TkTextIndexSetupToStartOfText(TkTextIndex *indexPtr, TkText *textPtr,
+ TkTextBTree tree);
+MODULE_SCOPE void TkTextIndexSetupToEndOfText(TkTextIndex *indexPtr, TkText *textPtr,
+ TkTextBTree tree);
+MODULE_SCOPE bool TkTextIndexAddToByteIndex(TkTextIndex *indexPtr, int numBytes);
+inline TkTextLine * TkTextIndexGetLine(const TkTextIndex *indexPtr);
+MODULE_SCOPE int TkTextIndexGetByteIndex(const TkTextIndex *indexPtr);
+MODULE_SCOPE unsigned TkTextIndexGetLineNumber(const TkTextIndex *indexPtr, const TkText *textPtr);
+inline TkTextSegment * TkTextIndexGetSegment(const TkTextIndex *indexPtr);
+MODULE_SCOPE TkTextSegment * TkTextIndexGetContentSegment(const TkTextIndex *indexPtr, int *offset);
+MODULE_SCOPE TkTextSegment * TkTextIndexGetFirstSegment(const TkTextIndex *indexPtr, int *offset);
+inline TkSharedText * TkTextIndexGetShared(const TkTextIndex *indexPtr);
+MODULE_SCOPE void TkTextIndexClear(TkTextIndex *indexPtr, TkText *textPtr);
+MODULE_SCOPE void TkTextIndexClear2(TkTextIndex *indexPtr, TkText *textPtr, TkTextBTree tree);
+inline void TkTextIndexInvalidate(TkTextIndex *indexPtr);
+MODULE_SCOPE void TkTextIndexToByteIndex(TkTextIndex *indexPtr);
+MODULE_SCOPE void TkTextIndexMakeShared(TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextIndexIsZero(const TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextIndexIsStartOfLine(const TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextIndexIsEndOfLine(const TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextIndexIsStartOfText(const TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextIndexIsEndOfText(const TkTextIndex *indexPtr);
+inline bool TkTextIndexSameLines(const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2);
+MODULE_SCOPE bool TkTextIndexIsEqual(const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2);
+MODULE_SCOPE int TkTextIndexCompare(const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2);
+inline void TkTextIndexSave(TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextIndexRebuild(TkTextIndex *indexPtr);
+MODULE_SCOPE int TkTextIndexRestrictToStartRange(TkTextIndex *indexPtr);
+MODULE_SCOPE int TkTextIndexRestrictToEndRange(TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextIndexEnsureBeforeLastChar(TkTextIndex *indexPtr);
+MODULE_SCOPE bool TkTextSkipElidedRegion(TkTextIndex *indexPtr);
-#endif /* _TKTEXT */
+/*
+ * Debugging info macro:
+ */
+
+#define TK_TEXT_DEBUG(expr) { if (tkTextDebug) { expr; } }
+#define TK_BTREE_DEBUG(expr) { if (tkBTreeDebug) { expr; } }
+
+/*
+ * Backport definitions for Tk 8.6/8.5.
+ */
+
+#if TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION < 7
+
+# if TCL_UTF_MAX > 4
+# define TkUtfToUniChar Tcl_UtfToUniChar
+# else /* if TCL_UTF_MAX <= 4 */
+inline int TkUtfToUniChar(const char *src, int *chPtr);
+# endif /* TCL_UTF_MAX > 4 */
+
+#endif /* end of backport for 8.6/8.5 */
+
+/*
+ * Backport definitions for Tk 8.5. Tk 8.6/8.7 under Mac OS X has event loop
+ * issues, so backporting is important.
+ */
+
+#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5
+
+#ifndef CLANG_ASSERT
+# define CLANG_ASSERT(expr) assert(expr)
+#endif
+
+#ifndef DEF_TEXT_INSERT_UNFOCUSSED
+# define DEF_TEXT_INSERT_UNFOCUSSED "none"
+#endif
+
+MODULE_SCOPE struct TkTextSegment * TkTextSetMark(struct TkText *textPtr, const char *name,
+ struct TkTextIndex *indexPtr);
+MODULE_SCOPE int TkBTreeNumLines(TkTextBTree tree, const struct TkText *textPtr);
+MODULE_SCOPE int TkTextGetIndex(Tcl_Interp *interp, struct TkText *textPtr,
+ const char *string, struct TkTextIndex *indexPtr);
+MODULE_SCOPE int TkTextIndexBackBytes(const struct TkText *textPtr,
+ const struct TkTextIndex *srcPtr, int count, struct TkTextIndex *dstPtr);
+MODULE_SCOPE int TkTextIndexForwBytes(const struct TkText *textPtr,
+ const struct TkTextIndex *srcPtr, int count, struct TkTextIndex *dstPtr);
+MODULE_SCOPE struct TkTextIndex *TkTextMakeByteIndex(TkTextBTree tree, const struct TkText *textPtr,
+ int lineIndex, int byteIndex, struct TkTextIndex *indexPtr);
+MODULE_SCOPE int TkTextPrintIndex(const struct TkText *textPtr,
+ const struct TkTextIndex *indexPtr, char *string);
+MODULE_SCOPE int TkTextXviewCmd(struct TkText *textPtr, Tcl_Interp *interp, int objc,
+ Tcl_Obj *const objv[]);
+MODULE_SCOPE void TkTextChanged(struct TkSharedText *sharedTextPtr, struct TkText *textPtr,
+ const struct TkTextIndex *index1Ptr, const struct TkTextIndex *index2Ptr);
+MODULE_SCOPE int TkBTreeNumLines(TkTextBTree tree, const struct TkText *textPtr);
+MODULE_SCOPE void TkTextInsertDisplayProc(struct TkText *textPtr, struct TkTextDispChunk *chunkPtr,
+ int x, int y, int height, int baseline, Display *display, Drawable dst,
+ int screenY);
+
+# define TkNewWindowObj(tkwin) Tcl_NewStringObj(Tk_PathName(tkwin), -1)
+# define Tcl_BackgroundException(interp, code) Tcl_BackgroundError(interp)
+
+/*
+ * Windows needs this.
+ */
+
+# undef TCL_STORAGE_CLASS
+# define TCL_STORAGE_CLASS DLLIMPORT
+
+#endif /* TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5 */
+#undef STRUCT
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+# define _TK_NEED_IMPLEMENTATION
+# include "tkTextPriv.h"
+#else
+# undef inline
+#endif
+
+#endif /* _TKTEXT */
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
- * fill-column: 78
+ * fill-column: 105
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/generic/tkTextBTree.c b/generic/tkTextBTree.c
index 0fdc280..570992b 100644
--- a/generic/tkTextBTree.c
+++ b/generic/tkTextBTree.c
@@ -1,12 +1,13 @@
/*
* tkTextBTree.c --
*
- * This file contains code that manages the B-tree representation of text
- * for Tk's text widget and implements character and toggle segment
- * types.
+ * This file contains code that manages the B-tree representation of text
+ * for Tk's text widget and implements the character, hyphen, branch and
+ * link segment types.
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1995 Sun Microsystems, Inc.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -14,6 +15,25 @@
#include "tkInt.h"
#include "tkText.h"
+#include "tkTextPriv.h"
+#include "tkTextTagSet.h"
+#include <assert.h>
+
+#ifndef MIN
+# define MIN(a,b) (((int) a) < ((int) b) ? a : b)
+#endif
+#ifndef MAX
+# define MAX(a,b) (((int) a) < ((int) b) ? b : a)
+#endif
+#ifndef ABS
+# define ABS(a) (a < 0 ? -a : a)
+#endif
+
+#if NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
/*
* Implementation notes:
@@ -22,6 +42,9 @@
* representation now. Without much effort this could be developed further
* into a new Tcl object type of which the Tk text widget is one example of a
* client.
+ * Note by GC: this independency is not useful, any sophisticated implementation
+ * is specialised and in general not sharable. The independency has been broken
+ * with the revised implementation (TkTextRedrawTag will be called here).
*
* The B-tree is set up with a dummy last line of text which must not be
* displayed, and must _never_ have a non-zero pixel count. This dummy line is
@@ -35,47 +58,49 @@
* valid line, rather it would point to the beginning of a non-existent line
* one beyond all current lines - we could perhaps define that as a
* TkTextIndex with a NULL TkTextLine ptr).
+ * Note by GC: the dummy line is quite useful, for instance it contains
+ * mark segments.
*/
/*
- * The data structure below keeps summary information about one tag as part of
- * the tag information in a node.
+ * Upper and lower bounds on how many children a node may have: rebalance when
+ * either of these limits is exceeded. MAX_CHILDREN should be twice
+ * MIN_CHILDREN, and MIN_CHILDREN must be >= 2.
*/
-typedef struct Summary {
- TkTextTag *tagPtr; /* Handle for tag. */
- int toggleCount; /* Number of transitions into or out of this
- * tag that occur in the subtree rooted at
- * this node. */
- struct Summary *nextPtr; /* Next in list of all tags for same node, or
- * NULL if at end of list. */
-} Summary;
+#define MIN_CHILDREN 16
+#define MAX_CHILDREN (2*MIN_CHILDREN)
/*
* The data structure below defines a node in the B-tree.
*/
+typedef struct TkBTreeNodePixelInfo {
+ uint32_t pixels; /* Number of vertical display pixels. */
+ uint32_t numDispLines; /* NUmber of display lines. */
+} NodePixelInfo;
+
typedef struct Node {
- struct Node *parentPtr; /* Pointer to parent node, or NULL if this is
- * the root. */
- struct Node *nextPtr; /* Next in list of siblings with the same
- * parent node, or NULL for end of list. */
- Summary *summaryPtr; /* First in malloc-ed list of info about tags
- * in this subtree (NULL if no tag info in the
- * subtree). */
- int level; /* Level of this node in the B-tree. 0 refers
- * to the bottom of the tree (children are
- * lines, not nodes). */
- union { /* First in linked list of children. */
- struct Node *nodePtr; /* Used if level > 0. */
- TkTextLine *linePtr; /* Used if level == 0. */
- } children;
- int numChildren; /* Number of children of this node. */
- int numLines; /* Total number of lines (leaves) in the
- * subtree rooted here. */
- int *numPixels; /* Array containing total number of vertical
- * display pixels in the subtree rooted here,
- * one entry for each peer widget. */
+ struct Node *parentPtr; /* Pointer to parent node, or NULL if this is the root. */
+ struct Node *nextPtr; /* Next in list of siblings with the same parent node, or
+ * NULL for end of list. */
+ struct Node *childPtr; /* List of children (used if level > 0). */
+ TkTextLine *linePtr; /* Level > 0: first line in leftmost leaf; else first line
+ * in children. */
+ TkTextLine *lastPtr; /* Level > 0: Last line in rightmost leaf; else last line
+ * in children. */
+ TkTextTagSet *tagonPtr; /* The union of tagonPtr over all childrens/lines. */
+ TkTextTagSet *tagoffPtr; /* The union of tagoffPtr over all childrens/lines. */
+ NodePixelInfo *pixelInfo; /* Array containing pixel information in the subtree rooted here,
+ * one entry for each peer widget. */
+ uint32_t level; /* Level of this node in the B-tree. 0 refers to the bottom of
+ * the tree (children are lines, not nodes). */
+ uint32_t size; /* Sum of size over all lines belonging to this node. */
+ uint32_t numChildren; /* Number of children of this node. */
+ uint32_t numLines; /* Total number of lines (leaves) in the subtree rooted here. */
+ uint32_t numLogicalLines; /* Total number of logical lines (a line whose predecessing line
+ * don't have an elided newline). */
+ uint32_t numBranches; /* Counting the number of branches in this node. */
} Node;
/*
@@ -83,117 +108,158 @@ typedef struct Node {
* commonly used functions. Must be > 0.
*/
-#define PIXEL_CLIENTS 5
+#define PIXEL_CLIENTS 8
/*
- * Upper and lower bounds on how many children a node may have: rebalance when
- * either of these limits is exceeded. MAX_CHILDREN should be twice
- * MIN_CHILDREN and MIN_CHILDREN must be >= 2.
+ * Number of segments inside a section of segments. MAX_TEXT_SEGS must be
+ * greater than MIN_TEXT_SEGS. Also take into account that the sum
+ * (MAX_TEXT_SEGS + NUM_TEXT_SEGS) should not exceed the bit length of
+ * 'length' in struct TkTextSection.
*/
-#define MAX_CHILDREN 12
-#define MIN_CHILDREN 6
+#define MIN_TEXT_SEGS 20
+#define MAX_TEXT_SEGS 60
+#define NUM_TEXT_SEGS (MAX_TEXT_SEGS - MIN_TEXT_SEGS)
/*
- * The data structure below defines an entire B-tree. Since text widgets are
- * the only current B-tree clients, 'clients' and 'pixelReferences' are
- * identical.
+ * Definition of flags for UpdateElideInfo.
*/
-typedef struct BTree {
- Node *rootPtr; /* Pointer to root of B-tree. */
- int clients; /* Number of clients of this B-tree. */
- int pixelReferences; /* Number of clients of this B-tree which care
- * about pixel heights. */
- int stateEpoch; /* Updated each time any aspect of the B-tree
- * changes. */
- TkSharedText *sharedTextPtr;/* Used to find tagTable in consistency
- * checking code, and to access list of all
- * B-tree clients. */
- int startEndCount;
- TkTextLine **startEnd;
- TkText **startEndRef;
-} BTree;
+enum { ELISION_WILL_BE_REMOVED, ELISION_HAS_BEEN_ADDED, ELISION_HAS_BEEN_CHANGED };
-/*
- * The structure below is used to pass information between
- * TkBTreeGetTags and IncCount:
- */
-
-typedef struct TagInfo {
- int numTags; /* Number of tags for which there is currently
- * information in tags and counts. */
- int arraySize; /* Number of entries allocated for tags and
- * counts. */
- TkTextTag **tagPtrs; /* Array of tags seen so far. Malloc-ed. */
- int *counts; /* Toggle count (so far) for each entry in
- * tags. Malloc-ed. */
-} TagInfo;
+typedef struct TkTextMyBTree BTree; /* see TkTextPriv.h */
/*
* Variable that indicates whether to enable consistency checks for debugging.
*/
-int tkBTreeDebug = 0;
+bool tkBTreeDebug = false;
/*
* Macros that determine how much space to allocate for new segments:
*/
-#define CSEG_SIZE(chars) ((unsigned) (Tk_Offset(TkTextSegment, body) \
- + 1 + (chars)))
-#define TSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \
- + sizeof(TkTextToggle)))
+/* Computer math magic: (k/8)*8 == k & -8 */
+#define CSEG_CAPACITY(chars) ((int) (chars + 8) & -8)
+#define CSEG_SIZE(capacity) ((unsigned) (Tk_Offset(TkTextSegment, body) + capacity))
/*
- * Forward declarations for functions defined in this file:
+ * Helper struct for SplitSeg.
*/
-static int AdjustPixelClient(BTree *treePtr, int defaultHeight,
- Node *nodePtr, TkTextLine *start, TkTextLine *end,
- int useReference, int newPixelReferences,
- int *counting);
-static void ChangeNodeToggleCount(Node *nodePtr,
- TkTextTag *tagPtr, int delta);
-static void CharCheckProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static int CharDeleteProc(TkTextSegment *segPtr,
- TkTextLine *linePtr, int treeGone);
-static TkTextSegment * CharCleanupProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static TkTextSegment * CharSplitProc(TkTextSegment *segPtr, int index);
-static void CheckNodeConsistency(Node *nodePtr, int references);
-static void CleanupLine(TkTextLine *linePtr);
-static void DeleteSummaries(Summary *tagPtr);
-static void DestroyNode(Node *nodePtr);
-static TkTextSegment * FindTagEnd(TkTextBTree tree, TkTextTag *tagPtr,
- TkTextIndex *indexPtr);
-static void IncCount(TkTextTag *tagPtr, int inc,
- TagInfo *tagInfoPtr);
-static void Rebalance(BTree *treePtr, Node *nodePtr);
-static void RecomputeNodeCounts(BTree *treePtr, Node *nodePtr);
-static void RemovePixelClient(BTree *treePtr, Node *nodePtr,
- int overwriteWithLast);
-static TkTextSegment * SplitSeg(TkTextIndex *indexPtr);
-static void ToggleCheckProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static TkTextSegment * ToggleCleanupProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static int ToggleDeleteProc(TkTextSegment *segPtr,
- TkTextLine *linePtr, int treeGone);
-static void ToggleLineChangeProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static TkTextSegment * FindTagStart(TkTextBTree tree, TkTextTag *tagPtr,
- TkTextIndex *indexPtr);
-static void AdjustStartEndRefs(BTree *treePtr, TkText *textPtr,
- int action);
+typedef struct SplitInfo {
+ int offset; /* Out: Offset for insertion, -1 if SplitSeg
+ * did not increase/decrease the segment. */
+ int increase; /* In: Additional bytes required for the insertion of new chars.
+ * Can be negative, in this case the size will be decreased.
+ */
+ bool splitted; /* Out: Flag whether a split has been done. */
+ bool forceSplit; /* In: The char segment must be split after offset, because a
+ * newline will be inserted, and we shift the content after
+ * offset into the new line. */
+ TkTextTagSet *tagInfoPtr;
+ /* in: Tag information of new segment, can be NULL.
+ * Out: Tag information of char segment, when inserting. */
+} SplitInfo;
/*
- * Actions for use by AdjustStartEndRefs
+ * Forward declarations for functions defined in this file:
*/
-#define TEXT_ADD_REFS 1
-#define TEXT_REMOVE_REFS 2
+struct UndoTokenInsert;
+
+static unsigned AdjustPixelClient(BTree *treePtr, unsigned defaultHeight, Node *nodePtr,
+ TkTextLine *startLine, TkTextLine *endLine, unsigned useReference,
+ unsigned newPixelReferences, unsigned *numDispLinesPtr);
+static TkTextSegment * JoinCharSegments(const TkSharedText *sharedTextPtr, TkTextSegment *segPtr);
+static void CleanupSplitPoint(TkTextSegment *segPtr, TkSharedText *sharedTextPtr);
+static void CharCheckProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+static bool CharDeleteProc(TkTextBTree tree, TkTextSegment *segPtr, int flags);
+static Tcl_Obj * CharInspectProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+static TkTextSegment * CleanupCharSegments(const TkSharedText *sharedTextPtr, TkTextSegment *segPtr);
+static bool HyphenDeleteProc(TkTextBTree tree, TkTextSegment *segPtr, int flags);
+static void HyphenCheckProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+static Tcl_Obj * HyphenInspectProc(const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr);
+static TkTextSegment * IncreaseCharSegment(TkTextSegment *segPtr, unsigned offset, int chunkSize);
+static void FreeLine(const BTree *treePtr, TkTextLine *linePtr);
+static void LinkSegment(TkTextLine *linePtr, TkTextSegment *predPtr, TkTextSegment *succPtr);
+static void LinkMark(const TkSharedText *sharedTextPtr, TkTextLine *linePtr,
+ TkTextSegment *prevPtr, TkTextSegment *segPtr);
+static void LinkSwitch(TkTextLine *linePtr, TkTextSegment *predPtr, TkTextSegment *succPtr);
+static TkTextSegment * MakeCharSeg(TkTextSection *sectionPtr, TkTextTagSet *tagInfoPtr,
+ unsigned newSize, const char *string, unsigned length);
+static TkTextSegment * CopyCharSeg(TkTextSegment *segPtr, unsigned offset,
+ unsigned length, unsigned newSize);
+static TkTextSegment * SplitCharSegment(TkTextSegment *segPtr, unsigned index);
+static void CheckNodeConsistency(const TkSharedText *sharedTextPtr, const Node *nodePtr,
+ const Node *rootPtr, unsigned references);
+static void RebuildSections(TkSharedText *sharedTextPtr, TkTextLine *linePtr,
+ bool propagateChangeOfNumBranches);
+static bool CheckSegments(const TkSharedText *sharedTextPtr, const TkTextLine *linePtr);
+static bool CheckSections(const TkTextLine *linePtr);
+static bool CheckSegmentItems(const TkSharedText *sharedTextPtr, const TkTextLine *linePtr);
+static void FreeNode(Node *nodePtr);
+static void DestroyNode(TkTextBTree tree, Node *nodePtr);
+static void DeleteEmptyNode(BTree *treePtr, Node *nodePtr);
+static TkTextSegment * FindTagStart(TkTextSearch *searchPtr, const TkTextIndex *stopIndex);
+static TkTextSegment * FindTagEnd(TkTextSearch *searchPtr, const TkTextIndex *stopIndex);
+static void Rebalance(BTree *treePtr, Node *nodePtr);
+static void RemovePixelClient(BTree *treePtr, Node *nodePtr, unsigned useReference,
+ int overwriteWithLast);
+static TkTextTagSet * MakeTagInfo(TkText *textPtr, TkTextSegment *segPtr);
+static TkTextLine * InsertNewLine(TkSharedText *sharedTextPtr, Node *nodePtr,
+ TkTextLine *prevLinePtr, TkTextSegment *segPtr);
+static TkTextSegment * SplitSeg(const TkTextIndex *indexPtr, SplitInfo *splitInfo);
+static TkTextSegment * PrepareInsertIntoCharSeg(TkTextSegment *segPtr,
+ unsigned offset, SplitInfo *splitInfo);
+static void SplitSection(TkTextSection *sectionPtr);
+static void JoinSections(TkTextSection *sectionPtr);
+static void FreeSections(TkTextSection *sectionPtr);
+static TkTextSegment * UnlinkSegment(TkTextSegment *segPtr);
+static void UnlinkSegmentAndCleanup(const TkSharedText *sharedTextPtr,
+ TkTextSegment *segPtr);
+static unsigned CountSegments(const TkTextSection *sectionPtr);
+static unsigned ComputeSectionSize(const TkTextSegment *segPtr);
+static void BranchCheckProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+static bool BranchDeleteProc(TkTextBTree tree, TkTextSegment *segPtr, int flags);
+static void BranchRestoreProc(TkTextSegment *segPtr);
+static Tcl_Obj * BranchInspectProc(const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr);
+static void LinkCheckProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+static bool LinkDeleteProc(TkTextBTree tree, TkTextSegment *segPtr, int flags);
+static void LinkRestoreProc(TkTextSegment *segPtr);
+static Tcl_Obj * LinkInspectProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+static bool ProtectionMarkDeleteProc(TkTextBTree tree, TkTextSegment *segPtr, int flags);
+static void ProtectionMarkCheckProc(const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr);
+static void AddPixelCount(BTree *treePtr, TkTextLine *linePtr,
+ const TkTextLine *refLinePtr, NodePixelInfo *changeToPixels);
+static void SubtractPixelInfo(BTree *treePtr, TkTextLine *linePtr);
+static void SubtractPixelCount2(BTree *treePtr, Node *nodePtr, int changeToLineCount,
+ int changeToLogicalLineCount, int changeToBranchCount, int changeToSize,
+ const NodePixelInfo *changeToPixelInfo);
+static void DeleteIndexRange(TkSharedText *sharedTextPtr,
+ TkTextIndex *indexPtr1, TkTextIndex *indexPtr2, int flags,
+ const struct UndoTokenInsert *undoToken, TkTextUndoInfo *redoInfo);
+static void DeleteRange(TkSharedText *sharedTextPtr,
+ TkTextSegment *firstSegPtr, TkTextSegment *lastSegPtr,
+ int flags, TkTextUndoInfo *redoInfo);
+static void UpdateNodeTags(const TkSharedText *sharedTextPtr, Node *nodePtr);
+static void MakeUndoIndex(const TkSharedText *sharedTextPtr, const TkTextIndex *indexPtr,
+ TkTextUndoIndex *undoIndexPtr, int gravity);
+static bool UndoIndexIsEqual(const TkTextUndoIndex *indexPtr1,
+ const TkTextUndoIndex *indexPtr2);
+static void AddTagToNode(Node *nodePtr, TkTextTag *tag, bool setTagoff);
+static void RemoveTagFromNode(Node *nodePtr, TkTextTag *tag);
+static void UpdateElideInfo(TkSharedText *sharedTextPtr, TkTextTag *tagPtr,
+ TkTextSegment *firstSegPtr, TkTextSegment *lastSegPtr, unsigned reason);
+static bool SegmentIsElided(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr,
+ const TkText *textPtr);
+static TkTextLine * GetStartLine(const TkSharedText *sharedTextPtr, const TkText *textPtr);
+static TkTextLine * GetLastLine(const TkSharedText *sharedTextPtr, const TkText *textPtr);
+static void ReInsertSegment(const TkSharedText *sharedTextPtr,
+ const TkTextUndoIndex *indexPtr, TkTextSegment *segPtr, bool updateNode);
/*
* Type record for character segments:
@@ -201,44 +267,1637 @@ static void AdjustStartEndRefs(BTree *treePtr, TkText *textPtr,
const Tk_SegType tkTextCharType = {
"character", /* name */
- 0, /* leftGravity */
- CharSplitProc, /* splitProc */
+ SEG_GROUP_CHAR, /* group */
+ GRAVITY_NEUTRAL, /* gravity */
CharDeleteProc, /* deleteProc */
- CharCleanupProc, /* cleanupProc */
- NULL, /* lineChangeProc */
+ NULL, /* restoreProc */
TkTextCharLayoutProc, /* layoutProc */
- CharCheckProc /* checkProc */
+ CharCheckProc, /* checkProc */
+ CharInspectProc /* inspectProc */
+};
+
+/*
+ * Type record for hyphenation support.
+ */
+
+const Tk_SegType tkTextHyphenType = {
+ "hyphen", /* name */
+ SEG_GROUP_HYPHEN, /* group */
+ GRAVITY_NEUTRAL, /* gravity */
+ HyphenDeleteProc, /* deleteProc */
+ NULL, /* restoreProc */
+ TkTextCharLayoutProc, /* layoutProc */
+ HyphenCheckProc, /* checkProc */
+ HyphenInspectProc /* inspectProc */
+};
+
+/*
+ * Type record for segments marking a branch for normal/elided text:
+ */
+
+const Tk_SegType tkTextBranchType = {
+ "branch", /* name */
+ SEG_GROUP_BRANCH, /* group */
+ GRAVITY_RIGHT, /* gravity */
+ BranchDeleteProc, /* deleteProc */
+ BranchRestoreProc, /* restoreProc */
+ NULL, /* layoutProc */
+ BranchCheckProc, /* checkProc */
+ BranchInspectProc /* inspectProc */
};
/*
- * Type record for segments marking the beginning of a tagged range:
+ * Type record for segments marking a link for a switched chain:
*/
-const Tk_SegType tkTextToggleOnType = {
- "toggleOn", /* name */
- 0, /* leftGravity */
- NULL, /* splitProc */
- ToggleDeleteProc, /* deleteProc */
- ToggleCleanupProc, /* cleanupProc */
- ToggleLineChangeProc, /* lineChangeProc */
+const Tk_SegType tkTextLinkType = {
+ "connection", /* name */
+ SEG_GROUP_BRANCH, /* group */
+ GRAVITY_LEFT, /* gravity */
+ LinkDeleteProc, /* deleteProc */
+ LinkRestoreProc, /* restoreProc */
NULL, /* layoutProc */
- ToggleCheckProc /* checkProc */
+ LinkCheckProc, /* checkProc */
+ LinkInspectProc /* inspectProc */
};
/*
- * Type record for segments marking the end of a tagged range:
+ * Type record for the deletion marks.
*/
-const Tk_SegType tkTextToggleOffType = {
- "toggleOff", /* name */
- 1, /* leftGravity */
- NULL, /* splitProc */
- ToggleDeleteProc, /* deleteProc */
- ToggleCleanupProc, /* cleanupProc */
- ToggleLineChangeProc, /* lineChangeProc */
+const Tk_SegType tkTextProtectionMarkType = {
+ "protection", /* name */
+ SEG_GROUP_PROTECT, /* group */
+ GRAVITY_NEUTRAL, /* gravity */
+ ProtectionMarkDeleteProc, /* deleteProc */
+ NULL, /* restoreProc */
NULL, /* layoutProc */
- ToggleCheckProc /* checkProc */
+ ProtectionMarkCheckProc, /* checkProc */
+ NULL /* inspectProc */
+};
+
+/*
+ * We need some private undo/redo stuff.
+ */
+
+typedef struct UndoTagChange {
+ TkTextTagSet *tagInfoPtr;
+ uint32_t skip;
+ uint32_t size;
+} UndoTagChange;
+
+static void UndoTagPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void UndoTagDestroy(TkSharedText *, TkTextUndoToken *token, bool);
+static Tcl_Obj *UndoTagGetCommand(const TkSharedText *, const TkTextUndoToken *);
+
+static void UndoClearTagsPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void RedoClearTagsPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void UndoClearTagsDestroy(TkSharedText *, TkTextUndoToken *token, bool);
+static Tcl_Obj *UndoClearTagsGetCommand(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *UndoClearTagsInspect(const TkSharedText *, const TkTextUndoToken *);
+
+static void UndoDeletePerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void RedoDeletePerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void UndoDeleteDestroy(TkSharedText *, TkTextUndoToken *token, bool);
+static Tcl_Obj *UndoDeleteGetCommand(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *UndoDeleteInspect(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *RedoDeleteInspect(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *RedoInsertInspect(const TkSharedText *, const TkTextUndoToken *);
+
+static void UndoInsertPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static Tcl_Obj *UndoInsertGetCommand(const TkSharedText *, const TkTextUndoToken *);
+
+static void UndoGetRange(const TkSharedText *, const TkTextUndoToken *, TkTextIndex *, TkTextIndex *);
+
+static const Tk_UndoType undoTokenTagType = {
+ TK_TEXT_UNDO_TAG, /* action */
+ UndoTagGetCommand, /* commandProc */
+ UndoTagPerform, /* undoProc */
+ UndoTagDestroy, /* destroyProc */
+ UndoGetRange, /* rangeProc */
+ TkBTreeUndoTagInspect /* inspectProc */
+};
+
+static const Tk_UndoType redoTokenTagType = {
+ TK_TEXT_REDO_TAG, /* action */
+ UndoTagGetCommand, /* commandProc */
+ UndoTagPerform, /* undoProc */
+ UndoTagDestroy, /* destroyProc */
+ UndoGetRange, /* rangeProc */
+ TkBTreeUndoTagInspect /* inspectProc */
+};
+
+static const Tk_UndoType undoTokenClearTagsType = {
+ TK_TEXT_UNDO_TAG_CLEAR, /* action */
+ UndoClearTagsGetCommand, /* commandProc */
+ UndoClearTagsPerform, /* undoProc */
+ UndoClearTagsDestroy, /* destroyProc */
+ UndoGetRange, /* rangeProc */
+ UndoClearTagsInspect /* inspectProc */
+};
+
+static const Tk_UndoType redoTokenClearTagsType = {
+ TK_TEXT_REDO_TAG_CLEAR, /* action */
+ UndoClearTagsGetCommand, /* commandProc */
+ RedoClearTagsPerform, /* undoProc */
+ NULL, /* destroyProc */
+ UndoGetRange, /* rangeProc */
+ UndoClearTagsGetCommand /* inspectProc */
+};
+
+static const Tk_UndoType undoTokenDeleteType = {
+ TK_TEXT_UNDO_DELETE, /* action */
+ UndoDeleteGetCommand, /* commandProc */
+ UndoDeletePerform, /* undoProc */
+ UndoDeleteDestroy, /* destroyProc */
+ UndoGetRange, /* rangeProc */
+ UndoDeleteInspect /* inspectProc */
+};
+
+static const Tk_UndoType redoTokenDeleteType = {
+ TK_TEXT_REDO_DELETE, /* action */
+ UndoDeleteGetCommand, /* commandProc */
+ RedoDeletePerform, /* undoProc */
+ NULL, /* destroyProc */
+ UndoGetRange, /* rangeProc */
+ RedoDeleteInspect /* inspectProc */
+};
+
+static const Tk_UndoType undoTokenInsertType = {
+ TK_TEXT_UNDO_INSERT, /* action */
+ UndoInsertGetCommand, /* commandProc */
+ UndoInsertPerform, /* undoProc */
+ NULL, /* destroyProc */
+ UndoGetRange, /* rangeProc */
+ UndoInsertGetCommand /* inspectProc */
};
+
+static const Tk_UndoType redoTokenInsertType = {
+ TK_TEXT_REDO_INSERT, /* action */
+ UndoInsertGetCommand, /* commandProc */
+ UndoDeletePerform, /* undoProc */
+ UndoDeleteDestroy, /* destroyProc */
+ UndoGetRange, /* rangeProc */
+ RedoInsertInspect /* inspectProc */
+};
+
+/* Derivation of TkTextUndoTokenRange */
+typedef struct UndoTokenDelete {
+ const Tk_UndoType *undoType;
+ TkTextUndoIndex startIndex; /* Start of deletion range. */
+ TkTextUndoIndex endIndex; /* End of deletion range. */
+ TkTextSegment **segments; /* Array containing the deleted segments. */
+ uint32_t numSegments:31; /* Number of segments. */
+ uint32_t inclusive:1; /* Inclusive bounds? */
+} UndoTokenDelete;
+
+/* Derivation of TkTextUndoTokenRange */
+typedef struct UndoTokenInsert {
+ const Tk_UndoType *undoType;
+ TkTextUndoIndex startIndex; /* Start of insertion range. */
+ TkTextUndoIndex endIndex; /* End of insertion range. */
+} UndoTokenInsert;
+
+/* Derivation of TkTextUndoTokenRange */
+typedef struct UndoTokenTagChange {
+ const Tk_UndoType *undoType;
+ TkTextUndoIndex startIndex; /* Start of insertion range. */
+ TkTextUndoIndex endIndex; /* End of insertion range. */
+ TkTextTag *tagPtr; /* Added/removed tag. */
+ int32_t *lengths; /* Array of tagged lengths (in byte size): if negative: skip this part;
+ * if positive: tag/untag this part. Last entry is 0 (zero). This
+ * attribute can be NULL. Any part outside of this array will be
+ * tagged/untagged. */
+} UndoTokenTagChange;
+
+/* Derivation of TkTextUndoTokenRange */
+typedef struct UndoTokenTagClear {
+ const Tk_UndoType *undoType;
+ TkTextUndoIndex startIndex; /* Start of clearing range. */
+ TkTextUndoIndex endIndex; /* End of clearing range. */
+ UndoTagChange *changeList;
+ unsigned changeListSize;
+} UndoTokenTagClear;
+
+/* Derivation of TkTextUndoTokenRange */
+typedef struct RedoTokenClearTags {
+ const Tk_UndoType *undoType;
+ TkTextUndoIndex startIndex; /* Start of clearing range. */
+ TkTextUndoIndex endIndex; /* End of clearing range. */
+} RedoTokenClearTags;
+
+/*
+ * Pointer to int, for some portable pointer hacks - it's guaranteed that
+ * 'uintptr_'t and 'void *' are convertible in both directions (C99 7.18.1.4).
+ */
+
+typedef union {
+ void *ptr;
+ uintptr_t flag;
+} __ptr_to_int;
+
+#define POINTER_IS_MARKED(ptr) (((__ptr_to_int *) &ptr)->flag & (uintptr_t) 1)
+#define MARK_POINTER(ptr) (((__ptr_to_int *) &ptr)->flag |= (uintptr_t) 1)
+#define UNMARK_POINTER(ptr) (((__ptr_to_int *) &ptr)->flag &= ~(uintptr_t) 1)
+#define UNMARKED_INT(ptr) (((__ptr_to_int *) &ptr)->flag & ~(uintptr_t) 1)
+
+DEBUG_ALLOC(extern unsigned tkTextCountNewSegment);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroySegment);
+DEBUG_ALLOC(extern unsigned tkTextCountNewNode);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroyNode);
+DEBUG_ALLOC(extern unsigned tkTextCountNewPixelInfo);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroyPixelInfo);
+DEBUG_ALLOC(extern unsigned tkTextCountNewLine);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroyLine);
+DEBUG_ALLOC(extern unsigned tkTextCountNewSection);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroySection);
+DEBUG_ALLOC(extern unsigned tkTextCountNewUndoToken);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroyDispInfo);
+
+/*
+ * Some helpers, especially for tag set operations.
+ */
+
+static unsigned
+GetByteLength(
+ Tcl_Obj *objPtr)
+{
+ assert(objPtr);
+
+ if (!objPtr->bytes) {
+ Tcl_GetString(objPtr);
+ }
+ return objPtr->length;
+}
+
+static bool
+SegIsAtStartOfLine(
+ const TkTextSegment *segPtr)
+{
+ while (segPtr && segPtr->size == 0) {
+ segPtr = segPtr->prevPtr;
+ }
+ return !segPtr;
+}
+
+static bool
+SegIsAtEndOfLine(
+ const TkTextSegment *segPtr)
+{
+ while (segPtr && segPtr->size == 0) {
+ segPtr = segPtr->nextPtr;
+ }
+ return !segPtr->nextPtr;
+}
+
+static TkTextSegment *
+GetPrevTagInfoSegment(
+ TkTextSegment *segPtr)
+{
+ assert(segPtr);
+
+ TkTextLine *linePtr = segPtr->sectionPtr->linePtr;
+
+ for (segPtr = segPtr->prevPtr; segPtr; segPtr = segPtr->prevPtr) {
+ if (segPtr->tagInfoPtr) {
+ return segPtr;
+ }
+ }
+
+ return (linePtr = linePtr->prevPtr) ? linePtr->lastPtr : NULL;
+}
+
+static TkTextSegment *
+GetNextTagInfoSegment(
+ TkTextSegment *segPtr)
+{
+ assert(segPtr);
+
+ for ( ; !segPtr->tagInfoPtr; segPtr = segPtr->nextPtr) {
+ assert(segPtr);
+ }
+ return segPtr;
+}
+
+static TkTextSegment *
+GetFirstTagInfoSegment(
+ const TkText *textPtr, /* can be NULL */
+ const TkTextLine *linePtr)
+{
+ TkTextSegment *segPtr;
+
+ assert(linePtr);
+
+ if (textPtr && linePtr == textPtr->startMarker->sectionPtr->linePtr) {
+ segPtr = textPtr->startMarker;
+ } else {
+ segPtr = linePtr->segPtr;
+ }
+
+ return GetNextTagInfoSegment(segPtr);
+}
+
+static bool
+TagSetTestBits(
+ const TkTextTagSet *tagInfoPtr,
+ const TkBitField *bitField) /* can be NULL */
+{
+ assert(tagInfoPtr);
+
+ if (TkTextTagSetIsEmpty(tagInfoPtr)) {
+ return false;
+ }
+ return !bitField || !TkTextTagBitContainsSet(bitField, tagInfoPtr);
+}
+
+static bool
+TagSetTestDisjunctiveBits(
+ const TkTextTagSet *tagInfoPtr,
+ const TkBitField *bitField) /* can be NULL */
+{
+ assert(tagInfoPtr);
+
+ if (bitField) {
+ return TkTextTagSetDisjunctiveBits(tagInfoPtr, bitField);
+ }
+ return !TkTextTagSetIsEmpty(tagInfoPtr);
+}
+
+static bool
+TagSetTestDontContainsAny(
+ const TkTextTagSet *tagonPtr,
+ const TkTextTagSet *tagoffPtr,
+ const TkBitField *bitField) /* can be NULL */
+{
+ assert(tagonPtr);
+ assert(tagoffPtr);
+
+ return !TagSetTestDisjunctiveBits(tagonPtr, bitField)
+ || TagSetTestDisjunctiveBits(tagoffPtr, bitField);
+}
+
+static bool
+TestTag(
+ const TkTextTagSet *tagInfoPtr,
+ const TkTextTag *tagPtr) /* can be NULL */
+{
+ return tagPtr ? TkTextTagSetTest(tagInfoPtr, tagPtr->index) : TkTextTagSetAny(tagInfoPtr);
+}
+
+static void
+TagSetAssign(
+ TkTextTagSet **dstRef,
+ TkTextTagSet *srcPtr)
+{
+ if (*dstRef != srcPtr) {
+ TkTextTagSetDecrRefCount(*dstRef);
+ TkTextTagSetIncrRefCount(srcPtr);
+ *dstRef = srcPtr;
+ }
+}
+
+static void
+TagSetReplace(
+ TkTextTagSet **dstRef,
+ TkTextTagSet *srcPtr)
+{
+ TkTextTagSetDecrRefCount(*dstRef);
+ *dstRef = srcPtr;
+}
+
+static TkTextTagSet *
+TagSetAdd(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTag *tagPtr)
+{
+#if !TK_TEXT_DONT_USE_BITFIELDS
+ if (tagPtr->index >= TkTextTagSetSize(tagInfoPtr)) {
+ assert(tagPtr->index < tagPtr->sharedTextPtr->tagInfoSize);
+ tagInfoPtr = TkTextTagSetResize(tagInfoPtr, tagPtr->sharedTextPtr->tagInfoSize);
+ }
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+ return TkTextTagSetAdd(tagInfoPtr, tagPtr->index);
+}
+
+static TkTextTagSet *
+TagSetErase(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTag *tagPtr)
+{
+ if (tagPtr->index >= TkTextTagSetSize(tagInfoPtr)) {
+ return tagInfoPtr;
+ }
+ if (TkTextTagSetIsEmpty(tagInfoPtr = TkTextTagSetErase(tagInfoPtr, tagPtr->index))) {
+ TagSetAssign(&tagInfoPtr, tagPtr->sharedTextPtr->emptyTagInfoPtr);
+ }
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetAddOrErase(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTag *tagPtr,
+ bool add)
+{
+ return add ? TagSetAdd(tagInfoPtr, tagPtr) : TagSetErase(tagInfoPtr, tagPtr);
+}
+
+static TkTextTagSet *
+TagSetRemove(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTagSet *otherInfoPtr,
+ const TkSharedText *sharedTextPtr)
+{
+ if (TkTextTagSetIsEmpty(tagInfoPtr = TkTextTagSetRemove(tagInfoPtr, otherInfoPtr))) {
+ TagSetAssign(&tagInfoPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetRemoveBits(
+ TkTextTagSet *tagInfoPtr,
+ const TkBitField *otherInfoPtr,
+ const TkSharedText *sharedTextPtr)
+{
+ if (TkTextTagSetIsEmpty(tagInfoPtr = TkTextTagSetRemoveBits(tagInfoPtr, otherInfoPtr))) {
+ TagSetAssign(&tagInfoPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetJoin(
+ TkTextTagSet *tagInfoPtr, /* can be NULL */
+ const TkTextTagSet *otherInfoPtr)
+{
+ if (!tagInfoPtr) {
+ TkTextTagSetIncrRefCount(tagInfoPtr = (TkTextTagSet *) otherInfoPtr);
+ } else {
+ tagInfoPtr = TkTextTagSetJoin(tagInfoPtr, otherInfoPtr);
+ }
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetJoinNonIntersection(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTagSet *otherInfoPtr1,
+ const TkTextTagSet *otherInfoPtr2,
+ const TkSharedText *sharedTextPtr)
+{
+ assert(tagInfoPtr);
+ assert(otherInfoPtr1);
+ assert(otherInfoPtr2);
+
+ if (otherInfoPtr1 == otherInfoPtr2) {
+ /* This is especially catching the case that both otherInfoPtr are empty. */
+ return tagInfoPtr;
+ }
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+ if (TkTextTagSetSize(tagInfoPtr) < sharedTextPtr->tagInfoSize) {
+ unsigned size = MAX(TkTextTagSetSize(otherInfoPtr1), TkTextTagSetSize(otherInfoPtr2));
+ tagInfoPtr = TkTextTagSetResize(tagInfoPtr, MAX(size, sharedTextPtr->tagInfoSize));
+ }
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+ tagInfoPtr = TkTextTagSetJoinNonIntersection(tagInfoPtr, otherInfoPtr1, otherInfoPtr2);
+
+ if (TkTextTagSetIsEmpty(tagInfoPtr)) {
+ TagSetAssign(&tagInfoPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetIntersect(
+ TkTextTagSet *tagInfoPtr, /* can be NULL */
+ const TkTextTagSet *otherInfoPtr,
+ const TkSharedText *sharedTextPtr)
+{
+ if (!tagInfoPtr) {
+ TkTextTagSetIncrRefCount(tagInfoPtr = (TkTextTagSet *) otherInfoPtr);
+ } else if (TkTextTagSetIsEmpty(tagInfoPtr = TkTextTagSetIntersect(tagInfoPtr, otherInfoPtr))) {
+ TagSetAssign(&tagInfoPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetIntersectBits(
+ TkTextTagSet *tagInfoPtr,
+ const TkBitField *otherInfoPtr,
+ const TkSharedText *sharedTextPtr)
+{
+ if (TkTextTagSetIsEmpty(tagInfoPtr = TkTextTagSetIntersectBits(tagInfoPtr, otherInfoPtr))) {
+ TagSetAssign(&tagInfoPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetComplementTo(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTagSet *otherInfoPtr,
+ const TkSharedText *sharedTextPtr)
+{
+ if (TkTextTagSetIsEmpty(tagInfoPtr = TkTextTagSetComplementTo(tagInfoPtr, otherInfoPtr))) {
+ TagSetAssign(&tagInfoPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetJoinComplementTo(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTagSet *otherInfoPtr1,
+ const TkTextTagSet *otherInfoPtr2,
+ const TkSharedText *sharedTextPtr)
+{
+ if (otherInfoPtr2 == sharedTextPtr->emptyTagInfoPtr) {
+ return tagInfoPtr;
+ }
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+ if (TkTextTagSetSize(tagInfoPtr) < sharedTextPtr->tagInfoSize) {
+ tagInfoPtr = TkTextTagSetResize(tagInfoPtr, sharedTextPtr->tagInfoSize);
+ }
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+ if (TkTextTagSetIsEmpty(
+ tagInfoPtr = TkTextTagSetJoinComplementTo(tagInfoPtr, otherInfoPtr1, otherInfoPtr2))) {
+ TagSetAssign(&tagInfoPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+ return tagInfoPtr;
+}
+
+static TkTextTagSet *
+TagSetJoinOfDifferences(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTagSet *otherInfoPtr1,
+ const TkTextTagSet *otherInfoPtr2,
+ const TkSharedText *sharedTextPtr)
+{
+#if !TK_TEXT_DONT_USE_BITFIELDS
+ if (TkTextTagSetSize(tagInfoPtr) < sharedTextPtr->tagInfoSize) {
+ tagInfoPtr = TkTextTagSetResize(tagInfoPtr, sharedTextPtr->tagInfoSize);
+ }
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+ return TkTextTagSetJoinOfDifferences(tagInfoPtr, otherInfoPtr1, otherInfoPtr2);
+}
+
+static TkTextTagSet *
+TagSetTestAndSet(
+ TkTextTagSet *tagInfoPtr,
+ const TkTextTag *tagPtr)
+{
+ unsigned tagIndex = tagPtr->index;
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+ if (tagPtr->index >= TkTextTagSetSize(tagInfoPtr)) {
+ tagInfoPtr = TkTextTagSetResize(tagInfoPtr, tagPtr->sharedTextPtr->tagInfoSize);
+ return TkTextTagSetAdd(tagInfoPtr, tagIndex);
+ }
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+ return TkTextTagSetTestAndSet(tagInfoPtr, tagIndex);
+}
+
+static bool
+LineTestAllSegments(
+ const TkTextLine *linePtr,
+ const TkTextTag *tagPtr,
+ bool tagged)
+{
+ unsigned tagIndex = tagPtr->index;
+
+ return TkTextTagSetTest(linePtr->tagonPtr, tagIndex) == tagged
+ && (!tagged || !TkTextTagSetTest(linePtr->tagoffPtr, tagIndex));
+}
+
+static bool
+LineTestIfAnyIsTagged(
+ TkTextSegment *firstPtr,
+ TkTextSegment *lastPtr,
+ unsigned tagIndex)
+{
+ assert(firstPtr || !lastPtr);
+
+ for ( ; firstPtr != lastPtr; firstPtr = firstPtr->nextPtr) {
+ if (firstPtr->tagInfoPtr && TkTextTagSetTest(firstPtr->tagInfoPtr, tagIndex)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool
+LineTestIfAnyIsUntagged(
+ TkTextSegment *firstSegPtr,
+ TkTextSegment *lastSegPtr,
+ unsigned tagIndex)
+{
+ assert(firstSegPtr);
+
+ for ( ; firstSegPtr != lastSegPtr; firstSegPtr = firstSegPtr->nextPtr) {
+ if (firstSegPtr->tagInfoPtr) {
+ if (!TkTextTagSetTest(firstSegPtr->tagInfoPtr, tagIndex)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+static bool
+LineTestIfToggleIsOpen(
+ const TkTextLine *linePtr, /* can be NULL */
+ unsigned tagIndex)
+{
+ return linePtr && TkTextTagSetTest(linePtr->lastPtr->tagInfoPtr, tagIndex);
+}
+
+static bool
+LineTestIfToggleIsClosed(
+ const TkTextLine *linePtr, /* can be NULL */
+ unsigned tagIndex)
+{
+ return !linePtr || !TkTextTagSetTest(GetFirstTagInfoSegment(NULL, linePtr)->tagInfoPtr, tagIndex);
+}
+
+static bool
+LineTestToggleFwd(
+ const TkTextLine *linePtr,
+ unsigned tagIndex,
+ bool testTagon)
+{
+ assert(linePtr);
+
+ /*
+ * testTagon == true: Test whether given tag is starting a range inside this line.
+ * In this case this function assumes that this tag is not open at end of previous line.
+ */
+
+ if (testTagon) {
+ return TkTextTagSetTest(linePtr->tagonPtr, tagIndex);
+ }
+
+ /*
+ * testTagon == false: Test whether given tag is ending a range inside this line.
+ * In this case this function assumes that this tag is open at end of previous line.
+ */
+
+ return TkTextTagSetTest(linePtr->tagoffPtr, tagIndex)
+ || !TkTextTagSetTest(linePtr->tagonPtr, tagIndex);
+}
+
+static bool
+LineTestToggleBack(
+ const TkTextLine *linePtr,
+ unsigned tagIndex,
+ bool testTagon)
+{
+ assert(linePtr);
+
+ /*
+ * testTagon == true: Test whether given tag is starting a range inside this line.
+ * In this case this function assumes that this tag is already open at start of next line.
+ */
+
+ if (testTagon) {
+ return TkTextTagSetTest(linePtr->tagonPtr, tagIndex)
+ && (TkTextTagSetTest(linePtr->tagoffPtr, tagIndex)
+ || !LineTestIfToggleIsOpen(linePtr->prevPtr, tagIndex));
+ }
+
+ /*
+ * testTagon == false: Test whether given tag is ending a range inside this line.
+ * In this case this function assumes that this tag is not open at start of next line.
+ */
+
+ return TkTextTagSetTest(linePtr->tagoffPtr, tagIndex)
+ || LineTestIfToggleIsOpen(linePtr->prevPtr, tagIndex)
+ || TkTextTagSetTest(GetFirstTagInfoSegment(NULL, linePtr)->tagInfoPtr, tagIndex);
+}
+
+static bool
+NodeTestAnySegment(
+ const Node *nodePtr,
+ unsigned tagIndex,
+ bool tagged)
+{
+ /*
+ * tagged == true: test whether any segments is tagged with specified tag.
+ * tagged == false: test whether any segments is not tagged with specified tag.
+ */
+
+ return TkTextTagSetTest(nodePtr->tagonPtr, tagIndex) == tagged
+ && (tagged || TkTextTagSetTest(nodePtr->tagoffPtr, tagIndex));
+}
+
+static bool
+NodeTestAllSegments(
+ const Node *nodePtr,
+ unsigned tagIndex,
+ bool tagged)
+{
+ return TkTextTagSetTest(nodePtr->tagonPtr, tagIndex) == tagged
+ && (!tagged || !TkTextTagSetTest(nodePtr->tagoffPtr, tagIndex));
+}
+
+static bool
+NodeTestToggleFwd(
+ const Node *nodePtr,
+ unsigned tagIndex,
+ bool testTagon)
+{
+ assert(nodePtr);
+
+ /*
+ * testTagon == true: Test whether given tag is starting a range inside this node.
+ * In this case this function assumes that this tag is not open at end of previous line
+ * (line before first line of this node).
+ */
+
+ if (testTagon) {
+ return TkTextTagSetTest(nodePtr->tagonPtr, tagIndex);
+ }
+
+ /*
+ * testTagon == false: Test whether given tag is ending a range inside this node.
+ * In this case this function assumes that this tag is open at end of previous line
+ * (line before first line of this node).
+ */
+
+ return TkTextTagSetTest(nodePtr->tagoffPtr, tagIndex)
+ || !TkTextTagSetTest(nodePtr->tagonPtr, tagIndex);
+}
+
+static bool
+NodeTestToggleBack(
+ const Node *nodePtr,
+ unsigned tagIndex,
+ bool testTagon)
+{
+ assert(nodePtr);
+
+ /*
+ * testTagon == true: Test whether given tag is starting a range inside this node.
+ * In this case this function assumes that this tag is already open at start of next line
+ * (line after last line of this node).
+ */
+
+ if (testTagon) {
+ return TkTextTagSetTest(nodePtr->tagonPtr, tagIndex)
+ && (TkTextTagSetTest(nodePtr->tagoffPtr, tagIndex)
+ || !LineTestIfToggleIsOpen(nodePtr->linePtr->prevPtr, tagIndex));
+ }
+
+ /*
+ * testTagon == false: Test whether given tag is ending a range inside this node.
+ * In this case this function assumes that this tag is not already open at start of next line
+ * (line after last line of this node).
+ */
+
+ return TkTextTagSetTest(nodePtr->tagoffPtr, tagIndex)
+ || LineTestIfToggleIsOpen(nodePtr->linePtr->prevPtr, tagIndex);
+}
+
+static void
+RecomputeLineTagInfo(
+ TkTextLine *linePtr,
+ const TkTextSegment *lastSegPtr,
+ const TkSharedText *sharedTextPtr)
+{
+ const TkTextSegment *segPtr;
+ TkTextTagSet *tagonPtr = NULL;
+ TkTextTagSet *tagoffPtr = NULL;
+
+ assert(linePtr);
+ assert(!lastSegPtr || lastSegPtr->sectionPtr->linePtr == linePtr);
+
+ /*
+ * Update the line tag information after inserting tagged characters.
+ * This function is not updating the tag information of the B-Tree.
+ */
+
+ for (segPtr = linePtr->segPtr; segPtr != lastSegPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->tagInfoPtr) {
+ tagonPtr = TagSetJoin(tagonPtr, segPtr->tagInfoPtr);
+ tagoffPtr = TagSetIntersect(tagoffPtr, segPtr->tagInfoPtr, sharedTextPtr);
+ }
+ }
+
+ if (!tagonPtr) {
+ TkTextTagSetIncrRefCount(tagonPtr = sharedTextPtr->emptyTagInfoPtr);
+ TkTextTagSetIncrRefCount(tagoffPtr = sharedTextPtr->emptyTagInfoPtr);
+ } else {
+ tagoffPtr = TagSetComplementTo(tagoffPtr, tagonPtr, sharedTextPtr);
+ }
+
+ TagSetReplace(&linePtr->tagonPtr, tagonPtr);
+ TagSetReplace(&linePtr->tagoffPtr, tagoffPtr);
+}
+
+static unsigned
+GetDisplayLines(
+ const TkTextLine *linePtr,
+ unsigned ref)
+{
+ return TkBTreeGetNumberOfDisplayLines(linePtr->pixelInfo + ref);
+}
+
+static void
+SetLineHasChanged(
+ const TkSharedText *sharedTextPtr,
+ TkTextLine *linePtr)
+{
+ if (!linePtr->logicalLine) {
+ linePtr = TkBTreeGetLogicalLine(sharedTextPtr, NULL, linePtr);
+ }
+ linePtr->changed = true;
+}
+
+/*
+ * Some helpers for segment creation and testing.
+ */
+
+static TkTextSegment *
+MakeSegment(
+ unsigned segByteSize,
+ unsigned contentSize,
+ const Tk_SegType *segType)
+{
+ TkTextSegment *segPtr;
+
+ assert(segType != &tkTextCharType);
+
+ segPtr = memset(malloc(segByteSize), 0, segByteSize);
+ segPtr->typePtr = segType;
+ segPtr->size = contentSize;
+ segPtr->refCount = 1;
+ DEBUG_ALLOC(tkTextCountNewSegment++);
+ return segPtr;
+}
+
+static TkTextSegment * MakeBranch() { return MakeSegment(SEG_SIZE(TkTextBranch), 0, &tkTextBranchType); }
+static TkTextSegment * MakeLink() { return MakeSegment(SEG_SIZE(TkTextLink), 0, &tkTextLinkType); }
+static TkTextSegment * MakeHyphen() { return MakeSegment(SEG_SIZE(TkTextHyphen), 1, &tkTextHyphenType); }
+
+static bool
+IsBranchSection(
+ const TkTextSection *sectionPtr)
+{
+ assert(sectionPtr);
+ return sectionPtr->nextPtr && sectionPtr->nextPtr->segPtr->prevPtr->typePtr == &tkTextBranchType;
+}
+
+static bool
+IsLinkSection(
+ const TkTextSection *sectionPtr)
+{
+ assert(sectionPtr);
+ return sectionPtr->segPtr->typePtr == &tkTextLinkType;
+}
+
+/*
+ * Some functions for the undo/redo mechanism.
+ */
+
+static void
+SetNodeLastPointer(
+ Node *nodePtr,
+ TkTextLine *linePtr)
+{
+ nodePtr->lastPtr = linePtr;
+ while (!nodePtr->nextPtr && (nodePtr = nodePtr->parentPtr)) {
+ nodePtr->lastPtr = linePtr;
+ }
+}
+
+static Tcl_Obj *
+MakeTagInfoObj(
+ const TkSharedText *sharedTextPtr,
+ const TkTextTagSet *tagInfoPtr)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ TkTextTag **tagLookup = sharedTextPtr->tagLookup;
+ unsigned i = TkTextTagSetFindFirst(tagInfoPtr);
+
+ for ( ; i != TK_TEXT_TAG_SET_NPOS; i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ const TkTextTag *tagPtr = tagLookup[i];
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(tagPtr->name, -1));
+ }
+
+ return objPtr;
+}
+
+static void
+UndoGetRange(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item,
+ TkTextIndex *startIndex,
+ TkTextIndex *endIndex)
+{
+ const TkTextUndoTokenRange *token = (const TkTextUndoTokenRange *) item;
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->startIndex, startIndex);
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->endIndex, endIndex);
+}
+
+/* DELETE ********************************************************************/
+
+static Tcl_Obj *
+UndoDeleteGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("delete", -1));
+ return objPtr;
+}
+
+static Tcl_Obj *
+UndoDeleteInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = UndoDeleteGetCommand(sharedTextPtr, item);
+ TkTextSegment **segments = ((const UndoTokenDelete *) item)->segments;
+ unsigned numSegments = ((const UndoTokenDelete *) item)->numSegments;
+ const TkTextSegment *segPtr;
+
+ for (segPtr = *segments++; numSegments > 0; segPtr = *segments++, --numSegments) {
+ assert(segPtr->typePtr->inspectProc);
+ Tcl_ListObjAppendElement(NULL, objPtr, segPtr->typePtr->inspectProc(sharedTextPtr, segPtr));
+ }
+
+ return objPtr;
+}
+
+static void
+UndoDeletePerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ TkTextLine *linePtr, *startLinePtr, *newLinePtr;
+ TkTextSegment *segPtr, *prevPtr, *nextPtr;
+ TkTextSegment *firstPtr, *lastPtr;
+ TkTextSegment *prevSegPtr;
+ Node *nodePtr;
+ NodePixelInfo *changeToPixelInfo;
+ BTree *treePtr = (BTree *) sharedTextPtr->tree;
+ UndoTokenDelete *undoToken = (UndoTokenDelete *) undoInfo->token;
+ TkTextSegment * const *segments = undoToken->segments;
+ TkTextTagSet *tagonPtr;
+ TkTextTagSet *tagoffPtr;
+ TkTextTagSet *additionalTagoffPtr;
+ unsigned numSegments = undoToken->numSegments - 1;
+ unsigned changeToLineCount = 0;
+ unsigned changeToLogicalLineCount = 0;
+ unsigned changeToBranchCount = 0;
+ unsigned size = 0;
+ bool reinsertFirstSegment = true;
+ unsigned i;
+
+ assert(segments);
+ assert(segments[0]);
+
+ changeToPixelInfo = treePtr->pixelInfoBuffer;
+ memset(changeToPixelInfo, 0, sizeof(changeToPixelInfo[0])*treePtr->numPixelReferences);
+ prevPtr = lastPtr = NULL;
+
+ if (undoToken->startIndex.lineIndex == -1) {
+ prevPtr = undoToken->startIndex.u.markPtr;
+ linePtr = prevPtr->sectionPtr->linePtr;
+ reinsertFirstSegment = false;
+ } else {
+ linePtr = TkBTreeFindLine(sharedTextPtr->tree, NULL, undoToken->startIndex.lineIndex);
+ }
+
+ startLinePtr = linePtr;
+ nodePtr = startLinePtr->parentPtr;
+ firstPtr = segPtr = *segments++;
+ firstPtr->protectionFlag = true;
+ prevSegPtr = NULL;
+
+ if (numSegments > 0) {
+ nextPtr = *segments++;
+ numSegments -= 1;
+ } else {
+ nextPtr = NULL;
+ }
+
+ TkTextTagSetIncrRefCount(tagonPtr = sharedTextPtr->emptyTagInfoPtr);
+ TkTextTagSetIncrRefCount(tagoffPtr = sharedTextPtr->emptyTagInfoPtr);
+ additionalTagoffPtr = NULL;
+
+ while (segPtr) {
+ if (POINTER_IS_MARKED(segPtr)) {
+ TkTextSection *sectionPtr;
+
+ UNMARK_POINTER(segPtr);
+
+ assert(segPtr->typePtr != &tkTextCharType);
+ assert(segPtr->sectionPtr);
+
+ /*
+ * This is a re-inserted segment, it will move.
+ */
+
+ sectionPtr = segPtr->sectionPtr;
+ UNMARK_POINTER(segPtr);
+ UnlinkSegment(segPtr);
+ JoinSections(sectionPtr);
+ } else {
+ size += segPtr->size;
+ }
+ lastPtr = segPtr;
+ DEBUG(segPtr->sectionPtr = NULL);
+ if (reinsertFirstSegment) {
+ ReInsertSegment(sharedTextPtr, &undoToken->startIndex, segPtr, false);
+ reinsertFirstSegment = false;
+ } else {
+ LinkSegment(linePtr, prevPtr, segPtr);
+ }
+ if (segPtr->typePtr == &tkTextCharType) {
+ assert(!segPtr->typePtr->restoreProc);
+
+ if (prevSegPtr) {
+ if ((prevSegPtr = CleanupCharSegments(sharedTextPtr, prevSegPtr))->nextPtr != segPtr) {
+ segPtr = prevSegPtr;
+ }
+ }
+
+ if (segPtr->body.chars[segPtr->size - 1] == '\n') {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr, linePtr, segPtr->nextPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ changeToLineCount += 1;
+ changeToLogicalLineCount += linePtr->logicalLine;
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ tagonPtr = TkTextTagSetJoin(tagonPtr, linePtr->tagonPtr);
+ tagoffPtr = TkTextTagSetJoin(tagoffPtr, linePtr->tagoffPtr);
+ additionalTagoffPtr = TagSetIntersect(additionalTagoffPtr,
+ linePtr->tagonPtr, sharedTextPtr);
+ linePtr = newLinePtr;
+ segPtr = NULL;
+ }
+
+ prevSegPtr = segPtr;
+ } else {
+ if (segPtr->typePtr->restoreProc) {
+ if (segPtr->typePtr == &tkTextBranchType) {
+ changeToBranchCount += 1;
+ }
+ segPtr->typePtr->restoreProc(segPtr);
+ }
+ prevSegPtr = NULL;
+ }
+ prevPtr = segPtr;
+ if ((segPtr = nextPtr)) {
+ if (numSegments > 0) {
+ nextPtr = *segments++;
+ numSegments -= 1;
+ } else {
+ nextPtr = NULL;
+ }
+ }
+ }
+
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ tagonPtr = TkTextTagSetJoin(tagonPtr, linePtr->tagonPtr);
+ tagoffPtr = TkTextTagSetJoin(tagoffPtr, linePtr->tagoffPtr);
+ additionalTagoffPtr = TagSetIntersect(additionalTagoffPtr, linePtr->tagonPtr, sharedTextPtr);
+ tagoffPtr = TagSetJoinComplementTo(tagoffPtr, additionalTagoffPtr, tagonPtr, sharedTextPtr);
+ tagoffPtr = TkTextTagSetRemove(tagoffPtr, nodePtr->tagoffPtr);
+ tagonPtr = TkTextTagSetRemove(tagonPtr, nodePtr->tagonPtr);
+ tagonPtr = TkTextTagSetRemove(tagonPtr, tagoffPtr);
+
+ /*
+ * Update the B-Tree tag information.
+ */
+
+ for (i = TkTextTagSetFindFirst(tagoffPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagoffPtr, i)) {
+ if (!TkTextTagSetTest(nodePtr->tagoffPtr, i)) {
+ AddTagToNode(nodePtr, sharedTextPtr->tagLookup[i], true);
+ }
+ }
+
+ for (i = TkTextTagSetFindFirst(tagonPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagonPtr, i)) {
+ AddTagToNode(nodePtr, sharedTextPtr->tagLookup[i], false);
+ }
+
+ TkTextTagSetDecrRefCount(tagonPtr);
+ TkTextTagSetDecrRefCount(tagoffPtr);
+ TkTextTagSetDecrRefCount(additionalTagoffPtr);
+
+ /*
+ * Rebuild sections, and increase the epoch.
+ */
+
+ RebuildSections(sharedTextPtr, linePtr, true);
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+
+ /*
+ * Cleanup char segments.
+ */
+
+ CleanupSplitPoint(firstPtr, sharedTextPtr);
+ CleanupSplitPoint(lastPtr, sharedTextPtr);
+
+ /*
+ * Prevent that the destroy function will delete these segments.
+ * This also makes the token reusable.
+ */
+
+ free(undoToken->segments);
+ undoToken->segments = NULL;
+ undoToken->numSegments = 0;
+
+ /*
+ * Update the redo information.
+ */
+
+ if (redoInfo) {
+ undoToken->undoType = &redoTokenDeleteType;
+ redoInfo->token = (TkTextUndoToken *) undoToken;
+ redoInfo->byteSize = 0;
+ }
+
+ /*
+ * Increment the line and pixel counts in all the parent nodes of the
+ * insertion point, then rebalance the tree if necessary.
+ */
+
+ SubtractPixelCount2(treePtr, nodePtr, -changeToLineCount,
+ -changeToLogicalLineCount, -changeToBranchCount, -size, changeToPixelInfo);
+ linePtr->parentPtr->numChildren += changeToLineCount;
+
+ if (nodePtr->numChildren > MAX_CHILDREN) {
+ Rebalance(treePtr, nodePtr);
+ }
+
+ /*
+ * This line now needs to have its height recalculated. This has to be done after Rebalance.
+ */
+
+ TkTextInvalidateLineMetrics(sharedTextPtr, NULL,
+ startLinePtr, changeToLineCount, TK_TEXT_INVALIDATE_INSERT);
+
+ TK_BTREE_DEBUG(TkBTreeCheck((TkTextBTree) treePtr));
+}
+
+static void
+UndoDeleteDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *token,
+ bool reused)
+{
+ TkTextSegment **segments = ((UndoTokenDelete *) token)->segments;
+ unsigned numSegments = ((UndoTokenDelete *) token)->numSegments;
+
+ assert(!reused);
+
+ if (numSegments > 0) {
+ TkTextSegment *segPtr;
+
+ for (segPtr = *segments++; numSegments > 0; segPtr = *segments++, --numSegments) {
+ UNMARK_POINTER(segPtr);
+ assert(segPtr->typePtr);
+ assert(segPtr->typePtr->deleteProc);
+ segPtr->typePtr->deleteProc(sharedTextPtr->tree, segPtr, DELETE_BRANCHES | DELETE_MARKS);
+ }
+
+ free(((UndoTokenDelete *) token)->segments);
+ }
+}
+
+static Tcl_Obj *
+RedoDeleteInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = UndoDeleteGetCommand(sharedTextPtr, item);
+
+#if 0 /* not possible to inspect the range, because this range may be deleted */
+ TkTextIndex startIndex, endIndex;
+ char buf[TK_POS_CHARS];
+
+ UndoGetRange(sharedTextPtr, item, &startIndex, &endIndex);
+ TkTextIndexPrint(sharedTextPtr, NULL, &startIndex, buf);
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(buf, -1));
+ TkTextIndexPrint(sharedTextPtr, NULL, &endIndex, buf);
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(buf, -1));
+#endif
+
+ return objPtr;
+}
+
+static void
+RedoDeletePerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ const UndoTokenDelete *token = (const UndoTokenDelete *) undoInfo->token;
+
+ if (token->startIndex.lineIndex == -1 && token->endIndex.lineIndex == -1) {
+ TkTextSegment *segPtr1 = token->startIndex.u.markPtr;
+ TkTextSegment *segPtr2 = token->endIndex.u.markPtr;
+ int flags = token->inclusive ? DELETE_INCLUSIVE : 0;
+
+ DeleteRange(sharedTextPtr, segPtr1, segPtr2, flags, redoInfo);
+
+ segPtr1->protectionFlag = true;
+ segPtr2->protectionFlag = true;
+ CleanupSplitPoint(segPtr1, sharedTextPtr);
+ CleanupSplitPoint(segPtr2, sharedTextPtr);
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+ } else {
+ TkTextIndex index1, index2;
+
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->startIndex, &index1);
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->endIndex, &index2);
+ DeleteIndexRange(sharedTextPtr, &index1, &index2, 0, (UndoTokenInsert *) token, redoInfo);
+ }
+}
+
+/* INSERT ********************************************************************/
+
+static Tcl_Obj *
+UndoInsertGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("insert", -1));
+ return objPtr;
+}
+
+static void
+UndoInsertPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ struct UndoTokenInsert *token = (UndoTokenInsert *) undoInfo->token;
+ TkTextIndex index1, index2;
+
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->startIndex, &index1);
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->endIndex, &index2);
+ DeleteIndexRange(sharedTextPtr, &index1, &index2, 0, token, redoInfo);
+ if (redoInfo && redoInfo->token) {
+ redoInfo->token->undoType = &redoTokenInsertType;
+ }
+}
+
+static Tcl_Obj *
+RedoInsertInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ TkTextSegment **segments = ((const UndoTokenDelete *) item)->segments;
+ unsigned numSegments = ((const UndoTokenDelete *) item)->numSegments;
+ const TkTextSegment *segPtr;
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("insert", -1));
+
+ for (segPtr = *segments++; numSegments > 0; segPtr = *segments++, --numSegments) {
+ UNMARK_POINTER(segPtr);
+ assert(segPtr->typePtr->inspectProc);
+ Tcl_ListObjAppendElement(NULL, objPtr, segPtr->typePtr->inspectProc(sharedTextPtr, segPtr));
+ }
+
+ return objPtr;
+}
+
+/* TAG ADD/REMOVE ************************************************************/
+
+static Tcl_Obj *
+UndoTagGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenTagChange *token = (const UndoTokenTagChange *) item;
+ bool isRedo = (item->undoType == &redoTokenTagType);
+ bool add = (isRedo == POINTER_IS_MARKED(token->tagPtr));
+ Tcl_Obj *objPtr = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("tag", -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(add ? "add" : "remove", -1));
+ return objPtr;
+}
+
+Tcl_Obj *
+TkBTreeUndoTagInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenTagChange *token = (const UndoTokenTagChange *) item;
+ Tcl_Obj *objPtr = UndoTagGetCommand(sharedTextPtr, item);
+ TkTextTag *tagPtr = token->tagPtr;
+
+ UNMARK_POINTER(tagPtr);
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(tagPtr->name, -1));
+ return objPtr;
+}
+
+static void
+UndoTagPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ UndoTokenTagChange *token = (UndoTokenTagChange *) undoInfo->token;
+ TkTextTag *tagPtr = token->tagPtr;
+ bool remove = POINTER_IS_MARKED(tagPtr);
+ bool add = (isRedo != remove);
+ TkTextIndex index1, index2;
+
+ UNMARK_POINTER(tagPtr);
+ TkTextEnableTag(sharedTextPtr, tagPtr);
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->startIndex, &index1);
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->endIndex, &index2);
+
+ if (token->lengths) {
+ TkTextIndex nextIndex = index1;
+ const int32_t *len;
+
+ for (len = token->lengths; *len; ++len) {
+ int length = *len;
+
+ TkTextIndexForwBytes(NULL, &nextIndex, ABS(length), &nextIndex);
+
+ if (length > 0) {
+ TkBTreeTag(sharedTextPtr, NULL, &index1, &nextIndex, tagPtr, add, NULL,
+ TkTextTagChangedUndoRedo);
+ }
+
+ index1 = nextIndex;
+ }
+
+ TkBTreeTag(sharedTextPtr, NULL, &index1, &index2, tagPtr, add, NULL, TkTextTagChangedUndoRedo);
+ } else {
+ TkBTreeTag(sharedTextPtr, NULL, &index1, &index2, tagPtr, add, NULL, TkTextTagChangedUndoRedo);
+ }
+
+ if (redoInfo) {
+ redoInfo->token = undoInfo->token;
+ redoInfo->token->undoType = isRedo? &undoTokenTagType : &redoTokenTagType;
+ }
+}
+
+static void
+UndoTagDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *item,
+ bool reused)
+{
+ if (!reused) {
+ UndoTokenTagChange *token = (UndoTokenTagChange *) item;
+
+ UNMARK_POINTER(token->tagPtr);
+ TkTextReleaseTag(sharedTextPtr, token->tagPtr, NULL);
+ free(token->lengths);
+ token->lengths = NULL;
+ }
+}
+
+/* TAG CLEAR *****************************************************************/
+
+static Tcl_Obj *
+UndoClearTagsGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("tag", -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("clear", -1));
+ return objPtr;
+}
+
+static Tcl_Obj *
+UndoClearTagsInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenTagClear *token = (const UndoTokenTagClear *) item;
+ Tcl_Obj *objPtr = UndoClearTagsGetCommand(sharedTextPtr, item);
+ Tcl_Obj *objPtr2 = Tcl_NewObj();
+ unsigned i;
+
+ for (i = 0; i < token->changeListSize; ++i) {
+ const UndoTagChange *change = token->changeList + i;
+
+ Tcl_ListObjAppendElement(NULL, objPtr2, MakeTagInfoObj(sharedTextPtr, change->tagInfoPtr));
+ Tcl_ListObjAppendElement(NULL, objPtr2, Tcl_NewIntObj(change->skip));
+ Tcl_ListObjAppendElement(NULL, objPtr2, Tcl_NewIntObj(change->size));
+ }
+
+ Tcl_ListObjAppendElement(NULL, objPtr, objPtr2);
+ return objPtr;
+}
+
+static void
+UndoClearTagsPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ UndoTokenTagClear *token = (UndoTokenTagClear *) undoInfo->token;
+ const UndoTagChange *entry = token->changeList;
+ TkTextSegment *firstSegPtr = NULL, *lastSegPtr = NULL;
+ TkTextIndex startIndex, endIndex;
+ unsigned n = token->changeListSize;
+ bool anyChanges = false;
+ bool affectsDisplayGeometry = false;
+ bool updateElideInfo = false;
+ TkTextSegment *segPtr;
+ TkTextLine *linePtr;
+ Node *nodePtr;
+ int offs = 0;
+ unsigned i;
+
+ assert(token->changeListSize > 0);
+
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->startIndex, &startIndex);
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->endIndex, &endIndex);
+
+ linePtr = TkTextIndexGetLine(&startIndex);
+ segPtr = linePtr->segPtr;
+ nodePtr = linePtr->parentPtr;
+
+ for (i = 0; i < n; ++i, ++entry) {
+ unsigned skip = entry->skip;
+ unsigned size = entry->size;
+
+ while (size > 0) {
+ while (linePtr->size - offs <= skip) {
+ assert(linePtr->nextPtr);
+ skip -= linePtr->size - offs;
+ if (anyChanges) {
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ if (nodePtr != linePtr->nextPtr->parentPtr) {
+ UpdateNodeTags(sharedTextPtr, nodePtr);
+ nodePtr = linePtr->nextPtr->parentPtr;
+ }
+ anyChanges = false;
+ }
+ linePtr = linePtr->nextPtr;
+ segPtr = linePtr->segPtr;
+ offs = 0;
+ }
+ if (segPtr == segPtr->sectionPtr->segPtr) {
+ TkTextSection *sectionPtr = segPtr->sectionPtr;
+
+ while (sectionPtr->size <= skip) {
+ skip -= sectionPtr->size;
+ offs += sectionPtr->size;
+ if (!(sectionPtr = sectionPtr->nextPtr)) {
+ if (anyChanges) {
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ if (nodePtr != linePtr->nextPtr->parentPtr) {
+ UpdateNodeTags(sharedTextPtr, nodePtr);
+ nodePtr = linePtr->nextPtr->parentPtr;
+ }
+ anyChanges = false;
+ }
+ linePtr = linePtr->nextPtr;
+ assert(linePtr);
+ sectionPtr = linePtr->segPtr->sectionPtr;
+ offs = 0;
+ }
+ segPtr = sectionPtr->segPtr;
+ }
+ }
+ while (segPtr->size <= skip) {
+ skip -= segPtr->size;
+ offs += segPtr->size;
+ if (!(segPtr = segPtr->nextPtr)) {
+ if (anyChanges) {
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ if (nodePtr != linePtr->nextPtr->parentPtr) {
+ UpdateNodeTags(sharedTextPtr, nodePtr);
+ nodePtr = linePtr->nextPtr->parentPtr;
+ }
+ anyChanges = false;
+ }
+ linePtr = linePtr->nextPtr;
+ assert(linePtr);
+ segPtr = linePtr->segPtr;
+ offs = 0;
+ }
+ }
+ while (size > 0 && segPtr) {
+ while (segPtr->size == 0) {
+ segPtr = segPtr->nextPtr;
+ }
+ if (size != segPtr->size) {
+ if (skip > 0) {
+ assert(skip < segPtr->size);
+ offs += skip;
+ segPtr = SplitCharSegment(segPtr, skip)->nextPtr;
+ }
+ if (size < segPtr->size) {
+ segPtr = SplitCharSegment(segPtr, size);
+ }
+ }
+ assert(segPtr->size <= size);
+ size -= segPtr->size;
+ offs += segPtr->size;
+ if (TkTextTagSetIntersectsBits(entry->tagInfoPtr, sharedTextPtr->affectGeometryTags)) {
+ affectsDisplayGeometry = true;
+ }
+ if (TkTextTagSetIntersectsBits(entry->tagInfoPtr, sharedTextPtr->elisionTags)) {
+ updateElideInfo = true;
+ }
+ TkTextTagSetDecrRefCount(segPtr->tagInfoPtr);
+ TkTextTagSetIncrRefCount(segPtr->tagInfoPtr = entry->tagInfoPtr);
+ if (!firstSegPtr) { firstSegPtr = segPtr; }
+ lastSegPtr = segPtr;
+ segPtr = segPtr->nextPtr;
+ anyChanges = true;
+ skip = 0;
+ }
+ }
+ }
+
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ UpdateNodeTags(sharedTextPtr, linePtr->parentPtr);
+ if (updateElideInfo) {
+ UpdateElideInfo(sharedTextPtr, NULL, firstSegPtr, lastSegPtr, ELISION_HAS_BEEN_CHANGED);
+ }
+ firstSegPtr->protectionFlag = true;
+ lastSegPtr->protectionFlag = true;
+ CleanupSplitPoint(firstSegPtr, sharedTextPtr);
+ CleanupSplitPoint(lastSegPtr, sharedTextPtr);
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+ TkTextRedrawTag(sharedTextPtr, NULL, &startIndex, &endIndex, NULL, affectsDisplayGeometry);
+
+ if (redoInfo) {
+ RedoTokenClearTags *redoToken = malloc(sizeof(RedoTokenClearTags));
+ redoToken->undoType = &redoTokenClearTagsType;
+ redoToken->startIndex = token->startIndex;
+ redoToken->endIndex = token->endIndex;
+ redoInfo->token = (TkTextUndoToken *) redoToken;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ }
+
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+}
+
+static void
+UndoClearTagsDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *token,
+ bool reused)
+{
+ UndoTokenTagClear *myToken = (UndoTokenTagClear *) token;
+ UndoTagChange *changeList = myToken->changeList;
+ unsigned i, n = myToken->changeListSize;
+
+ assert(!reused);
+
+ for (i = 0; i < n; ++i) {
+ TkTextTagSetDecrRefCount(changeList[i].tagInfoPtr);
+ }
+
+ free(changeList);
+}
+
+static void
+RedoClearTagsPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ RedoTokenClearTags *token = (RedoTokenClearTags *) undoInfo->token;
+ TkTextIndex index1, index2;
+
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->startIndex, &index1);
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->endIndex, &index2);
+ TkBTreeClearTags(sharedTextPtr, NULL, &index1, &index2, redoInfo, true, TkTextTagChangedUndoRedo);
+}
/*
*----------------------------------------------------------------------
@@ -259,70 +1918,58 @@ const Tk_SegType tkTextToggleOffType = {
TkTextBTree
TkBTreeCreate(
- TkSharedText *sharedTextPtr)
+ TkSharedText *sharedTextPtr,
+ unsigned epoch)
{
- register BTree *treePtr;
- register Node *rootPtr;
- register TkTextLine *linePtr, *linePtr2;
- register TkTextSegment *segPtr;
+ BTree *treePtr;
+ Node *rootPtr;
+ TkTextLine *linePtr, *linePtr2;
+ TkTextSegment *segPtr;
/*
- * The tree will initially have two empty lines. The second line isn't
+ * The tree will initially have two empty lines. The first line contains
+ * the start marker, this marker will never move. The second line isn't
* actually part of the tree's contents, but its presence makes several
- * operations easier. The tree will have one node, which is also the root
- * of the tree.
- */
-
- rootPtr = ckalloc(sizeof(Node));
- linePtr = ckalloc(sizeof(TkTextLine));
- linePtr2 = ckalloc(sizeof(TkTextLine));
-
- rootPtr->parentPtr = NULL;
- rootPtr->nextPtr = NULL;
- rootPtr->summaryPtr = NULL;
- rootPtr->level = 0;
- rootPtr->children.linePtr = linePtr;
- rootPtr->numChildren = 2;
- rootPtr->numLines = 2;
-
- /*
+ * operations easier. The second line contains the end marker. The tree
+ * will have one node, which is also the root of the tree.
+ *
* The tree currently has no registered clients, so all pixel count
* pointers are simply NULL.
*/
- rootPtr->numPixels = NULL;
- linePtr->pixels = NULL;
- linePtr2->pixels = NULL;
-
- linePtr->parentPtr = rootPtr;
- linePtr->nextPtr = linePtr2;
- segPtr = ckalloc(CSEG_SIZE(1));
- linePtr->segPtr = segPtr;
- segPtr->typePtr = &tkTextCharType;
- segPtr->nextPtr = NULL;
- segPtr->size = 1;
- segPtr->body.chars[0] = '\n';
- segPtr->body.chars[1] = 0;
-
- linePtr2->parentPtr = rootPtr;
- linePtr2->nextPtr = NULL;
- segPtr = ckalloc(CSEG_SIZE(1));
- linePtr2->segPtr = segPtr;
- segPtr->typePtr = &tkTextCharType;
- segPtr->nextPtr = NULL;
- segPtr->size = 1;
- segPtr->body.chars[0] = '\n';
- segPtr->body.chars[1] = 0;
-
- treePtr = ckalloc(sizeof(BTree));
- treePtr->sharedTextPtr = sharedTextPtr;
+ rootPtr = memset(malloc(sizeof(Node)), 0, sizeof(Node));
+ DEBUG_ALLOC(tkTextCountNewNode++);
+
+ treePtr = memset(malloc(sizeof(BTree)), 0, sizeof(BTree));
treePtr->rootPtr = rootPtr;
- treePtr->clients = 0;
- treePtr->stateEpoch = 0;
- treePtr->pixelReferences = 0;
- treePtr->startEndCount = 0;
- treePtr->startEnd = NULL;
- treePtr->startEndRef = NULL;
+ treePtr->sharedTextPtr = sharedTextPtr;
+ treePtr->stateEpoch = epoch;
+ sharedTextPtr->tree = (TkTextBTree) treePtr;
+
+ assert(!sharedTextPtr->startMarker->nextPtr);
+ linePtr = InsertNewLine(sharedTextPtr, rootPtr, NULL, sharedTextPtr->startMarker);
+ segPtr = MakeCharSeg(NULL, sharedTextPtr->emptyTagInfoPtr, 1, "\n", 1);
+ LinkSegment(linePtr, sharedTextPtr->startMarker, segPtr);
+
+ assert(!sharedTextPtr->endMarker->nextPtr);
+ linePtr2 = InsertNewLine(sharedTextPtr, rootPtr, linePtr, sharedTextPtr->endMarker);
+ segPtr = MakeCharSeg(NULL, sharedTextPtr->emptyTagInfoPtr, 1, "\n", 1);
+ LinkSegment(linePtr2, sharedTextPtr->endMarker, segPtr);
+
+ rootPtr->linePtr = linePtr;
+ rootPtr->lastPtr = linePtr2;
+ rootPtr->size = 2;
+ rootPtr->numLines = 2;
+ rootPtr->numLogicalLines = 2;
+ rootPtr->numChildren = 2;
+ TkTextTagSetIncrRefCount(rootPtr->tagonPtr = sharedTextPtr->emptyTagInfoPtr);
+ TkTextTagSetIncrRefCount(rootPtr->tagoffPtr = sharedTextPtr->emptyTagInfoPtr);
+
+ if (tkBTreeDebug) {
+ sharedTextPtr->refCount += 1;
+ TkBTreeCheck((TkTextBTree) treePtr);
+ sharedTextPtr->refCount -= 1;
+ }
return (TkTextBTree) treePtr;
}
@@ -330,183 +1977,186 @@ TkBTreeCreate(
/*
*----------------------------------------------------------------------
*
- * TkBTreeAddClient --
+ * GetStartLine --
*
- * This function is called to provide a client with access to a given
- * B-tree. If the client wishes to make use of the B-tree's pixel height
- * storage, caching and calculation mechanisms, then a non-negative
- * 'defaultHeight' must be provided. In this case the return value is a
- * pixel tree reference which must be provided in all of the B-tree API
- * which refers to or modifies pixel heights:
+ * This function returns the first line for given text widget, if
+ * NULL it returns the first line of shared resource.
*
- * TkBTreeAdjustPixelHeight,
- * TkBTreeFindPixelLine,
- * TkBTreeNumPixels,
- * TkBTreePixelsTo,
- * (and two private functions AdjustPixelClient, RemovePixelClient).
+ * Results:
+ * The first line in this widget (or shared resource).
*
- * If this is not provided, then the above functions must never be called
- * for this client.
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextLine *
+GetStartLine(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr)
+{
+ return textPtr ? TkBTreeGetStartLine(textPtr) : sharedTextPtr->startMarker->sectionPtr->linePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetLastLine --
+ *
+ * This function returns the last line for given text widget.
*
* Results:
- * The return value is the pixelReference used by the B-tree to refer to
- * pixel counts for the new client. It should be stored by the caller. If
- * defaultHeight was negative, then the return value will be -1.
+ * The last line in this widget.
*
* Side effects:
- * Memory may be allocated and initialized.
+ * None.
*
*----------------------------------------------------------------------
*/
-void
-TkBTreeAddClient(
- TkTextBTree tree, /* B-tree to add a client to. */
- TkText *textPtr, /* Client to add. */
- int defaultHeight) /* Default line height for the new client, or
- * -1 if no pixel heights are to be kept. */
+static TkTextLine *
+GetLastLine(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr)
{
- register BTree *treePtr = (BTree *) tree;
+ TkTextLine *endLine;
- if (treePtr == NULL) {
- Tcl_Panic("NULL treePtr in TkBTreeAddClient");
- }
+ assert(sharedTextPtr || textPtr);
- if (textPtr->start != NULL || textPtr->end != NULL) {
- AdjustStartEndRefs(treePtr, textPtr, TEXT_ADD_REFS);
+ if (!textPtr) {
+ return sharedTextPtr->endMarker->sectionPtr->linePtr;
}
- if (defaultHeight >= 0) {
- TkTextLine *end;
- int counting = (textPtr->start == NULL ? 1 : 0);
- int useReference = treePtr->pixelReferences;
-
- /*
- * We must set the 'end' value in AdjustPixelClient so that the last
- * dummy line in the B-tree doesn't contain a pixel height.
- */
-
- end = textPtr->end;
- if (end == NULL) {
- end = TkBTreeFindLine(tree, NULL, TkBTreeNumLines(tree, NULL));
- }
- AdjustPixelClient(treePtr, defaultHeight, treePtr->rootPtr,
- textPtr->start, end, useReference, useReference+1, &counting);
-
- textPtr->pixelReference = useReference;
- treePtr->pixelReferences++;
- } else {
- textPtr->pixelReference = -1;
- }
- treePtr->clients++;
+ endLine = textPtr->endMarker->sectionPtr->linePtr;
+ return endLine->nextPtr ? endLine->nextPtr : endLine;
}
/*
*----------------------------------------------------------------------
*
- * TkBTreeClientRangeChanged --
+ * TkBTreeNumLines --
*
- * Called when the -startline or -endline options of a text widget client
- * of the B-tree have changed.
+ * This function returns a count of the number of lines of text
+ * present in a given B-tree.
*
* Results:
- * None.
+ * The return value is a count of the number of usable lines in tree
+ * (i.e. it doesn't include the dummy line that is just used to mark the
+ * end of the tree).
*
* Side effects:
- * Lots of processing of the B-tree is done, with potential for memory to
- * be allocated and initialized for the pixel heights of the widget.
+ * None.
*
*----------------------------------------------------------------------
*/
-void
-TkBTreeClientRangeChanged(
- TkText *textPtr, /* Client whose start, end have changed. */
- int defaultHeight) /* Default line height for the new client, or
- * -1 if no pixel heights are to be kept. */
+int
+TkBTreeNumLines(
+ TkTextBTree tree, /* Information about tree. */
+ const TkText *textPtr) /* Relative to this client of the B-tree. */
{
- TkTextLine *end;
- BTree *treePtr = (BTree *) textPtr->sharedTextPtr->tree;
-
- int counting = (textPtr->start == NULL ? 1 : 0);
- int useReference = textPtr->pixelReference;
-
- AdjustStartEndRefs(treePtr, textPtr, TEXT_ADD_REFS | TEXT_REMOVE_REFS);
-
- /*
- * We must set the 'end' value in AdjustPixelClient so that the last dummy
- * line in the B-tree doesn't contain a pixel height.
- */
+ int count;
- end = textPtr->end;
- if (end == NULL) {
- end = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- NULL, TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL));
+ if (textPtr) {
+ count = TkBTreeLinesTo(tree, NULL, TkBTreeGetLastLine(textPtr), NULL);
+ count -= TkBTreeLinesTo(tree, NULL, TkBTreeGetStartLine(textPtr), NULL);
+ } else {
+ count = TkBTreeGetRoot(tree)->numLines - 1;
}
- AdjustPixelClient(treePtr, defaultHeight, treePtr->rootPtr,
- textPtr->start, end, useReference, treePtr->pixelReferences,
- &counting);
+
+ return count;
}
/*
*----------------------------------------------------------------------
*
- * TkBTreeDestroy --
+ * TkBTreeAddClient --
*
- * Delete a B-tree, recycling all of the storage it contains.
+ * This function is called to provide a client with access to a given
+ * B-tree. If the client wishes to make use of the B-tree's pixel height
+ * storage, caching and calculation mechanisms, then a non-negative
+ * 'defaultHeight' must be provided. In this case the return value is a
+ * pixel tree reference which must be provided in all of the B-tree API
+ * which refers to or modifies pixel heights:
+ *
+ * TkBTreeAdjustPixelHeight,
+ * TkBTreeFindPixelLine,
+ * TkBTreeNumPixels,
+ * TkBTreePixelsTo,
+ * (and two private functions AdjustPixelClient, RemovePixelClient).
+ *
+ * If this is not provided, then the above functions must never be called
+ * for this client.
*
* Results:
- * The tree is deleted, so 'tree' should never again be used.
+ * None.
*
* Side effects:
- * Memory is freed.
+ * Memory may be allocated and initialized.
*
*----------------------------------------------------------------------
*/
void
-TkBTreeDestroy(
- TkTextBTree tree) /* Tree to clean up. */
+TkBTreeAddClient(
+ TkTextBTree tree, /* B-tree to add a client to. */
+ TkText *textPtr, /* Client to add. */
+ int defaultHeight) /* Default line height for the new client, or
+ * -1 if no pixel heights are to be kept. */
{
BTree *treePtr = (BTree *) tree;
- /*
- * There's no need to loop over each client of the tree, calling
- * 'TkBTreeRemoveClient', since the 'DestroyNode' will clean everything up
- * itself.
- */
+ assert(treePtr);
+
+ if (defaultHeight >= 0) {
+ unsigned useReference = treePtr->numPixelReferences;
- DestroyNode(treePtr->rootPtr);
- if (treePtr->startEnd != NULL) {
- ckfree(treePtr->startEnd);
- ckfree(treePtr->startEndRef);
+ AdjustPixelClient(treePtr, defaultHeight, treePtr->rootPtr, TkBTreeGetStartLine(textPtr),
+ TkBTreeGetLastLine(textPtr), useReference, useReference + 1, NULL);
+
+ textPtr->pixelReference = useReference;
+ treePtr->numPixelReferences += 1;
+ treePtr->pixelInfoBuffer = realloc(treePtr->pixelInfoBuffer,
+ sizeof(treePtr->pixelInfoBuffer[0])*treePtr->numPixelReferences);
+ } else {
+ textPtr->pixelReference = -1;
}
- ckfree(treePtr);
+
+ treePtr->clients += 1;
}
/*
*----------------------------------------------------------------------
*
- * TkBTreeEpoch --
+ * TkBTreeClientRangeChanged --
*
- * Return the epoch for the B-tree. This number is incremented any time
- * anything changes in the tree.
+ * Called when the -startindex or -endindex options of a text widget client
+ * of the B-tree have changed.
*
* Results:
- * The epoch number.
+ * None.
*
* Side effects:
- * None.
+ * Lots of processing of the B-tree is done, with potential for memory to
+ * be allocated and initialized for the pixel heights of the widget.
*
*----------------------------------------------------------------------
*/
-int
-TkBTreeEpoch(
- TkTextBTree tree) /* Tree to get epoch for. */
+void
+TkBTreeClientRangeChanged(
+ TkText *textPtr, /* Client whose start, end have changed. */
+ unsigned defaultHeight) /* Default line height for the new client, or
+ * zero if no pixel heights are to be kept. */
{
- BTree *treePtr = (BTree *) tree;
- return treePtr->stateEpoch;
+ BTree *treePtr = (BTree *) textPtr->sharedTextPtr->tree;
+ TkTextLine *startLine = TkBTreeGetStartLine(textPtr);
+ TkTextLine *endLine = TkBTreeGetLastLine(textPtr);
+
+ AdjustPixelClient(treePtr, defaultHeight, treePtr->rootPtr, startLine,
+ endLine, textPtr->pixelReference, treePtr->numPixelReferences, NULL);
+
}
/*
@@ -542,31 +2192,32 @@ TkBTreeRemoveClient(
* The last reference to the tree.
*/
- DestroyNode(treePtr->rootPtr);
- ckfree(treePtr);
+ DestroyNode(tree, treePtr->rootPtr);
+ free(treePtr);
return;
- } else if (pixelReference == -1) {
+ }
+
+ if (pixelReference == -1) {
/*
* A client which doesn't care about pixels.
*/
- treePtr->clients--;
+ treePtr->clients -= 1;
} else {
/*
* Clean up pixel data for the given reference.
*/
- if (pixelReference == (treePtr->pixelReferences-1)) {
+ if (pixelReference == (treePtr->numPixelReferences - 1)) {
/*
- * The widget we're removing has the last index, so deletion is
- * easier.
+ * The widget we're removing has the last index, so deletion is easier.
*/
- RemovePixelClient(treePtr, treePtr->rootPtr, -1);
+ RemovePixelClient(treePtr, treePtr->rootPtr, pixelReference, -1);
} else {
TkText *adjustPtr;
- RemovePixelClient(treePtr, treePtr->rootPtr, pixelReference);
+ RemovePixelClient(treePtr, treePtr->rootPtr, pixelReference, pixelReference);
/*
* Now we need to adjust the 'pixelReference' of the peer widget
@@ -574,97 +2225,20 @@ TkBTreeRemoveClient(
*/
adjustPtr = treePtr->sharedTextPtr->peers;
- while (adjustPtr != NULL) {
- if (adjustPtr->pixelReference == treePtr->pixelReferences-1) {
+ while (adjustPtr) {
+ if (adjustPtr->pixelReference == treePtr->numPixelReferences - 1) {
adjustPtr->pixelReference = pixelReference;
break;
}
adjustPtr = adjustPtr->next;
}
- if (adjustPtr == NULL) {
- Tcl_Panic("Couldn't find text widget with correct reference");
- }
- }
- treePtr->pixelReferences--;
- treePtr->clients--;
- }
-
- if (textPtr->start != NULL || textPtr->end != NULL) {
- AdjustStartEndRefs(treePtr, textPtr, TEXT_REMOVE_REFS);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * AdjustStartEndRefs --
- *
- * Modify B-tree's cache of start, end lines for the given text widget.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The number of cached items may change (treePtr->startEndCount).
- *
- *----------------------------------------------------------------------
- */
-
-static void
-AdjustStartEndRefs(
- BTree *treePtr, /* The entire B-tree. */
- TkText *textPtr, /* The text widget for which we want to adjust
- * it's start and end cache. */
- int action) /* Action to perform. */
-{
- if (action & TEXT_REMOVE_REFS) {
- int i = 0;
- int count = 0;
-
- while (i < treePtr->startEndCount) {
- if (i != count) {
- treePtr->startEnd[count] = treePtr->startEnd[i];
- treePtr->startEndRef[count] = treePtr->startEndRef[i];
- }
- if (treePtr->startEndRef[i] != textPtr) {
- count++;
- }
- i++;
- }
- treePtr->startEndCount = count;
- treePtr->startEnd = ckrealloc(treePtr->startEnd,
- sizeof(TkTextLine *) * count);
- treePtr->startEndRef = ckrealloc(treePtr->startEndRef,
- sizeof(TkText *) * count);
- }
- if ((action & TEXT_ADD_REFS)
- && (textPtr->start != NULL || textPtr->end != NULL)) {
- int count;
-
- if (textPtr->start != NULL) {
- treePtr->startEndCount++;
- }
- if (textPtr->end != NULL) {
- treePtr->startEndCount++;
+ assert(adjustPtr);
}
- count = treePtr->startEndCount;
-
- treePtr->startEnd = ckrealloc(treePtr->startEnd,
- sizeof(TkTextLine *) * count);
- treePtr->startEndRef = ckrealloc(treePtr->startEndRef,
- sizeof(TkText *) * count);
-
- if (textPtr->start != NULL) {
- count--;
- treePtr->startEnd[count] = textPtr->start;
- treePtr->startEndRef[count] = textPtr;
- }
- if (textPtr->end != NULL) {
- count--;
- treePtr->startEnd[count] = textPtr->end;
- treePtr->startEndRef[count] = textPtr;
- }
+ treePtr->numPixelReferences -= 1;
+ treePtr->clients -= 1;
+ treePtr->pixelInfoBuffer = realloc(treePtr->pixelInfoBuffer,
+ sizeof(treePtr->pixelInfoBuffer[0])*treePtr->numPixelReferences);
}
}
@@ -677,7 +2251,7 @@ AdjustStartEndRefs(
* of a new peer widget based on this B-tree, or for the modification of
* the start, end lines of an existing peer widget.
*
- * Immediately _after_ calling this, treePtr->pixelReferences and
+ * Immediately _after_ calling this, treePtr->numPixelReferences and
* treePtr->clients should be adjusted if needed (i.e. if this is a new
* peer).
*
@@ -690,23 +2264,23 @@ AdjustStartEndRefs(
*----------------------------------------------------------------------
*/
-static int
+static unsigned
AdjustPixelClient(
BTree *treePtr, /* Pointer to tree. */
- int defaultHeight, /* Default pixel line height, which can be
- * zero. */
+ unsigned defaultHeight, /* Default pixel line height, which can be zero. */
Node *nodePtr, /* Adjust from this node downwards. */
- TkTextLine *start, /* First line for this pixel client. */
- TkTextLine *end, /* Last line for this pixel client. */
- int useReference, /* pixel reference for the client we are
- * adding or changing. */
- int newPixelReferences, /* New number of pixel references to this
- * B-tree. */
- int *counting) /* References an integer which is zero if
- * we're outside the relevant range for this
- * client, and 1 if we're inside. */
-{
- int pixelCount = 0;
+ TkTextLine *startLine, /* First line for this pixel client. */
+ TkTextLine *endLine, /* Last line for this pixel client. */
+ unsigned useReference, /* Pixel reference for the client we are adding or changing. */
+ unsigned newPixelReferences,/* New number of pixel references to this B-tree. */
+ unsigned *numDispLinesPtr) /* Number of display lines in this sub-tree, can be NULL. */
+{
+ unsigned pixelCount = 0;
+ unsigned numDispLines = 0;
+
+ assert(startLine);
+ assert(endLine);
+ assert(!nodePtr->parentPtr == !numDispLinesPtr);
/*
* Traverse entire tree down from nodePtr, reallocating pixel structures
@@ -716,27 +2290,27 @@ AdjustPixelClient(
* contains something sensible).
*/
- if (nodePtr->level != 0) {
- Node *loopPtr = nodePtr->children.nodePtr;
+ if (nodePtr->level > 0) {
+ Node *loopPtr = nodePtr->childPtr;
- while (loopPtr != NULL) {
+ while (loopPtr) {
pixelCount += AdjustPixelClient(treePtr, defaultHeight, loopPtr,
- start, end, useReference, newPixelReferences, counting);
+ startLine, endLine, useReference, newPixelReferences, &numDispLines);
loopPtr = loopPtr->nextPtr;
}
} else {
- register TkTextLine *linePtr = nodePtr->children.linePtr;
-
- while (linePtr != NULL) {
- if (!*counting && (linePtr == start)) {
- *counting = 1;
- }
- if (*counting && (linePtr == end)) {
- *counting = 0;
- }
- if (newPixelReferences != treePtr->pixelReferences) {
- linePtr->pixels = ckrealloc(linePtr->pixels,
- sizeof(int) * 2 * newPixelReferences);
+ TkTextLine *linePtr = nodePtr->linePtr;
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+ unsigned height = 0;
+ unsigned epoch = 1;
+
+ for ( ; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ if (linePtr == startLine) {
+ height = defaultHeight;
+ epoch = 0;
+ } else if (linePtr == endLine) {
+ height = 0;
+ epoch = 1;
}
/*
@@ -744,18 +2318,33 @@ AdjustPixelClient(
* therefore this always has a height of 0 and an epoch of 1.
*/
- linePtr->pixels[2*useReference] = (*counting ? defaultHeight : 0);
- linePtr->pixels[2*useReference+1] = (*counting ? 0 : 1);
- pixelCount += linePtr->pixels[2*useReference];
+ if (newPixelReferences > treePtr->numPixelReferences) {
+ DEBUG_ALLOC(if (!linePtr->pixelInfo) tkTextCountNewPixelInfo++);
+ linePtr->pixelInfo = realloc(linePtr->pixelInfo,
+ sizeof(linePtr->pixelInfo[0])*newPixelReferences);
+ memset(&linePtr->pixelInfo[useReference], 0, sizeof(TkTextPixelInfo));
+ } else if (linePtr->pixelInfo[useReference].dispLineInfo) {
+ free(linePtr->pixelInfo[useReference].dispLineInfo);
+ linePtr->pixelInfo[useReference].dispLineInfo = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
+ }
- linePtr = linePtr->nextPtr;
+ linePtr->pixelInfo[useReference].epoch = epoch;
+ pixelCount += (linePtr->pixelInfo[useReference].height = height);
+ numDispLines += GetDisplayLines(linePtr, useReference);
}
}
- if (newPixelReferences != treePtr->pixelReferences) {
- nodePtr->numPixels = ckrealloc(nodePtr->numPixels,
- sizeof(int) * newPixelReferences);
+
+ if (newPixelReferences > treePtr->numPixelReferences) {
+ DEBUG_ALLOC(if (!nodePtr->pixelInfo) tkTextCountNewPixelInfo++);
+ nodePtr->pixelInfo = realloc(nodePtr->pixelInfo,
+ sizeof(nodePtr->pixelInfo[0])*newPixelReferences);
+ }
+ nodePtr->pixelInfo[useReference].pixels = pixelCount;
+ nodePtr->pixelInfo[useReference].numDispLines = numDispLines;
+ if (numDispLinesPtr) {
+ *numDispLinesPtr += numDispLines;
}
- nodePtr->numPixels[useReference] = pixelCount;
return pixelCount;
}
@@ -783,8 +2372,8 @@ static void
RemovePixelClient(
BTree *treePtr, /* Pointer to tree. */
Node *nodePtr, /* Adjust from this node downwards. */
- int overwriteWithLast) /* Over-write this peer widget's information
- * with the last one. */
+ unsigned useReference, /* Pixel reference for the client we are removing. */
+ int overwriteWithLast) /* Over-write this peer widget's information with the last one. */
{
/*
* Traverse entire tree down from nodePtr, reallocating pixel structures
@@ -795,35 +2384,42 @@ RemovePixelClient(
*/
if (overwriteWithLast != -1) {
- nodePtr->numPixels[overwriteWithLast] =
- nodePtr->numPixels[treePtr->pixelReferences-1];
+ nodePtr->pixelInfo[overwriteWithLast] = nodePtr->pixelInfo[treePtr->numPixelReferences - 1];
}
- if (treePtr->pixelReferences == 1) {
- nodePtr->numPixels = NULL;
+ if (treePtr->numPixelReferences == 1) {
+ free(nodePtr->pixelInfo);
+ nodePtr->pixelInfo = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyPixelInfo++);
} else {
- nodePtr->numPixels = ckrealloc(nodePtr->numPixels,
- sizeof(int) * (treePtr->pixelReferences - 1));
+ nodePtr->pixelInfo = realloc(nodePtr->pixelInfo,
+ sizeof(nodePtr->pixelInfo[0])*(treePtr->numPixelReferences - 1));
}
if (nodePtr->level != 0) {
- nodePtr = nodePtr->children.nodePtr;
- while (nodePtr != NULL) {
- RemovePixelClient(treePtr, nodePtr, overwriteWithLast);
+ nodePtr = nodePtr->childPtr;
+ while (nodePtr) {
+ RemovePixelClient(treePtr, nodePtr, useReference, overwriteWithLast);
nodePtr = nodePtr->nextPtr;
}
} else {
- register TkTextLine *linePtr = nodePtr->children.linePtr;
- while (linePtr != NULL) {
+ TkTextLine *linePtr = nodePtr->linePtr;
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+
+ while (linePtr != lastPtr) {
+ if (linePtr->pixelInfo[useReference].dispLineInfo) {
+ free(linePtr->pixelInfo[useReference].dispLineInfo);
+ DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
+ }
if (overwriteWithLast != -1) {
- linePtr->pixels[2*overwriteWithLast] =
- linePtr->pixels[2*(treePtr->pixelReferences-1)];
- linePtr->pixels[1+2*overwriteWithLast] =
- linePtr->pixels[1+2*(treePtr->pixelReferences-1)];
+ linePtr->pixelInfo[overwriteWithLast] =
+ linePtr->pixelInfo[treePtr->numPixelReferences - 1];
}
- if (treePtr->pixelReferences == 1) {
- linePtr->pixels = NULL;
+ if (treePtr->numPixelReferences == 1) {
+ free(linePtr->pixelInfo);
+ linePtr->pixelInfo = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyPixelInfo++);
} else {
- linePtr->pixels = ckrealloc(linePtr->pixels,
- sizeof(int) * 2 * (treePtr->pixelReferences-1));
+ linePtr->pixelInfo = realloc(linePtr->pixelInfo,
+ sizeof(linePtr->pixelInfo[0])*(treePtr->numPixelReferences - 1));
}
linePtr = linePtr->nextPtr;
}
@@ -833,6 +2429,218 @@ RemovePixelClient(
/*
*----------------------------------------------------------------------
*
+ * TkBTreeJoinUndoInsert --
+ *
+ * Joins an undo token with another token.
+ *
+ * Results:
+ * Return whether the join was possible.
+ *
+ * Side effects:
+ * The first given will be modified.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkBTreeJoinUndoInsert(
+ TkTextUndoToken *token1,
+ unsigned byteSize1,
+ TkTextUndoToken *token2,
+ unsigned byteSize2)
+{
+ struct UndoTokenInsert *myToken1 = (UndoTokenInsert *) token1;
+ struct UndoTokenInsert *myToken2 = (UndoTokenInsert *) token2;
+
+ if (UndoIndexIsEqual(&myToken1->endIndex, &myToken2->startIndex)) {
+ /* append to first token */
+ myToken1->endIndex = myToken2->endIndex;
+ } else if (UndoIndexIsEqual(&myToken1->startIndex, &myToken2->endIndex)) {
+ /* prepend to first token */
+ myToken1->startIndex = myToken2->startIndex;
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeJoinUndoDelete --
+ *
+ * Joins an undo token with another token.
+ *
+ * Results:
+ * Return whether the join was possible.
+ *
+ * Side effects:
+ * The first given will be modified.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkBTreeJoinUndoDelete(
+ TkTextUndoToken *token1,
+ unsigned byteSize1,
+ TkTextUndoToken *token2,
+ unsigned byteSize2)
+{
+ struct UndoTokenDelete *myToken1 = (UndoTokenDelete *) token1;
+ struct UndoTokenDelete *myToken2 = (UndoTokenDelete *) token2;
+
+ if (myToken1->inclusive != myToken2->inclusive) {
+ return false;
+ }
+
+ if (UndoIndexIsEqual(&myToken1->startIndex, &myToken2->startIndex)) {
+ unsigned numSegments1 = myToken1->numSegments;
+
+ if (myToken2->endIndex.lineIndex == -1) {
+ myToken1->endIndex = myToken2->endIndex;
+ } else if (myToken1->endIndex.lineIndex != -1) {
+ myToken1->endIndex.u.byteIndex += byteSize2;
+ } else if (myToken2->endIndex.lineIndex != -1) {
+ myToken1->endIndex.u.byteIndex = myToken2->endIndex.u.byteIndex + byteSize1;
+ myToken1->endIndex.lineIndex = myToken2->endIndex.lineIndex;
+ } else if (myToken2->startIndex.lineIndex != -1) {
+ myToken1->endIndex.u.byteIndex = myToken2->startIndex.u.byteIndex + byteSize1 + byteSize2;
+ myToken1->endIndex.lineIndex = myToken2->startIndex.lineIndex;
+ } else {
+ myToken1->endIndex.u.byteIndex = myToken1->startIndex.u.byteIndex + byteSize1 + byteSize2;
+ myToken1->endIndex.lineIndex = myToken1->startIndex.lineIndex;
+ }
+
+ myToken1->numSegments += myToken2->numSegments;
+ myToken1->segments = realloc(myToken1->segments,
+ myToken1->numSegments*sizeof(myToken1->segments[0]));
+ memcpy(myToken1->segments + numSegments1, myToken2->segments,
+ myToken2->numSegments*sizeof(myToken2->segments[0]));
+ free(myToken2->segments);
+ myToken2->numSegments = 0;
+ } else if (UndoIndexIsEqual(&myToken1->startIndex, &myToken2->endIndex)) {
+ unsigned numSegments1 = myToken1->numSegments;
+ TkTextSegment **segments;
+
+ if (myToken2->startIndex.lineIndex == -1) {
+ myToken1->startIndex = myToken2->startIndex;
+ } else if (myToken2->endIndex.lineIndex != -1) {
+ myToken1->startIndex.u.byteIndex = myToken2->endIndex.u.byteIndex - byteSize1;
+ myToken1->startIndex.lineIndex = myToken2->endIndex.lineIndex;
+ } else if (myToken1->endIndex.lineIndex != -1) {
+ myToken1->startIndex.u.byteIndex = myToken1->endIndex.u.byteIndex - byteSize1 - byteSize2;
+ myToken1->startIndex.lineIndex = myToken1->endIndex.lineIndex;
+ } else {
+ myToken1->startIndex.u.byteIndex = myToken1->startIndex.u.byteIndex + byteSize1 + byteSize2;
+ myToken1->startIndex.lineIndex = myToken1->startIndex.lineIndex;
+ }
+
+ myToken1->numSegments += myToken2->numSegments;
+ segments = malloc(myToken1->numSegments*sizeof(segments[0]));
+ memcpy(segments, myToken2->segments, myToken2->numSegments*sizeof(myToken2->segments[0]));
+ memcpy(segments + myToken2->numSegments, myToken1->segments,
+ numSegments1*sizeof(myToken1->segments[0]));
+ free(myToken1->segments);
+ free(myToken2->segments);
+ myToken1->segments = segments;
+ myToken2->numSegments = 0;
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeDestroy --
+ *
+ * Delete a B-tree, recycling all of the storage it contains.
+ *
+ * Results:
+ * The tree is deleted, so 'tree' should never again be used.
+ *
+ * Side effects:
+ * Memory is freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkBTreeDestroy(
+ TkTextBTree tree) /* Tree to clean up. */
+{
+ BTree *treePtr = (BTree *) tree;
+
+ /*
+ * There's no need to loop over each client of the tree, calling
+ * 'TkBTreeRemoveClient', since the 'DestroyNode' will clean everything up
+ * itself.
+ */
+
+ DestroyNode(tree, treePtr->rootPtr);
+ free(treePtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeHaveElidedSegments --
+ *
+ * Return whether this tree contains elided segments.
+ *
+ * Results:
+ * 'true' if this tree contains elided segments, otherwise 'false'.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkBTreeHaveElidedSegments(
+ const TkSharedText *sharedTextPtr)
+{
+ return TkBTreeGetRoot(sharedTextPtr->tree)->numBranches > 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeNode --
+ *
+ * Free the storage of a node.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The storage of given node will be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeNode(
+ Node *nodePtr) /* Free storage of this node. */
+{
+ assert(nodePtr->level > 0 || nodePtr->linePtr);
+ TkTextTagSetDecrRefCount(nodePtr->tagonPtr);
+ TkTextTagSetDecrRefCount(nodePtr->tagoffPtr);
+ free(nodePtr->pixelInfo);
+ DEBUG(nodePtr->linePtr = NULL); /* guarded deallocation */
+ free(nodePtr);
+ DEBUG_ALLOC(tkTextCountDestroyPixelInfo++);
+ DEBUG_ALLOC(tkTextCountDestroyNode++);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* DestroyNode --
*
* This is a recursive utility function used during the deletion of a
@@ -849,66 +2657,115 @@ RemovePixelClient(
static void
DestroyNode(
- register Node *nodePtr) /* Destroy from this node downwards. */
+ TkTextBTree tree,
+ Node *nodePtr) /* Destroy from this node downwards. */
{
if (nodePtr->level == 0) {
TkTextLine *linePtr;
+ TkTextLine *lastPtr;
TkTextSegment *segPtr;
+ TkTextSection *sectionPtr;
- while (nodePtr->children.linePtr != NULL) {
- linePtr = nodePtr->children.linePtr;
- nodePtr->children.linePtr = linePtr->nextPtr;
- while (linePtr->segPtr != NULL) {
- segPtr = linePtr->segPtr;
- linePtr->segPtr = segPtr->nextPtr;
- segPtr->typePtr->deleteProc(segPtr, linePtr, 1);
+ lastPtr = nodePtr->lastPtr->nextPtr;
+ linePtr = nodePtr->linePtr;
+
+ while (linePtr != lastPtr) {
+ TkTextLine *nextPtr = linePtr->nextPtr;
+ segPtr = linePtr->segPtr;
+ sectionPtr = segPtr->sectionPtr;
+ while (segPtr) {
+ TkTextSegment *nextPtr = segPtr->nextPtr;
+ assert(segPtr->typePtr); /* still existing? */
+ assert(segPtr->sectionPtr->linePtr == linePtr);
+ assert(segPtr->typePtr->deleteProc);
+ segPtr->typePtr->deleteProc(tree, segPtr, TREE_GONE);
+ segPtr = nextPtr;
}
- ckfree(linePtr->pixels);
- ckfree(linePtr);
+ FreeSections(sectionPtr);
+ FreeLine((const BTree *) tree, linePtr);
+ linePtr = nextPtr;
}
} else {
- register Node *childPtr;
+ Node *childPtr = nodePtr->childPtr;
- while (nodePtr->children.nodePtr != NULL) {
- childPtr = nodePtr->children.nodePtr;
- nodePtr->children.nodePtr = childPtr->nextPtr;
- DestroyNode(childPtr);
+ while (childPtr) {
+ Node *nextPtr = childPtr->nextPtr;
+ DestroyNode(tree, childPtr);
+ childPtr = nextPtr;
}
}
- DeleteSummaries(nodePtr->summaryPtr);
- ckfree(nodePtr->numPixels);
- ckfree(nodePtr);
+ FreeNode(nodePtr);
}
/*
*----------------------------------------------------------------------
*
- * DeleteSummaries --
+ * TkBTreeResetDisplayLineCounts --
*
- * Free up all of the memory in a list of tag summaries associated with a
- * node.
+ * Reset the display line counts for given line range.
*
* Results:
* None.
*
* Side effects:
- * Storage is released.
+ * Updates overall data structures so display line count is consistent.
*
*----------------------------------------------------------------------
*/
static void
-DeleteSummaries(
- register Summary *summaryPtr)
- /* First in list of node's tag summaries. */
+PropagateDispLineChange(
+ Node *nodePtr,
+ unsigned pixelReference,
+ int subtractFromDispLines,
+ int subtractFromPixels)
{
- register Summary *nextPtr;
+ if (subtractFromDispLines != 0 || subtractFromPixels != 0) {
+ for ( ; nodePtr; nodePtr = nodePtr->parentPtr) {
+ NodePixelInfo *pixelInfo = nodePtr->pixelInfo + pixelReference;
+ pixelInfo->numDispLines -= subtractFromDispLines;
+ pixelInfo->pixels -= subtractFromPixels;
+ }
+ }
+}
+
+void
+TkBTreeResetDisplayLineCounts(
+ TkText *textPtr,
+ TkTextLine *linePtr, /* Start at this line. */
+ unsigned numLines) /* Number of succeeding lines to reset (includes the start line). */
+{
+ Node *nodePtr = linePtr->parentPtr;
+ unsigned pixelReference = textPtr->pixelReference;
+ int changeToDispLines = 0;
+ int changeToPixels = 0;
+
+ assert(textPtr->pixelReference != -1);
+
+ for ( ; numLines > 0; --numLines) {
+ TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
+
+ changeToDispLines += (int) GetDisplayLines(linePtr, pixelReference);
+ changeToPixels += pixelInfo->height;
+ pixelInfo->epoch = 0;
+ pixelInfo->height = 0;
+ linePtr = linePtr->nextPtr;
+
+ if (pixelInfo->dispLineInfo) {
+ free(pixelInfo->dispLineInfo);
+ pixelInfo->dispLineInfo = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
+ }
- while (summaryPtr != NULL) {
- nextPtr = summaryPtr->nextPtr;
- ckfree(summaryPtr);
- summaryPtr = nextPtr;
+ if (nodePtr != linePtr->parentPtr) {
+ PropagateDispLineChange(nodePtr, pixelReference, changeToDispLines, changeToPixels);
+ changeToDispLines = 0;
+ changeToPixels = 0;
+ nodePtr = linePtr->parentPtr;
+ }
}
+
+ PropagateDispLineChange(nodePtr, pixelReference, changeToDispLines, changeToPixels);
}
/*
@@ -928,55 +2785,1367 @@ DeleteSummaries(
*----------------------------------------------------------------------
*/
-int
+static void
+PropagatePixelCountChange(
+ Node *nodePtr,
+ unsigned pixelReference,
+ int changeToPixels,
+ int changeToDispLines)
+{
+ /*
+ * Increment the pixel counts also in all the parent nodes.
+ */
+
+ for ( ; nodePtr; nodePtr = nodePtr->parentPtr) {
+ NodePixelInfo *pixelInfo = nodePtr->pixelInfo + pixelReference;
+
+ pixelInfo->pixels += changeToPixels;
+ pixelInfo->numDispLines += changeToDispLines;
+ }
+}
+
+void
TkBTreeAdjustPixelHeight(
const TkText *textPtr, /* Client of the B-tree. */
- register TkTextLine *linePtr,
- /* The logical line to update. */
+ TkTextLine *linePtr, /* The logical line to update. */
int newPixelHeight, /* The line's known height in pixels. */
- int mergedLogicalLines) /* The number of extra logical lines which
- * have been merged with this one (due to
- * elided eols). They will have their pixel
- * height set to zero, and the total pixel
- * height associated with the given
- * linePtr. */
-{
- register Node *nodePtr;
- int changeToPixelCount; /* Counts change to total number of pixels in
- * file. */
- int pixelReference = textPtr->pixelReference;
+ unsigned mergedLines, /* The number of extra lines which have been merged with this one
+ * (due to elided eols). They will have their pixel height set to
+ * zero, and the total pixel height associated with the given linePtr. */
+ unsigned numDispLines) /* The new number of display lines for this logical line. */
+{
+ Node *nodePtr = linePtr->parentPtr;
+ unsigned pixelReference = textPtr->pixelReference;
+ int changeToPixels = 0;
+ int changeToDispLines = 0;
+
+ assert(textPtr->pixelReference != -1);
+ assert(linePtr->logicalLine || linePtr == GetStartLine(textPtr->sharedTextPtr, textPtr));
+
+ while (true) {
+ /*
+ * Do this before updating the line height.
+ */
+
+ changeToDispLines += (int) numDispLines - (int) GetDisplayLines(linePtr, pixelReference);
+ changeToPixels += newPixelHeight - linePtr->pixelInfo[pixelReference].height;
+
+ linePtr->pixelInfo[pixelReference].height = newPixelHeight;
+
+ if (mergedLines == 0) {
+ if (changeToPixels || changeToDispLines) {
+ PropagatePixelCountChange(nodePtr, pixelReference, changeToPixels, changeToDispLines);
+ }
+ return;
+ }
+
+ /*
+ * Any merged logical lines must have their height set to zero.
+ */
+
+ linePtr = linePtr->nextPtr;
+ newPixelHeight = 0;
+ mergedLines -= 1;
+ numDispLines = 0;
+
+ if (nodePtr != linePtr->parentPtr) {
+ if (changeToPixels || changeToDispLines) {
+ PropagatePixelCountChange(nodePtr, pixelReference, changeToPixels, changeToDispLines);
+ }
+ changeToPixels = 0;
+ changeToDispLines = 0;
+ nodePtr = linePtr->parentPtr;
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeUpdatePixelHeights --
+ *
+ * Update the pixel heights, starting with given line. This function
+ * assumes that all logical lines will have monospaced line heights.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Updates overall data structures so pixel height count is consistent.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkBTreeUpdatePixelHeights(
+ const TkText *textPtr, /* Client of the B-tree. */
+ TkTextLine *linePtr, /* Start with this logical line. */
+ int numLines, /* Number of lines for update (inclusively start line). If negative,
+ * this is the number of deleted lines. */
+ unsigned epoch) /* Current line metric epoch. */
+{
+ Node *nodePtr = linePtr->parentPtr;
+ unsigned pixelReference = textPtr->pixelReference;
+ int lineHeight = textPtr->lineHeight;
+ int changeToDispLines = 0;
+ int changeToPixels = 0;
+ int nlines = ABS(numLines);
+
+ assert(textPtr->pixelReference >= 0);
+ assert(textPtr->wrapMode == TEXT_WRAPMODE_NONE);
+ assert(lineHeight > 0);
+
+ for ( ; nlines > 0; --nlines) {
+ TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
+
+ if (pixelInfo->dispLineInfo) {
+ changeToDispLines -= (int) GetDisplayLines(linePtr, pixelReference);
+ if (pixelInfo->height > 0) {
+ changeToDispLines += 1;
+ }
+ if (pixelInfo->dispLineInfo) {
+ free(pixelInfo->dispLineInfo);
+ pixelInfo->dispLineInfo = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
+ }
+ }
+
+ pixelInfo->epoch = epoch;
+ changeToPixels -= pixelInfo->height;
+
+ if (pixelInfo->height == 0) {
+ changeToDispLines += 1;
+ }
+
+ pixelInfo->height = lineHeight;
+
+ if (numLines > 0) {
+ changeToPixels += lineHeight;
+ }
+
+ linePtr = linePtr->nextPtr;
+
+ if (nodePtr != linePtr->parentPtr) {
+ if (changeToPixels || changeToDispLines) {
+ PropagatePixelCountChange(nodePtr, pixelReference, changeToPixels, changeToDispLines);
+ }
+ changeToDispLines = 0;
+ changeToPixels = 0;
+ nodePtr = linePtr->parentPtr;
+ }
+ }
+
+ if (changeToPixels || changeToDispLines) {
+ PropagatePixelCountChange(nodePtr, pixelReference, changeToPixels, changeToDispLines);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SubtractPixelInfo --
+ *
+ * Decrement the line and pixel counts in all the parent nodes.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The pixel counts in the B-tree will be adjusted.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+SubtractPixelInfo(
+ BTree *treePtr, /* Tree that is being affected. */
+ TkTextLine *linePtr) /* This line will be deleted. */
+{
+ Node *nodePtr = linePtr->parentPtr;
+ unsigned ref;
+
+ for ( ; nodePtr; nodePtr = nodePtr->parentPtr) {
+ NodePixelInfo *dst = nodePtr->pixelInfo;
- changeToPixelCount = newPixelHeight - linePtr->pixels[2 * pixelReference];
+ nodePtr->numLines -= 1;
+ nodePtr->numLogicalLines -= linePtr->logicalLine;
+ nodePtr->size -= linePtr->size;
+
+ for (ref = 0; ref < treePtr->numPixelReferences; ++ref, ++dst) {
+ dst->pixels -= linePtr->pixelInfo[ref].height;
+ dst->numDispLines -= GetDisplayLines(linePtr, ref);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SubtractPixelCount2 --
+ *
+ * Decrement the line and pixel counts in all the parent nodes.
+ * This function can also be used for incrementation, simply negate
+ * the values 'changeToLineCount' and 'changeToPixelInfo'.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The pixel counts in the B-tree will be adjusted.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+SubtractPixelCount2(
+ BTree *treePtr, /* Tree that is being affected. */
+ Node *nodePtr, /* Node that will be adjusted. */
+ int changeToLineCount, /* Number of lines removed. */
+ int changeToLogicalLineCount, /* Number of logical lines removed. */
+ int changeToBranchCount, /* Number of branches removed. */
+ int changeToSize, /* Subtract this size. */
+ const NodePixelInfo *changeToPixelInfo)
+ /* Values for pixel info adjustment. */
+{
+ unsigned ref;
+
+ assert(changeToLineCount != 0 || changeToLogicalLineCount == 0);
+ assert(changeToLineCount != 0 || changeToBranchCount == 0);
+
+ if (changeToLineCount != 0) {
+ for ( ; nodePtr; nodePtr = nodePtr->parentPtr) {
+ NodePixelInfo *dst = nodePtr->pixelInfo;
+ const NodePixelInfo *src = changeToPixelInfo;
+
+ nodePtr->numLines -= changeToLineCount;
+ nodePtr->numLogicalLines -= changeToLogicalLineCount;
+ nodePtr->numBranches -= changeToBranchCount;
+ nodePtr->size -= changeToSize;
+
+ for (ref = 0; ref < treePtr->numPixelReferences; ++ref, ++dst, ++src) {
+ dst->pixels -= src->pixels;
+ dst->numDispLines -= src->numDispLines;
+ }
+ }
+ } else if (changeToSize != 0) {
+ for ( ; nodePtr; nodePtr = nodePtr->parentPtr) {
+ nodePtr->size -= changeToSize;
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AddPixelCount --
+ *
+ * Set up a starting default height, which will be re-adjusted later.
+ * We need to do this for each referenced widget.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory will be allocated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+AddPixelCount(
+ BTree *treePtr,
+ TkTextLine *linePtr,
+ const TkTextLine *refLinePtr,
+ NodePixelInfo *changeToPixelInfo)
+{
+ unsigned ref;
/*
- * Increment the pixel counts in all the parent nodes of the current line,
- * then rebalance the tree if necessary.
+ * Set up a starting default height, which will be re-adjusted later.
+ * We need to do this for each referenced widget.
*/
- nodePtr = linePtr->parentPtr;
- nodePtr->numPixels[pixelReference] += changeToPixelCount;
+ linePtr->pixelInfo = malloc(sizeof(TkTextPixelInfo)*treePtr->numPixelReferences);
+ DEBUG_ALLOC(tkTextCountNewPixelInfo++);
+
+ for (ref = 0; ref < treePtr->numPixelReferences; ++ref) {
+ TkTextPixelInfo *pixelInfo = linePtr->pixelInfo + ref;
+ const TkTextPixelInfo *refPixelInfo = refLinePtr->pixelInfo + ref;
+ NodePixelInfo *pixelInfoChange = changeToPixelInfo + ref;
+ int height = refPixelInfo->height;
+ int numDispLines = height > 0;
- while (nodePtr->parentPtr != NULL) {
- nodePtr = nodePtr->parentPtr;
- nodePtr->numPixels[pixelReference] += changeToPixelCount;
+ pixelInfo->dispLineInfo = NULL;
+ pixelInfo->height = height;
+ pixelInfo->epoch = 0;
+ pixelInfoChange->pixels -= height;
+ pixelInfoChange->numDispLines -= numDispLines;
}
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextTestTag --
+ *
+ * Return whether the segment at specified position is tagged with
+ * specified tag.
+ *
+ * Results:
+ * Returns whether this text is tagged with specified tag.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- linePtr->pixels[2 * pixelReference] = newPixelHeight;
+bool
+TkTextTestTag(
+ const TkTextIndex *indexPtr,/* The character in the text for which display information is wanted. */
+ const TkTextTag *tagPtr) /* Test for this tag. */
+{
+ assert(tagPtr);
+ return TkTextTagSetTest(TkTextIndexGetContentSegment(indexPtr, NULL)->tagInfoPtr, tagPtr->index);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsElided --
+ *
+ * Special case to just return information about elided attribute.
+ * Just need to keep track of invisibility settings for each priority,
+ * pick highest one active at end.
+ *
+ * Results:
+ * Returns whether this text should be elided or not.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static bool
+TestIfElided(
+ const TkTextTag *tagPtr)
+{
+ int highestPriority = -1;
+ bool elide = false;
+
+ for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
+ if (tagPtr->elideString && tagPtr->priority > highestPriority) {
+ elide = tagPtr->elide;
+ highestPriority = tagPtr->priority;
+ }
+ }
+
+ return elide;
+}
+
+bool
+TkTextIsElided(
+ const TkTextIndex *indexPtr)/* The character in the text for which display information is wanted. */
+{
+ return TkBTreeGetRoot(indexPtr->tree)->numBranches > 0 && TestIfElided(TkBTreeGetTags(indexPtr));
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SegmentIsElided --
+ *
+ * Return information about elided attribute of this segment.
+ *
+ * Results:
+ * Returns whether this component should be elided or not.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static bool
+SegmentIsElided(
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr,
+ const TkText *textPtr) /* can be NULL */
+{
+ assert(segPtr->tagInfoPtr);
+
+ return TkTextTagSetIntersectsBits(segPtr->tagInfoPtr, sharedTextPtr->elisionTags)
+ && TestIfElided(TkBTreeGetSegmentTags(sharedTextPtr, segPtr, textPtr));
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextSegmentIsElided --
+ *
+ * Return information about elided attribute of this segment.
+ *
+ * Results:
+ * Returns whether this component should be elided or not.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextSegmentIsElided(
+ const TkText *textPtr,
+ const TkTextSegment *segPtr)
+{
+ TkSharedText *sharedTextPtr;
+
+ assert(segPtr->tagInfoPtr);
+ assert(textPtr);
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ return TkBTreeHaveElidedSegments(sharedTextPtr) && SegmentIsElided(sharedTextPtr, segPtr, textPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InsertNewLine --
+ *
+ * This function makes a new line, and inserts the given segment as
+ * the first segment in new line. All the required actions to fulfill
+ * the consistency of the B-Tree will be done. But this function is
+ * not rebalancing the B-Tree, nor is it changing the pixel count of
+ * the peers.
+ *
+ * Results:
+ * The return value is the new line.
+ *
+ * Side effects:
+ * All the required changes to fulfill the consistency of the
+ * B-Tree, except rebalancing.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static bool
+HasElidedNewline(
+ const TkSharedText *sharedTextPtr,
+ const TkTextLine *linePtr)
+{
+ return TkBTreeHaveElidedSegments(sharedTextPtr)
+ && SegmentIsElided(sharedTextPtr, linePtr->lastPtr, NULL);
+}
+
+static TkTextLine *
+InsertNewLine(
+ TkSharedText *sharedTextPtr,/* Handle to shared text resource. */
+ Node *nodePtr, /* The node which will contain the new line. */
+ TkTextLine *prevLinePtr, /* Predecessor of the new line, can be NULL. */
+ TkTextSegment *segPtr) /* First segment of this line. */
+{
+ TkTextLine *newLinePtr;
+ TkTextSegment *prevPtr;
+ TkTextSegment *lastPtr = segPtr;
+
+ assert(segPtr);
+ assert(nodePtr);
+ assert(segPtr->sectionPtr || !segPtr->prevPtr);
+ assert(!segPtr->prevPtr || segPtr->prevPtr->sectionPtr->linePtr == prevLinePtr);
+ assert(!segPtr->prevPtr || prevLinePtr);
+ assert(!prevLinePtr || prevLinePtr->parentPtr == nodePtr);
+
+ prevPtr = segPtr->prevPtr;
+
+ if (prevPtr) {
+ prevPtr->nextPtr = NULL;
+ lastPtr = prevLinePtr->lastPtr;
+ prevLinePtr->lastPtr = prevPtr;
+ segPtr->prevPtr = NULL;
+ }
+
+ newLinePtr = memset(malloc(sizeof(TkTextLine)), 0, sizeof(TkTextLine));
+ newLinePtr->parentPtr = nodePtr;
+ newLinePtr->prevPtr = prevLinePtr;
+ newLinePtr->segPtr = segPtr;
+ newLinePtr->lastPtr = lastPtr;
+ newLinePtr->logicalLine = true;
+ newLinePtr->changed = true;
+ DEBUG_ALLOC(tkTextCountNewLine++);
+
+ TkTextTagSetIncrRefCount(newLinePtr->tagonPtr = sharedTextPtr->emptyTagInfoPtr);
+ TkTextTagSetIncrRefCount(newLinePtr->tagoffPtr = sharedTextPtr->emptyTagInfoPtr);
+
+ if (prevLinePtr) {
+ newLinePtr->logicalLine = !HasElidedNewline(sharedTextPtr, prevLinePtr);
+ if ((newLinePtr->nextPtr = prevLinePtr->nextPtr)) {
+ newLinePtr->nextPtr->prevPtr = newLinePtr;
+ }
+ prevLinePtr->nextPtr = newLinePtr;
+ }
+
+ if (segPtr->sectionPtr) {
+ if (prevPtr && prevPtr->sectionPtr == segPtr->sectionPtr) {
+ if ((segPtr->sectionPtr = segPtr->sectionPtr->nextPtr)) {
+ segPtr->sectionPtr->prevPtr = NULL;
+ }
+ prevPtr->sectionPtr->nextPtr = NULL;
+ } else {
+ if (segPtr->sectionPtr->prevPtr) {
+ segPtr->sectionPtr->prevPtr->nextPtr = NULL;
+ }
+ segPtr->sectionPtr->prevPtr = NULL;
+ }
+ }
+
+ RebuildSections(sharedTextPtr, newLinePtr, false);
+
+ if (newLinePtr->numBranches > 0 || newLinePtr->numLinks > 0) {
+ assert(prevLinePtr);
+ assert(prevLinePtr->numBranches >= newLinePtr->numBranches);
+ assert(prevLinePtr->numLinks >= newLinePtr->numLinks);
+ prevLinePtr->numBranches -= newLinePtr->numBranches;
+ prevLinePtr->numLinks -= newLinePtr->numLinks;
+ }
+
+ if (prevPtr) {
+ prevPtr->sectionPtr->size = ComputeSectionSize(prevPtr->sectionPtr->segPtr);
+ prevPtr->sectionPtr->length = CountSegments(prevPtr->sectionPtr);
+ assert(prevPtr->sectionPtr->length == CountSegments(prevPtr->sectionPtr)); /* checks overflow */
+ prevPtr->sectionPtr->linePtr->size -= newLinePtr->size;
+ }
+
+ if (nodePtr->lastPtr == prevLinePtr) {
+ SetNodeLastPointer(nodePtr, newLinePtr);
+ }
+
+ assert(!prevLinePtr || CheckSections(prevLinePtr));
+ return newLinePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * MakeTagInfo --
+ *
+ * Find the associated tag information of the adjacent segment
+ * depending on the current tagging mode. This function is
+ * incrementing the reference count of the returned tag information
+ * set.
+ *
+ * Results:
+ * The associated tag information.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextTagSet *
+GetPrevLineTagSet(
+ TkText *textPtr,
+ TkTextSegment *segPtr)
+{
+ TkTextLine *linePtr = segPtr->sectionPtr->linePtr->prevPtr;
+
+ if (!linePtr) {
+ return textPtr->sharedTextPtr->emptyTagInfoPtr;
+ }
/*
- * Any merged logical lines must have their height set to zero.
+ * Didn't find any tag information in this line, so try the last segment of the
+ * previous line, this segment must have a tag information.
*/
- while (mergedLogicalLines-- > 0) {
- linePtr = TkBTreeNextLine(textPtr, linePtr);
- TkBTreeAdjustPixelHeight(textPtr, linePtr, 0, 0);
+ segPtr = linePtr->lastPtr;
+ assert(segPtr->tagInfoPtr);
+ return segPtr->tagInfoPtr;
+}
+
+static TkTextTagSet *
+MakeTagInfo(
+ TkText *textPtr,
+ TkTextSegment *segPtr) /* The first inserted text segment. */
+{
+ TkTextTagSet *tagInfoPtr = textPtr->sharedTextPtr->emptyTagInfoPtr;
+
+ assert(segPtr);
+ assert(textPtr);
+ assert(textPtr->insertMarkPtr);
+
+ switch (textPtr->tagging) {
+ case TK_TEXT_TAGGING_WITHIN: {
+ /*
+ * This is the traditional tagging mode. Search for the tags on both sides
+ * of the inserted text.
+ */
+
+ TkTextSegment *segPtr2;
+ TkTextTagSet *tagInfoPtr2 = NULL;
+
+ for (segPtr2 = segPtr->nextPtr; !segPtr2->tagInfoPtr; segPtr2 = segPtr2->nextPtr) {
+ assert(segPtr2);
+ }
+ TkTextTagSetIncrRefCount(tagInfoPtr = segPtr2->tagInfoPtr);
+ segPtr2 = segPtr;
+ while (!tagInfoPtr2) {
+ segPtr2 = segPtr2->prevPtr;
+ if (!segPtr2) {
+ tagInfoPtr2 = GetPrevLineTagSet(textPtr, segPtr);
+ } else if (segPtr2->tagInfoPtr) {
+ tagInfoPtr2 = segPtr2->tagInfoPtr;
+ }
+ }
+ return TagSetIntersect(tagInfoPtr, tagInfoPtr2, textPtr->sharedTextPtr);
+ }
+ case TK_TEXT_TAGGING_GRAVITY:
+ /*
+ * Search for a adjcacent content segment which will provide the appropriate tag
+ * information, the direction of the search depends on the gravity of the "insert"
+ * mark. If we cannot find a segment, then the tag information will be empty.
+ */
+
+ if (textPtr->insertMarkPtr->typePtr == &tkTextLeftMarkType) {
+ if ((segPtr = segPtr->nextPtr)) {
+ while (segPtr->typePtr->gravity != GRAVITY_LEFT || segPtr->typePtr == &tkTextLinkType) {
+ if (segPtr->tagInfoPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ tagInfoPtr = segPtr->tagInfoPtr;
+ }
+ TkTextTagSetIncrRefCount(tagInfoPtr);
+ return tagInfoPtr;
+ }
+ segPtr = segPtr->nextPtr;
+ assert(segPtr);
+ }
+ }
+ } else {
+ if (!segPtr->prevPtr) {
+ TkTextTagSetIncrRefCount(tagInfoPtr = GetPrevLineTagSet(textPtr, segPtr));
+ return tagInfoPtr;
+ }
+ while (segPtr->typePtr->gravity != GRAVITY_RIGHT || segPtr->typePtr == &tkTextBranchType) {
+ if (segPtr->tagInfoPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ tagInfoPtr = segPtr->tagInfoPtr;
+ }
+ TkTextTagSetIncrRefCount(tagInfoPtr);
+ return tagInfoPtr;
+ }
+ if (!segPtr->prevPtr) {
+ TkTextTagSetIncrRefCount(tagInfoPtr = GetPrevLineTagSet(textPtr, segPtr));
+ return tagInfoPtr;
+ }
+ segPtr = segPtr->prevPtr;
+ }
+ }
+ break;
+ case TK_TEXT_TAGGING_NONE:
+ /*
+ * The new text will not be tagged.
+ */
+ break;
+ }
+
+ return tagInfoPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeLoad --
+ *
+ * Load the given content into the widget. The content must be the
+ * result of the "inspect" command.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * The B-Tree structure will change, and some segments will be
+ * added.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+LoadError(
+ Tcl_Interp *interp, /* Current interpreter. */
+ const char *msg, /* Error message, can be NULL. */
+ int index0, /* List index at level 0. */
+ int index1, /* List index at level 1, is -1 if undefined. */
+ int index2, /* List index at level 2, is -1 if undefined. */
+ TkTextTagSet *tagInfoPtr) /* Decrement reference count if not NULL. */
+{
+ char buf[100] = { '\0' };
+ Tcl_Obj *errObjPtr = NULL;
+
+ if (!msg) {
+ Tcl_IncrRefCount(errObjPtr = Tcl_GetObjResult(interp));
+ msg = Tcl_GetString(errObjPtr);
+ }
+ if (tagInfoPtr) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ }
+ if (index0 >= 0) {
+ if (index1 >= 0) {
+ if (index2 >= 0) {
+ snprintf(buf, sizeof(buf), " (at index %d %d %d)", index0, index1, index2);
+ } else {
+ snprintf(buf, sizeof(buf), " (at index %d %d)", index0, index1);
+ }
+ } else {
+ snprintf(buf, sizeof(buf), " (at index %d)", index0);
+ }
+ }
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("error while loading%s: %s", buf, msg));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "LOAD", NULL);
+ if (errObjPtr) {
+ Tcl_DecrRefCount(errObjPtr);
+ }
+ return TCL_ERROR;
+}
+
+static bool
+LoadMakeTagInfo(
+ TkText *textPtr,
+ TkTextTagSet **tagInfoPtr,
+ Tcl_Obj *obj)
+{
+ int objc, i;
+ Tcl_Obj **objv;
+
+ if (Tcl_ListObjGetElements(textPtr->interp, obj, &objc, &objv) != TCL_OK) {
+ return false;
+ }
+ if (!*tagInfoPtr) {
+ TkTextTagSetIncrRefCount(*tagInfoPtr = textPtr->sharedTextPtr->emptyTagInfoPtr);
+ }
+ for (i = 0; i < objc; ++i) {
+ *tagInfoPtr = TagSetAdd(*tagInfoPtr, TkTextCreateTag(textPtr, Tcl_GetString(objv[i]), NULL));
+ }
+ return true;
+}
+
+static bool
+LoadRemoveTags(
+ TkText *textPtr,
+ TkTextTagSet **tagInfoPtr,
+ Tcl_Obj *obj)
+{
+ int objc, i;
+ Tcl_Obj **objv;
+
+ assert(*tagInfoPtr);
+
+ if (Tcl_ListObjGetElements(textPtr->interp, obj, &objc, &objv) != TCL_OK) {
+ return false;
+ }
+ for (i = 0; i < objc; ++i) {
+ *tagInfoPtr = TagSetErase(*tagInfoPtr, TkTextCreateTag(textPtr, Tcl_GetString(objv[i]), NULL));
+ }
+ return true;
+}
+
+static TkTextSegment *
+LoadPerformElision(
+ TkText *textPtr,
+ TkTextSegment *segPtr, /* newly inserted segment */
+ TkTextSegment **branchPtr, /* last inserted branch segment */
+ TkTextSegment *contentPtr, /* last char/hyphen/image/window segment in current line */
+ bool *isElided) /* elided state of last inserted segment */
+{
+ TkTextSegment *nextPtr = segPtr; /* next segment to insert into line */
+ bool elide = SegmentIsElided(textPtr->sharedTextPtr, segPtr, textPtr);
+
+ if (elide != *isElided) {
+ TkTextSegment *linkPtr;
+
+ if (elide) {
+ nextPtr = *branchPtr = MakeBranch();
+ (*branchPtr)->nextPtr = segPtr;
+ segPtr->prevPtr = *branchPtr;
+ } else {
+ assert(*branchPtr);
+ linkPtr = MakeLink();
+ linkPtr->body.link.prevPtr = *branchPtr;
+ (*branchPtr)->body.branch.nextPtr = linkPtr;
+ if (contentPtr) {
+ linkPtr->nextPtr = contentPtr->nextPtr;
+ linkPtr->prevPtr = contentPtr;
+ contentPtr->nextPtr = linkPtr;
+ } else {
+ linkPtr->nextPtr = segPtr;
+ segPtr->prevPtr = linkPtr;
+ nextPtr = linkPtr;
+ }
+ }
+
+ *isElided = elide;
+ }
+
+ return nextPtr;
+}
+
+int
+TkBTreeLoad(
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Obj *content) /* New content of this text widget. */
+{
+ enum {
+ STATE_START = 1 << 0,
+ STATE_SETUP = 1 << 1,
+ STATE_CONFIG = 1 << 2,
+ STATE_LEFT = 1 << 3,
+ STATE_RIGHT = 1 << 4,
+ STATE_LEFT_INSERT = 1 << 5,
+ STATE_RIGHT_INSERT = 1 << 6,
+ STATE_TEXT = 1 << 7,
+ STATE_BREAK = 1 << 8
+ };
+
+ Tcl_Obj **objv;
+ int objc, i;
+ int byteLength;
+ TkTextTagSet *tagInfoPtr;
+ TkSharedText *sharedTextPtr;
+ TkTextSegment *segPtr;
+ TkTextSegment *charSegPtr;
+ TkTextSegment *nextSegPtr;
+ TkTextSegment *branchPtr;
+ TkTextSegment *hyphPtr;
+ TkTextSegment *embPtr;
+ TkTextSegment *contentPtr;
+ TkTextLine *linePtr;
+ TkTextLine *newLinePtr;
+ TkTextLine *startLinePtr;
+ BTree *treePtr;
+ NodePixelInfo *changeToPixelInfo;
+ Tcl_Interp *interp = textPtr->interp;
+ TkTextState textState;
+ const char *name;
+ const char *s;
+ unsigned tagInfoCount;
+ unsigned state;
+ int changeToLineCount;
+ int changeToLogicalLineCount;
+ int changeToBranchCount;
+ int size;
+ bool isElided;
+ bool isInsert;
+
+ if (Tcl_ListObjGetElements(interp, content, &objc, &objv) != TCL_OK) {
+ return LoadError(interp, "list of items expected", -1, -1, -1, NULL);
+ }
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ treePtr = (BTree *) sharedTextPtr->tree;
+ linePtr = startLinePtr = treePtr->rootPtr->linePtr;
+ segPtr = linePtr->segPtr;
+ contentPtr = NULL;
+ branchPtr = NULL;
+ hyphPtr = NULL;
+ tagInfoPtr = NULL;
+ changeToLineCount = 0;
+ changeToLogicalLineCount = 0;
+ changeToBranchCount = 0;
+ tagInfoCount = 0;
+ textState = textPtr->state;
+ textPtr->state = TK_TEXT_STATE_NORMAL;
+ isElided = false;
+ state = STATE_START;
+ size = 0;
+
+ assert(segPtr->typePtr != &tkTextCharType);
+
+ changeToPixelInfo = treePtr->pixelInfoBuffer;
+ memset(changeToPixelInfo, 0, sizeof(changeToPixelInfo[0])*treePtr->numPixelReferences);
+
+ while (segPtr->nextPtr->typePtr != &tkTextCharType) {
+ segPtr = segPtr->nextPtr;
+ }
+ charSegPtr = NULL;
+
+ for (i = 0; i < objc; ++i) {
+ const char *type;
+ Tcl_Obj **argv;
+ int argc;
+
+ if (Tcl_ListObjGetElements(interp, objv[i], &argc, &argv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (argc == 0) {
+ return LoadError(interp, "empty item", i, 0, -1, tagInfoPtr);
+ }
+
+ type = Tcl_GetString(argv[0]);
+
+ switch (type[0]) {
+ case 's': {
+ /*
+ * {"setup" pathname configuration}
+ */
+
+ Tcl_Obj **objv;
+ int objc;
+
+ if (strcmp(type, "setup") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (state != STATE_START) {
+ return LoadError(interp, "unexpected \"setup\" item", i, -1, -1, tagInfoPtr);
+ }
+ if (argc != 3) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ if (Tcl_ListObjGetElements(interp, argv[2], &objc, &objv) != TCL_OK
+ || TkConfigureText(interp, textPtr, objc, objv) != TCL_OK) {
+ return LoadError(interp, NULL, i, 2, -1, tagInfoPtr);
+ }
+ textState = textPtr->state;
+ textPtr->state = TK_TEXT_STATE_READONLY;
+ state = STATE_SETUP;
+ break;
+ }
+ case 'b':
+ switch (type[1]) {
+ case 'r':
+ /*
+ * {"break" taginfo ?taginfo?}
+ */
+
+ if (strcmp(type, "break") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (tagInfoCount == 0) {
+ tagInfoCount = argc - 1;
+ }
+ if (argc < 2 || 3 < argc || argc - tagInfoCount != 1) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ if (!LoadMakeTagInfo(textPtr, &tagInfoPtr, argv[1])) {
+ return LoadError(interp, "list of tag names expected", i, 1, -1, tagInfoPtr);
+ }
+ if (charSegPtr && TkTextTagSetIsEqual(tagInfoPtr, charSegPtr->tagInfoPtr)) {
+ charSegPtr = IncreaseCharSegment(charSegPtr, charSegPtr->size, 1);
+ charSegPtr->body.chars[charSegPtr->size - 1] = '\n';
+ linePtr->lastPtr = charSegPtr;
+ RebuildSections(sharedTextPtr, linePtr, true);
+ } else {
+ nextSegPtr = charSegPtr = MakeCharSeg(NULL, tagInfoPtr, 1, "\n", 1);
+ if (sharedTextPtr->numElisionTags > 0) {
+ nextSegPtr = LoadPerformElision(textPtr, charSegPtr, &branchPtr, contentPtr,
+ &isElided);
+ }
+ if (segPtr) {
+ segPtr->nextPtr = nextSegPtr;
+ nextSegPtr->prevPtr = segPtr;
+ linePtr->lastPtr = charSegPtr;
+ RebuildSections(sharedTextPtr, linePtr, true);
+ } else {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr,
+ linePtr, nextSegPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ linePtr = newLinePtr;
+ }
+ }
+ changeToLineCount += 1;
+ if (!isElided) {
+ changeToLogicalLineCount += 1;
+ }
+ size += 1;
+ contentPtr = charSegPtr;
+ segPtr = charSegPtr = NULL;
+ state = STATE_BREAK;
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ if (argc != 3) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ tagInfoPtr = NULL;
+ } else if (!LoadRemoveTags(textPtr, &tagInfoPtr, argv[2])) {
+ return LoadError(interp, "list of tag names expected", i, 2, -1, tagInfoPtr);
+ }
+ break;
+ case 'i': {
+ TkTextTag *tagPtr;
+
+ /*
+ * {"bind" tagname event script}
+ */
+
+ if (strcmp(type, "bind") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (argc != 4) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(argv[1]), NULL);
+ if (TkTextBindEvent(interp, argc - 2, argv + 2, textPtr->sharedTextPtr,
+ &sharedTextPtr->tagBindingTable, tagPtr->name) != TCL_OK) {
+ return LoadError(interp, NULL, i, 2, -1, tagInfoPtr);
+ }
+ state = STATE_TEXT;
+ break;
+ }
+ }
+ break;
+ case 'c': {
+ /*
+ * {"configure" tagname ?configuration?}
+ */
+
+ Tcl_Obj **objv;
+ int objc;
+
+ if (strcmp(type, "configure") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (!(state & (STATE_START|STATE_SETUP|STATE_CONFIG))) {
+ return LoadError(interp, "unexpected \"configure\" item", i, -1, -1, tagInfoPtr);
+ }
+ if (argc == 2) {
+ TkTextCreateTag(textPtr, Tcl_GetString(argv[1]), NULL);
+ } else if (argc != 3) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ } else if (Tcl_ListObjGetElements(interp, argv[2], &objc, &objv) != TCL_OK
+ && TkConfigureTag(interp, textPtr, Tcl_GetString(argv[1]), objc, objv) != TCL_OK) {
+ return LoadError(interp, NULL, i, 2, -1, tagInfoPtr);
+ }
+ state = STATE_CONFIG;
+ break;
+ }
+ case 't':
+ /*
+ * {"text" content taginfo ?taginfo?}
+ */
+
+ if (strcmp(type, "text") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (tagInfoCount == 0) {
+ tagInfoCount = argc - 2;
+ }
+ if (argc < 3 || 4 < argc || argc - tagInfoCount != 2) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ if (!LoadMakeTagInfo(textPtr, &tagInfoPtr, argv[2])) {
+ return LoadError(interp, "list of tag names expected", i, 2, -1, tagInfoPtr);
+ }
+ for (s = Tcl_GetString(argv[1]); *s; ++s) {
+ switch (UCHAR(*s)) {
+ case 0x0a:
+ return LoadError(interp, "newline not allowed in text content",
+ i, 1, -1, tagInfoPtr);
+ case 0xc2:
+ if (UCHAR(s[1]) == 0xad) {
+ return LoadError(interp, "soft hyphen (U+002D) not allowed in text content",
+ i, 1, -1, tagInfoPtr);
+ }
+ break;
+ }
+ }
+ byteLength = GetByteLength(argv[1]);
+ if (charSegPtr && TkTextTagSetIsEqual(tagInfoPtr, charSegPtr->tagInfoPtr)) {
+ int size = charSegPtr->size;
+ charSegPtr = IncreaseCharSegment(charSegPtr, size, byteLength);
+ memcpy(charSegPtr->body.chars + size, Tcl_GetString(argv[1]), byteLength);
+ } else {
+ nextSegPtr = charSegPtr = MakeCharSeg(NULL, tagInfoPtr,
+ byteLength, Tcl_GetString(argv[1]), byteLength);
+ if (sharedTextPtr->numElisionTags > 0) {
+ nextSegPtr = LoadPerformElision(textPtr, charSegPtr, &branchPtr, contentPtr,
+ &isElided);
+ }
+ if (segPtr) {
+ segPtr->nextPtr = nextSegPtr;
+ nextSegPtr->prevPtr = segPtr;
+ } else {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr, linePtr, nextSegPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ linePtr = newLinePtr;
+ }
+ }
+ size += byteLength;
+ contentPtr = segPtr = charSegPtr;
+ state = STATE_TEXT;
+ if (argc != 4) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ tagInfoPtr = NULL;
+ } else if (!LoadRemoveTags(textPtr, &tagInfoPtr, argv[3])) {
+ return LoadError(interp, "list of tag names expected", i, 3, -1, tagInfoPtr);
+ }
+ break;
+ case 'h':
+ /*
+ * {"hyphen" taginfo ?taginfo?}
+ */
+
+ if (strcmp(type, "hyphen") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (tagInfoCount == 0) {
+ tagInfoCount = argc - 1;
+ }
+ if (argc < 2 || 3 < argc || argc - tagInfoCount != 1) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ if (!LoadMakeTagInfo(textPtr, &tagInfoPtr, argv[1])) {
+ return LoadError(interp, "list of tag names expected", i, 1, -1, tagInfoPtr);
+ }
+ nextSegPtr = hyphPtr = MakeHyphen();
+ TkTextTagSetIncrRefCount(hyphPtr->tagInfoPtr = tagInfoPtr);
+ if (sharedTextPtr->numElisionTags > 0) {
+ nextSegPtr = LoadPerformElision(textPtr, charSegPtr, &branchPtr, contentPtr, &isElided);
+ }
+ if (segPtr) {
+ segPtr->nextPtr = nextSegPtr;
+ nextSegPtr->prevPtr = segPtr;
+ } else {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr, linePtr, nextSegPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ linePtr = newLinePtr;
+ }
+ size += 1;
+ contentPtr = segPtr = hyphPtr;
+ state = STATE_TEXT;
+ if (argc != 3) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ tagInfoPtr = NULL;
+ } else if (!LoadRemoveTags(textPtr, &tagInfoPtr, argv[2])) {
+ return LoadError(interp, "list of tag names expected", i, 2, -1, tagInfoPtr);
+ }
+ break;
+ case 'l':
+ /*
+ * {"left" markname}
+ */
+
+ if (strcmp(type, "left") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ name = Tcl_GetString(argv[1]);
+ isInsert = (strcmp(name, "insert") == 0);
+ if (sharedTextPtr->steadyMarks
+ ? state == STATE_RIGHT_INSERT || (isInsert && state == STATE_LEFT)
+ : state == STATE_RIGHT) {
+ return LoadError(interp, "unexpected \"left\" item", i, -1, -1, tagInfoPtr);
+ }
+ if (argc != 2) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ if (isInsert) {
+ UnlinkSegment(nextSegPtr = textPtr->insertMarkPtr);
+ } else if (!(nextSegPtr = TkTextMakeNewMark(textPtr, name))) {
+ return LoadError(interp, "mark already exists", i, 1, -1, tagInfoPtr);
+ }
+ nextSegPtr->typePtr = &tkTextLeftMarkType;
+ if (segPtr) {
+ segPtr->nextPtr = nextSegPtr;
+ nextSegPtr->prevPtr = segPtr;
+ } else {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr, linePtr, nextSegPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ linePtr = newLinePtr;
+ }
+ segPtr = nextSegPtr;
+ contentPtr = NULL;
+ state = isInsert ? STATE_LEFT_INSERT : STATE_LEFT;
+ break;
+ case 'r':
+ /*
+ * {"right" markname}
+ */
+
+ if (strcmp(type, "right") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (argc != 2) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ name = Tcl_GetString(argv[1]);
+ isInsert = (strcmp(name, "insert") == 0);
+ if (isInsert
+ && sharedTextPtr->steadyMarks
+ && (state & (STATE_LEFT|STATE_RIGHT))) {
+ return LoadError(interp, "unexpected \"insert\" mark", i, -1, -1, tagInfoPtr);
+ }
+ if (isInsert) {
+ UnlinkSegment(nextSegPtr = textPtr->insertMarkPtr);
+ } else if (!(nextSegPtr = TkTextMakeNewMark(textPtr, name))) {
+ return LoadError(interp, "mark already exists", i, 1, -1, tagInfoPtr);
+ }
+ assert(nextSegPtr->typePtr == &tkTextRightMarkType);
+ if (segPtr) {
+ segPtr->nextPtr = nextSegPtr;
+ nextSegPtr->prevPtr = segPtr;
+ } else {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr, linePtr, nextSegPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ linePtr = newLinePtr;
+ }
+ segPtr = nextSegPtr;
+ contentPtr = NULL;
+ state = isInsert ? STATE_RIGHT_INSERT : STATE_RIGHT;
+ break;
+ case 'e':
+ /*
+ * {"elide" "on"}, {"elide" "off"}
+ * These elements will be skipped, nevertheless we check the syntax.
+ */
+
+ if (strcmp(type, "elide") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (argc != 2) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ if (strcmp(Tcl_GetString(argv[1]), "on") != 0
+ && strcmp(Tcl_GetString(argv[1]), "off") != 0) {
+ return LoadError(interp, "\"on\" or \"off\" expected", i, 0, -1, tagInfoPtr);
+ }
+ state = STATE_TEXT;
+ break;
+ case 'i':
+ /*
+ * {"image" options tagInfo ?tagInfo?}
+ */
+
+ if (strcmp(type, "image") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (tagInfoCount == 0) {
+ tagInfoCount = argc - 2;
+ }
+ if (argc < 3 || 4 < argc || argc - tagInfoCount != 2) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ if (!(embPtr = TkTextMakeImage(textPtr, argv[1]))) {
+ return LoadError(interp, Tcl_GetString(Tcl_GetObjResult(interp)), i, 1, -1, tagInfoPtr);
+ }
+ if (!LoadMakeTagInfo(textPtr, &tagInfoPtr, argv[2])) {
+ return LoadError(interp, "list of tag names expected", i, 2, -1, tagInfoPtr);
+ }
+ TkTextTagSetIncrRefCount((nextSegPtr = embPtr)->tagInfoPtr = tagInfoPtr);
+ if (sharedTextPtr->numElisionTags > 0) {
+ nextSegPtr = LoadPerformElision(textPtr, embPtr, &branchPtr, contentPtr, &isElided);
+ }
+ if (segPtr) {
+ segPtr->nextPtr = nextSegPtr;
+ nextSegPtr->prevPtr = segPtr;
+ } else {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr, linePtr, nextSegPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ linePtr = newLinePtr;
+ }
+ size += 1;
+ contentPtr = segPtr = embPtr;
+ state = STATE_TEXT;
+ if (argc != 4) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ tagInfoPtr = NULL;
+ } else if (!LoadRemoveTags(textPtr, &tagInfoPtr, argv[3])) {
+ return LoadError(interp, "list of tag names expected", i, 3, -1, tagInfoPtr);
+ }
+ break;
+ case 'w':
+ /*
+ * {"window" options tagInfo ?tagInfo?}
+ */
+
+ if (strcmp(type, "window") != 0) {
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
+ if (tagInfoCount == 0) {
+ tagInfoCount = argc - 2;
+ }
+ if (argc < 3 || 4 < argc || argc - tagInfoCount != 2) {
+ return LoadError(interp, "wrong number of items", i, -1, -1, tagInfoPtr);
+ }
+ if (!(embPtr = TkTextMakeImage(textPtr, argv[1]))) {
+ return LoadError(interp, Tcl_GetString(Tcl_GetObjResult(interp)), i, 1, -1, tagInfoPtr);
+ }
+ if (!LoadMakeTagInfo(textPtr, &tagInfoPtr, argv[2])) {
+ return LoadError(interp, "list of tag names expected", i, 2, -1, tagInfoPtr);
+ }
+ TkTextTagSetIncrRefCount((nextSegPtr = embPtr)->tagInfoPtr = tagInfoPtr);
+ if (sharedTextPtr->numElisionTags > 0) {
+ nextSegPtr = LoadPerformElision(textPtr, embPtr, &branchPtr, contentPtr, &isElided);
+ }
+ if (segPtr) {
+ segPtr->nextPtr = nextSegPtr;
+ nextSegPtr->prevPtr = segPtr;
+ } else {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr, linePtr, nextSegPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ linePtr = newLinePtr;
+ }
+ size += 1;
+ contentPtr = segPtr = embPtr;
+ state = STATE_TEXT;
+ if (argc != 4) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ tagInfoPtr = NULL;
+ } else if (!LoadRemoveTags(textPtr, &tagInfoPtr, argv[3])) {
+ return LoadError(interp, "list of tag names expected", i, 3, -1, tagInfoPtr);
+ }
+ break;
+ default:
+ return LoadError(interp, "invalid item identifier", i, 0, -1, tagInfoPtr);
+ }
}
/*
- * Return total number of pixels in the tree.
+ * Possible we have to add last newline.
*/
- return nodePtr->numPixels[pixelReference];
+ if (state != STATE_BREAK) {
+ if (charSegPtr && TkTextTagSetIsEmpty(charSegPtr->tagInfoPtr)) {
+ charSegPtr = IncreaseCharSegment(charSegPtr, charSegPtr->size, 1);
+ charSegPtr->body.chars[charSegPtr->size - 1] = '\n';
+ linePtr->lastPtr = charSegPtr;
+ RebuildSections(sharedTextPtr, linePtr, true);
+ } else {
+ nextSegPtr = charSegPtr = MakeCharSeg(NULL, sharedTextPtr->emptyTagInfoPtr, 1, "\n", 1);
+ if (segPtr) {
+ segPtr->nextPtr = nextSegPtr;
+ nextSegPtr->prevPtr = segPtr;
+ linePtr->lastPtr = charSegPtr;
+ RebuildSections(sharedTextPtr, linePtr, true);
+ } else {
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr,
+ linePtr, nextSegPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ linePtr = newLinePtr;
+ }
+ }
+ size += 1;
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ } else {
+ changeToLineCount -= 1;
+ if (!isElided) {
+ changeToLogicalLineCount -= 1;
+ }
+ }
+
+ textPtr->state = textState;
+
+ if (tagInfoPtr) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ }
+
+ SubtractPixelCount2(treePtr, startLinePtr->parentPtr, -changeToLineCount,
+ -changeToLogicalLineCount, -changeToBranchCount, -size, changeToPixelInfo);
+ startLinePtr->parentPtr->numChildren += changeToLineCount;
+ UpdateNodeTags(sharedTextPtr, startLinePtr->parentPtr);
+
+ if (startLinePtr->parentPtr->numChildren > MAX_CHILDREN) {
+ Rebalance(treePtr, startLinePtr->parentPtr);
+ }
+
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+ return TCL_OK;
}
/*
@@ -1000,160 +4169,2000 @@ TkBTreeAdjustPixelHeight(
void
TkBTreeInsertChars(
TkTextBTree tree, /* Tree to insert into. */
- register TkTextIndex *indexPtr,
- /* Indicates where to insert text. When the
- * function returns, this index is no longer
- * valid because of changes to the segment
- * structure. */
- const char *string) /* Pointer to bytes to insert (may contain
- * newlines, must be null-terminated). */
-{
- register Node *nodePtr;
- register TkTextSegment *prevPtr;
- /* The segment just before the first new
- * segment (NULL means new segment is at
- * beginning of line). */
- TkTextSegment *curPtr; /* Current segment; new characters are
- * inserted just after this one. NULL means
- * insert at beginning of line. */
- TkTextLine *linePtr; /* Current line (new segments are added to
- * this line). */
- register TkTextSegment *segPtr;
+ TkTextIndex *indexPtr, /* Indicates where to insert text. When the function returns,
+ * this index contains the new position. */
+ const char *string, /* Pointer to bytes to insert (may contain newlines, must be
+ * null-terminated). */
+ TkTextTagSet *tagInfoPtr, /* Tag information for the new segments, can be NULL. */
+ TkTextTag *hyphenTagPtr, /* Tag information for hyphen segments, can be NULL. If not NULL
+ * this is a list of tags connected via 'nextPtr'. */
+ TkTextUndoInfo *undoInfo) /* Undo information, can be NULL. */
+{
+ TkSharedText *sharedTextPtr;
+ TkTextSegment *prevPtr; /* The segment just before the first new segment (NULL means new
+ * segment is at beginning of line). */
+ TkTextLine *linePtr; /* Current line (new segments are added to this line). */
+ int changeToLineCount; /* Counts change to total number of lines in file. */
+ int changeToLogicalLineCount;
+ /* Counts change to total number of logical lines in file. */
+ NodePixelInfo *changeToPixelInfo;
+ TkTextSegment *segPtr = NULL;
+ TkTextSegment *firstSegPtr;
+ TkTextSegment *lastSegPtr;
TkTextLine *newLinePtr;
- int chunkSize; /* # characters in current chunk. */
- register const char *eol; /* Pointer to character just after last one in
- * current chunk. */
- int changeToLineCount; /* Counts change to total number of lines in
- * file. */
- int *changeToPixelCount; /* Counts change to total number of pixels in
- * file. */
- int ref;
- int pixels[PIXEL_CLIENTS];
-
+ TkTextLine *firstLinePtr;
+ TkTextTagSet *emptyTagInfoPtr;
+ TkTextTagSet *hyphenTagInfoPtr = NULL;
+ TkTextTagSet *myTagInfoPtr;
+ TkTextTag *tagPtr;
+ TkTextTag *hyphenElideTagPtr = NULL;
+ TkTextIndex index;
+ UndoTokenInsert *undoToken = NULL;
BTree *treePtr = (BTree *) tree;
- treePtr->stateEpoch++;
- prevPtr = SplitSeg(indexPtr);
- linePtr = indexPtr->linePtr;
- curPtr = prevPtr;
+ bool split = true;
+ SplitInfo info;
+ unsigned chunkSize = 0; /* satisifies the compiler */
+ unsigned size = 0;
+ int hyphenRules = 0;
+
+ assert(*string); /* otherwise tag information might become erroneous */
+ assert(indexPtr->textPtr);
+
+ sharedTextPtr = treePtr->sharedTextPtr;
+
+ if (undoInfo) {
+ undoToken = malloc(sizeof(UndoTokenInsert));
+ undoToken->undoType = &undoTokenInsertType;
+ undoInfo->token = (TkTextUndoToken *) undoToken;
+ undoInfo->byteSize = 0;
+ MakeUndoIndex(sharedTextPtr, indexPtr, &undoToken->startIndex, GRAVITY_LEFT);
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ }
+
+ emptyTagInfoPtr = sharedTextPtr->emptyTagInfoPtr;
+ firstSegPtr = lastSegPtr = NULL;
+ prevPtr = NULL;
+ memset(&info, 0, sizeof(SplitInfo));
+ info.offset = -1;
+ info.tagInfoPtr = tagInfoPtr;
+ firstLinePtr = linePtr = TkTextIndexGetLine(indexPtr);
+ index = *indexPtr;
+ TkTextIndexGetByteIndex(indexPtr); /* we need byte offset */
+ changeToLineCount = 0;
+ changeToLogicalLineCount = 0;
+ changeToPixelInfo = treePtr->pixelInfoBuffer;
+ SetLineHasChanged(sharedTextPtr, linePtr);
+
+ if (tagInfoPtr && !TkTextTagSetContains(linePtr->parentPtr->tagonPtr, tagInfoPtr)) {
+ unsigned i;
+
+ /*
+ * Update the tag information of the B-Tree. Because the content of
+ * the node cannot be empty (it contains at least one newline char)
+ * we have also to add all new tags, not yet used inside this node,
+ * to the tagoff information.
+ */
+
+ for (i = TkTextTagSetFindFirst(tagInfoPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ if (!TkTextTagSetTest(linePtr->parentPtr->tagonPtr, i)) {
+ AddTagToNode(linePtr->parentPtr, sharedTextPtr->tagLookup[i], true);
+ }
+ }
+ }
+
+ if (hyphenTagPtr) {
+ int highestPriority = -1;
+ TkText *textPtr = index.textPtr;
+
+ for (tagPtr = hyphenTagPtr; tagPtr; tagPtr = tagPtr->nextPtr) {
+ if (!TkTextTagSetTest(linePtr->parentPtr->tagonPtr, tagPtr->index)) {
+ AddTagToNode(linePtr->parentPtr, tagPtr, true);
+ }
+ if (tagPtr->elideString
+ && tagPtr->priority > highestPriority
+ && (!tagPtr->textPtr || tagPtr->textPtr == textPtr)) {
+ highestPriority = (hyphenElideTagPtr = tagPtr)->priority;
+ }
+ }
+ }
+
+ DEBUG(indexPtr->discardConsistencyCheck = true);
/*
* Chop the string up into lines and create a new segment for each line,
* plus a new line for the leftovers from the previous line.
*/
- changeToLineCount = 0;
- if (treePtr->pixelReferences > PIXEL_CLIENTS) {
- changeToPixelCount = ckalloc(sizeof(int) * treePtr->pixelReferences);
- } else {
- changeToPixelCount = pixels;
- }
- for (ref = 0; ref < treePtr->pixelReferences; ref++) {
- changeToPixelCount[ref] = 0;
- }
+ while (*string) {
+ bool isNewline = false;
+ const char *strEnd = NULL;
+ const char *s;
- while (*string != 0) {
- for (eol = string; *eol != 0; eol++) {
- if (*eol == '\n') {
- eol++;
+ for (s = string; !strEnd; ++s) {
+ switch (UCHAR(*s)) {
+ case 0x00:
+ /* nul */
+ strEnd = s;
+ break;
+ case 0x0a:
+ /* line feed */
+ strEnd = s + 1;
+ isNewline = true;
+ break;
+ case 0xc2:
+ if (UCHAR(s[1]) == 0xad) {
+ /* soft hyphen (U+002D) */
+ strEnd = s;
+ hyphenRules = 0;
+ }
+ break;
+ case 0xff:
+ /*
+ * Hyphen support (0xff is not allowed in UTF-8 strings, it's a private flag
+ * denoting a soft hyphen, see ParseHyphens [tkText.c]).
+ */
+
+ strEnd = s;
+
+ switch (*++s) {
+ case '-': hyphenRules = 0; break;
+ case '+': hyphenRules = TK_TEXT_HYPHEN_MASK; break;
+ default: hyphenRules = UCHAR(*s); break;
+ }
break;
}
}
- chunkSize = eol-string;
- segPtr = ckalloc(CSEG_SIZE(chunkSize));
- segPtr->typePtr = &tkTextCharType;
- if (curPtr == NULL) {
- segPtr->nextPtr = linePtr->segPtr;
- linePtr->segPtr = segPtr;
+
+ chunkSize = strEnd - string;
+
+ if (chunkSize == 0) {
+ TkTextTag *tagPtr;
+
+ prevPtr = SplitSeg(indexPtr, NULL);
+ segPtr = MakeHyphen();
+ segPtr->body.hyphen.rules = hyphenRules;
+ LinkSegment(linePtr, prevPtr, segPtr);
+ SplitSection(segPtr->sectionPtr);
+ TkBTreeIncrEpoch(tree);
+ if (hyphenTagInfoPtr) {
+ assert(firstSegPtr);
+ TkTextTagSetIncrRefCount(segPtr->tagInfoPtr = hyphenTagInfoPtr);
+ } else {
+ if (tagInfoPtr) {
+ assert(tagInfoPtr == info.tagInfoPtr);
+ TkTextTagSetIncrRefCount(segPtr->tagInfoPtr = tagInfoPtr);
+ if (!firstSegPtr) {
+ firstSegPtr = segPtr;
+ }
+ } else {
+ assert(!firstSegPtr);
+ assert(!info.tagInfoPtr);
+ tagInfoPtr = segPtr->tagInfoPtr = MakeTagInfo(index.textPtr, segPtr);
+ info.tagInfoPtr = tagInfoPtr;
+ }
+ for (tagPtr = hyphenTagPtr; tagPtr; tagPtr = tagPtr->nextPtr) {
+ segPtr->tagInfoPtr = TagSetAdd(segPtr->tagInfoPtr, tagPtr);
+ }
+ hyphenTagInfoPtr = segPtr->tagInfoPtr;
+ }
+ info.offset = -1;
+ prevPtr = segPtr;
+ split = false;
+ size += segPtr->size;
} else {
- segPtr->nextPtr = curPtr->nextPtr;
- curPtr->nextPtr = segPtr;
+ size += chunkSize;
+
+ if (split) {
+ info.increase = chunkSize;
+ info.forceSplit = isNewline;
+ prevPtr = SplitSeg(indexPtr, &info);
+ }
+ if (info.offset >= 0) {
+ /*
+ * Fill increased/decreased char segment.
+ */
+ segPtr = prevPtr;
+ assert(segPtr->size >= info.offset + chunkSize);
+ memcpy(segPtr->body.chars + info.offset, string, chunkSize);
+ segPtr->sectionPtr->size += chunkSize;
+ linePtr->size += chunkSize;
+ assert(!tagInfoPtr || TkTextTagSetIsEqual(tagInfoPtr, segPtr->tagInfoPtr));
+ tagInfoPtr = segPtr->tagInfoPtr;
+ } else {
+ /*
+ * Insert new segment.
+ */
+
+ segPtr = MakeCharSeg(NULL, tagInfoPtr, chunkSize, string, chunkSize);
+ LinkSegment(linePtr, prevPtr, segPtr);
+ SplitSection(segPtr->sectionPtr);
+ TkBTreeIncrEpoch(tree);
+ }
+ prevPtr = segPtr;
+
+ assert(!firstSegPtr || tagInfoPtr);
+
+ if (!firstSegPtr) {
+ firstSegPtr = segPtr;
+
+ if (!tagInfoPtr) {
+ if (segPtr->tagInfoPtr) {
+ tagInfoPtr = segPtr->tagInfoPtr;
+ } else {
+ tagInfoPtr = MakeTagInfo(index.textPtr, segPtr);
+ }
+ info.tagInfoPtr = tagInfoPtr;
+ }
+ }
+
+ if (!segPtr->tagInfoPtr) {
+ TkTextTagSetIncrRefCount(segPtr->tagInfoPtr = tagInfoPtr);
+ } else {
+ assert(TkTextTagSetIsEqual(tagInfoPtr, segPtr->tagInfoPtr));
+ }
}
- segPtr->size = chunkSize;
- memcpy(segPtr->body.chars, string, (size_t) chunkSize);
- segPtr->body.chars[chunkSize] = 0;
- if (eol[-1] != '\n') {
- break;
+ assert(prevPtr);
+ lastSegPtr = segPtr;
+ string = strEnd + (chunkSize == 0 ? 2 : 0);
+ TkTextIndexAddToByteIndex(indexPtr, MAX(chunkSize, 1));
+
+ if (!isNewline) {
+ continue;
}
/*
- * The chunk ended with a newline, so create a new TkTextLine and move
- * the remainder of the old line to it.
+ * Update line tag information.
*/
- newLinePtr = ckalloc(sizeof(TkTextLine));
- newLinePtr->pixels =
- ckalloc(sizeof(int) * 2 * treePtr->pixelReferences);
+ if (changeToLineCount == 0
+ && (hyphenTagInfoPtr
+ || (tagInfoPtr && linePtr->tagonPtr != tagInfoPtr)
+ || linePtr->tagoffPtr != emptyTagInfoPtr)) {
+ /*
+ * In this case we have to recompute the line tag information, because
+ * the line will be split before segPtr->nextPtr.
+ */
+ RecomputeLineTagInfo(linePtr, segPtr->nextPtr, sharedTextPtr);
+ }
+
+ assert(segPtr->nextPtr);
- newLinePtr->parentPtr = linePtr->parentPtr;
- newLinePtr->nextPtr = linePtr->nextPtr;
- linePtr->nextPtr = newLinePtr;
- newLinePtr->segPtr = segPtr->nextPtr;
+ split = info.splitted;
+ info.splitted = false;
+ info.offset = -1;
/*
- * Set up a starting default height, which will be re-adjusted later.
- * We need to do this for each referenced widget.
+ * This chunk ended with a newline, so create a new text line and move
+ * the remainder of the old line to it.
*/
- for (ref = 0; ref < treePtr->pixelReferences; ref++) {
- newLinePtr->pixels[2 * ref] = linePtr->pixels[2 * ref];
- newLinePtr->pixels[2 * ref + 1] = 0;
- changeToPixelCount[ref] += newLinePtr->pixels[2 * ref];
+ if (changeToLineCount == 0) {
+ memset(changeToPixelInfo, 0, sizeof(changeToPixelInfo[0])*treePtr->numPixelReferences);
}
- segPtr->nextPtr = NULL;
+ newLinePtr = InsertNewLine(sharedTextPtr, linePtr->parentPtr, linePtr, segPtr->nextPtr);
+ AddPixelCount(treePtr, newLinePtr, linePtr, changeToPixelInfo);
+ if (hyphenTagInfoPtr) {
+ assert(TkTextTagSetContains(hyphenTagInfoPtr, tagInfoPtr));
+ assert(linePtr->tagoffPtr == emptyTagInfoPtr);
+ TagSetAssign(&newLinePtr->tagonPtr, hyphenTagInfoPtr);
+ TagSetAssign(&newLinePtr->tagoffPtr, hyphenTagInfoPtr);
+ newLinePtr->tagoffPtr = TagSetRemove(newLinePtr->tagoffPtr, tagInfoPtr, sharedTextPtr);
+ } else if (tagInfoPtr) {
+ TagSetAssign(&newLinePtr->tagonPtr, tagInfoPtr);
+ }
+ TkTextIndexSetByteIndex2(indexPtr, newLinePtr, 0);
+ prevPtr = NULL;
linePtr = newLinePtr;
- curPtr = NULL;
- changeToLineCount++;
+ changeToLineCount += 1;
+ changeToLogicalLineCount += linePtr->logicalLine;
+ }
+
+ /*
+ * Update line tag information of last line.
+ */
+
+ assert(tagInfoPtr || hyphenTagInfoPtr);
+
+ if (changeToLineCount == 0) {
+ if (hyphenTagInfoPtr) {
+ assert(TkTextTagSetContains(hyphenTagInfoPtr, tagInfoPtr));
+ linePtr->tagoffPtr = TagSetJoinNonIntersection(
+ linePtr->tagoffPtr, linePtr->tagonPtr, hyphenTagInfoPtr, sharedTextPtr);
+ linePtr->tagonPtr = TkTextTagSetJoin(linePtr->tagonPtr, hyphenTagInfoPtr);
+ myTagInfoPtr = hyphenTagInfoPtr;
+ } else if (linePtr->tagonPtr != tagInfoPtr || linePtr->tagoffPtr != emptyTagInfoPtr) {
+ linePtr->tagoffPtr = TagSetJoinNonIntersection(
+ linePtr->tagoffPtr, linePtr->tagonPtr, tagInfoPtr, sharedTextPtr);
+ linePtr->tagonPtr = TkTextTagSetJoin(linePtr->tagonPtr, tagInfoPtr);
+ }
+ } else {
+ SetLineHasChanged(sharedTextPtr, linePtr);
+ RecomputeLineTagInfo(linePtr, NULL, sharedTextPtr);
+ }
- string = eol;
+ myTagInfoPtr = hyphenTagInfoPtr ? hyphenTagInfoPtr : tagInfoPtr;
+
+ if (myTagInfoPtr) {
+ Node *nodePtr = linePtr->parentPtr;
+
+ if (nodePtr->tagonPtr != emptyTagInfoPtr) {
+ unsigned i;
+
+ /*
+ * Update the tag information of the B-Tree. Any tag in tagon of this
+ * node, which is not contained in myTagInfoPtr, has to be added to the
+ * tagoff information of this node.
+ */
+
+ for (i = TkTextTagSetFindFirst(nodePtr->tagonPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(nodePtr->tagonPtr, i)) {
+ if (!TkTextTagSetTest(myTagInfoPtr, i)) {
+ AddTagToNode(nodePtr, sharedTextPtr->tagLookup[i], true);
+ }
+ }
+ }
+ }
+
+ if (undoInfo) {
+ MakeUndoIndex(sharedTextPtr, indexPtr, &undoToken->endIndex, GRAVITY_LEFT);
}
/*
- * I don't believe it's possible for either of the two lines passed to
- * this function to be the last line of text, but the function is robust
- * to that case anyway. (We must never re-calculate the line height of
- * the last line).
+ * Increment the line and pixel counts in all the parent nodes of the
+ * insertion point, then rebalance the tree if necessary.
*/
- TkTextInvalidateLineMetrics(treePtr->sharedTextPtr, NULL,
- indexPtr->linePtr, changeToLineCount, TK_TEXT_INVALIDATE_INSERT);
+ SubtractPixelCount2(treePtr, linePtr->parentPtr, -changeToLineCount,
+ -changeToLogicalLineCount, 0, -size, changeToPixelInfo);
+
+ if ((linePtr->parentPtr->numChildren += changeToLineCount) > MAX_CHILDREN) {
+ Rebalance(treePtr, linePtr->parentPtr);
+ }
/*
- * Cleanup the starting line for the insertion, plus the ending line if
- * it's different.
+ * This line now needs to have its height recalculated. This has to be done after Rebalance.
*/
- CleanupLine(indexPtr->linePtr);
- if (linePtr != indexPtr->linePtr) {
- CleanupLine(linePtr);
+ TkTextInvalidateLineMetrics(sharedTextPtr, NULL, firstLinePtr,
+ changeToLineCount, TK_TEXT_INVALIDATE_INSERT);
+
+ /*
+ * Next step: update elision states if needed.
+ */
+
+ if (tagInfoPtr
+ && tagInfoPtr != emptyTagInfoPtr
+ && TkTextTagSetIntersectsBits(tagInfoPtr, sharedTextPtr->elisionTags)) {
+ int highestPriority = -1;
+ TkTextTag *tagPtr = NULL;
+ TkText *textPtr = index.textPtr;
+ unsigned i = TkTextTagSetFindFirst(tagInfoPtr);
+
+ /*
+ * We have to update the elision info, but only for the tag with the highest
+ * elide priority. This has to be done after TkTextInvalidateLineMetrics.
+ */
+
+ for ( ; i != TK_TEXT_TAG_SET_NPOS; i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ TkTextTag *tPtr = sharedTextPtr->tagLookup[i];
+
+ assert(tPtr);
+ assert(!tPtr->isDisabled);
+
+ if (tPtr->elideString
+ && tPtr->priority > highestPriority
+ && (!tPtr->textPtr || tPtr->textPtr == textPtr)) {
+ highestPriority = (tagPtr = tPtr)->priority;
+ }
+ }
+
+ if (tagPtr) {
+ firstSegPtr->protectionFlag = true;
+ lastSegPtr->protectionFlag = true;
+
+ UpdateElideInfo(sharedTextPtr, tagPtr, firstSegPtr, lastSegPtr, ELISION_HAS_BEEN_ADDED);
+
+ CleanupSplitPoint(firstSegPtr, sharedTextPtr);
+ CleanupSplitPoint(lastSegPtr, sharedTextPtr);
+
+ if (hyphenElideTagPtr == tagPtr) {
+ hyphenElideTagPtr = NULL;
+ }
+ }
}
+ if (hyphenElideTagPtr) {
+ firstSegPtr->protectionFlag = true;
+ lastSegPtr->protectionFlag = true;
+
+ UpdateElideInfo(sharedTextPtr, hyphenElideTagPtr, firstSegPtr, lastSegPtr,
+ ELISION_HAS_BEEN_ADDED);
+
+ CleanupSplitPoint(firstSegPtr, sharedTextPtr);
+ CleanupSplitPoint(lastSegPtr, sharedTextPtr);
+ }
+
+ TkTextIndexSetEpoch(indexPtr, TkBTreeIncrEpoch(tree));
+
+ TK_BTREE_DEBUG(TkBTreeCheck(indexPtr->tree));
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * MakeUndoIndex --
+ *
+ * Find undo/redo index of given segment. We prefer a predecessing
+ * mark segment at the same byte index, because such a mark is stable
+ * enough to work as a predecessor segment (e.g. for insertion),
+ * but alternatively, if no predecessing mark segments exists, we
+ * will store the line index, byte index, and possible offset inside
+ * a chain of (splitted) char segments. The gravity is specifiying
+ * the direction where we are searching for a mark, either at left
+ * side (for a starting index), or at right side (for an ending index).
+ *
+ * Results:
+ * 'indexPtr' will be filled appropriately.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+MakeUndoIndex(
+ const TkSharedText *sharedTextPtr,
+ const TkTextIndex *indexPtr, /* Convert this index. */
+ TkTextUndoIndex *undoIndexPtr, /* Pointer to resulting index. */
+ int gravity) /* +1 = right gravity, -1 = left gravity */
+{
+ TkTextSegment *segPtr;
+
+ assert(indexPtr);
+ assert(gravity == GRAVITY_LEFT || gravity == GRAVITY_RIGHT);
+
/*
- * Increment the line and pixel counts in all the parent nodes of the
- * insertion point, then rebalance the tree if necessary.
+ * At first, try to find a neighboring mark segment at the same byte
+ * index, but we cannot use the special marks "insert" and "current",
+ * and we cannot not use private marks.
+ */
+
+ if (sharedTextPtr->steadyMarks
+ && (segPtr = TkTextIndexGetSegment(indexPtr))
+ && segPtr->typePtr->group == SEG_GROUP_MARK) {
+ TkTextSegment *searchPtr = (gravity == GRAVITY_LEFT) ? segPtr->prevPtr : segPtr->nextPtr;
+
+ while (searchPtr && TkTextIsSpecialOrPrivateMark(searchPtr)) {
+ searchPtr = (gravity == GRAVITY_LEFT) ? searchPtr->prevPtr : searchPtr->nextPtr;
+ }
+
+ if (searchPtr && TkTextIsStableMark(searchPtr)) {
+ undoIndexPtr->u.markPtr = searchPtr;
+ undoIndexPtr->lineIndex = -1;
+ return;
+ }
+ }
+
+ undoIndexPtr->lineIndex = TkTextIndexGetLineNumber(indexPtr, NULL);
+ undoIndexPtr->u.byteIndex = TkTextIndexGetByteIndex(indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeMakeUndoIndex --
+ *
+ * Find undo/redo index of given segment. We prefer a predecessing
+ * mark segment at the same byte index, because such a mark is stable
+ * enough to work as a predecessor segment (e.g. for insertion),
+ * but alternatively, if no predecessing mark segments exists, we
+ * will store the line index, byte index, and possible offset inside
+ * a chain of (splitted) char segments. The search for the mark segment
+ * will be done at the left side of the specified segment.
+ *
+ * Results:
+ * 'indexPtr' will be filled appropriately.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkBTreeMakeUndoIndex(
+ const TkSharedText *sharedTextPtr,
+ TkTextSegment *segPtr, /* Find index of this segment. */
+ TkTextUndoIndex *indexPtr) /* Pointer to resulting index. */
+{
+ TkTextIndex index;
+
+ assert(segPtr);
+ assert(segPtr->typePtr); /* expired? */
+ assert(segPtr->sectionPtr); /* linked? */
+ assert(segPtr->typePtr != &tkTextCharType);
+
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&index, segPtr);
+ MakeUndoIndex(sharedTextPtr, &index, indexPtr, GRAVITY_LEFT);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeUndoIndexToIndex --
+ *
+ * Convert an undo/redo index to a normal index.
+ *
+ * Results:
+ * 'dstPtr' will be filled appropriately.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkBTreeUndoIndexToIndex(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoIndex *srcPtr,
+ TkTextIndex *dstPtr)
+{
+ TkTextIndexClear2(dstPtr, NULL, sharedTextPtr->tree);
+
+ if (srcPtr->lineIndex == -1) {
+ TkTextIndexSetSegment(dstPtr, srcPtr->u.markPtr);
+ } else {
+ TkTextLine *linePtr = TkBTreeFindLine(sharedTextPtr->tree, NULL, srcPtr->lineIndex);
+ assert(linePtr);
+ TkTextIndexSetByteIndex2(dstPtr, linePtr, srcPtr->u.byteIndex);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UndoIndexIsEqual --
+ *
+ * Test whether both indices are equal. Note that this test
+ * may return false even if both indices are referring the
+ * same position.
+ *
+ * Results:
+ * Return whether both indices are (probably) equal.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static bool
+UndoIndexIsEqual(
+ const TkTextUndoIndex *indexPtr1,
+ const TkTextUndoIndex *indexPtr2)
+{
+ if (indexPtr1->lineIndex == -1) {
+ return indexPtr2->u.markPtr && indexPtr1->u.markPtr == indexPtr2->u.markPtr;
+ }
+
+ if (indexPtr2->lineIndex == -1) {
+ return indexPtr1->u.markPtr && indexPtr1->u.markPtr == indexPtr2->u.markPtr;
+ }
+
+ return indexPtr1->lineIndex == indexPtr2->lineIndex
+ && indexPtr1->u.byteIndex == indexPtr2->u.byteIndex;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ReInsertSegment --
+ *
+ * Re-insert a previously removed segment at the given index.
+ * This function is not handling the special cases when a
+ * char segment will be inserted (join with neighbors, handling
+ * of newline char, updating the line tag information), the caller
+ * is responsible for this.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * A segment will be inserted into a segment chain.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ReInsertSegment(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoIndex *indexPtr,
+ TkTextSegment *segPtr,
+ bool updateNode)
+{
+ TkTextSegment *prevPtr;
+ TkTextLine *linePtr;
+
+ assert(sharedTextPtr);
+ assert(indexPtr);
+ assert(segPtr);
+ assert(!TkTextIsSpecialOrPrivateMark(segPtr));
+
+ if (indexPtr->lineIndex == -1) {
+ prevPtr = indexPtr->u.markPtr;
+ linePtr = prevPtr->sectionPtr->linePtr;
+
+ if (updateNode) {
+ TkTextIndex index;
+
+ linePtr = TkBTreeFindLine(sharedTextPtr->tree, NULL, indexPtr->lineIndex);
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ TkTextIndexSetByteIndex2(&index, linePtr, indexPtr->u.byteIndex);
+ TkBTreeLinkSegment(sharedTextPtr, segPtr, &index);
+ return;
+ }
+ } else {
+ TkTextIndex index;
+
+ assert(indexPtr->lineIndex >= 0);
+ assert(indexPtr->u.byteIndex >= 0);
+
+ linePtr = TkBTreeFindLine(sharedTextPtr->tree, NULL, indexPtr->lineIndex);
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ TkTextIndexSetByteIndex2(&index, linePtr, indexPtr->u.byteIndex);
+
+ if (updateNode) {
+ TkBTreeLinkSegment(sharedTextPtr, segPtr, &index);
+ return;
+ }
+
+ prevPtr = SplitSeg(&index, NULL);
+ }
+
+ LinkSegment(linePtr, prevPtr, segPtr);
+ SplitSection(segPtr->sectionPtr);
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeReInsertSegment --
+ *
+ * Re-insert a previously removed segment at the given index.
+ * This function is not handling the special cases when a
+ * char segment will be inserted (join with neighbors, handling
+ * of newline char, updating the line tag information), the caller
+ * is responsible for this.
+ *
+ * This function is updating the B-Tree.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * A segment will be inserted into a segment chain.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkBTreeReInsertSegment(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoIndex *indexPtr,
+ TkTextSegment *segPtr)
+{
+ ReInsertSegment(sharedTextPtr, indexPtr, segPtr, true);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * LinkMark --
+ *
+ * This function adds a mark segment to a B-tree at given location.
+ * It takes into account some rules about positions of marks and
+ * switches.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * 'succPtr' will be linked into its tree.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+LinkMark(
+ const TkSharedText *sharedTextPtr,
+ TkTextLine *linePtr,
+ TkTextSegment *prevPtr,
+ TkTextSegment *segPtr)
+{
+ assert(segPtr->typePtr->group == SEG_GROUP_MARK);
+
+ /*
+ * Start markers will be the left most mark.
+ * End markers will be the right most mark.
*/
- for (nodePtr = linePtr->parentPtr ; nodePtr != NULL;
- nodePtr = nodePtr->parentPtr) {
- nodePtr->numLines += changeToLineCount;
- for (ref = 0; ref < treePtr->pixelReferences; ref++) {
- nodePtr->numPixels[ref] += changeToPixelCount[ref];
+ if (segPtr->startEndMarkFlag) {
+ if (segPtr->typePtr == &tkTextLeftMarkType) {
+ /* This is a start marker. */
+ while (prevPtr
+ && prevPtr->typePtr->group == SEG_GROUP_MARK
+ && !prevPtr->startEndMarkFlag) {
+ prevPtr = prevPtr->prevPtr;
+ }
+ } else {
+ /* This is an end marker. */
+ if (!prevPtr
+ && linePtr->segPtr->typePtr->group == SEG_GROUP_MARK
+ && !linePtr->segPtr->startEndMarkFlag) {
+ prevPtr = linePtr->segPtr;
+ }
+ if (prevPtr) {
+ while (prevPtr->nextPtr
+ && prevPtr->nextPtr->typePtr->group == SEG_GROUP_MARK
+ && !prevPtr->nextPtr->startEndMarkFlag) {
+ prevPtr = prevPtr->nextPtr;
+ }
+ }
+ }
+ } else {
+ if (!prevPtr
+ && linePtr->segPtr->startEndMarkFlag
+ && linePtr->segPtr->typePtr == &tkTextLeftMarkType) {
+ prevPtr = linePtr->segPtr;
+ }
+ if (prevPtr) {
+ while (prevPtr->nextPtr
+ && prevPtr->nextPtr->startEndMarkFlag
+ && prevPtr->nextPtr->typePtr == &tkTextLeftMarkType) {
+ prevPtr = prevPtr->nextPtr;
+ }
+ }
+ while (prevPtr
+ && prevPtr->startEndMarkFlag
+ && prevPtr->typePtr == &tkTextRightMarkType) {
+ prevPtr = prevPtr->prevPtr;
}
}
- if (treePtr->pixelReferences > PIXEL_CLIENTS) {
- ckfree(changeToPixelCount);
+
+ /*
+ * We have to ensure that a branch will not be followed by marks,
+ * and a link will not be preceded by marks.
+ */
+
+ assert(!prevPtr || prevPtr->nextPtr); /* mark cannot be last segment */
+ assert(linePtr->segPtr); /* dito */
+
+ if (TkBTreeHaveElidedSegments(sharedTextPtr)) {
+ if (prevPtr) {
+ if (prevPtr->typePtr == &tkTextBranchType) {
+ prevPtr = prevPtr->prevPtr;
+ } else if (prevPtr->nextPtr->typePtr == &tkTextLinkType) {
+ prevPtr = prevPtr->nextPtr;
+ }
+ } else if (linePtr->segPtr->typePtr == &tkTextLinkType) {
+ prevPtr = linePtr->segPtr;
+ }
}
- nodePtr = linePtr->parentPtr;
- nodePtr->numChildren += changeToLineCount;
- if (nodePtr->numChildren > MAX_CHILDREN) {
- Rebalance(treePtr, nodePtr);
+ LinkSegment(linePtr, prevPtr, segPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * LinkSwitch --
+ *
+ * This function adds a new branch/link segment to a B-tree at given
+ * location. It takes into account that a branch will never be
+ * followed by marks, and a link will never by preceded by marks.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * 'succPtr' will be linked into its tree.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+LinkSwitch(
+ TkTextLine *linePtr, /* Pointer to line. */
+ TkTextSegment *predPtr, /* Pointer to segment within this line, can be NULL. */
+ TkTextSegment *succPtr) /* Link this segment after predPtr. */
+{
+ assert(predPtr || linePtr);
+ assert(succPtr);
+ assert(succPtr->typePtr->group == SEG_GROUP_BRANCH);
+
+ /*
+ * Note that the (temporary) protected segments are transparent.
+ */
+
+ if (succPtr->typePtr == &tkTextBranchType) {
+ if (!predPtr && (linePtr->segPtr->typePtr->group & (SEG_GROUP_MARK|SEG_GROUP_PROTECT))) {
+ predPtr = linePtr->segPtr;
+ }
+ if (predPtr) {
+ while (predPtr->nextPtr
+ && (predPtr->nextPtr->typePtr->group & (SEG_GROUP_MARK|SEG_GROUP_PROTECT))) {
+ predPtr = predPtr->nextPtr;
+ assert(predPtr); /* mark cannot be last segment */
+ }
+ }
+ } else { /* if (succPtr->typePtr == &tkTextLinkType) */
+ while (predPtr && (predPtr->typePtr->group & (SEG_GROUP_MARK|SEG_GROUP_PROTECT))) {
+ predPtr = predPtr->prevPtr;
+ }
}
- if (tkBTreeDebug) {
- TkBTreeCheck(indexPtr->tree);
+ LinkSegment(linePtr, predPtr, succPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * LinkSegment --
+ *
+ * This function adds a new segment to a B-tree at given location.
+ * Note that this function is not updating the tag information of
+ * the line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * 'succPtr' will be linked into its tree after 'predPtr', or
+ * at start of given line if 'predPtr' is NULL.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+LinkSegment(
+ TkTextLine *linePtr, /* Pointer to line. */
+ TkTextSegment *predPtr, /* Pointer to segment within this line, can be NULL. */
+ TkTextSegment *succPtr) /* Link this segment after predPtr. */
+{
+ assert(predPtr || linePtr);
+ assert(succPtr);
+ assert(!succPtr->sectionPtr); /* unlinked? */
+
+ if (predPtr) {
+ if (predPtr->typePtr == &tkTextBranchType) {
+ succPtr->sectionPtr = predPtr->nextPtr->sectionPtr;
+ succPtr->sectionPtr->segPtr = succPtr;
+ } else {
+ succPtr->sectionPtr = predPtr->sectionPtr;
+ }
+ succPtr->nextPtr = predPtr->nextPtr;
+ succPtr->prevPtr = predPtr;
+ predPtr->nextPtr = succPtr;
+ if (linePtr->lastPtr == predPtr) {
+ linePtr->lastPtr = succPtr;
+ }
+ } else {
+ assert(linePtr->segPtr);
+ if (linePtr->segPtr->typePtr == &tkTextLinkType) {
+ TkTextSection *newSectionPtr;
+
+ newSectionPtr = malloc(sizeof(TkTextSection));
+ newSectionPtr->linePtr = linePtr;
+ newSectionPtr->segPtr = succPtr;
+ newSectionPtr->nextPtr = linePtr->segPtr->sectionPtr->nextPtr;
+ newSectionPtr->prevPtr = NULL;
+ newSectionPtr->size = 0;
+ newSectionPtr->length = 0;
+ linePtr->segPtr->sectionPtr->prevPtr = newSectionPtr;
+ } else {
+ succPtr->sectionPtr = linePtr->segPtr->sectionPtr;
+ succPtr->sectionPtr->segPtr = succPtr;
+ }
+ succPtr->nextPtr = linePtr->segPtr;
+ succPtr->prevPtr = NULL;
+ linePtr->segPtr = succPtr;
+ }
+ if (succPtr->nextPtr) {
+ succPtr->nextPtr->prevPtr = succPtr;
+ }
+ linePtr->size += succPtr->size;
+ succPtr->sectionPtr->size += succPtr->size;
+ succPtr->sectionPtr->length += 1;
+ assert(succPtr->sectionPtr->length != 0); /* test for overflow */
+
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UnlinkSegmentAndCleanup --
+ *
+ * This function removes a segment from a B-tree. Furthermore
+ * it will do a cleanup with the predecessing segment.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * 'segPtr' will be unlinked from its tree, possibly a cleanup will
+ * be done.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+UnlinkSegmentAndCleanup(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ TkTextSegment *segPtr) /* Unlink this segment. */
+{
+ TkTextSegment *prevPtr;
+
+ assert(segPtr);
+
+ prevPtr = segPtr->prevPtr;
+ UnlinkSegment(segPtr);
+
+ if (prevPtr && prevPtr->typePtr == &tkTextCharType) {
+ CleanupCharSegments(sharedTextPtr, prevPtr);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UnlinkSegment --
+ *
+ * This function removes a segment from a B-tree.
+ *
+ * Results:
+ * The predecessor of the unlinked segment.
+ *
+ * Side effects:
+ * 'segPtr' will be unlinked from its tree.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeSection(
+ TkTextSection *sectionPtr)
+{
+ assert(sectionPtr->linePtr);
+ assert(!(sectionPtr->linePtr = NULL));
+ free(sectionPtr);
+ DEBUG_ALLOC(tkTextCountDestroySection++);
+}
+
+static TkTextSegment *
+UnlinkSegment(
+ TkTextSegment *segPtr) /* Unlink this segment. */
+{
+ TkTextSegment *prevPtr = segPtr->prevPtr;
+
+ if (prevPtr) {
+ prevPtr->nextPtr = segPtr->nextPtr;
+ } else {
+ segPtr->sectionPtr->linePtr->segPtr = segPtr->nextPtr;
+ }
+ if (segPtr->nextPtr) {
+ segPtr->nextPtr->prevPtr = prevPtr;
+ }
+ if (segPtr->sectionPtr->segPtr == segPtr) {
+ segPtr->sectionPtr->segPtr = segPtr->nextPtr;
+ }
+ if (segPtr->sectionPtr->linePtr->lastPtr == segPtr) {
+ segPtr->sectionPtr->linePtr->lastPtr = prevPtr;
+ }
+ segPtr->sectionPtr->linePtr->size -= segPtr->size;
+ if (--segPtr->sectionPtr->length == 0) {
+ /*
+ * This can happen in rare cases, e.g. the line is starting with a Branch.
+ * We have to free the unused section.
+ */
+ FreeSection(segPtr->sectionPtr);
+ segPtr->nextPtr->sectionPtr->prevPtr = NULL;
+ } else {
+ segPtr->sectionPtr->size -= segPtr->size;
+ }
+ segPtr->sectionPtr = NULL;
+ return prevPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * ComputeSectionSize --
+ *
+ * Count the sum of all sizes in current section starting at
+ * given section.
+ *
+ * Results:
+ * The return value is the sum.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static unsigned
+ComputeSectionSize(
+ const TkTextSegment *segPtr) /* Start counting at this segment. */
+{
+ const TkTextSection *sectionPtr = segPtr->sectionPtr;
+ unsigned size = 0;
+
+ for ( ; segPtr && segPtr->sectionPtr == sectionPtr; segPtr = segPtr->nextPtr) {
+ size += segPtr->size;
+ }
+
+ return size;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * CountSegments --
+ *
+ * Count the number of segments belonging to the given section.
+ *
+ * Results:
+ * The return value is the count.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static unsigned
+CountSegments(
+ const TkTextSection *sectionPtr) /* Pointer to section of text segments. */
+{
+ const TkTextSegment *segPtr;
+ unsigned count = 0;
+
+ for (segPtr = sectionPtr->segPtr;
+ segPtr && segPtr->sectionPtr == sectionPtr;
+ segPtr = segPtr->nextPtr, ++count) {
+ /* empty body */
+ }
+
+ return count;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * SplitSection --
+ *
+ * This function is called after new segments has been added to a
+ * section. It ensures that no more than MAX_TEXT_SEGS segments will
+ * belong to this section, by reducing the number of segments. If
+ * necessary a new section will be created.
+ *
+ * It is guaranteed that a split operation will be performed in
+ * constant time.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The section referred to by sectionPtr may change, and also the
+ * the neighboring sections may change.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+SplitSection(
+ TkTextSection *sectionPtr) /* Pointer to section of text segments. */
+{
+ TkTextSegment *segPtr, *splitSegPtr;
+ TkTextSection *newSectionPtr, *prevPtr, *nextPtr;
+ int length;
+ int lengthLHS, lengthRHS;
+ int shiftLHS, shiftRHS;
+ int capacityLHS, capacityRHS;
+
+ assert(!sectionPtr->prevPtr || sectionPtr->prevPtr->length <= MAX_TEXT_SEGS);
+ assert(!sectionPtr->nextPtr || sectionPtr->nextPtr->length <= MAX_TEXT_SEGS);
+
+ if ((length = sectionPtr->length) <= NUM_TEXT_SEGS) {
+ return;
+ }
+
+ /*
+ * The correctness of this implementation depends on the fact that
+ * a section can never contain more than MAX_TEXT_SEGS+NUM_TEXT_SEGS
+ * segments.
+ */
+ assert(length <= MAX_TEXT_SEGS + NUM_TEXT_SEGS);
+
+ segPtr = sectionPtr->nextPtr ? sectionPtr->nextPtr->segPtr->prevPtr : sectionPtr->linePtr->lastPtr;
+ for (lengthLHS = length - 1; lengthLHS > NUM_TEXT_SEGS; --lengthLHS) {
+ segPtr = segPtr->prevPtr;
+ }
+ splitSegPtr = segPtr;
+
+ /*
+ * We have to take into account that a branch segment must be
+ * the last segment inside a section, and a link segment must
+ * be the first segment inside a section.
+ */
+
+ prevPtr = sectionPtr->prevPtr;
+ nextPtr = sectionPtr->nextPtr;
+
+ if (prevPtr && IsBranchSection(prevPtr)) {
+ prevPtr = NULL; /* we cannot shift to the left */
+ }
+ if (nextPtr && IsLinkSection(nextPtr)) {
+ nextPtr = NULL; /* we cannot shift to the right */
+ }
+
+ lengthLHS = prevPtr ? prevPtr->length : 0;
+ lengthRHS = nextPtr ? nextPtr->length : 0;
+
+ capacityLHS = lengthLHS ? MAX(0, NUM_TEXT_SEGS - lengthLHS) : 0;
+ capacityRHS = lengthRHS ? MAX(0, NUM_TEXT_SEGS - lengthRHS) : 0;
+
+ /*
+ * We have to consider two cases:
+ *
+ * =====================================================================
+ * (capacityLHS + capacityRHS < length - MAX_TEXT_SEGS) OR
+ * (lengthRHS == 0 AND capacityLHS < length - NUM_TEXT_SEGS):
+ * =====================================================================
+ *
+ * 1. Shift as many segments as possible to the left segment (if
+ * exisiting), but the length of NUM_TEXT_SEGS should not be
+ * exceeded.
+ *
+ * 2. We have to insert a new section at the right. Shift segments into
+ * this new segment, until this section has NUM_TEXT_SEGS segments.
+ *
+ * =====================================================================
+ * otherwise:
+ * =====================================================================
+ *
+ * In this case this section will reduced while shifting to left and
+ * right neighbors, so that each neighbor will not exceed NUM_TEXT_SEGS
+ * segments with this operation.
+ */
+
+ if (capacityLHS + capacityRHS < length - MAX_TEXT_SEGS
+ || (lengthRHS == 0 && capacityLHS < length - NUM_TEXT_SEGS)) {
+ if (capacityLHS) {
+ TkTextSegment *segPtr = sectionPtr->segPtr;
+ int i;
+
+ for (i = capacityLHS; i < capacityLHS; ++i) {
+ sectionPtr->size -= segPtr->size;
+ sectionPtr->length -= 1;
+ sectionPtr->prevPtr->size += segPtr->size;
+ sectionPtr->prevPtr->length += 1;
+ assert(sectionPtr->prevPtr->length != 0); /* test for overflow */
+ segPtr->sectionPtr = sectionPtr->prevPtr;
+ segPtr = segPtr->nextPtr;
+ splitSegPtr = splitSegPtr->nextPtr;
+ }
+ sectionPtr->segPtr = segPtr;
+ }
+
+ assert(splitSegPtr);
+ assert(lengthRHS == 0 || length - capacityLHS >= MIN_TEXT_SEGS);
+
+ newSectionPtr = malloc(sizeof(TkTextSection));
+ newSectionPtr->linePtr = sectionPtr->linePtr;
+ newSectionPtr->segPtr = splitSegPtr;
+ newSectionPtr->nextPtr = sectionPtr->nextPtr;
+ newSectionPtr->prevPtr = sectionPtr;
+ newSectionPtr->size = 0;
+ newSectionPtr->length = 0;
+ if (sectionPtr->nextPtr) {
+ sectionPtr->nextPtr->prevPtr = newSectionPtr;
+ }
+ sectionPtr->nextPtr = newSectionPtr;
+ DEBUG_ALLOC(tkTextCountNewSection++);
+
+ for ( ; splitSegPtr && splitSegPtr->sectionPtr == sectionPtr;
+ splitSegPtr = splitSegPtr->nextPtr) {
+ newSectionPtr->size += splitSegPtr->size;
+ newSectionPtr->length += 1;
+ assert(newSectionPtr->length != 0); /* test for overflow */
+ sectionPtr->size -= splitSegPtr->size;
+ sectionPtr->length -= 1;
+ splitSegPtr->sectionPtr = newSectionPtr;
+ }
+ } else {
+ int exceed;
+
+ shiftLHS = MIN(capacityLHS, MAX(0, length - NUM_TEXT_SEGS));
+ shiftRHS = MIN(capacityRHS, length - NUM_TEXT_SEGS - shiftLHS);
+
+ if (shiftLHS > 0) {
+ TkTextSegment *segPtr = sectionPtr->segPtr;
+
+ for ( ; shiftLHS > 0; --shiftLHS) {
+ sectionPtr->size -= segPtr->size;
+ sectionPtr->length -= 1;
+ sectionPtr->prevPtr->size += segPtr->size;
+ sectionPtr->prevPtr->length += 1;
+ assert(sectionPtr->prevPtr->length != 0); /* test for overflow */
+ segPtr->sectionPtr = sectionPtr->prevPtr;
+ segPtr = segPtr->nextPtr;
+ }
+ sectionPtr->segPtr = segPtr;
+ }
+
+ if (shiftRHS > 0) {
+ /*
+ * Reduce the split until it fits the capacity of the neighbor.
+ */
+
+ exceed = length - NUM_TEXT_SEGS - shiftLHS - shiftRHS;
+ for ( ; exceed > 0; splitSegPtr = splitSegPtr->nextPtr, --exceed) {
+ /* empty loop body */
+ }
+
+ assert(splitSegPtr);
+ sectionPtr->nextPtr->segPtr = splitSegPtr;
+ while (splitSegPtr && splitSegPtr->sectionPtr == sectionPtr) {
+ sectionPtr->size -= splitSegPtr->size;
+ sectionPtr->length -= 1;
+ sectionPtr->nextPtr->size += splitSegPtr->size;
+ sectionPtr->nextPtr->length += 1;
+ assert(sectionPtr->nextPtr->length != 0); /* test for overflow */
+ splitSegPtr->sectionPtr = sectionPtr->nextPtr;
+ splitSegPtr = splitSegPtr->nextPtr;
+ }
+ }
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * JoinSections --
+ *
+ * This function is called after segments has been removed from a
+ * section. It ensures that either at least MIN_TEXT_SEGS will belong
+ * to this section, or that this section will be removed. Of course
+ * this must be ensured only if this section is not the rightmost
+ * section of this line.
+ *
+ * It is guaranteed that a join operation will be constant in constant
+ * time.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The section referred to by sectionPtr may change, and also the
+ * the neighboring sections may change. The section referred to by
+ * sectionPtr will be destroyed if not needed anymore.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+JoinSections(
+ TkTextSection *sectionPtr) /* Pointer to section of text segments. */
+{
+ TkTextSegment *segPtr;
+ bool isBranchSegment, isLinkSegment;
+ int length;
+
+ assert(!sectionPtr->prevPtr || sectionPtr->prevPtr->length <= MAX_TEXT_SEGS);
+ assert(!sectionPtr->nextPtr || sectionPtr->nextPtr->length <= MAX_TEXT_SEGS);
+
+ length = sectionPtr->length;
+
+ if (length == 0) {
+ /*
+ * This section is empty, so remove it. Note that this
+ * cannot happen if the line contains only one section.
+ */
+ assert(sectionPtr->prevPtr);
+ assert(sectionPtr->length == 0);
+ sectionPtr->prevPtr->nextPtr = sectionPtr->nextPtr;
+ if (sectionPtr->nextPtr) {
+ sectionPtr->nextPtr->prevPtr = sectionPtr->prevPtr;
+ }
+ FreeSection(sectionPtr);
+ return;
+ }
+
+ isBranchSegment = IsBranchSection(sectionPtr);
+ isLinkSegment = IsLinkSection(sectionPtr);
+
+ if (sectionPtr->nextPtr
+ && !isBranchSegment
+ && !IsLinkSection(sectionPtr->nextPtr)
+ && length < MIN_TEXT_SEGS) {
+ /*
+ * This section does not end with a Branch, we have a right
+ * neighbor, and the length of this section has undershot
+ * MIN_TEXT_SEGS segments. We have to remove this section,
+ * while shifting the content to the neighbors.
+ */
+
+ int lengthRHS = 0, capacity, shift;
+
+ if (sectionPtr->prevPtr && !isLinkSegment && !IsBranchSection(sectionPtr->prevPtr)) {
+ int lengthLHS = sectionPtr->prevPtr->length;
+ assert(lengthLHS > 0);
+
+ /*
+ * Move segments to left neighbor, but regard that the
+ * neighbor will not exceed NUM_TEXT_SEGS segments with
+ * this operation.
+ */
+
+ if ((capacity = MAX(0, NUM_TEXT_SEGS - lengthLHS)) > 0) {
+ shift = MIN(capacity, length);
+ segPtr = sectionPtr->segPtr;
+ for ( ; lengthLHS < NUM_TEXT_SEGS && 0 < shift; --shift) {
+ length -= 1;
+ sectionPtr->prevPtr->size += segPtr->size;
+ sectionPtr->prevPtr->length += 1;
+ assert(sectionPtr->prevPtr->length != 0); /* test for overflow */
+ sectionPtr->size -= segPtr->size;
+ sectionPtr->length -= 1;
+ segPtr->sectionPtr = sectionPtr->prevPtr;
+ segPtr = segPtr->nextPtr;
+ }
+ sectionPtr->segPtr = segPtr;
+ }
+ }
+
+ if (length > 0) {
+ lengthRHS = sectionPtr->nextPtr->length;
+ assert(lengthRHS > 0);
+
+ /*
+ * Move the remaining segments to right neighbor. Here
+ * it may happen that MAX_TEXT_SEGS will be exceeded.
+ */
+
+ sectionPtr->nextPtr->segPtr = sectionPtr->segPtr;
+ sectionPtr->nextPtr->size += sectionPtr->size;
+ sectionPtr->nextPtr->length += sectionPtr->length;
+ assert(sectionPtr->nextPtr->length >= sectionPtr->length); /* test for overflow */
+ for (segPtr = sectionPtr->segPtr;
+ segPtr && segPtr->sectionPtr == sectionPtr;
+ segPtr = segPtr->nextPtr) {
+ segPtr->sectionPtr = sectionPtr->nextPtr;
+ }
+ }
+
+ if (sectionPtr->prevPtr) {
+ sectionPtr->prevPtr->nextPtr = sectionPtr->nextPtr;
+ }
+ sectionPtr->nextPtr->prevPtr = sectionPtr->prevPtr;
+ FreeSection(sectionPtr);
+
+ if (lengthRHS + length > MAX_TEXT_SEGS) {
+ /*
+ * Right shift operation has exceeded MAX_TEXT_SEGS, so we
+ * have to split the right neighbor.
+ */
+ SplitSection(sectionPtr->nextPtr);
+ }
+ } else if (length > NUM_TEXT_SEGS) {
+ int lengthRHS, shift;
+
+ /*
+ * Move some segments to the neighbors for a better dstribution,
+ * but do not exceed NUM_TEXT_SEGS segments of the neighbors
+ * with this operation. Also do not undershot NUM_TEXT_SEGS of
+ * current section.
+ */
+
+ if (sectionPtr->prevPtr
+ && !isLinkSegment
+ && !IsBranchSection(sectionPtr->prevPtr)) {
+ int lengthLHS = sectionPtr->prevPtr->length;
+
+ if (lengthLHS < NUM_TEXT_SEGS) {
+ shift = MIN(length - NUM_TEXT_SEGS, NUM_TEXT_SEGS - lengthLHS);
+ assert(shift < length);
+ if (shift > 0) {
+ segPtr = sectionPtr->segPtr;
+ for ( ; shift > 0; --shift, --length) {
+ sectionPtr->prevPtr->size += segPtr->size;
+ sectionPtr->prevPtr->length += 1;
+ assert(sectionPtr->prevPtr->length != 0); /* test for overflow */
+ sectionPtr->size -= segPtr->size;
+ sectionPtr->length -= 1;
+ segPtr->sectionPtr = sectionPtr->prevPtr;
+ segPtr = segPtr->nextPtr;
+ }
+ sectionPtr->segPtr = segPtr;
+ }
+ }
+ }
+
+ if (sectionPtr->nextPtr && !isBranchSegment && !IsLinkSection(sectionPtr->nextPtr)) {
+ lengthRHS = sectionPtr->nextPtr->length;
+
+ if (lengthRHS < NUM_TEXT_SEGS) {
+ shift = MIN(length - NUM_TEXT_SEGS, NUM_TEXT_SEGS - lengthRHS);
+ assert(shift < length);
+ if (shift > 0) {
+ int i;
+ segPtr = sectionPtr->segPtr;
+ for (i = length - shift; i > 0; --i) {
+ segPtr = segPtr->nextPtr;
+ }
+ sectionPtr->nextPtr->segPtr = segPtr;
+ for ( ; shift > 0; --shift) {
+ sectionPtr->nextPtr->size += segPtr->size;
+ sectionPtr->nextPtr->length += 1;
+ assert(sectionPtr->nextPtr->length != 0); /* test for overflow */
+ sectionPtr->size -= segPtr->size;
+ sectionPtr->length -= 1;
+ segPtr->sectionPtr = sectionPtr->nextPtr;
+ segPtr = segPtr->nextPtr;
+ }
+ assert(segPtr->sectionPtr != sectionPtr);
+ }
+ }
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RebuildSections --
+ *
+ * The line has massively changed, so we have to rebuild all the sections
+ * in this line. This function will also recompute the total char size in
+ * this line. Furthermore superfluous sections will be freed.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Possibly new sections will be allocated, some sections may be freed,
+ * many sections will be modified, and the char size of the line will be
+ * modified.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+PropagateChangeOfNumBranches(
+ Node *nodePtr,
+ int changeToNumBranches)
+{
+ for ( ; nodePtr; nodePtr = nodePtr->parentPtr) {
+ nodePtr->numBranches += changeToNumBranches;
+ assert((int) nodePtr->numBranches >= 0);
+ }
+}
+
+static void
+RebuildSections(
+ TkSharedText *sharedTextPtr,
+ TkTextLine *linePtr, /* Pointer to existing line */
+ bool propagateChangeOfNumBranches) /* Should we propagate a change in number of branches
+ * to B-Tree? */
+{
+ TkTextSection *sectionPtr, *prevSectionPtr;
+ TkTextSegment *segPtr;
+ unsigned length;
+ int changeToNumBranches;
+
+ prevSectionPtr = NULL;
+ sectionPtr = linePtr->segPtr->sectionPtr;
+
+ assert(!sectionPtr || !sectionPtr->prevPtr);
+ assert(!linePtr->lastPtr->nextPtr);
+ assert(!propagateChangeOfNumBranches
+ || TkBTreeGetRoot(sharedTextPtr->tree)->numBranches >= linePtr->numBranches);
+
+ changeToNumBranches = -linePtr->numBranches;
+ linePtr->numBranches = 0;
+ linePtr->numLinks = 0;
+ linePtr->size = 0;
+
+ for (segPtr = linePtr->segPtr; segPtr; ) {
+ if (!sectionPtr) {
+ TkTextSection *newSectionPtr;
+
+ newSectionPtr = memset(malloc(sizeof(TkTextSection)), 0, sizeof(TkTextSection));
+ if (prevSectionPtr) {
+ prevSectionPtr->nextPtr = newSectionPtr;
+ } else {
+ linePtr->segPtr->sectionPtr = newSectionPtr;
+ }
+ newSectionPtr->prevPtr = prevSectionPtr;
+ sectionPtr = newSectionPtr;
+ DEBUG_ALLOC(tkTextCountNewSection++);
+ } else {
+ sectionPtr->size = 0;
+ sectionPtr->length = 0;
+ }
+
+ sectionPtr->segPtr = segPtr;
+ sectionPtr->linePtr = linePtr;
+
+ if (segPtr->typePtr == &tkTextLinkType) {
+ linePtr->numLinks += 1;
+ }
+
+ /*
+ * It is important to consider that a Branch is always at the end
+ * of a section, and a Link is always at the start of a section.
+ */
+
+ for (length = 0; length < NUM_TEXT_SEGS; ++length) {
+ TkTextSegment *prevPtr = segPtr;
+
+ sectionPtr->size += segPtr->size;
+ sectionPtr->length += 1;
+ assert(sectionPtr->length != 0); /* test for overflow */
+ segPtr->sectionPtr = sectionPtr;
+ segPtr = segPtr->nextPtr;
+
+ if (prevPtr->typePtr == &tkTextBranchType) {
+ linePtr->numBranches += 1;
+ break;
+ }
+ if (!segPtr || segPtr->typePtr == &tkTextLinkType) {
+ break;
+ }
+ }
+
+ linePtr->size += sectionPtr->size;
+ prevSectionPtr = sectionPtr;
+ sectionPtr = sectionPtr->nextPtr;
+ }
+
+ if (propagateChangeOfNumBranches && (changeToNumBranches += linePtr->numBranches) != 0) {
+ PropagateChangeOfNumBranches(linePtr->parentPtr, changeToNumBranches);
+ }
+
+ if (sectionPtr) {
+ /*
+ * Free unused sections.
+ */
+ if (sectionPtr->prevPtr) {
+ sectionPtr->prevPtr->nextPtr = NULL;
+ }
+ FreeSections(sectionPtr);
+ }
+
+ assert(CheckSections(linePtr));
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * FreeSections --
+ *
+ * This function is freeing all sections belonging to the text line
+ * starting at sectionPtr.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * All the section structures in this line starting at sectionPtr will
+ * be freed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+FreeSections(
+ TkTextSection *sectionPtr) /* Pointer to first section to be freed. */
+{
+ TkTextSection *nextPtr;
+
+ while (sectionPtr) {
+ assert(sectionPtr->linePtr); /* otherwise already freed */
+ nextPtr = sectionPtr->nextPtr;
+ FreeSection(sectionPtr);
+ sectionPtr = nextPtr;
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkBTreeFreeSegment --
+ *
+ * Decrement reference counter and free the segment if not
+ * referenced anymore.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The reference counter will be decrement, and if zero,
+ * then the storage for this segment will be freed.
+ *
+ *--------------------------------------------------------------
+ */
+
+void
+TkBTreeFreeSegment(
+ TkTextSegment *segPtr)
+{
+ assert(segPtr->refCount > 0);
+
+ if (--segPtr->refCount == 0) {
+ if (segPtr->tagInfoPtr) {
+ TkTextTagSetDecrRefCount(segPtr->tagInfoPtr);
+ }
+ FREE_SEGMENT(segPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * FreeLine --
+ *
+ * Free all resources of the given line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some storage will be freed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+FreeLine(
+ const BTree *treePtr,
+ TkTextLine *linePtr)
+{
+ int i;
+
+ assert(linePtr->parentPtr);
+ DEBUG(linePtr->parentPtr = NULL);
+
+ for (i = 0; i < treePtr->numPixelReferences; ++i) {
+ TkTextDispLineInfo *dispLineInfo = linePtr->pixelInfo[i].dispLineInfo;
+
+ if (dispLineInfo) {
+ free(dispLineInfo);
+ DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
+ }
+ }
+
+ TkTextTagSetDecrRefCount(linePtr->tagoffPtr);
+ TkTextTagSetDecrRefCount(linePtr->tagonPtr);
+ free(linePtr->pixelInfo);
+ DEBUG(linePtr->pixelInfo = NULL);
+ free(linePtr);
+ DEBUG_ALLOC(tkTextCountDestroyPixelInfo++);
+ DEBUG_ALLOC(tkTextCountDestroyLine++);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * MakeCharSeg --
+ *
+ * Make new char segment with given text.
+ *
+ * Results:
+ * The return value is a pointer to the new segment.
+ *
+ * Side effects:
+ * Storage for new segment will be allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static TkTextSegment *
+MakeCharSeg(
+ TkTextSection *sectionPtr, /* Section of new segment, can be NULL. */
+ TkTextTagSet *tagInfoPtr, /* Tga information for new segment, can be NULL. */
+ unsigned newSize, /* Character size of the new segment. */
+ const char *string, /* New text content. */
+ unsigned length) /* Number of characters to copy. */
+{
+ unsigned capacity;
+ TkTextSegment *newPtr;
+
+ assert(length <= newSize);
+
+ capacity = CSEG_CAPACITY(newSize);
+ newPtr = memset(malloc(CSEG_SIZE(capacity)), 0, SEG_SIZE(0));
+ newPtr->typePtr = &tkTextCharType;
+ newPtr->sectionPtr = sectionPtr;
+ newPtr->size = newSize;
+ newPtr->refCount = 1;
+ memcpy(newPtr->body.chars, string, length);
+ memset(newPtr->body.chars + length, 0, capacity - length);
+ if ((newPtr->tagInfoPtr = tagInfoPtr)) {
+ TkTextTagSetIncrRefCount(tagInfoPtr);
+ }
+ DEBUG_ALLOC(tkTextCountNewSegment++);
+ return newPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * CopyCharSeg --
+ *
+ * Make new char segment, and copy text from given segment.
+ *
+ * Results:
+ * The return value is a pointer to the new segment.
+ *
+ * Side effects:
+ * Storage for new segment will be allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static TkTextSegment *
+CopyCharSeg(
+ TkTextSegment *segPtr, /* Copy text from this segment. */
+ unsigned offset, /* Copy text starting at this offset. */
+ unsigned length, /* Number of characters to copy. */
+ unsigned newSize) /* Character size of the new segment. */
+{
+ assert(segPtr);
+ assert(segPtr->typePtr == &tkTextCharType);
+ assert(segPtr->size >= offset + length);
+
+ return MakeCharSeg(segPtr->sectionPtr, segPtr->tagInfoPtr, newSize,
+ segPtr->body.chars + offset, length);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * SplitCharSegment --
+ *
+ * This function 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 TkTextSegment *
+SplitCharSegment(
+ TkTextSegment *segPtr, /* Pointer to segment to split. */
+ unsigned index) /* Position within segment at which to split. */
+{
+ TkTextSegment *newPtr1, *newPtr2;
+
+ assert(segPtr);
+ assert(segPtr->typePtr == &tkTextCharType);
+ assert(segPtr->sectionPtr); /* otherwise segment is freed */
+ assert(index > 0);
+ assert(index < segPtr->size);
+
+ newPtr1 = CopyCharSeg(segPtr, 0, index, index);
+ newPtr2 = CopyCharSeg(segPtr, index, segPtr->size - index, segPtr->size - index);
+
+ newPtr1->nextPtr = newPtr2;
+ newPtr1->prevPtr = segPtr->prevPtr;
+ newPtr2->nextPtr = segPtr->nextPtr;
+ newPtr2->prevPtr = newPtr1;
+
+ if (segPtr->prevPtr) {
+ segPtr->prevPtr->nextPtr = newPtr1;
+ } else {
+ segPtr->sectionPtr->linePtr->segPtr = newPtr1;
+ }
+ if (segPtr->nextPtr) {
+ segPtr->nextPtr->prevPtr = newPtr2;
+ }
+ if (segPtr->sectionPtr->segPtr == segPtr) {
+ segPtr->sectionPtr->segPtr = newPtr1;
+ }
+ if (segPtr->sectionPtr->linePtr->lastPtr == segPtr) {
+ segPtr->sectionPtr->linePtr->lastPtr = newPtr2;
+ }
+ newPtr1->sectionPtr->length += 1;
+ assert(newPtr1->sectionPtr->length != 0); /* test for overflow */
+ TkBTreeFreeSegment(segPtr);
+ return newPtr1;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * IncreaseCharSegment --
+ *
+ * This function make a larger (or smaller) char segment, the new
+ * segment will replace the old one.
+ *
+ * Results:
+ * The return value is a pointer to the new segment.
+ *
+ * Side effects:
+ * Storage for old segment is freed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static TkTextSegment *
+IncreaseCharSegment(
+ TkTextSegment *segPtr, /* Pointer to segment. */
+ unsigned offset, /* Split point in char segment. */
+ int chunkSize) /* Add/subtract this size to the new segment. */
+{
+ TkTextSegment *newPtr;
+
+ assert(chunkSize != 0);
+
+ newPtr = CopyCharSeg(segPtr, 0, offset, segPtr->size + chunkSize);
+ if (chunkSize > 0) {
+ memcpy(newPtr->body.chars + offset + chunkSize,
+ segPtr->body.chars + offset,
+ segPtr->size - offset);
+ }
+ newPtr->nextPtr = segPtr->nextPtr;
+ newPtr->prevPtr = segPtr->prevPtr;
+
+ if (segPtr->prevPtr) {
+ segPtr->prevPtr->nextPtr = newPtr;
+ } else {
+ segPtr->sectionPtr->linePtr->segPtr = newPtr;
+ }
+ if (segPtr->nextPtr) {
+ segPtr->nextPtr->prevPtr = newPtr;
}
+ if (segPtr->sectionPtr) {
+ if (segPtr->sectionPtr->segPtr == segPtr) {
+ segPtr->sectionPtr->segPtr = newPtr;
+ }
+ if (segPtr->sectionPtr->linePtr->lastPtr == segPtr) {
+ segPtr->sectionPtr->linePtr->lastPtr = newPtr;
+ }
+ }
+ TkBTreeFreeSegment(segPtr);
+ return newPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * PrepareInsertIntoCharSeg --
+ *
+ * This function is called within SplitSeg() to finalize the work:
+ *
+ * a) We want to insert chars, and segPtr is a char segment, so
+ * we will change the size of the segment (this may require a
+ * replacement with a newly segment). If 'splitInfo->forceSplit'
+ * is set, and offset is not zero, then we must split because
+ * in this case the caller will insert chars with a trailing
+ * newline.
+ *
+ * b) We want to insert chars, and segPtr is not a char segment,
+ * just return segPtr, the caller will insert a new segment.
+ *
+ * c) We want to insert a non-char segment, so we must split
+ * anyway if offset > 0, and the latter case only happens
+ * in case of char segments.
+ *
+ * Results:
+ * The return value is a pointer to a segment, probably NULL if the
+ * given segment is NULL. 'splitInfo->offset' will be updated with
+ * offset (insertion point) in increased/decreased segment, or with
+ * -1 if we didn't increase/decrease the segment.
+ *
+ * Side effects:
+ * The segment referred by 'segPtr' may become modified or replaced.
+ * Pobably a new char segment will be inserted.
+ *
+ *--------------------------------------------------------------
+ */
+
+static TkTextSegment *
+PrepareInsertIntoCharSeg(
+ TkTextSegment *segPtr, /* Split or modify this segment. */
+ unsigned offset, /* Offset in segment. */
+ SplitInfo *splitInfo) /* Additional arguments. */
+{
+ assert(splitInfo);
+ assert(!splitInfo->splitted);
+ assert(splitInfo->increase != 0);
+ assert(segPtr);
+ assert(segPtr->typePtr == &tkTextCharType);
+ assert(offset <= segPtr->size);
+ assert(offset < segPtr->size || segPtr->body.chars[segPtr->size - 1] != '\n');
+
+ /*
+ * We must not split if the new char content will be appended
+ * to the current content (i.e. offset == segPtr->size).
+ */
+
+ if (splitInfo->forceSplit && offset < segPtr->size) {
+ unsigned newSize, decreasedSize;
+ TkTextSegment *newPtr;
+
+ splitInfo->splitted = true;
+
+ if (offset == 0 && segPtr == segPtr->sectionPtr->linePtr->segPtr) {
+ /*
+ * This is a bit tricky: we are not doing a split here, because inserting
+ * a newline at start of line is an implicit split (the callee inserts a
+ * new line), and the callee has to know that he can join the next content
+ * part into this char segment. Note that 'splitInfo->offset' is still
+ * negative, this has the effect that this segment will be shifted to the
+ * next line until an insertion of chars will be done.
+ */
+ return NULL;
+ }
+
+ /*
+ * We must split after offset for the new line.
+ */
+
+ newSize = segPtr->size - offset;
+ decreasedSize = segPtr->size - newSize;
+ newPtr = CopyCharSeg(segPtr, offset, newSize, newSize);
+ DEBUG(newPtr->sectionPtr = NULL);
+ memset(segPtr->body.chars + decreasedSize, 0, segPtr->size - decreasedSize);
+ segPtr->size = decreasedSize;
+ newPtr->size = 0; /* temporary; LinkSegment() should not change total size */
+ LinkSegment(segPtr->sectionPtr->linePtr, segPtr, newPtr);
+ newPtr->size = newSize;
+ SplitSection(segPtr->sectionPtr);
+ }
+
+ unsigned oldCapacity = CSEG_CAPACITY(segPtr->size);
+ unsigned newCapacity = CSEG_CAPACITY(segPtr->size + splitInfo->increase);
+
+ if (oldCapacity != newCapacity) {
+ /*
+ * We replace this segment by a larger (or smaller) one.
+ */
+ segPtr = IncreaseCharSegment(segPtr, offset, splitInfo->increase);
+ } else {
+ /*
+ * This segment has the right capacity for new content, so it's just
+ * an insertion/replacement. We did consider the trailing nul byte.
+ */
+ if (splitInfo->increase > 0) {
+ memmove(segPtr->body.chars + offset + splitInfo->increase,
+ segPtr->body.chars + offset,
+ segPtr->size - offset);
+ } else {
+ memset(segPtr->body.chars + offset, 0, newCapacity - offset);
+ }
+ segPtr->size += splitInfo->increase;
+ }
+
+ splitInfo->offset = offset;
+ return segPtr;
}
/*
@@ -1165,9 +6174,10 @@ TkBTreeInsertChars(
* three things: (a) it finds the segment containing indexPtr; (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.
+ * gravity; (c) if the index refers to the middle of a segment and we
+ * want to insert a segment without chars (splitInfo is NULL), 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
@@ -1176,402 +6186,1131 @@ TkBTreeInsertChars(
* value is NULL.
*
* Side effects:
- * The segment referred to by indexPtr is split unless indexPtr refers to
- * its first character.
+ * The segment referred to by indexPtr may be either split or replaced
+ * by a larger one.
*
*--------------------------------------------------------------
*/
+static bool
+CanInsertLeft(
+ const TkText *textPtr,
+ int offset,
+ TkTextSegment *segPtr)
+{
+ TkTextSegment *prevPtr;
+
+ assert(segPtr->tagInfoPtr);
+
+ if (!TkTextTagSetIsEmpty(segPtr->tagInfoPtr)) {
+ switch (textPtr->tagging) {
+ case TK_TEXT_TAGGING_GRAVITY:
+ return offset > 0 || textPtr->insertMarkPtr->typePtr == &tkTextLeftMarkType;
+ case TK_TEXT_TAGGING_WITHIN:
+ if (offset > 0) {
+ return true; /* inserting into a char segment */
+ }
+ prevPtr = GetPrevTagInfoSegment(segPtr);
+ return prevPtr && TkTextTagSetContains(prevPtr->tagInfoPtr, segPtr->tagInfoPtr);
+ case TK_TEXT_TAGGING_NONE:
+ if (offset == 0) {
+ return false;
+ }
+ prevPtr = GetPrevTagInfoSegment(segPtr);
+ return !prevPtr || TkTextTagSetIsEmpty(prevPtr->tagInfoPtr);
+ }
+ }
+ return true;
+}
+
+static bool
+CanInsertRight(
+ const TkText *textPtr,
+ TkTextSegment *prevPtr,
+ TkTextSegment *segPtr)
+{
+ assert(prevPtr->tagInfoPtr);
+
+ switch (textPtr->tagging) {
+ case TK_TEXT_TAGGING_GRAVITY:
+ return textPtr->insertMarkPtr->typePtr == &tkTextRightMarkType;
+ case TK_TEXT_TAGGING_WITHIN:
+ return TkTextTagSetContains(GetNextTagInfoSegment(segPtr)->tagInfoPtr, prevPtr->tagInfoPtr);
+ case TK_TEXT_TAGGING_NONE:
+ return TkTextTagSetIsEmpty(prevPtr->tagInfoPtr);
+ }
+ return false; /* never reached */
+}
+
static TkTextSegment *
SplitSeg(
- TkTextIndex *indexPtr) /* Index identifying position at which to
- * split a segment. */
+ const TkTextIndex *indexPtr,/* Index identifying position at which to split a segment. */
+ SplitInfo *splitInfo) /* Additional arguments for split, only given when inserting chars. */
{
- TkTextSegment *prevPtr, *segPtr;
- TkTextLine *linePtr;
- int count = indexPtr->byteIndex;
+ TkTextSegment *segPtr;
+ int count;
- linePtr = indexPtr->linePtr;
- prevPtr = NULL;
- segPtr = linePtr->segPtr;
+ if (splitInfo) {
+ /*
+ * We assume that 'splitInfo' is already initialized.
+ */
+
+ assert(splitInfo->offset == -1);
+ assert(splitInfo->increase != 0);
+ assert(!splitInfo->splitted);
+ }
- while (segPtr != NULL) {
+ assert(indexPtr->textPtr || !splitInfo);
+
+ if (TkTextIndexGetShared(indexPtr)->steadyMarks) {
+ /*
+ * With steadymarks we need the exact position, if given by a mark.
+ */
+
+ segPtr = TkTextIndexGetSegment(indexPtr);
+ if (segPtr && segPtr->typePtr->group == SEG_GROUP_MARK) {
+ count = 0;
+ } else {
+ segPtr = TkTextIndexGetFirstSegment(indexPtr, &count);
+ }
+ } else {
+ segPtr = TkTextIndexGetFirstSegment(indexPtr, &count);
+ }
+
+ for ( ; segPtr; segPtr = segPtr->nextPtr) {
if (segPtr->size > count) {
- if (count == 0) {
- return prevPtr;
+ if (splitInfo && segPtr->typePtr == &tkTextCharType) {
+ TkTextSegment *prevPtr;
+
+ if (splitInfo->tagInfoPtr
+ ? TkTextTagSetIsEqual(segPtr->tagInfoPtr, splitInfo->tagInfoPtr)
+ : CanInsertLeft(indexPtr->textPtr, count, segPtr)) {
+ /*
+ * Insert text into this char segment.
+ */
+ splitInfo->tagInfoPtr = segPtr->tagInfoPtr;
+ return PrepareInsertIntoCharSeg(segPtr, count, splitInfo);
+ }
+ if (count > 0) {
+ /*
+ * We have different tags for the new char segment, so we need a split.
+ */
+ return SplitCharSegment(segPtr, count);
+ }
+ if ((prevPtr = segPtr->prevPtr)
+ && prevPtr->typePtr == &tkTextCharType
+ && (splitInfo->tagInfoPtr
+ ? TkTextTagSetIsEqual(prevPtr->tagInfoPtr, splitInfo->tagInfoPtr)
+ : CanInsertRight(indexPtr->textPtr, prevPtr, segPtr))) {
+ /*
+ * Append more content at the end of the preceding char segment.
+ */
+ splitInfo->tagInfoPtr = prevPtr->tagInfoPtr;
+ return PrepareInsertIntoCharSeg(prevPtr, prevPtr->size, splitInfo);
+ }
}
- segPtr = segPtr->typePtr->splitProc(segPtr, count);
- if (prevPtr == NULL) {
- indexPtr->linePtr->segPtr = segPtr;
- } else {
- prevPtr->nextPtr = segPtr;
+ if (count == 0) {
+ /*
+ * We are one segment too far ahead. This case must
+ * also catch hyphens, embedded images, and windows.
+ */
+ return segPtr->prevPtr;
}
- return segPtr;
- } else if ((segPtr->size == 0) && (count == 0)
- && !segPtr->typePtr->leftGravity) {
- return prevPtr;
- }
-
- count -= segPtr->size;
- prevPtr = segPtr;
- segPtr = segPtr->nextPtr;
- if (segPtr == NULL) {
/*
- * Two logical lines merged into one display line through eliding
- * of a newline.
+ * Actually a split of a char segment necessary.
*/
-
- linePtr = TkBTreeNextLine(NULL, linePtr);
- if (linePtr == NULL) {
+ segPtr = SplitCharSegment(segPtr, count);
+ TkTextIndexToByteIndex((TkTextIndex *) indexPtr); /* mutable due to concept */
+ return segPtr;
+ }
+ if (count == 0 && segPtr->typePtr->gravity == GRAVITY_RIGHT) {
+ TkTextSegment *prevPtr = segPtr->prevPtr;
+ assert(segPtr->size == 0);
+ if (splitInfo
+ && prevPtr
+ && prevPtr->typePtr == &tkTextCharType
+ && (splitInfo->tagInfoPtr
+ ? TkTextTagSetIsEqual(prevPtr->tagInfoPtr, splitInfo->tagInfoPtr)
+ : CanInsertRight(indexPtr->textPtr, prevPtr, segPtr))) {
/*
- * Reached end of the text.
+ * Append more content at the end of the preceding char segment.
*/
- } else {
- segPtr = linePtr->segPtr;
+ splitInfo->tagInfoPtr = prevPtr->tagInfoPtr;
+ return PrepareInsertIntoCharSeg(prevPtr, prevPtr->size, splitInfo);
}
+ /*
+ * Right gravity is inserting at left side.
+ */
+ return prevPtr;
}
+ count -= segPtr->size;
}
- Tcl_Panic("SplitSeg reached end of line!");
+ assert(!"SplitSeg reached end of line!");
return NULL;
}
/*
*--------------------------------------------------------------
*
- * CleanupLine --
+ * TkBTreeMakeCharSegment --
*
- * This function is called after modifications have been made to a line.
- * It scans over all of the segments in the line, giving each a chance to
- * clean itself up, e.g. by merging with the following segments, updating
- * internal information, etc.
+ * Make new char segment with given text.
*
* Results:
- * None.
+ * The return value is a pointer to the new segment.
*
* Side effects:
- * Depends on what the segment-specific cleanup functions do.
+ * Storage for new segment will be allocated.
*
*--------------------------------------------------------------
*/
+TkTextSegment *
+TkBTreeMakeCharSegment(
+ const char *string,
+ unsigned length,
+ TkTextTagSet *tagInfoPtr) /* can be NULL */
+{
+ TkTextSegment *newPtr;
+ unsigned memsize = CSEG_SIZE(length + 1);
+
+ assert(string);
+ assert(tagInfoPtr);
+
+ newPtr = memset(malloc(memsize), 0, memsize);
+ newPtr->typePtr = &tkTextCharType;
+ newPtr->size = length;
+ newPtr->refCount = 1;
+ TkTextTagSetIncrRefCount(newPtr->tagInfoPtr = tagInfoPtr);
+ memcpy(newPtr->body.chars, string, length);
+ newPtr->body.chars[length] = '\0';
+ DEBUG_ALLOC(tkTextCountNewSegment++);
+ return newPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UpdateNodeTags --
+ *
+ * Update the node tag information after the tag information in any
+ * line of this node has been changed.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Information is deleted/added from/to the B-tree.
+ *
+ *----------------------------------------------------------------------
+ */
+
static void
-CleanupLine(
- TkTextLine *linePtr) /* Line to be cleaned up. */
+RemoveTagoffFromNode(
+ Node *nodePtr,
+ TkTextTag *tagPtr)
{
- TkTextSegment *segPtr, **prevPtrPtr;
- int anyChanges;
+ Node *parentPtr;
+ unsigned tagIndex = tagPtr->index;
- /*
- * Make a pass over all of the segments in the line, giving each a chance
- * to clean itself up. This could potentially change the structure of the
- * line, e.g. by merging two segments together or having two segments
- * cancel themselves; if so, then repeat the whole process again, since
- * the first structure change might make other structure changes possible.
- * Repeat until eventually there are no changes.
- */
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+ assert(nodePtr->level == 0);
+ assert(TkTextTagSetTest(nodePtr->tagoffPtr, tagIndex));
- while (1) {
- anyChanges = 0;
- for (prevPtrPtr = &linePtr->segPtr, segPtr = *prevPtrPtr;
- segPtr != NULL;
- prevPtrPtr = &(*prevPtrPtr)->nextPtr, segPtr = *prevPtrPtr) {
- if (segPtr->typePtr->cleanupProc != NULL) {
- *prevPtrPtr = segPtr->typePtr->cleanupProc(segPtr, linePtr);
- if (segPtr != *prevPtrPtr) {
- anyChanges = 1;
- }
+ nodePtr->tagoffPtr = TagSetErase(nodePtr->tagoffPtr, tagPtr);
+
+ while ((parentPtr = nodePtr->parentPtr)) {
+ for (nodePtr = parentPtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TkTextTagSetTest(nodePtr->tagonPtr, tagIndex)) {
+ return; /* still referenced in this node */
}
}
- if (!anyChanges) {
- break;
+ parentPtr->tagoffPtr = TagSetErase(nodePtr->tagoffPtr, tagPtr);
+ }
+}
+
+static void
+AddTagoffToNode(
+ Node *nodePtr,
+ const TkTextTagSet *tagoffPtr)
+{
+ assert(nodePtr->level == 0);
+
+ do {
+ nodePtr->tagoffPtr = TkTextTagSetJoin(nodePtr->tagoffPtr, tagoffPtr);
+ } while ((nodePtr = nodePtr->parentPtr));
+}
+
+static void
+UpdateNodeTags(
+ const TkSharedText *sharedTextPtr,
+ Node *nodePtr)
+{
+ const TkTextLine *linePtr = nodePtr->linePtr;
+ const TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+ TkTextTagSet *tagonPtr;
+ TkTextTagSet *tagoffPtr;
+ TkTextTagSet *additionalTagoffPtr;
+ TkTextTagSet *nodeTagonPtr;
+ TkTextTagSet *nodeTagoffPtr;
+ unsigned i;
+
+ assert(nodePtr->level == 0);
+ assert(linePtr);
+
+ TkTextTagSetIncrRefCount(tagonPtr = linePtr->tagonPtr);
+ TkTextTagSetIncrRefCount(tagoffPtr = linePtr->tagoffPtr);
+ TkTextTagSetIncrRefCount(additionalTagoffPtr = tagonPtr);
+ TkTextTagSetIncrRefCount(nodeTagonPtr = nodePtr->tagonPtr);
+ TkTextTagSetIncrRefCount(nodeTagoffPtr = nodePtr->tagoffPtr);
+
+ if (linePtr != lastPtr) {
+ for (linePtr = linePtr->nextPtr; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ tagonPtr = TkTextTagSetJoin(tagonPtr, linePtr->tagonPtr);
+ tagoffPtr = TkTextTagSetJoin(tagoffPtr, linePtr->tagoffPtr);
+ additionalTagoffPtr = TagSetIntersect(additionalTagoffPtr, linePtr->tagonPtr, sharedTextPtr);
+ }
+ }
+
+ if (!TkTextTagSetIsEqual(tagonPtr, nodeTagonPtr) || !TkTextTagSetIsEqual(tagoffPtr, nodeTagoffPtr)) {
+ if (additionalTagoffPtr) {
+ tagoffPtr = TagSetJoinComplementTo(tagoffPtr, additionalTagoffPtr, tagonPtr, sharedTextPtr);
+ TkTextTagSetDecrRefCount(additionalTagoffPtr);
+ } else {
+ TagSetAssign(&tagoffPtr, tagonPtr);
+ }
+
+ for (i = TkTextTagSetFindFirst(nodeTagonPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(nodeTagonPtr, i)) {
+ if (!TkTextTagSetTest(tagonPtr, i)) {
+ RemoveTagFromNode(nodePtr, sharedTextPtr->tagLookup[i]);
+ }
+ }
+
+ for (i = TkTextTagSetFindFirst(tagonPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagonPtr, i)) {
+ if (!TkTextTagSetTest(nodeTagonPtr, i)) {
+ AddTagToNode(nodePtr, sharedTextPtr->tagLookup[i], false);
+ }
+ }
+
+ if (!TkTextTagSetContains(tagoffPtr, nodeTagoffPtr)) {
+ for (i = TkTextTagSetFindFirst(nodeTagoffPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(nodeTagoffPtr, i)) {
+ if (!TkTextTagSetTest(tagoffPtr, i) && TkTextTagSetTest(tagonPtr, i)) {
+ RemoveTagoffFromNode(nodePtr, sharedTextPtr->tagLookup[i]);
+ }
+ }
}
+
+ AddTagoffToNode(nodePtr, tagoffPtr);
+
+ assert(TkTextTagSetIsEqual(tagonPtr, nodePtr->tagonPtr));
+ assert(TkTextTagSetIsEqual(tagoffPtr, nodePtr->tagoffPtr));
+ } else if (additionalTagoffPtr) {
+ TkTextTagSetDecrRefCount(additionalTagoffPtr);
}
+
+ TkTextTagSetDecrRefCount(tagonPtr);
+ TkTextTagSetDecrRefCount(tagoffPtr);
+ TkTextTagSetDecrRefCount(nodeTagonPtr);
+ TkTextTagSetDecrRefCount(nodeTagoffPtr);
}
-
/*
*----------------------------------------------------------------------
*
- * TkBTreeDeleteIndexRange --
+ * DeleteRange --
*
- * Delete a range of characters from a B-tree. The caller must make sure
- * that the final newline of the B-tree is never deleted.
+ * Delete a range of segments from a B-tree. The caller must make sure
+ * that the final newline of the B-tree will not be affected.
*
* Results:
* None.
*
* Side effects:
* Information is deleted from the B-tree. This can cause the internal
- * structure of the B-tree to change. Note: because of changes to the
- * B-tree structure, the indices pointed to by index1Ptr and index2Ptr
- * should not be used after this function returns.
+ * structure of the B-tree to change.
*
*----------------------------------------------------------------------
*/
-void
-TkBTreeDeleteIndexRange(
- TkTextBTree tree, /* Tree to delete from. */
- register TkTextIndex *index1Ptr,
- /* Indicates first character that is to be
- * deleted. */
- register TkTextIndex *index2Ptr)
- /* Indicates character just after the last one
- * that is to be deleted. */
-{
- TkTextSegment *prevPtr; /* The segment just before the start of the
- * deletion range. */
- TkTextSegment *lastPtr; /* The segment just after the end of the
- * deletion range. */
- TkTextSegment *segPtr, *nextPtr;
- TkTextLine *curLinePtr;
- Node *curNodePtr, *nodePtr;
- int changeToLineCount = 0;
- int ref;
- BTree *treePtr = (BTree *) tree;
+static void
+SetNodeFirstPointer(
+ Node *nodePtr,
+ TkTextLine *linePtr)
+{
+ TkTextLine *oldLinePtr = nodePtr->linePtr;
+
+ nodePtr->linePtr = linePtr;
+ while ((nodePtr = nodePtr->parentPtr) && nodePtr->linePtr == oldLinePtr) {
+ nodePtr->linePtr = linePtr;
+ }
+}
+
+static void
+MoveSegmentToLeft(
+ TkTextSegment *branchPtr,
+ TkTextSegment *movePtr) /* movePtr will become a predecessor of branchPtr */
+{
+ assert(movePtr);
+ assert(branchPtr);
+ assert(branchPtr->sectionPtr->linePtr == movePtr->sectionPtr->linePtr);
+ assert(movePtr->nextPtr != branchPtr);
+ assert(branchPtr->nextPtr);
+ assert(movePtr->prevPtr);
+
+ movePtr->prevPtr->nextPtr = movePtr->nextPtr;
+ if (movePtr->nextPtr) {
+ movePtr->nextPtr->prevPtr = movePtr->prevPtr;
+ }
+ movePtr->nextPtr = branchPtr;
- treePtr->stateEpoch++;
+ if (branchPtr->prevPtr) {
+ branchPtr->prevPtr->nextPtr = movePtr;
+ }
+ branchPtr->prevPtr = movePtr;
/*
- * Tricky point: split at index2Ptr first; otherwise the split at
- * index2Ptr may invalidate segPtr and/or prevPtr.
+ * We don't care about the sections, they will be rebuilt later,
+ * but ensure that the order of the sections will not change.
*/
- lastPtr = SplitSeg(index2Ptr);
- if (lastPtr != NULL) {
- lastPtr = lastPtr->nextPtr;
- } else {
- lastPtr = index2Ptr->linePtr->segPtr;
+ if (--movePtr->sectionPtr->length == 0) {
+ FreeSection(movePtr->sectionPtr);
+ }
+ movePtr->sectionPtr = branchPtr->sectionPtr;
+}
+
+static void
+MoveSegmentToRight(
+ TkTextSegment *linkPtr,
+ TkTextSegment *movePtr) /* movePtr will become a successor of linkPtr */
+{
+ assert(movePtr);
+ assert(linkPtr);
+ assert(linkPtr->sectionPtr->linePtr == movePtr->sectionPtr->linePtr);
+ assert(movePtr->prevPtr != linkPtr);
+ assert(linkPtr->prevPtr);
+ assert(movePtr->nextPtr);
+
+ if (movePtr->prevPtr) {
+ movePtr->prevPtr->nextPtr = movePtr->nextPtr;
+ }
+ movePtr->nextPtr->prevPtr = movePtr->prevPtr;
+ movePtr->prevPtr = linkPtr;
+
+ if (linkPtr->nextPtr) {
+ linkPtr->nextPtr->prevPtr = movePtr;
}
- prevPtr = SplitSeg(index1Ptr);
- if (prevPtr != NULL) {
- segPtr = prevPtr->nextPtr;
- prevPtr->nextPtr = lastPtr;
+ linkPtr->nextPtr = movePtr;
+
+ /*
+ * We don't care about the sections, they will be rebuilt later,
+ * but ensure that the order of the sections will not change.
+ */
+
+ if (--linkPtr->sectionPtr->length == 0) {
+ FreeSection(linkPtr->sectionPtr);
+ }
+ linkPtr->sectionPtr = movePtr->sectionPtr;
+}
+
+static void
+DeleteRange(
+ TkSharedText *sharedTextPtr,/* Handle to shared text resource. */
+ TkTextSegment *firstSegPtr, /* Indicates the segment just before where the deletion starts. */
+ TkTextSegment *lastSegPtr, /* Indicates the last segment where the deletion stops (exclusive
+ * this segment). FirstSegPtr and lastSegPtr may belong to
+ * different lines. */
+ int flags, /* Flags controlling the deletion. If DELETE_INCLUSIVE is set then
+ * also firstSegPtr and lastSegPtr will be deleted. */
+ TkTextUndoInfo *undoInfo) /* Store undo information, can be NULL. */
+{
+ BTree *treePtr;
+ TkTextSegment *prevPtr;
+ TkTextSegment *nextPtr;
+ TkTextSegment *segPtr;
+ TkTextSegment **segments;
+ TkTextSegment *prevLinkPtr;
+ TkTextSection *firstSectionPtr;
+ TkTextSection *prevSectionPtr;
+ TkTextSection *lastSectionPtr;
+ TkTextSection *sectionPtr;
+ TkTextLine *linePtr1;
+ TkTextLine *linePtr2;
+ TkTextLine *nextLinePtr;
+ TkTextLine *curLinePtr;
+ Node *curNodePtr;
+ Node *nodePtr1;
+ Node *nodePtr2;
+ unsigned numSegments;
+ unsigned maxSegments;
+ unsigned byteSize;
+ unsigned lineDiff;
+ bool steadyMarks;
+ int lineNo1;
+ int lineNo2;
+
+ assert(firstSegPtr);
+ assert(lastSegPtr);
+ assert(!undoInfo || undoInfo->token);
+
+ assert(!(flags & DELETE_INCLUSIVE)
+ || firstSegPtr->typePtr->group & (SEG_GROUP_MARK|SEG_GROUP_PROTECT));
+ assert(!(flags & DELETE_INCLUSIVE)
+ || lastSegPtr->typePtr->group & (SEG_GROUP_MARK|SEG_GROUP_PROTECT));
+ assert((firstSegPtr->typePtr->group == SEG_GROUP_PROTECT) ==
+ (lastSegPtr->typePtr->group == SEG_GROUP_PROTECT));
+
+ assert(firstSegPtr->nextPtr);
+
+ if (TkBTreeHaveElidedSegments(sharedTextPtr)) {
+ /*
+ * Include the surrounding branches and links into the deletion range.
+ */
+
+ assert(firstSegPtr->typePtr != &tkTextBranchType);
+ assert(lastSegPtr->typePtr != &tkTextLinkType);
+
+ if (!sharedTextPtr->steadyMarks || !TkTextIsStableMark(firstSegPtr)) {
+ for (segPtr = firstSegPtr->prevPtr; segPtr && segPtr->size == 0; segPtr = segPtr->prevPtr) {
+ if (segPtr->typePtr == &tkTextBranchType) {
+ /* firstSegPtr will become predecessor of this branch */
+ MoveSegmentToLeft(segPtr, firstSegPtr);
+ segPtr = firstSegPtr;
+ }
+ }
+ }
+
+ if (!sharedTextPtr->steadyMarks || !TkTextIsStableMark(lastSegPtr)) {
+ for (segPtr = lastSegPtr->nextPtr; segPtr && segPtr->size == 0; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr == &tkTextLinkType) {
+ /* lastSegPtr will become successor of this link */
+ MoveSegmentToRight(segPtr, lastSegPtr);
+ segPtr = lastSegPtr;
+ }
+ }
+ }
+ }
+
+ treePtr = (BTree *) sharedTextPtr->tree;
+ curLinePtr = firstSegPtr->sectionPtr->linePtr;
+ sectionPtr = curLinePtr->segPtr->sectionPtr;
+ prevSectionPtr = curLinePtr->lastPtr->sectionPtr;
+ prevPtr = firstSegPtr;
+ segPtr = firstSegPtr->nextPtr;
+ steadyMarks = sharedTextPtr->steadyMarks;
+ numSegments = 0;
+ segments = NULL;
+
+ linePtr1 = sectionPtr->linePtr;
+ linePtr2 = lastSegPtr->sectionPtr->linePtr;
+ nodePtr1 = linePtr1->parentPtr;
+ nodePtr2 = linePtr2->parentPtr;
+ lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr1, NULL);
+ lineNo2 = linePtr1 == linePtr2 ? lineNo1 : TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr2, NULL);
+ lineDiff = linePtr1->size;
+
+ SetLineHasChanged(sharedTextPtr, linePtr1);
+ if (linePtr1 != linePtr2) {
+ SetLineHasChanged(sharedTextPtr, linePtr2);
+ }
+
+ if (undoInfo) {
+ /* reserve the first entry if needed */
+ numSegments = (flags & DELETE_INCLUSIVE) && TkTextIsStableMark(firstSegPtr) ? 1 : 0;
+ maxSegments = 100;
+ segments = malloc(maxSegments * sizeof(TkTextSegment *));
+ DEBUG(segments[0] = NULL);
} else {
- segPtr = index1Ptr->linePtr->segPtr;
- index1Ptr->linePtr->segPtr = lastPtr;
+ flags |= DELETE_BRANCHES;
}
/*
- * Delete all of the segments between prevPtr and lastPtr.
+ * This line now needs to have its height recalculated. This has to be done
+ * before the lines will be removed.
*/
- curLinePtr = index1Ptr->linePtr;
+ TkTextInvalidateLineMetrics(treePtr->sharedTextPtr, NULL,
+ linePtr1, lineNo2 - lineNo1, TK_TEXT_INVALIDATE_DELETE);
- curNodePtr = curLinePtr->parentPtr;
- while (segPtr != lastPtr) {
- if (segPtr == NULL) {
- TkTextLine *nextLinePtr;
+ /*
+ * Connect start and end point.
+ */
+
+ firstSegPtr->nextPtr = lastSegPtr;
+ lastSegPtr->prevPtr = firstSegPtr;
+
+ if (nodePtr1 != nodePtr2 && nodePtr2->lastPtr == linePtr2) {
+ /*
+ * This node is going to be deleted.
+ */
+ nodePtr2 = NULL;
+ }
+
+ /*
+ * Delete all of the segments between firstSegPtr (exclusive) and lastSegPtr (exclusive).
+ */
+ curNodePtr = curLinePtr->parentPtr;
+ assert(curLinePtr->nextPtr);
+ prevLinkPtr = NULL;
+ firstSectionPtr = NULL;
+ lastSectionPtr = NULL;
+ byteSize = 0;
+
+ while (segPtr != lastSegPtr) {
+ if (!segPtr) {
/*
- * We just ran off the end of a line. First find the next line,
- * then go back to the old line and delete it (unless it's the
- * starting line for the range).
+ * We just ran off the end of a line.
*/
- nextLinePtr = TkBTreeNextLine(NULL, curLinePtr);
- if (curLinePtr != index1Ptr->linePtr) {
- if (curNodePtr == index1Ptr->linePtr->parentPtr) {
- index1Ptr->linePtr->nextPtr = curLinePtr->nextPtr;
- } else {
- curNodePtr->children.linePtr = curLinePtr->nextPtr;
- }
- for (nodePtr = curNodePtr; nodePtr != NULL;
- nodePtr = nodePtr->parentPtr) {
- nodePtr->numLines--;
- for (ref = 0; ref < treePtr->pixelReferences; ref++) {
- nodePtr->numPixels[ref] -= curLinePtr->pixels[2*ref];
+ if (curLinePtr != linePtr1) {
+ /*
+ * Join unused section, RebuildSections will reuse/delete those sections.
+ */
+
+ prevSectionPtr->nextPtr = firstSectionPtr;
+ firstSectionPtr->prevPtr = prevSectionPtr;
+ prevSectionPtr = lastSectionPtr;
+
+ if (curNodePtr == nodePtr1 || curNodePtr == nodePtr2) {
+ /*
+ * Update only those nodes which will not be deleted,
+ * because DeleteEmptyNode will do a faster update.
+ */
+ SubtractPixelInfo(treePtr, curLinePtr);
+ if (curLinePtr->numBranches) {
+ PropagateChangeOfNumBranches(curLinePtr->parentPtr, -curLinePtr->numBranches);
}
}
- changeToLineCount++;
- CLANG_ASSERT(curNodePtr);
- curNodePtr->numChildren--;
- /*
- * Check if we need to adjust any partial clients.
- */
+ if (--curNodePtr->numChildren == 0) {
+ DeleteEmptyNode(treePtr, curNodePtr);
+ }
+ }
+ curLinePtr = curLinePtr->nextPtr;
+ curNodePtr = curLinePtr->parentPtr;
+ segPtr = curLinePtr->segPtr;
+ firstSectionPtr = curLinePtr->segPtr->sectionPtr;
+ lastSectionPtr = curLinePtr->lastPtr->sectionPtr;
+ } else {
+ assert(segPtr->sectionPtr->linePtr == curLinePtr);
+ assert(segPtr->typePtr->deleteProc);
+ nextPtr = segPtr->nextPtr;
+ byteSize += segPtr->size;
+ if (undoInfo && !TkTextIsSpecialOrPrivateMark(segPtr)) {
+ if (numSegments == maxSegments) {
+ maxSegments = MAX(50, numSegments * 2);
+ segments = realloc(segments, maxSegments * sizeof(TkTextSegment *));
+ }
+ if (segPtr->tagInfoPtr) {
+ segPtr->tagInfoPtr = TagSetRemoveBits(segPtr->tagInfoPtr,
+ sharedTextPtr->dontUndoTags, sharedTextPtr);
+ }
+ segments[numSegments++] = segPtr;
+ segPtr->refCount += 1;
+ }
+ if (!segPtr->typePtr->deleteProc((TkTextBTree) treePtr, segPtr, flags)) {
+ assert(segPtr->typePtr); /* really still living? */
+ assert(segPtr->typePtr->group == SEG_GROUP_MARK
+ || segPtr->typePtr->group == SEG_GROUP_BRANCH);
- if (treePtr->startEnd != NULL) {
- int checkCount = 0;
+ if (prevLinkPtr && segPtr->typePtr == &tkTextBranchType) {
+ /*
+ * This is a superfluous link/branch pair, delete both.
+ */
- while (checkCount < treePtr->startEndCount) {
- if (treePtr->startEnd[checkCount] == curLinePtr) {
- TkText *peer = treePtr->startEndRef[checkCount];
+ /* make new relationship (old one is already saved) */
+ prevLinkPtr->body.link.prevPtr->body.branch.nextPtr = segPtr->body.branch.nextPtr;
+ segPtr->body.branch.nextPtr->body.link.prevPtr = prevLinkPtr->body.link.prevPtr;
+ /* remove this pair from chain */
+ nextPtr = segPtr->nextPtr;
+ UnlinkSegment(segPtr);
+ TkBTreeFreeSegment(segPtr);
+ UnlinkSegmentAndCleanup(sharedTextPtr, prevLinkPtr);
+ TkBTreeFreeSegment(prevLinkPtr);
+ if (nextPtr->prevPtr && nextPtr->prevPtr->typePtr == &tkTextCharType) {
+ TkTextSegment *sPtr = CleanupCharSegments(sharedTextPtr, nextPtr);
+ if (sPtr != nextPtr) { nextPtr = nextPtr->nextPtr; }
+ }
+ prevLinkPtr = NULL;
+ } else {
+ /*
+ * This segment refuses to die, it's either a switch with a counterpart
+ * outside of the deletion range, or it's a mark. Link this segment
+ * after prevPtr.
+ */
- /*
- * We're deleting a line which is the start or end
- * of a current client. This means we need to
- * adjust that client.
- */
+ assert(prevPtr);
+ DEBUG(segPtr->sectionPtr = NULL);
- treePtr->startEnd[checkCount] = nextLinePtr;
- if (peer->start == curLinePtr) {
- peer->start = nextLinePtr;
- }
- if (peer->end == curLinePtr) {
- peer->end = nextLinePtr;
- }
+ if (segPtr->typePtr == &tkTextLinkType) {
+ assert(!prevLinkPtr);
+ prevLinkPtr = segPtr;
+ LinkSwitch(linePtr1, prevPtr, segPtr);
+
+ if (prevPtr->typePtr->group != SEG_GROUP_MARK) {
+ prevPtr = segPtr;
+ }
+ } else {
+ assert(segPtr->typePtr->group == SEG_GROUP_MARK);
+ LinkMark(sharedTextPtr, linePtr1, prevPtr, segPtr);
+
+ /*
+ * Option 'steadymarks' is off:
+ * 'prevPtr' will be advanced only if the segment don't has right gravity.
+ *
+ * Option 'steadymarks' is on:
+ * 'prevPtr' will always be advanced, because we keep the order of the marks.
+ */
+
+ if (steadyMarks || segPtr->typePtr->gravity != GRAVITY_RIGHT) {
+ prevPtr = segPtr;
}
- checkCount++;
}
+
+ assert(segPtr->prevPtr);
+ segPtr->sectionPtr = segPtr->prevPtr->sectionPtr;
+
+ if (segments && !TkTextIsSpecialOrPrivateMark(segPtr)) {
+ /* Mark this segment as re-inserted. */
+ MARK_POINTER(segments[numSegments - 1]);
+ }
+
+ /*
+ * Prevent an overflow of the section length, because this may happen
+ * when deleting segments. The section length doesn't matter here,
+ * because the section structure will be rebuilt later. But LinkSegment
+ * will trap into an assertion if we do not prevent this.
+ */
+ DEBUG(segPtr->sectionPtr->length = 0);
}
- ckfree(curLinePtr->pixels);
- ckfree(curLinePtr);
}
- curLinePtr = nextLinePtr;
- segPtr = curLinePtr->segPtr;
+ segPtr = nextPtr;
+ }
+ }
- /*
- * If the node is empty then delete it and its parents recursively
- * upwards until a non-empty node is found.
- */
+ nextLinePtr = linePtr1->nextPtr;
- while (curNodePtr->numChildren == 0) {
- Node *parentPtr;
+ if (linePtr1 != linePtr2) {
+ /*
+ * Finalize update of B-tree (children and pixel count).
+ */
- parentPtr = curNodePtr->parentPtr;
- if (parentPtr->children.nodePtr == curNodePtr) {
- parentPtr->children.nodePtr = curNodePtr->nextPtr;
- } else {
- Node *prevNodePtr = parentPtr->children.nodePtr;
- while (prevNodePtr->nextPtr != curNodePtr) {
- prevNodePtr = prevNodePtr->nextPtr;
- }
- prevNodePtr->nextPtr = curNodePtr->nextPtr;
- }
- parentPtr->numChildren--;
- ckfree(curNodePtr);
- curNodePtr = parentPtr;
+ nodePtr2 = linePtr2->parentPtr;
+ if (nodePtr1 != nodePtr2) {
+ SetNodeLastPointer(nodePtr1, linePtr1);
+ }
+
+ if (--nodePtr2->numChildren == 0) {
+ assert(nodePtr2->lastPtr == linePtr2);
+ DeleteEmptyNode(treePtr, nodePtr2);
+ nodePtr2 = NULL;
+ } else {
+ SubtractPixelInfo(treePtr, linePtr2);
+ assert(nodePtr2->lastPtr != linePtr2 || nodePtr1 == nodePtr2);
+ if (nodePtr1 != nodePtr2) {
+ SetNodeFirstPointer(nodePtr2, linePtr2->nextPtr);
+ } else if (nodePtr2->lastPtr == linePtr2) {
+ SetNodeLastPointer(nodePtr2, linePtr1);
}
- curNodePtr = curLinePtr->parentPtr;
- continue;
+ assert(nodePtr2->numLines == nodePtr2->numChildren);
}
- nextPtr = segPtr->nextPtr;
- if (segPtr->typePtr->deleteProc(segPtr, curLinePtr, 0) != 0) {
- /*
- * This segment refuses to die. Move it to prevPtr and advance
- * prevPtr if the segment has left gravity.
- */
+ /*
+ * The beginning and end of the deletion range are in different lines,
+ * so join the two lines and discard the ending line.
+ */
- if (prevPtr == NULL) {
- segPtr->nextPtr = index1Ptr->linePtr->segPtr;
- index1Ptr->linePtr->segPtr = segPtr;
- } else {
- segPtr->nextPtr = prevPtr->nextPtr;
- prevPtr->nextPtr = segPtr;
+ linePtr1->lastPtr = linePtr2->lastPtr;
+ if ((linePtr1->nextPtr = linePtr2->nextPtr)) {
+ linePtr1->nextPtr->prevPtr = linePtr1;
+ }
+ prevSectionPtr->nextPtr = firstSectionPtr;
+ firstSectionPtr->prevPtr = prevSectionPtr;
+ }
+
+ if (TkBTreeHaveElidedSegments(sharedTextPtr)) {
+ /*
+ * We have moved surrounding branches and links into the deletion range,
+ * now we have to revert this (for remaining switches) before RebuildSections
+ * will be invoked.
+ */
+
+ if (firstSegPtr->size == 0 && firstSegPtr->nextPtr->typePtr == &tkTextBranchType) {
+ TkTextSegment *leftSegPtr = firstSegPtr;
+ TkTextSegment *branchPtr = firstSegPtr;
+
+ while (leftSegPtr && leftSegPtr->prevPtr && leftSegPtr->prevPtr->size == 0) {
+ leftSegPtr = leftSegPtr->prevPtr;
}
- if (segPtr->typePtr->leftGravity) {
- prevPtr = segPtr;
+ do {
+ TkTextSegment *nextPtr = branchPtr->nextPtr;
+ /* branchPtr will become a predecessor of leftSegPtr */
+ MoveSegmentToLeft(leftSegPtr, branchPtr);
+ branchPtr = nextPtr;
+ } while (branchPtr->typePtr == &tkTextBranchType);
+ }
+
+ if (lastSegPtr->size == 0
+ && lastSegPtr->prevPtr
+ && lastSegPtr->prevPtr->typePtr == &tkTextLinkType) {
+ TkTextSegment *rightPtr = lastSegPtr;
+ TkTextSegment *linkPtr = lastSegPtr->prevPtr;
+
+ while (rightPtr && rightPtr->nextPtr->size == 0) {
+ rightPtr = rightPtr->nextPtr;
}
+ do {
+ TkTextSegment *prevPtr = linkPtr->prevPtr;
+ /* linkPtr will become a successor of rightPtr */
+ MoveSegmentToRight(rightPtr, linkPtr);
+ linkPtr = prevPtr;
+ } while (linkPtr && linkPtr->typePtr == &tkTextLinkType);
}
- segPtr = nextPtr;
}
/*
- * If the beginning and end of the deletion range are in different lines,
- * join the two lines together and discard the ending line.
+ * Rebuild the sections in the new line. This must be done before other
+ * cleanups will be done. Be sure that the first segment really points
+ * to the first section, because LinkSegment may have changed this pointer.
+ */
+
+ linePtr1->segPtr->sectionPtr = sectionPtr;
+ RebuildSections(sharedTextPtr, linePtr1, true);
+
+ /*
+ * Recompute the line tag information of first line.
+ */
+
+ RecomputeLineTagInfo(linePtr1, NULL, sharedTextPtr);
+
+ /*
+ * Update the size of the node which holds the first line.
+ */
+
+ lineDiff -= linePtr1->size;
+ for (curNodePtr = nodePtr1; curNodePtr; curNodePtr = curNodePtr->parentPtr) {
+ curNodePtr->size -= lineDiff;
+ }
+
+ /*
+ * Finally delete the bounding segments if necessary. This cannot be
+ * done before RebuildSections has been performed. We are doing this
+ * as a separate step, this is avoiding special cases in the main
+ * deletion loop. Also consider that only marks (including protection
+ * marks) are allowed as deletable boundaries.
*/
- if (index1Ptr->linePtr != index2Ptr->linePtr) {
- TkTextLine *prevLinePtr;
+ if (flags & DELETE_INCLUSIVE) {
+ unsigned countChanges = 0;
- for (segPtr = lastPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- if (segPtr->typePtr->lineChangeProc != NULL) {
- segPtr->typePtr->lineChangeProc(segPtr, index2Ptr->linePtr);
+ assert(firstSegPtr->typePtr->group & (SEG_GROUP_MARK|SEG_GROUP_PROTECT));
+ assert(lastSegPtr->typePtr->group & (SEG_GROUP_MARK|SEG_GROUP_PROTECT));
+
+ /*
+ * Do not unlink the special/private marks.
+ * And don't forget the undo chain.
+ */
+
+ if (!TkTextIsSpecialOrPrivateMark(firstSegPtr)) {
+ UnlinkSegment(firstSegPtr);
+ assert(firstSegPtr->typePtr->deleteProc);
+ if (!firstSegPtr->typePtr->deleteProc((TkTextBTree) treePtr, firstSegPtr, flags)) {
+ assert(!"mark refuses to die"); /* this should not happen */
+ } else if (segments && TkTextIsStableMark(firstSegPtr)) {
+ firstSegPtr->refCount += 1;
+ assert(!segments[0]); /* this slot must be reserved */
+ segments[0] = firstSegPtr;
}
+ countChanges += 1;
}
- curNodePtr = index2Ptr->linePtr->parentPtr;
- for (nodePtr = curNodePtr; nodePtr != NULL;
- nodePtr = nodePtr->parentPtr) {
- nodePtr->numLines--;
- for (ref = 0; ref < treePtr->pixelReferences; ref++) {
- nodePtr->numPixels[ref] -= index2Ptr->linePtr->pixels[2*ref];
+ if (!TkTextIsSpecialOrPrivateMark(lastSegPtr)) {
+ UnlinkSegment(lastSegPtr);
+ assert(lastSegPtr->typePtr->deleteProc);
+ if (!lastSegPtr->typePtr->deleteProc((TkTextBTree) treePtr, lastSegPtr, flags)) {
+ assert(!"mark refuses to die"); /* this should not happen */
+ } else if (segments && TkTextIsStableMark(lastSegPtr)) {
+ if (numSegments == maxSegments) {
+ maxSegments += 2;
+ segments = realloc(segments, maxSegments * sizeof(TkTextSegment *));
+ }
+ segments[numSegments++] = lastSegPtr;
+ lastSegPtr->refCount += 1;
}
+ countChanges += 1;
}
- changeToLineCount++;
- curNodePtr->numChildren--;
- prevLinePtr = curNodePtr->children.linePtr;
- if (prevLinePtr == index2Ptr->linePtr) {
- curNodePtr->children.linePtr = index2Ptr->linePtr->nextPtr;
- } else {
- while (prevLinePtr->nextPtr != index2Ptr->linePtr) {
- prevLinePtr = prevLinePtr->nextPtr;
- }
- prevLinePtr->nextPtr = index2Ptr->linePtr->nextPtr;
+ if (countChanges == 0) {
+ flags &= ~DELETE_INCLUSIVE;
}
+ }
- /*
- * Check if we need to adjust any partial clients. In this case if
- * we're deleting the line, we actually move back to the previous line
- * for our (start,end) storage. We do this because we still want the
- * portion of the second line that still exists to be in the start,end
- * range.
- */
+ /*
+ * Do final update of nodes which contains the first/last line. This
+ * has to be performed before any rebalance of the B-Tree will be done.
+ */
- if (treePtr->startEnd != NULL) {
- int checkCount = 0;
+ if (nodePtr2 && nodePtr2 != nodePtr1) {
+ assert(nodePtr2 == linePtr2->nextPtr->parentPtr);
+ UpdateNodeTags(sharedTextPtr, nodePtr2);
+ }
+ UpdateNodeTags(sharedTextPtr, nodePtr1);
- while (checkCount < treePtr->startEndCount &&
- treePtr->startEnd[checkCount] != NULL) {
- if (treePtr->startEnd[checkCount] == index2Ptr->linePtr) {
- TkText *peer = treePtr->startEndRef[checkCount];
+ /*
+ * Now its time to deallocate all unused lines.
+ */
- /*
- * We're deleting a line which is the start or end of a
- * current client. This means we need to adjust that
- * client.
- */
+ curLinePtr = nextLinePtr;
+ nextLinePtr = linePtr2->nextPtr;
+ while (curLinePtr != nextLinePtr) {
+ TkTextLine *nextLinePtr = curLinePtr->nextPtr;
+ FreeLine(treePtr, curLinePtr);
+ curLinePtr = nextLinePtr;
+ }
- treePtr->startEnd[checkCount] = index1Ptr->linePtr;
- if (peer->start == index2Ptr->linePtr) {
- peer->start = index1Ptr->linePtr;
- }
- if (peer->end == index2Ptr->linePtr) {
- peer->end = index1Ptr->linePtr;
- }
+ /*
+ * Finish the setup of the redo information.
+ */
+
+ if (undoInfo) {
+ UndoTokenDelete *undoToken = (UndoTokenDelete *) undoInfo->token;
+
+ assert(numSegments == 0 || segments[0]);
+
+ if (numSegments + 1 != maxSegments) {
+ segments = realloc(segments, (numSegments + 1)*sizeof(TkTextSegment *));
+ }
+ undoToken->segments = segments;
+ undoToken->numSegments = numSegments;
+ undoToken->inclusive = !!(flags & DELETE_INCLUSIVE);
+ undoInfo->byteSize = byteSize;
+ }
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+ {
+ TkText *peer;
+ bool oldBTreeDebug = tkBTreeDebug;
+
+ tkBTreeDebug = false;
+
+ /*
+ * We have to adjust startline/endline.
+ */
+
+ for (peer = sharedTextPtr->peers; peer; peer = peer->next) {
+ if (peer->startLine) {
+ peer->startLine = peer->startMarker->sectionPtr->linePtr;
+ if (!SegIsAtStartOfLine(peer->startMarker)) {
+ TkTextIndex index;
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ TkTextIndexSetToStartOfLine2(&index, peer->startLine);
+ TkBTreeUnlinkSegment(sharedTextPtr, peer->startMarker);
+ TkBTreeLinkSegment(sharedTextPtr, peer->startMarker, &index);
+ }
+ }
+ if (peer->endLine) {
+ TkTextLine *endLinePtr = peer->endMarker->sectionPtr->linePtr;
+ bool atEndOfLine = SegIsAtEndOfLine(peer->endMarker);
+ bool atStartOfLine = SegIsAtStartOfLine(peer->endMarker);
+
+ if ((!atEndOfLine || atStartOfLine) && peer->startLine != endLinePtr) {
+ TkTextIndex index;
+
+ assert(endLinePtr->prevPtr);
+ TkTextInvalidateLineMetrics(NULL, peer, endLinePtr->prevPtr, 1,
+ TK_TEXT_INVALIDATE_DELETE);
+ peer->endLine = endLinePtr;
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ TkTextIndexSetToLastChar2(&index, endLinePtr->prevPtr);
+ TkBTreeUnlinkSegment(sharedTextPtr, peer->endMarker);
+ TkBTreeLinkSegment(sharedTextPtr, peer->endMarker, &index);
+ } else {
+ assert(endLinePtr->nextPtr);
+ peer->endLine = endLinePtr->nextPtr;
}
- checkCount++;
}
}
- ckfree(index2Ptr->linePtr->pixels);
- ckfree(index2Ptr->linePtr);
- Rebalance((BTree *) index2Ptr->tree, curNodePtr);
+ tkBTreeDebug = oldBTreeDebug;
}
+#endif
/*
- * Cleanup the segments in the new line.
+ * Don't forget to increase the epoch.
*/
- CleanupLine(index1Ptr->linePtr);
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
/*
- * This line now needs to have its height recalculated. For safety, ensure
- * we don't call this function with the last artificial line of text. I
- * _believe_ that it isn't possible to get this far with the last line,
- * but it is good to be safe.
+ * Rebalance the node of the last deleted line, but only if the start line is
+ * not contained in same node.
*/
- if (TkBTreeNextLine(NULL, index1Ptr->linePtr) != NULL) {
- TkTextInvalidateLineMetrics(treePtr->sharedTextPtr, NULL,
- index1Ptr->linePtr, changeToLineCount,
- TK_TEXT_INVALIDATE_DELETE);
+ if (nodePtr2 && nodePtr2 != nodePtr1) {
+ Rebalance(treePtr, nodePtr2);
+ nodePtr1 = linePtr1->parentPtr; /* may have changed during rebalancing */
}
/*
* Lastly, rebalance the first node of the range.
*/
- Rebalance((BTree *) index1Ptr->tree, index1Ptr->linePtr->parentPtr);
- if (tkBTreeDebug) {
- TkBTreeCheck(index1Ptr->tree);
+ if (linePtr1 != linePtr2) {
+ Rebalance(treePtr, nodePtr1);
}
}
/*
*----------------------------------------------------------------------
*
+ * TkBTreeDeleteIndexRange --
+ *
+ * Delete a range of characters from a B-tree. The caller must make sure
+ * that the final newline of the B-tree is never deleted.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Information is deleted from the B-tree. This can cause the internal
+ * structure of the B-tree to change. Note: because of changes to the
+ * B-tree structure, the indices pointed to by indexPtr1 and indexPtr2
+ * should not be used after this function returns.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DeleteIndexRange(
+ TkSharedText *sharedTextPtr,/* Handle to shared text resource. */
+ TkTextIndex *indexPtr1, /* Indicates first character that is to be deleted. */
+ TkTextIndex *indexPtr2, /* Indicates character just after the last one that is to be deleted. */
+ int flags, /* Flags controlling the deletion. */
+ const UndoTokenInsert *undoToken,
+ /* Perform undo, can be NULL. */
+ TkTextUndoInfo *redoInfo) /* Store undo information, can be NULL. */
+{
+ TkTextSegment *segPtr1; /* The segment just before the start of the deletion range. */
+ TkTextSegment *segPtr2; /* The segment just after the end of the deletion range. */
+ TkTextSegment *firstPtr;
+ TkTextSegment *lastPtr;
+ TkTextLine *linePtr1 = TkTextIndexGetLine(indexPtr1);
+ TkTextLine *linePtr2 = TkTextIndexGetLine(indexPtr2);
+ int myFlags = flags;
+
+ assert(sharedTextPtr);
+ assert(indexPtr1->tree == indexPtr2->tree);
+ assert(indexPtr1->textPtr == indexPtr2->textPtr);
+ assert((flags & DELETE_MARKS)
+ ? TkTextIndexCompare(indexPtr1, indexPtr2) <= 0
+ : TkTextIndexCompare(indexPtr1, indexPtr2) < 0);
+
+ /*
+ * Take care when doing the splits, none of the resulting segment pointers
+ * should become invalid, so we will use protection marks to avoid this.
+ */
+
+ segPtr1 = TkTextIndexGetSegment(indexPtr1);
+ segPtr2 = TkTextIndexGetSegment(indexPtr2);
+
+ assert(!sharedTextPtr->protectionMark[0]->sectionPtr); /* this protection mark must be unused */
+ assert(!sharedTextPtr->protectionMark[1]->sectionPtr); /* this protection mark must be unused */
+
+ if (segPtr1 && TkTextIsStableMark(segPtr1)) {
+ firstPtr = segPtr1;
+ if (!(flags & DELETE_INCLUSIVE) && !(segPtr2 && TkTextIsStableMark(segPtr2))) {
+ LinkSegment(linePtr1, segPtr1->prevPtr, firstPtr = sharedTextPtr->protectionMark[0]);
+ myFlags |= DELETE_INCLUSIVE;
+ }
+ } else {
+ segPtr1 = SplitSeg(indexPtr1, NULL);
+ if (segPtr1) { segPtr1->protectionFlag = true; }
+ LinkSegment(linePtr1, segPtr1, firstPtr = sharedTextPtr->protectionMark[0]);
+ myFlags |= DELETE_INCLUSIVE;
+ }
+
+ if (segPtr2 && TkTextIsStableMark(segPtr2)) {
+ lastPtr = segPtr2;
+ if (!(flags & DELETE_INCLUSIVE) && (myFlags & DELETE_INCLUSIVE)) {
+ LinkSegment(linePtr2, segPtr2, lastPtr = sharedTextPtr->protectionMark[1]);
+ }
+ } else {
+ segPtr2 = SplitSeg(indexPtr2, NULL);
+ LinkSegment(linePtr2, segPtr2, lastPtr = sharedTextPtr->protectionMark[1]);
+ segPtr2 = lastPtr->nextPtr;
+ segPtr2->protectionFlag = true;
+ myFlags |= DELETE_INCLUSIVE;
+ }
+
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+
+ if (redoInfo) {
+ UndoTokenDelete *redoToken;
+
+ redoToken = malloc(sizeof(UndoTokenDelete));
+ redoToken->undoType = &undoTokenDeleteType;
+ redoToken->segments = NULL;
+ redoToken->numSegments = 0;
+ if (undoToken) {
+ redoToken->startIndex = undoToken->startIndex;
+ redoToken->endIndex = undoToken->endIndex;
+ } else {
+ if (segPtr1 && TkTextIsStableMark(segPtr1) && !(flags & DELETE_MARKS)) {
+ redoToken->startIndex.u.markPtr = segPtr1;
+ redoToken->startIndex.lineIndex = -1;
+ } else {
+ TkTextIndex index = *indexPtr1;
+ TkTextIndexSetSegment(&index, firstPtr);
+ MakeUndoIndex(sharedTextPtr, &index, &redoToken->startIndex, GRAVITY_LEFT);
+ }
+ if (segPtr2 && TkTextIsStableMark(segPtr2) && !(flags & DELETE_MARKS)) {
+ redoToken->endIndex.u.markPtr = segPtr2;
+ redoToken->endIndex.lineIndex = -1;
+ } else {
+ TkTextIndex index = *indexPtr2;
+ TkTextIndexSetSegment(&index, lastPtr);
+ MakeUndoIndex(sharedTextPtr, &index, &redoToken->endIndex, GRAVITY_RIGHT);
+ }
+ }
+ redoInfo->token = (TkTextUndoToken *) redoToken;
+ redoInfo->byteSize = 0;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ }
+
+ DeleteRange(sharedTextPtr, firstPtr, lastPtr, myFlags, redoInfo);
+
+ CleanupSplitPoint(segPtr1, sharedTextPtr);
+ CleanupSplitPoint(segPtr2, sharedTextPtr);
+
+ /*
+ * The indices are no longer valid.
+ */
+
+ DEBUG(TkTextIndexInvalidate(indexPtr1));
+ DEBUG(TkTextIndexInvalidate(indexPtr2));
+
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+}
+
+void
+TkBTreeDeleteIndexRange(
+ TkSharedText *sharedTextPtr,/* Handle to shared text resource. */
+ TkTextIndex *indexPtr1, /* Indicates first character that is to be deleted. */
+ TkTextIndex *indexPtr2, /* Indicates character just after the last one that is to be deleted. */
+ int flags, /* Flags controlling the deletion. */
+ TkTextUndoInfo *undoInfo) /* Store undo information, can be NULL. */
+{
+ DeleteIndexRange(sharedTextPtr, indexPtr1, indexPtr2, flags, NULL, undoInfo);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkBTreeFindLine --
*
* Find a particular line in a B-tree based on its line number.
@@ -1593,15 +7332,18 @@ TkBTreeFindLine(
int line) /* Index of desired line. */
{
BTree *treePtr = (BTree *) tree;
- register Node *nodePtr;
- register TkTextLine *linePtr;
+ Node *nodePtr;
+ TkTextLine *linePtr;
- if (treePtr == NULL) {
- treePtr = (BTree *) textPtr->sharedTextPtr->tree;
+ assert(tree || textPtr);
+
+ if (!treePtr) {
+ tree = textPtr->sharedTextPtr->tree;
+ treePtr = (BTree *) tree;
}
nodePtr = treePtr->rootPtr;
- if ((line < 0) || (line >= nodePtr->numLines)) {
+ if (line < 0 || nodePtr->numLines <= line) {
return NULL;
}
@@ -1609,45 +7351,42 @@ TkBTreeFindLine(
* Check for any start/end offset for this text widget.
*/
- if (textPtr != NULL) {
- if (textPtr->start != NULL) {
- line += TkBTreeLinesTo(NULL, textPtr->start);
- if (line >= nodePtr->numLines) {
- return NULL;
- }
+ if (textPtr) {
+ line += TkBTreeLinesTo(tree, NULL, TkBTreeGetStartLine(textPtr), NULL);
+ if (line >= nodePtr->numLines) {
+ return NULL;
}
- if (textPtr->end != NULL) {
- if (line > TkBTreeLinesTo(NULL, textPtr->end)) {
- return NULL;
- }
+ if (line > TkBTreeLinesTo(tree, NULL, TkBTreeGetLastLine(textPtr), NULL)) {
+ return NULL;
}
}
+ if (line == 0) {
+ return nodePtr->linePtr;
+ }
+ if (line == nodePtr->numLines - 1) {
+ return nodePtr->lastPtr;
+ }
+
/*
* Work down through levels of the tree until a node is found at level 0.
*/
- while (nodePtr->level != 0) {
- for (nodePtr = nodePtr->children.nodePtr;
- nodePtr->numLines <= line;
+ while (nodePtr->level > 0) {
+ for (nodePtr = nodePtr->childPtr;
+ nodePtr && nodePtr->numLines <= line;
nodePtr = nodePtr->nextPtr) {
- if (nodePtr == NULL) {
- Tcl_Panic("TkBTreeFindLine ran out of nodes");
- }
line -= nodePtr->numLines;
}
+ assert(nodePtr);
}
/*
* Work through the lines attached to the level-0 node.
*/
- for (linePtr = nodePtr->children.linePtr; line > 0;
- linePtr = linePtr->nextPtr) {
- if (linePtr == NULL) {
- Tcl_Panic("TkBTreeFindLine ran out of lines");
- }
- line -= 1;
+ for (linePtr = nodePtr->linePtr; line > 0; linePtr = linePtr->nextPtr, --line) {
+ assert(linePtr != nodePtr->lastPtr->nextPtr);
}
return linePtr;
}
@@ -1680,21 +7419,24 @@ TkBTreeFindPixelLine(
TkTextBTree tree, /* B-tree to use. */
const TkText *textPtr, /* Relative to this client of the B-tree. */
int pixels, /* Pixel index of desired line. */
- int *pixelOffset) /* Used to return offset. */
+ int32_t *pixelOffset) /* Used to return offset. */
{
BTree *treePtr = (BTree *) tree;
- register Node *nodePtr;
- register TkTextLine *linePtr;
- int pixelReference = textPtr->pixelReference;
+ Node *nodePtr;
+ TkTextLine *linePtr;
+ unsigned pixelReference;
+ assert(textPtr);
+ assert(textPtr->pixelReference != -1);
+
+ pixelReference = textPtr->pixelReference;
nodePtr = treePtr->rootPtr;
- if ((pixels < 0) || (pixels > nodePtr->numPixels[pixelReference])) {
+ if (0 > pixels) {
return NULL;
}
-
- if (nodePtr->numPixels[pixelReference] == 0) {
- Tcl_Panic("TkBTreeFindPixelLine called with empty window");
+ if (pixels >= nodePtr->pixelInfo[pixelReference].pixels) {
+ return TkBTreeGetLastLine(textPtr);
}
/*
@@ -1702,13 +7444,11 @@ TkBTreeFindPixelLine(
*/
while (nodePtr->level != 0) {
- for (nodePtr = nodePtr->children.nodePtr;
- nodePtr->numPixels[pixelReference] <= pixels;
+ for (nodePtr = nodePtr->childPtr;
+ nodePtr->pixelInfo[pixelReference].pixels <= pixels;
nodePtr = nodePtr->nextPtr) {
- if (nodePtr == NULL) {
- Tcl_Panic("TkBTreeFindPixelLine ran out of nodes");
- }
- pixels -= nodePtr->numPixels[pixelReference];
+ assert(nodePtr);
+ pixels -= nodePtr->pixelInfo[pixelReference].pixels;
}
}
@@ -1716,153 +7456,29 @@ TkBTreeFindPixelLine(
* Work through the lines attached to the level-0 node.
*/
- for (linePtr = nodePtr->children.linePtr;
- linePtr->pixels[2 * pixelReference] < pixels;
+ for (linePtr = nodePtr->linePtr;
+ linePtr->pixelInfo[pixelReference].height <= pixels;
linePtr = linePtr->nextPtr) {
- if (linePtr == NULL) {
- Tcl_Panic("TkBTreeFindPixelLine ran out of lines");
- }
- pixels -= linePtr->pixels[2 * pixelReference];
- }
- if (pixelOffset != NULL && linePtr != NULL) {
- *pixelOffset = pixels;
+ assert(linePtr != nodePtr->lastPtr->nextPtr);
+ pixels -= linePtr->pixelInfo[pixelReference].height;
}
- return linePtr;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TkBTreeNextLine --
- *
- * Given an existing line in a B-tree, this function locates the next
- * line in the B-tree. This function is used for scanning through the
- * B-tree.
- *
- * Results:
- * The return value is a pointer to the line that immediately follows
- * linePtr, or NULL if there is no such line.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-TkTextLine *
-TkBTreeNextLine(
- const TkText *textPtr, /* Next line in the context of this client. */
- register TkTextLine *linePtr)
- /* Pointer to existing line in B-tree. */
-{
- register Node *nodePtr;
+ assert(linePtr);
- if (linePtr->nextPtr != NULL) {
- if (textPtr != NULL && (linePtr == textPtr->end)) {
- return NULL;
- } else {
- return linePtr->nextPtr;
- }
- }
+ if (textPtr->endMarker != textPtr->sharedTextPtr->endMarker) {
+ TkTextLine *endLinePtr = textPtr->endMarker->sectionPtr->linePtr;
- /*
- * This was the last line associated with the particular parent node.
- * Search up the tree for the next node, then search down from that node
- * to find the first line.
- */
-
- for (nodePtr = linePtr->parentPtr; ; nodePtr = nodePtr->parentPtr) {
- if (nodePtr->nextPtr != NULL) {
- nodePtr = nodePtr->nextPtr;
- break;
- }
- if (nodePtr->parentPtr == NULL) {
- return NULL;
+ if (TkBTreeLinesTo(tree, textPtr, linePtr, NULL) >
+ TkBTreeLinesTo(tree, textPtr, endLinePtr, NULL)) {
+ linePtr = endLinePtr;
}
}
- while (nodePtr->level > 0) {
- nodePtr = nodePtr->children.nodePtr;
- }
- return nodePtr->children.linePtr;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TkBTreePreviousLine --
- *
- * Given an existing line in a B-tree, this function locates the previous
- * line in the B-tree. This function is used for scanning through the
- * B-tree in the reverse direction.
- *
- * Results:
- * The return value is a pointer to the line that immediately preceeds
- * linePtr, or NULL if there is no such line.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-TkTextLine *
-TkBTreePreviousLine(
- TkText *textPtr, /* Relative to this client of the B-tree. */
- register TkTextLine *linePtr)
- /* Pointer to existing line in B-tree. */
-{
- register Node *nodePtr;
- register Node *node2Ptr;
- register TkTextLine *prevPtr;
-
- if (textPtr != NULL && textPtr->start == linePtr) {
- return NULL;
- }
-
- /*
- * Find the line under this node just before the starting line.
- */
- prevPtr = linePtr->parentPtr->children.linePtr; /* First line at leaf. */
- while (prevPtr != linePtr) {
- if (prevPtr->nextPtr == linePtr) {
- return prevPtr;
- }
- prevPtr = prevPtr->nextPtr;
- if (prevPtr == NULL) {
- Tcl_Panic("TkBTreePreviousLine ran out of lines");
- }
+ if (pixelOffset) {
+ *pixelOffset = pixels;
}
- /*
- * This was the first line associated with the particular parent node.
- * Search up the tree for the previous node, then search down from that
- * node to find its last line.
- */
-
- for (nodePtr = linePtr->parentPtr; ; nodePtr = nodePtr->parentPtr) {
- if (nodePtr == NULL || nodePtr->parentPtr == NULL) {
- return NULL;
- }
- if (nodePtr != nodePtr->parentPtr->children.nodePtr) {
- break;
- }
- }
- for (node2Ptr = nodePtr->parentPtr->children.nodePtr; ;
- node2Ptr = node2Ptr->children.nodePtr) {
- while (node2Ptr->nextPtr != nodePtr) {
- node2Ptr = node2Ptr->nextPtr;
- }
- if (node2Ptr->level == 0) {
- break;
- }
- nodePtr = NULL;
- }
- for (prevPtr = node2Ptr->children.linePtr ; ; prevPtr = prevPtr->nextPtr) {
- if (prevPtr->nextPtr == NULL) {
- return prevPtr;
- }
- }
+ return linePtr;
}
/*
@@ -1872,14 +7488,15 @@ TkBTreePreviousLine(
*
* Given a pointer to a line in a B-tree, return the numerical pixel
* index of the top of that line (i.e. the result does not include the
- * height of the given line).
+ * height of the logical line for given line).
*
* Since the last line of text (the artificial one) has zero height by
* defintion, calling this with the last line will return the total
* number of pixels in the widget.
*
* Results:
- * The result is the pixel height of the top of the given line.
+ * The result is the pixel height of the top of the logical line which
+ * belongs to given line.
*
* Side effects:
* None.
@@ -1887,47 +7504,67 @@ TkBTreePreviousLine(
*----------------------------------------------------------------------
*/
-int
+unsigned
TkBTreePixelsTo(
const TkText *textPtr, /* Relative to this client of the B-tree. */
- TkTextLine *linePtr) /* Pointer to existing line in B-tree. */
+ const TkTextLine *linePtr) /* Pointer to existing line in B-tree. */
{
- register TkTextLine *linePtr2;
- register Node *nodePtr, *parentPtr;
- int index;
- int pixelReference = textPtr->pixelReference;
+ const TkSharedText *sharedTextPtr;
+ Node *nodePtr, *parentPtr;
+ unsigned pixelReference;
+ unsigned index;
- /*
- * First count how many pixels precede this line in its level-0 node.
- */
+ assert(textPtr);
+ assert(textPtr->pixelReference != -1);
- nodePtr = linePtr->parentPtr;
- index = 0;
- for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr;
- linePtr2 = linePtr2->nextPtr) {
- if (linePtr2 == NULL) {
- Tcl_Panic("TkBTreePixelsTo couldn't find line");
- }
- index += linePtr2->pixels[2 * pixelReference];
+ if (linePtr == TkBTreeGetStartLine(textPtr)) {
+ return 0;
}
- /*
- * Now work up through the levels of the tree one at a time, counting how
- * many pixels are in nodes preceding the current node.
- */
+ pixelReference = textPtr->pixelReference;
+ sharedTextPtr = textPtr->sharedTextPtr;
- for (parentPtr = nodePtr->parentPtr ; parentPtr != NULL;
- nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) {
- register Node *nodePtr2;
+ if (linePtr == TkBTreeGetLastLine(textPtr)) {
+ index = ((BTree *) sharedTextPtr->tree)->rootPtr->pixelInfo[pixelReference].pixels;
+ } else {
+ linePtr = TkBTreeGetLogicalLine(sharedTextPtr, textPtr, (TkTextLine *) linePtr);
+
+ /*
+ * First count how many pixels precede this line in its level-0 node.
+ */
+
+ nodePtr = linePtr->parentPtr;
+ index = 0;
- for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr;
- nodePtr2 = nodePtr2->nextPtr) {
- if (nodePtr2 == NULL) {
- Tcl_Panic("TkBTreePixelsTo couldn't find node");
+ if (linePtr == nodePtr->lastPtr->nextPtr) {
+ index = nodePtr->pixelInfo[pixelReference].pixels;
+ } else {
+ TkTextLine *linePtr2;
+
+ for (linePtr2 = nodePtr->linePtr; linePtr2 != linePtr; linePtr2 = linePtr2->nextPtr) {
+ assert(linePtr2);
+ assert(linePtr2->pixelInfo);
+ index += linePtr2->pixelInfo[pixelReference].height;
+ }
+ }
+
+ /*
+ * Now work up through the levels of the tree one at a time, counting how
+ * many pixels are in nodes preceding the current node.
+ */
+
+ for (parentPtr = nodePtr->parentPtr;
+ parentPtr;
+ nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) {
+ Node *nodePtr2;
+
+ for (nodePtr2 = parentPtr->childPtr; nodePtr2 != nodePtr; nodePtr2 = nodePtr2->nextPtr) {
+ assert(nodePtr2);
+ index += nodePtr2->pixelInfo[pixelReference].pixels;
}
- index += nodePtr2->numPixels[pixelReference];
}
}
+
return index;
}
@@ -1940,8 +7577,12 @@ TkBTreePixelsTo(
* that line.
*
* Results:
- * The result is the index of linePtr within the tree, where 0
- * corresponds to the first line in the tree.
+ * The result is the index of linePtr within the tree, where zero
+ * corresponds to the first line in the tree. also the derivation
+ * will be set (if given), in case that the given line is before
+ * first line in this widget the deviation will be positive, and
+ * if given line is after last line in this client then the deviation
+ * will be negative.
*
* Side effects:
* None.
@@ -1949,14 +7590,40 @@ TkBTreePixelsTo(
*----------------------------------------------------------------------
*/
-int
+unsigned
TkBTreeLinesTo(
- const TkText *textPtr, /* Relative to this client of the B-tree. */
- TkTextLine *linePtr) /* Pointer to existing line in B-tree. */
+ TkTextBTree tree,
+ const TkText *textPtr, /* Relative to this client of the B-tree, can be NULL. */
+ const TkTextLine *linePtr, /* Pointer to existing line in B-tree. */
+ int *deviation) /* Deviation to existing line, can be NULL. */
{
- register TkTextLine *linePtr2;
- register Node *nodePtr, *parentPtr, *nodePtr2;
- int index;
+ const TkTextLine *linePtr2;
+ const Node *nodePtr;
+ const Node *parentPtr;
+ const Node *nodePtr2;
+ unsigned index;
+
+ assert(linePtr);
+
+ if (textPtr) {
+ if (linePtr == textPtr->startMarker->sectionPtr->linePtr) {
+ if (deviation) { *deviation = 0; }
+ return 0;
+ }
+ if (!linePtr->nextPtr && textPtr->endMarker == textPtr->sharedTextPtr->endMarker) {
+ if (deviation) { *deviation = 0; }
+ return TkBTreeGetRoot(tree)->numLines - 1;
+ }
+ } else {
+ if (!linePtr->prevPtr) {
+ if (deviation) { *deviation = 0; }
+ return 0;
+ }
+ if (!linePtr->nextPtr) {
+ if (deviation) { *deviation = 0; }
+ return TkBTreeGetRoot(tree)->numLines - 1;
+ }
+ }
/*
* First count how many lines precede this one in its level-0 node.
@@ -1964,11 +7631,8 @@ TkBTreeLinesTo(
nodePtr = linePtr->parentPtr;
index = 0;
- for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr;
- linePtr2 = linePtr2->nextPtr) {
- if (linePtr2 == NULL) {
- Tcl_Panic("TkBTreeLinesTo couldn't find line");
- }
+ for (linePtr2 = nodePtr->linePtr; linePtr2 != linePtr; linePtr2 = linePtr2->nextPtr) {
+ assert(linePtr2);
index += 1;
}
@@ -1977,87 +7641,156 @@ TkBTreeLinesTo(
* many lines are in nodes preceding the current node.
*/
- for (parentPtr = nodePtr->parentPtr ; parentPtr != NULL;
+ for (parentPtr = nodePtr->parentPtr;
+ parentPtr;
nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) {
- for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr;
- nodePtr2 = nodePtr2->nextPtr) {
- if (nodePtr2 == NULL) {
- Tcl_Panic("TkBTreeLinesTo couldn't find node");
- }
+ for (nodePtr2 = parentPtr->childPtr; nodePtr2 != nodePtr; nodePtr2 = nodePtr2->nextPtr) {
+ assert(nodePtr2);
index += nodePtr2->numLines;
}
}
- if (textPtr != NULL) {
+
+ if (textPtr) {
/*
* The index to return must be relative to textPtr, not to the entire
* tree. Take care to never return a negative index when linePtr
- * denotes a line before -startline, or an index larger than the
- * number of lines in textPtr when linePtr is a line past -endline.
+ * denotes a line before -startindex, or an index larger than the
+ * number of lines in textPtr when linePtr is a line past -endindex.
*/
- int indexStart, indexEnd;
+ unsigned indexStart, indexEnd;
+
+ indexStart = TkBTreeLinesTo(tree, NULL, TkBTreeGetStartLine(textPtr), NULL);
+ indexEnd = TkBTreeLinesTo(tree, NULL, TkBTreeGetLastLine(textPtr), NULL);
- if (textPtr->start != NULL) {
- indexStart = TkBTreeLinesTo(NULL, textPtr->start);
- } else {
- indexStart = 0;
- }
- if (textPtr->end != NULL) {
- indexEnd = TkBTreeLinesTo(NULL, textPtr->end);
- } else {
- indexEnd = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL);
- }
if (index < indexStart) {
+ if (deviation) { *deviation = indexStart - index; }
index = 0;
} else if (index > indexEnd) {
- index = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
+ if (deviation) { *deviation = indexEnd - index; }
+ index = indexEnd;
} else {
+ if (deviation) { *deviation = 0; }
index -= indexStart;
}
+ } else if (deviation) {
+ *deviation = 0;
}
+
return index;
}
-
+
/*
*----------------------------------------------------------------------
*
* TkBTreeLinkSegment --
*
* This function adds a new segment to a B-tree at a given location.
+ * This function cannot be used for char segments, or for switches.
*
* Results:
* None.
*
* Side effects:
- * SegPtr will be linked into its tree.
+ * 'segPtr' will be linked into its tree.
*
*----------------------------------------------------------------------
*/
- /* ARGSUSED */
void
TkBTreeLinkSegment(
+ const TkSharedText *sharedTextPtr,
+ /* Handle to shared text resource. */
TkTextSegment *segPtr, /* Pointer to new segment to be added to
- * B-tree. Should be completely initialized by
- * caller except for nextPtr field. */
- TkTextIndex *indexPtr) /* Where to add segment: it gets linked in
- * just before the segment indicated here. */
+ * B-tree. Should be completely initialized by caller except for
+ * nextPtr field. */
+ TkTextIndex *indexPtr) /* Where to add segment: it gets linked in just before the segment
+ * indicated here. */
{
- register TkTextSegment *prevPtr;
+ TkTextSegment *prevPtr;
+ TkTextLine *linePtr;
+
+ assert(!segPtr->sectionPtr); /* otherwise still in use */
+ assert(segPtr->typePtr->group != SEG_GROUP_CHAR);
+ assert(segPtr->typePtr->group != SEG_GROUP_PROTECT);
+ assert(segPtr->typePtr->group != SEG_GROUP_BRANCH);
+ assert(segPtr->size == 0 || segPtr->tagInfoPtr || indexPtr->textPtr);
+
+ linePtr = TkTextIndexGetLine(indexPtr);
+
+ if (sharedTextPtr->steadyMarks) {
+ prevPtr = TkTextIndexGetSegment(indexPtr);
+
+ if (prevPtr && prevPtr->typePtr->group == SEG_GROUP_MARK) {
+ /*
+ * We have steady marks, and the insertion point is a mark segment,
+ * so insert the new segment according to the gravity of this mark.
+ */
+
+ if (prevPtr->typePtr == &tkTextRightMarkType) {
+ prevPtr = prevPtr->prevPtr;
+ }
+ } else {
+ prevPtr = SplitSeg(indexPtr, NULL);
+ }
+ } else {
+ prevPtr = SplitSeg(indexPtr, NULL);
+ }
- prevPtr = SplitSeg(indexPtr);
- if (prevPtr == NULL) {
- segPtr->nextPtr = indexPtr->linePtr->segPtr;
- indexPtr->linePtr->segPtr = segPtr;
+ if (segPtr->typePtr->group == SEG_GROUP_MARK) {
+ LinkMark(sharedTextPtr, linePtr, prevPtr, segPtr);
} else {
- segPtr->nextPtr = prevPtr->nextPtr;
- prevPtr->nextPtr = segPtr;
+ LinkSegment(linePtr, prevPtr, segPtr);
}
- CleanupLine(indexPtr->linePtr);
- if (tkBTreeDebug) {
- TkBTreeCheck(indexPtr->tree);
+ SplitSection(segPtr->sectionPtr);
+ TkBTreeIncrEpoch(indexPtr->tree);
+
+ if (segPtr->size > 0) {
+ TkTextSegment *prevPtr = segPtr->prevPtr;
+ TkTextSegment *nextPtr = segPtr->nextPtr;
+ TkTextTagSet *tagoffPtr;
+ Node *nodePtr;
+
+ SetLineHasChanged(sharedTextPtr, linePtr);
+
+ /*
+ * We have to update the tag information of the line and the related node.
+ */
+
+ while (prevPtr && !prevPtr->tagInfoPtr) {
+ prevPtr = prevPtr->prevPtr;
+ }
+ while (nextPtr && !nextPtr->tagInfoPtr) {
+ nextPtr = nextPtr->nextPtr;
+ }
+
+ if (segPtr->tagInfoPtr) {
+ linePtr->tagonPtr = TkTextTagSetJoin(linePtr->tagonPtr, segPtr->tagInfoPtr);
+ } else {
+ segPtr->tagInfoPtr = MakeTagInfo(indexPtr->textPtr, segPtr);
+ }
+
+ TkTextTagSetIncrRefCount(tagoffPtr = sharedTextPtr->emptyTagInfoPtr);
+ if (prevPtr) { tagoffPtr = TkTextTagSetJoin(tagoffPtr, prevPtr->tagInfoPtr); }
+ if (nextPtr) { tagoffPtr = TkTextTagSetJoin(tagoffPtr, nextPtr->tagInfoPtr); }
+ tagoffPtr = TkTextTagSetRemove(tagoffPtr, segPtr->tagInfoPtr);
+
+ if (!TkTextTagSetContains(linePtr->tagoffPtr, tagoffPtr)) {
+ linePtr->tagoffPtr = TkTextTagSetJoin(linePtr->tagoffPtr, tagoffPtr);
+ AddTagoffToNode(linePtr->parentPtr, tagoffPtr);
+ }
+ TkTextTagSetDecrRefCount(tagoffPtr);
+
+ /*
+ * Propagate change of size in B-Tree.
+ */
+
+ for (nodePtr = linePtr->parentPtr; nodePtr; nodePtr = nodePtr->parentPtr) {
+ nodePtr->size += segPtr->size;
+ }
}
- ((BTree *)indexPtr->tree)->stateEpoch++;
+
+ TK_BTREE_DEBUG(TkBTreeCheck(indexPtr->tree));
}
/*
@@ -2066,45 +7799,779 @@ TkBTreeLinkSegment(
* TkBTreeUnlinkSegment --
*
* This function unlinks a segment from its line in a B-tree.
+ * This function cannot be used for char segments, or for switches.
*
* Results:
* None.
*
* Side effects:
* SegPtr will be unlinked from linePtr. The segment itself isn't
- * modified by this function.
+ * modified by this function, but the section containing this
+ * segment will be modified.
*
*----------------------------------------------------------------------
*/
- /* ARGSUSED */
void
TkBTreeUnlinkSegment(
- TkTextSegment *segPtr, /* Segment to be unlinked. */
- TkTextLine *linePtr) /* Line that currently contains segment. */
+ const TkSharedText *sharedTextPtr,
+ TkTextSegment *segPtr) /* Segment to be unlinked. */
{
- register TkTextSegment *prevPtr;
+ TkTextSegment *prevPtr;
+ TkTextSection *sectionPtr;
+ TkTextLine *linePtr;
+
+ assert(segPtr->typePtr != &tkTextCharType);
+ assert(segPtr->typePtr != &tkTextLinkType);
+ assert(segPtr->typePtr != &tkTextBranchType);
+ assert(segPtr->typePtr != &tkTextHyphenType);
+ assert(segPtr->typePtr->group != SEG_GROUP_PROTECT);
+ assert(segPtr->typePtr->group != SEG_GROUP_BRANCH);
+
+ prevPtr = segPtr->prevPtr;
+ sectionPtr = segPtr->sectionPtr;
+ assert(sectionPtr); /* segment is already freed? */
+ assert(sectionPtr->linePtr); /* section is already freed? */
+ UnlinkSegment(segPtr);
+ linePtr = sectionPtr->linePtr;
+ JoinSections(sectionPtr);
+ if (prevPtr && prevPtr->typePtr == &tkTextCharType) {
+ CleanupCharSegments(sharedTextPtr, prevPtr);
+ }
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+
+ assert((segPtr->size == 0) == !segPtr->tagInfoPtr);
+
+ if (segPtr->size > 0) {
+ Node *nodePtr;
+
+ SetLineHasChanged(sharedTextPtr, linePtr);
- if (linePtr->segPtr == segPtr) {
- linePtr->segPtr = segPtr->nextPtr;
+ if (!TkTextTagSetIsEmpty(linePtr->tagoffPtr)) {
+ RecomputeLineTagInfo(sectionPtr->linePtr, NULL, sharedTextPtr);
+ UpdateNodeTags(sharedTextPtr, sectionPtr->linePtr->parentPtr);
+ }
+
+ /*
+ * Propagate change of size in B-Tree.
+ */
+
+ for (nodePtr = linePtr->parentPtr; nodePtr; nodePtr = nodePtr->parentPtr) {
+ nodePtr->size -= segPtr->size;
+ }
+ }
+
+ TK_BTREE_DEBUG(if (!segPtr->startEndMarkFlag) TkBTreeCheck(sharedTextPtr->tree));
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AddTagToNode --
+ *
+ * Add the specified tag to the given node, so we can check whether
+ * any segment in this node contains this tag.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Updates the tag information of some nodes in the B-Tree.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static unsigned
+CountChildsWithTag(
+ const Node *nodePtr,
+ unsigned tagIndex)
+{
+ unsigned count = 0;
+
+ if (nodePtr->level == 0) {
+ const TkTextLine *linePtr;
+ const TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+
+ for (linePtr = nodePtr->linePtr; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ if (TkTextTagSetTest(linePtr->tagonPtr, tagIndex)) {
+ count += 1;
+ }
+ }
} else {
- prevPtr = linePtr->segPtr;
- while (prevPtr->nextPtr != segPtr) {
- prevPtr = prevPtr->nextPtr;
+ const Node *childPtr;
+
+ for (childPtr = nodePtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ if (TkTextTagSetTest(childPtr->tagonPtr, tagIndex)) {
+ count += 1;
+ }
+ }
+ }
+
+ return count;
+}
+
+static void
+AddTagToNode(
+ Node *nodePtr,
+ TkTextTag *tagPtr,
+ bool setTagoff)
+{
+ unsigned rootLevel;
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+ assert(nodePtr->level == 0);
+
+ if (!tagPtr->rootPtr) {
+ tagPtr->rootPtr = nodePtr;
+ }
+
+ rootLevel = tagPtr->rootPtr->level;
- if (prevPtr == NULL) {
+ do {
+ TkTextTagSet *tagInfoPtr = TagSetTestAndSet(nodePtr->tagonPtr, tagPtr);
+
+ if (!tagInfoPtr) {
+ Node *rootPtr = nodePtr;
+
+ /*
+ * This tag is already included, but possibly we have to push up the tag root.
+ */
+
+ while (rootLevel < rootPtr->level) {
+ rootLevel = (tagPtr->rootPtr = tagPtr->rootPtr->parentPtr)->level;
+ }
+ while (rootLevel == rootPtr->level && rootPtr != tagPtr->rootPtr) {
+ rootLevel = (tagPtr->rootPtr = tagPtr->rootPtr->parentPtr)->level;
+ rootPtr = rootPtr->parentPtr;
+ }
+
+ if (setTagoff) {
/*
- * Two logical lines merged into one display line through
- * eliding of a newline.
+ * And still we have to propagate the tagoff information.
*/
- linePtr = TkBTreeNextLine(NULL, linePtr);
- prevPtr = linePtr->segPtr;
+ do {
+ if (!(tagInfoPtr = TagSetTestAndSet(nodePtr->tagoffPtr, tagPtr))) {
+ return;
+ }
+ nodePtr->tagoffPtr = tagInfoPtr;
+ } while ((nodePtr = nodePtr->parentPtr));
+ }
+
+ return;
+ }
+
+ nodePtr->tagonPtr = tagInfoPtr;
+
+ if (setTagoff) {
+ nodePtr->tagoffPtr = TagSetAdd(nodePtr->tagoffPtr, tagPtr);
+ } else {
+ unsigned nchilds = CountChildsWithTag(nodePtr, tagPtr->index);
+
+ if (nchilds == 0) {
+ nodePtr->tagoffPtr = TagSetErase(nodePtr->tagoffPtr, tagPtr);
+ assert(!nodePtr->parentPtr || nodePtr->parentPtr->numChildren > 1);
+ setTagoff = true; /* but parent now has tagoff */
+ } else if (nchilds < nodePtr->numLines) {
+ nodePtr->tagoffPtr = TagSetAdd(nodePtr->tagoffPtr, tagPtr);
+ setTagoff = true; /* propagate to parent */
}
}
- prevPtr->nextPtr = segPtr->nextPtr;
+
+ if (rootLevel == nodePtr->level && nodePtr != tagPtr->rootPtr) {
+ /*
+ * The old tag root is at the same level in the tree as this node,
+ * but it isn't at this node. Move the tag root up one level.
+ */
+ rootLevel = (tagPtr->rootPtr = tagPtr->rootPtr->parentPtr)->level;
+ }
+ } while ((nodePtr = nodePtr->parentPtr));
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RemoveTagFromNode --
+ *
+ * Remove the specified tag from the given node, so we can check whether
+ * any segment in this node contains this tag.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Updates the tag information of some nodes in the B-Tree.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RemoveTagFromNode(
+ Node *nodePtr,
+ TkTextTag *tagPtr)
+{
+ Node *parentPtr;
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+ assert(nodePtr->level == 0);
+ assert(TkTextTagSetTest(nodePtr->tagonPtr, tagPtr->index));
+
+ nodePtr->tagonPtr = TagSetErase(nodePtr->tagonPtr, tagPtr);
+ nodePtr->tagoffPtr = TagSetErase(nodePtr->tagoffPtr, tagPtr);
+
+ if (nodePtr == tagPtr->rootPtr) {
+ tagPtr->rootPtr = NULL;
+
+ while ((nodePtr = nodePtr->parentPtr)) {
+ nodePtr->tagonPtr = TagSetErase(nodePtr->tagonPtr, tagPtr);
+ nodePtr->tagoffPtr = TagSetErase(nodePtr->tagoffPtr, tagPtr);
+ };
+ } else if ((parentPtr = nodePtr->parentPtr)) {
+ unsigned tagIndex = tagPtr->index;
+ Node *childPtr = NULL;
+
+ tagPtr->rootPtr = NULL;
+
+ do {
+ unsigned count = 0;
+
+ /*
+ * Test if any of the children is still referencing the tag.
+ */
+
+ for (nodePtr = parentPtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TkTextTagSetTest(nodePtr->tagonPtr, tagIndex)) {
+ if (!childPtr) { childPtr = nodePtr; }
+ count += 1;
+ }
+ }
+
+ if (count == 0) {
+ parentPtr->tagonPtr = TagSetErase(parentPtr->tagonPtr, tagPtr);
+ parentPtr->tagoffPtr = TagSetErase(parentPtr->tagoffPtr, tagPtr);
+ } else {
+ if (count > 1) {
+ /* this is now the best candidate for pushing down the root */
+ tagPtr->rootPtr = parentPtr;
+ }
+ parentPtr->tagoffPtr = TagSetAdd(parentPtr->tagoffPtr, tagPtr);
+ }
+ } while ((parentPtr = parentPtr->parentPtr));
+
+ if (childPtr && !tagPtr->rootPtr) {
+ /*
+ * We have to search down for a new tag root.
+ */
+
+ tagPtr->rootPtr = childPtr;
+
+ while (childPtr->level > 0) {
+ unsigned count = 0;
+
+ for (nodePtr = childPtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TkTextTagSetTest(nodePtr->tagonPtr, tagIndex)) {
+ childPtr = nodePtr;
+ count += 1;
+ }
+ }
+
+ assert(count > 0);
+
+ if (count > 1) {
+ break;
+ }
+
+ tagPtr->rootPtr = childPtr;
+ }
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeUpdateElideInfo --
+ *
+ * This function will be called if the elide info of any tag has been
+ * changed.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some branches and links may be inserted, or removed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+PropagateChangeToLineCount(
+ Node *nodePtr,
+ int changeToLogicalLineCount)
+{
+ if (changeToLogicalLineCount) {
+ for ( ; nodePtr; nodePtr = nodePtr->parentPtr) {
+ nodePtr->numLogicalLines += changeToLogicalLineCount;
+ }
+ }
+}
+
+static TkTextSegment *
+FindNextLink(
+ const TkSharedText *sharedTextPtr,
+ TkTextSegment *segPtr)
+{
+ TkTextSection *sectionPtr = segPtr->sectionPtr;
+ TkTextLine *linePtr = sectionPtr->linePtr;
+
+ if (linePtr->numLinks > 0) {
+ sectionPtr = sectionPtr->nextPtr;
+ while (sectionPtr) {
+ if (sectionPtr->segPtr->typePtr == &tkTextLinkType) {
+ return sectionPtr->segPtr;
+ }
+ }
+ }
+
+ linePtr = TkBTreeNextLogicalLine(sharedTextPtr, NULL, linePtr)->prevPtr;
+ assert(linePtr);
+ if (linePtr->numLinks == 0) {
+ linePtr = linePtr->nextPtr;
+ assert(linePtr);
+ }
+ sectionPtr = linePtr->segPtr->sectionPtr;
+
+ while (true) {
+ if (sectionPtr->segPtr->typePtr == &tkTextLinkType) {
+ return sectionPtr->segPtr;
+ }
+ sectionPtr = sectionPtr->nextPtr;
+ assert(sectionPtr);
+ }
+
+ return NULL; /* never reached */
+}
+
+static void
+UpdateElideInfo(
+ TkSharedText *sharedTextPtr,
+ TkTextTag *tagPtr, /* can be NULL */
+ TkTextSegment *firstSegPtr,
+ TkTextSegment *lastSegPtr,
+ unsigned reason)
+{
+ TkTextSegment *prevBranchPtr;
+ TkTextSegment *lastBranchPtr;
+ TkTextSegment *prevLinkPtr;
+ TkTextSegment *lastLinkPtr;
+ TkTextSegment *newBranchPtr;
+ TkTextSegment *startSegPtr;
+ TkTextSegment *deletedBranchPtr;
+ TkTextSegment *deletedLinkPtr;
+ TkTextSegment *endSegPtr;
+ TkTextSegment *segPtr;
+ TkTextLine *linePtr;
+ TkTextLine *lastLinePtr;
+ TkTextLine *startLinePtr;
+ TkTextLine *endLinePtr;
+ TkText *oldTextPtr;
+ TkText *textPtr;
+ Node *nodePtr;
+ bool anyChanges;
+ bool actualElidden;
+ int changeToLogicalLineCount;
+
+ /*
+ * --------------------------------------------------------------------------
+ * This function will be called in four cases:
+ * --------------------------------------------------------------------------
+ * 1. A tag with elide information has been added to the specified region.
+ *
+ * 2. A tag with elide information will be removed from the specified region.
+ *
+ * Because the removal has not yet been done, we will temporarily
+ * disable this tag for further processing of the region.
+ *
+ * 3. The elide state of a tag has been changed.
+ *
+ * Here we need the elide state of the predecessing segment before
+ * the tag has been changed, thus we will temporarily reset the elide
+ * state as long as we are computing this predecessing elide state.
+ *
+ * 4. All tags will be removed from the specified region.
+ */
+
+ assert(tagPtr || reason == ELISION_WILL_BE_REMOVED);
+ assert(tagPtr || TkBTreeHaveElidedSegments(sharedTextPtr));
+
+ /*
+ * This function assumes that the start/end points are already protected.
+ */
+
+ assert(firstSegPtr->protectionFlag);
+ assert(lastSegPtr->protectionFlag);
+
+ linePtr = firstSegPtr->sectionPtr->linePtr;
+ prevBranchPtr = lastBranchPtr = newBranchPtr = NULL;
+ deletedBranchPtr = deletedLinkPtr = NULL;
+ prevLinkPtr = lastLinkPtr = NULL;
+ anyChanges = false;
+ oldTextPtr = textPtr = NULL;
+ lastLinePtr = lastSegPtr->sectionPtr->linePtr;
+ changeToLogicalLineCount = 0;
+ nodePtr = NULL;
+ startLinePtr = NULL;
+ endLinePtr = NULL;
+
+ /*
+ * Ensure that the range will include final branches.
+ */
+
+ endSegPtr = lastSegPtr;
+ while (endSegPtr->size == 0) {
+ endSegPtr = endSegPtr->nextPtr;
+ assert(endSegPtr);
+ }
+ if (!(endSegPtr = endSegPtr->nextPtr)) {
+ endSegPtr = lastLinePtr->nextPtr ? lastLinePtr->nextPtr->segPtr : lastLinePtr->segPtr;
+ }
+ while (endSegPtr->size == 0) {
+ endSegPtr = endSegPtr->nextPtr;
+ assert(endSegPtr);
+ }
+
+ /*
+ * Prepare the tag for finding the actual elide state.
+ */
+
+ if (tagPtr && reason == ELISION_HAS_BEEN_CHANGED) {
+ tagPtr->elide = !tagPtr->elide;
+ }
+
+ /*
+ * At first find the elide state of the segment which is predecessing the
+ * specified region.
+ */
+
+ startSegPtr = firstSegPtr;
+ do {
+ if (!(startSegPtr = startSegPtr->prevPtr) && linePtr->prevPtr) {
+ startSegPtr = linePtr->prevPtr->lastPtr;
+ }
+ } while (startSegPtr && !startSegPtr->tagInfoPtr);
+
+ actualElidden = startSegPtr && SegmentIsElided(sharedTextPtr, startSegPtr, NULL);
+
+ /*
+ * Now find next segment for start of range.
+ */
+
+ if (startSegPtr) {
+ startSegPtr = startSegPtr->nextPtr;
+ }
+ if (!startSegPtr) {
+ startSegPtr = firstSegPtr->sectionPtr->linePtr->segPtr;
+ }
+
+ /*
+ * We have found the predecessing elide state, now reset/prepare the tag
+ * for further processing.
+ */
+
+ if (tagPtr) {
+ if (reason == ELISION_HAS_BEEN_CHANGED) {
+ tagPtr->elide = !tagPtr->elide;
+ } else if (reason == ELISION_WILL_BE_REMOVED) {
+ oldTextPtr = tagPtr->textPtr;
+ /* this little trick is disabling the tag */
+ tagPtr->textPtr = (TkText *) tagPtr;
+ textPtr = sharedTextPtr->peers;
+ }
+ }
+
+ endSegPtr->protectionFlag = true;
+ linePtr = startSegPtr->sectionPtr->linePtr;
+ lastLinePtr = lastSegPtr->sectionPtr->linePtr;
+ segPtr = startSegPtr;
+ SetLineHasChanged(sharedTextPtr, linePtr);
+
+ while (true) {
+ if (!segPtr) {
+ if (anyChanges) {
+ /*
+ * The branches and links are influencing the section structure.
+ */
+ RebuildSections(sharedTextPtr, linePtr, true);
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+ }
+
+ anyChanges = false;
+ if (linePtr->logicalLine) {
+ linePtr->changed = true;
+ }
+ linePtr = linePtr->nextPtr;
+ assert(linePtr);
+
+ if (linePtr != endSegPtr->sectionPtr->linePtr) {
+ while (linePtr != lastLinePtr
+ && linePtr->numLinks == 0
+ && linePtr->numBranches == 0
+ && !TestTag(linePtr->tagonPtr, tagPtr)) {
+ /* Skip (nearly) unaffected line. */
+ if (linePtr->logicalLine == actualElidden) {
+ if (nodePtr && linePtr->parentPtr != nodePtr) {
+ PropagateChangeToLineCount(nodePtr, changeToLogicalLineCount);
+ changeToLogicalLineCount = 0;
+ }
+ changeToLogicalLineCount += linePtr->logicalLine ? -1 : +1;
+ linePtr->logicalLine = !actualElidden;
+ nodePtr = linePtr->parentPtr;
+ endLinePtr = linePtr;
+ }
+ if (linePtr->logicalLine) {
+ linePtr->changed = true;
+ }
+ linePtr = linePtr->nextPtr;
+ }
+ }
+
+ if (linePtr->logicalLine == actualElidden) {
+ if (nodePtr && linePtr->parentPtr != nodePtr) {
+ PropagateChangeToLineCount(nodePtr, changeToLogicalLineCount);
+ changeToLogicalLineCount = 0;
+ }
+ changeToLogicalLineCount += linePtr->logicalLine ? -1 : +1;
+ linePtr->logicalLine = !actualElidden;
+ nodePtr = linePtr->parentPtr;
+ endLinePtr = linePtr;
+ }
+
+ segPtr = linePtr->segPtr;
+ }
+ if (segPtr->tagInfoPtr) {
+ bool shouldBeElidden = tagPtr ? SegmentIsElided(sharedTextPtr, segPtr, textPtr) : false;
+ bool somethingHasChanged = false;
+
+ if (prevBranchPtr) {
+ if (!shouldBeElidden || actualElidden) {
+ /*
+ * Remove expired branch.
+ */
+
+ assert(TkBTreeHaveElidedSegments(sharedTextPtr));
+ assert(prevBranchPtr->sectionPtr->linePtr->numBranches > 0);
+
+ UnlinkSegmentAndCleanup(sharedTextPtr, prevBranchPtr);
+ if (deletedBranchPtr) {
+ TkBTreeFreeSegment(prevBranchPtr);
+ } else {
+ deletedBranchPtr = prevBranchPtr;
+ }
+ lastBranchPtr = NULL;
+ somethingHasChanged = true;
+ }
+ } else if (prevLinkPtr) {
+ if (shouldBeElidden || !actualElidden) {
+ /*
+ * Remove expired link.
+ */
+
+ UnlinkSegmentAndCleanup(sharedTextPtr, prevLinkPtr);
+ if (deletedLinkPtr) {
+ TkBTreeFreeSegment(prevLinkPtr);
+ } else {
+ deletedLinkPtr = prevLinkPtr;
+ }
+ lastBranchPtr = NULL;
+ somethingHasChanged = true;
+ }
+ } else if (actualElidden != shouldBeElidden) {
+ if (shouldBeElidden) {
+ /*
+ * We have to insert a branch.
+ */
+
+ if (deletedBranchPtr) {
+ lastBranchPtr = deletedBranchPtr;
+ deletedBranchPtr = NULL;
+ } else {
+ lastBranchPtr = MakeBranch();
+ }
+ LinkSwitch(linePtr, segPtr->prevPtr, lastBranchPtr);
+ newBranchPtr = lastBranchPtr;
+ somethingHasChanged = true;
+ } else { /* if (!actualElidden) */
+ /*
+ * We have to insert a link.
+ */
+
+ if (!lastBranchPtr) {
+ /*
+ * The related branch is starting outside of this range,
+ * so we have to search for it.
+ */
+ lastBranchPtr = TkBTreeFindStartOfElidedRange(sharedTextPtr, NULL, firstSegPtr);
+ assert(lastBranchPtr->typePtr == &tkTextBranchType);
+ }
+
+ if (deletedLinkPtr) {
+ lastLinkPtr = deletedLinkPtr;
+ deletedLinkPtr = NULL;
+ } else {
+ lastLinkPtr = MakeLink();
+ }
+
+ /* connect the branches */
+ lastBranchPtr->body.branch.nextPtr = lastLinkPtr;
+ lastLinkPtr->body.link.prevPtr = lastBranchPtr;
+ /* finally link new segment */
+ LinkSwitch(linePtr, segPtr->prevPtr, lastLinkPtr);
+ newBranchPtr = lastBranchPtr = NULL;
+ somethingHasChanged = true;
+ }
+ }
+
+ if (somethingHasChanged) {
+ if (!startLinePtr) { startLinePtr = linePtr; }
+ endLinePtr = linePtr;
+ lastLinkPtr = NULL;
+ anyChanges = true;
+ }
+
+ actualElidden = shouldBeElidden;
+ prevBranchPtr = prevLinkPtr = NULL;
+ } else if (segPtr->typePtr == &tkTextBranchType) {
+ lastBranchPtr = prevBranchPtr = segPtr;
+ lastLinkPtr = prevLinkPtr = NULL;
+ } else if (segPtr->typePtr == &tkTextLinkType) {
+ lastBranchPtr = prevBranchPtr = NULL;
+ lastLinkPtr = prevLinkPtr = segPtr;
+ }
+ if (segPtr == endSegPtr) {
+ break;
+ }
+ segPtr = segPtr->nextPtr;
+ }
+
+ if (newBranchPtr) {
+ /*
+ * Connect the inserted branch.
+ */
+
+ if (!lastLinkPtr) {
+ if (reason == ELISION_HAS_BEEN_CHANGED) { tagPtr->elide = !tagPtr->elide; }
+ actualElidden = SegmentIsElided(sharedTextPtr, endSegPtr, NULL);
+ if (reason == ELISION_HAS_BEEN_CHANGED) { tagPtr->elide = !tagPtr->elide; }
+
+ if (actualElidden) {
+ /*
+ * In this case the related link is outside of the range,
+ * so we have to search for it.
+ */
+
+ lastLinkPtr = FindNextLink(sharedTextPtr, lastSegPtr);
+ assert(lastLinkPtr);
+ } else {
+ if (deletedLinkPtr) {
+ lastLinkPtr = deletedLinkPtr;
+ deletedLinkPtr = NULL;
+ } else {
+ lastLinkPtr = MakeLink();
+ }
+ lastLinePtr = endSegPtr->sectionPtr->linePtr;
+ LinkSwitch(lastLinePtr, endSegPtr->prevPtr, lastLinkPtr);
+ if (linePtr == lastLinePtr) {
+ anyChanges = true;
+ } else {
+ RebuildSections(sharedTextPtr, lastLinePtr, true);
+ }
+ }
+ }
+
+ newBranchPtr->body.branch.nextPtr = lastLinkPtr;
+ lastLinkPtr->body.link.prevPtr = newBranchPtr;
}
- CleanupLine(linePtr);
+
+ if (deletedBranchPtr) { TkBTreeFreeSegment(deletedBranchPtr); }
+ if (deletedLinkPtr) { TkBTreeFreeSegment(deletedLinkPtr); }
+
+ if (linePtr->logicalLine) {
+ linePtr->changed = true;
+ }
+
+ if (anyChanges) {
+ /* The branches and links are influencing the section structure. */
+ RebuildSections(sharedTextPtr, linePtr, true);
+ }
+
+ if (endSegPtr != lastSegPtr) {
+ CleanupSplitPoint(endSegPtr, sharedTextPtr);
+ }
+
+ if (nodePtr) {
+ PropagateChangeToLineCount(nodePtr, changeToLogicalLineCount);
+ }
+
+ if (startLinePtr) {
+ unsigned lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, startLinePtr, NULL);
+ unsigned lineNo2 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, endLinePtr, NULL);
+
+ if (!endLinePtr->nextPtr) {
+ assert(lineNo1 < lineNo2);
+ lineNo2 -= 1; /* don't invalidate very last line */
+ }
+
+ TkTextInvalidateLineMetrics(sharedTextPtr, NULL, startLinePtr,
+ lineNo2 - lineNo1, TK_TEXT_INVALIDATE_ELIDE);
+ }
+
+ if (tagPtr && reason == ELISION_WILL_BE_REMOVED) {
+ /* Re-enable the tag. */
+ tagPtr->textPtr = oldTextPtr;
+ }
+}
+
+void
+TkBTreeUpdateElideInfo(
+ TkText *textPtr,
+ TkTextTag *tagPtr)
+{
+ TkSharedText *sharedTextPtr;
+ TkTextIndex index1, index2;
+ TkTextSearch search;
+
+ assert(textPtr);
+ assert(tagPtr);
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+
+ if (!tagPtr->elide && !TkBTreeHaveElidedSegments(sharedTextPtr)) {
+ return;
+ }
+
+ TkTextIndexSetupToStartOfText(&index1, textPtr, sharedTextPtr->tree);
+ TkTextIndexSetupToEndOfText(&index2, textPtr, sharedTextPtr->tree);
+ TkBTreeStartSearch(&index1, &index2, tagPtr, &search, SEARCH_NEXT_TAGON);
+
+ while (TkBTreeNextTag(&search)) {
+ TkTextSegment *firstSegPtr;
+
+ firstSegPtr = search.segPtr;
+ TkBTreeNextTag(&search);
+ assert(search.segPtr);
+ firstSegPtr->protectionFlag = true;
+ search.segPtr->protectionFlag = true;
+
+ UpdateElideInfo(sharedTextPtr, tagPtr, firstSegPtr, search.segPtr, ELISION_HAS_BEEN_CHANGED);
+
+ CleanupSplitPoint(firstSegPtr, sharedTextPtr);
+ CleanupSplitPoint(search.segPtr, sharedTextPtr);
+ }
+
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
}
/*
@@ -2116,331 +8583,1694 @@ TkBTreeUnlinkSegment(
* of text.
*
* Results:
- * 1 if the tags on any characters in the range were changed, and zero
- * otherwise (i.e. if the tag was already absent (add = 0) or present
- * (add = 1) on the index range in question).
+ * True if the tags on any characters in the range were changed, and false
+ * otherwise (i.e. if the tag was already absent (add = false) or present
+ * (add = true) on the index range in question).
*
* Side effects:
* The given tag is added to the given range of characters in the tree or
* removed from all those characters, depending on the "add" argument.
- * The structure of the btree is modified enough that index1Ptr and
- * index2Ptr are no longer valid after this function returns, and the
- * indexes may be modified by this function.
+ * Furthermore some branches and links may be inserted, or removed.
*
*----------------------------------------------------------------------
*/
-int
-TkBTreeTag(
- register TkTextIndex *index1Ptr,
- /* Indicates first character in range. */
- register TkTextIndex *index2Ptr,
- /* Indicates character just after the last one
- * in range. */
- TkTextTag *tagPtr, /* Tag to add or remove. */
- int add) /* One means add tag to the given range of
- * characters; zero means remove the tag from
- * the range. */
-{
- TkTextSegment *segPtr, *prevPtr;
- TkTextSearch search;
- TkTextLine *cleanupLinePtr;
- int oldState, changed, anyChanges = 0;
+enum {
+ HAS_TAGON = (1 << 0),
+ HAS_TAGOFF = (1 << 1),
+ DID_SKIP = (1 << 2)
+};
- /*
- * See whether the tag is present at the start of the range. If the state
- * doesn't already match what we want then add a toggle there.
- */
+enum {
+ UNDO_NEEDED,
+ UNDO_MERGED,
+ UNDO_ANNIHILATED,
+};
+
+typedef struct {
+ TkText *textPtr;
+ unsigned lineNo1;
+ unsigned lineNo2;
+ TkTextTag *tagPtr;
+ bool add;
+ TkTextUndoInfo *undoInfo;
+ TkTextTagChangedProc *changedProc;
+ const TkTextTagSet *tagonPtr;
+ const TkTextTagSet *addTagoffPtr;
+ const TkTextTagSet *eraseTagoffPtr;
+ const TkTextTagSet *tagInfoPtr;
+ TkTextTagSet *newTagonPtr;
+ TkTextTagSet *newAddTagoffPtr;
+ TkTextTagSet *newEraseTagoffPtr;
+ TkTextTagSet *newTagInfoPtr;
+ TkTextSegment *firstSegPtr;
+ TkTextSegment *lastSegPtr;
+ int32_t firstOffset;
+ int32_t lastOffset;
+ int32_t lengthsBuf[200];
+ int32_t *lengths;
+ unsigned sizeOfLengths;
+ unsigned capacityOfLengths;
+ int32_t currLength;
+} TreeTagData;
- oldState = TkBTreeCharTagged(index1Ptr, tagPtr);
- if ((add != 0) ^ oldState) {
- segPtr = ckalloc(TSEG_SIZE);
- segPtr->typePtr = (add) ? &tkTextToggleOnType : &tkTextToggleOffType;
- prevPtr = SplitSeg(index1Ptr);
- if (prevPtr == NULL) {
- segPtr->nextPtr = index1Ptr->linePtr->segPtr;
- index1Ptr->linePtr->segPtr = segPtr;
+static void
+SaveLength(
+ TreeTagData *data)
+{
+ if (++data->sizeOfLengths == data->capacityOfLengths) {
+ unsigned newCapacity = 2*data->capacityOfLengths;
+ data->lengths = realloc(data->lengths == data->lengthsBuf ? NULL : data->lengths, newCapacity);
+ data->capacityOfLengths = newCapacity;
+ }
+
+ data->lengths[data->sizeOfLengths - 1] = data->currLength;
+ data->currLength = 0;
+}
+
+static void
+AddLength(
+ TreeTagData *data,
+ int length)
+{
+ if (data->currLength < 0) {
+ SaveLength(data);
+ }
+ data->currLength += length;
+}
+
+static void
+SubLength(
+ TreeTagData *data,
+ int length)
+{
+ if (data->currLength > 0) {
+ SaveLength(data);
+ }
+ if (data->sizeOfLengths > 0) {
+ data->currLength -= length;
+ }
+}
+
+static int
+CompareIndices(
+ const TkTextIndex *indexPtr1,
+ const TkTextUndoIndex *indexPtr2)
+{
+ int cmp;
+
+ if (indexPtr2->lineIndex == -1) {
+ TkTextIndex index = *indexPtr1;
+ TkTextIndexSetSegment(&index, indexPtr2->u.markPtr);
+ return TkTextIndexCompare(indexPtr1, &index);
+ }
+
+ if ((cmp = TkTextIndexGetLineNumber(indexPtr1, NULL) - indexPtr2->lineIndex) == 0) {
+ cmp = TkTextIndexGetByteIndex(indexPtr1) - indexPtr2->u.byteIndex;
+ }
+
+ return cmp;
+}
+
+static int
+MergeTagUndoToken(
+ TkSharedText *sharedTextPtr,
+ const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2,
+ const TreeTagData *data)
+{
+ UndoTokenTagChange *prevToken;
+ TkTextTag *tagPtr = data->tagPtr;
+ int cmp1, cmp2;
+ bool remove;
+ bool wholeRange;
+
+ if (!tagPtr->recentTagAddRemoveToken || tagPtr->recentTagAddRemoveTokenIsNull) {
+ return UNDO_NEEDED;
+ }
+
+ prevToken = (UndoTokenTagChange *) tagPtr->recentTagAddRemoveToken;
+
+ assert(prevToken);
+ assert(UNMARKED_INT(((UndoTokenTagChange *) prevToken)->tagPtr) == UNMARKED_INT(tagPtr));
+
+ remove = POINTER_IS_MARKED(prevToken->tagPtr);
+ cmp1 = CompareIndices(indexPtr1, &prevToken->startIndex);
+ cmp2 = CompareIndices(indexPtr2, &prevToken->endIndex);
+ wholeRange = data->sizeOfLengths == 0
+ && !((UndoTokenTagChange *) tagPtr->recentTagAddRemoveToken)->lengths;
+
+ if (data->add == remove) {
+ if (cmp1 <= 0 && cmp2 >= 0) {
+ if (!data->add || wholeRange) {
+ free(prevToken->lengths);
+ prevToken->lengths = NULL;
+ return UNDO_ANNIHILATED;
+ }
+ return UNDO_NEEDED;
+ }
+ if (!wholeRange) {
+ return UNDO_NEEDED;
+ }
+ if (cmp1 < 0 && cmp2 <= 0 && CompareIndices(indexPtr2, &prevToken->startIndex) >= 0) {
+ MakeUndoIndex(sharedTextPtr, indexPtr1, &prevToken->startIndex, GRAVITY_LEFT);
+ if (cmp2 > 0) {
+ MakeUndoIndex(sharedTextPtr, indexPtr2, &prevToken->endIndex, GRAVITY_RIGHT);
+ }
+ if (data->add) {
+ UNMARK_POINTER(prevToken->tagPtr);
+ } else {
+ MARK_POINTER(prevToken->tagPtr);
+ }
+ return UNDO_MERGED;
+ }
+ if (cmp2 > 0 && cmp1 >= 0 && CompareIndices(indexPtr1, &prevToken->endIndex) <= 0) {
+ if (cmp1 > 0) {
+ MakeUndoIndex(sharedTextPtr, indexPtr1, &prevToken->startIndex, GRAVITY_LEFT);
+ }
+ MakeUndoIndex(sharedTextPtr, indexPtr2, &prevToken->endIndex, GRAVITY_RIGHT);
+ if (data->add) {
+ UNMARK_POINTER(prevToken->tagPtr);
+ } else {
+ MARK_POINTER(prevToken->tagPtr);
+ }
+ return UNDO_MERGED;
+ }
+ } else if (wholeRange) {
+ int cmp3 = CompareIndices(indexPtr2, &prevToken->startIndex);
+ int cmp4 = CompareIndices(indexPtr1, &prevToken->endIndex);
+
+ if (cmp3 == 0 || cmp4 == 0 || (cmp1 <= 0 && cmp2 >= 0) || (cmp1 >= 0 && cmp2 <= 0)) {
+ if (cmp1 < 0) {
+ MakeUndoIndex(sharedTextPtr, indexPtr1, &prevToken->startIndex, GRAVITY_LEFT);
+ }
+ if (cmp2 > 0) {
+ MakeUndoIndex(sharedTextPtr, indexPtr2, &prevToken->endIndex, GRAVITY_RIGHT);
+ }
+ return UNDO_MERGED;
+ }
+ }
+
+ return UNDO_NEEDED;
+}
+
+static unsigned
+AddRemoveTag(
+ TreeTagData *data,
+ TkTextLine *linePtr,
+ TkTextSegment *firstPtr,
+ TkTextSegment *lastPtr,
+ TkTextTagSet *(*addRemoveFunc)(TkTextTagSet *, const TkTextTag *))
+{
+ const TkTextTag *tagPtr = data->tagPtr;
+ const TkSharedText *sharedTextPtr = tagPtr->sharedTextPtr;
+ TkTextSegment *segPtr = firstPtr ? firstPtr : linePtr->segPtr;
+ TkTextSegment *prevPtr = NULL;
+ unsigned flags = 0;
+
+ assert(tagPtr);
+
+ while (segPtr != lastPtr) {
+ TkTextSegment *nextPtr = segPtr->nextPtr;
+
+ if (segPtr->tagInfoPtr) {
+ if (data->undoInfo) {
+ if (TkTextTagSetTest(segPtr->tagInfoPtr, tagPtr->index) != data->add) {
+ AddLength(data, segPtr->size);
+ if (!data->firstSegPtr) {
+ data->firstSegPtr = segPtr;
+ }
+ data->lastSegPtr = segPtr;
+ data->lastOffset = segPtr->size;
+ } else {
+ SubLength(data, segPtr->size);
+ }
+ } else if (!data->firstSegPtr) {
+ if (TkTextTagSetTest(segPtr->tagInfoPtr, tagPtr->index) != data->add) {
+ /* needed for test whether modifications have been done */
+ data->firstSegPtr = segPtr;
+ }
+ }
+ if (segPtr->tagInfoPtr == data->tagInfoPtr) {
+ assert(TkTextTagSetRefCount(data->newTagInfoPtr) > 0);
+ TagSetAssign(&segPtr->tagInfoPtr, data->newTagInfoPtr);
+ } else {
+ data->tagInfoPtr = segPtr->tagInfoPtr;
+ segPtr->tagInfoPtr = addRemoveFunc(segPtr->tagInfoPtr, tagPtr);
+ data->newTagInfoPtr = segPtr->tagInfoPtr;
+ }
+ if (segPtr->typePtr == &tkTextCharType && !segPtr->protectionFlag) {
+ if (prevPtr && TkTextTagSetIsEqual(segPtr->tagInfoPtr, prevPtr->tagInfoPtr)) {
+ TkTextSegment *pPtr = prevPtr;
+
+ prevPtr = JoinCharSegments(sharedTextPtr, prevPtr);
+ if (data->firstSegPtr == segPtr) {
+ data->firstOffset += prevPtr->size - segPtr->size;
+ data->firstSegPtr = prevPtr;
+ } else if (data->firstSegPtr == pPtr) {
+ data->firstSegPtr = prevPtr;
+ }
+ if (data->lastSegPtr == segPtr) {
+ data->lastOffset += prevPtr->size - segPtr->size;
+ data->lastSegPtr = prevPtr;
+ } else if (data->lastSegPtr == pPtr) {
+ data->lastSegPtr = prevPtr;
+ }
+ if (data->newTagInfoPtr == segPtr->tagInfoPtr
+ || data->newTagInfoPtr == pPtr->tagInfoPtr) {
+ data->newTagInfoPtr = prevPtr->tagInfoPtr;
+ }
+ } else {
+ prevPtr = segPtr;
+ }
+ } else {
+ prevPtr = NULL;
+ }
} else {
- segPtr->nextPtr = prevPtr->nextPtr;
- prevPtr->nextPtr = segPtr;
+ prevPtr = NULL;
}
- segPtr->size = 0;
- segPtr->body.toggle.tagPtr = tagPtr;
- segPtr->body.toggle.inNodeCounts = 0;
- anyChanges = 1;
+
+ segPtr = nextPtr;
}
- /*
- * Scan the range of characters and delete any internal tag transitions.
- * Keep track of what the old state was at the end of the range, and add a
- * toggle there if it's needed.
- */
+ return flags;
+}
- TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
- cleanupLinePtr = index1Ptr->linePtr;
- while (TkBTreeNextTag(&search)) {
- anyChanges = 1;
- oldState ^= 1;
- segPtr = search.segPtr;
- prevPtr = search.curIndex.linePtr->segPtr;
- if (prevPtr == segPtr) {
- search.curIndex.linePtr->segPtr = segPtr->nextPtr;
+static unsigned
+TreeTagLine(
+ TreeTagData *data,
+ TkTextLine *linePtr,
+ TkTextSegment *segPtr1,
+ TkTextSegment *segPtr2)
+{
+ unsigned flags = 0;
+ const TkTextTag *tagPtr = data->tagPtr;
+ unsigned tagIndex = tagPtr->index;
+ TkTextSegment *segPtr = segPtr1 ? segPtr1 : linePtr->segPtr;
+ bool add = data->add;
+
+ while (segPtr->size == 0 && segPtr1 != segPtr2) {
+ segPtr = segPtr->nextPtr;
+ }
+ while (segPtr2 && segPtr2->prevPtr && segPtr2->prevPtr->size == 0 && segPtr2 != segPtr1) {
+ segPtr2 = segPtr2->prevPtr;
+ }
+ if (segPtr == segPtr2) {
+ flags = DID_SKIP;
+ } else if (add) {
+ if (linePtr->tagonPtr == data->tagonPtr) {
+ assert(TkTextTagSetRefCount(data->newTagInfoPtr) > 0);
+ TagSetAssign(&linePtr->tagonPtr, data->newTagonPtr);
} else {
- while (prevPtr->nextPtr != segPtr) {
- prevPtr = prevPtr->nextPtr;
+ data->tagonPtr = linePtr->tagonPtr;
+ linePtr->tagonPtr = TagSetAdd(linePtr->tagonPtr, tagPtr);
+ data->newTagonPtr = linePtr->tagonPtr;
+ }
+ flags |= HAS_TAGON;
+ if (LineTestIfAnyIsUntagged(linePtr->segPtr, segPtr, tagIndex)
+ || (segPtr2 && LineTestIfAnyIsUntagged(segPtr2, NULL, tagIndex))) {
+ if (linePtr->tagoffPtr == data->addTagoffPtr) {
+ assert(TkTextTagSetRefCount(data->newAddTagoffPtr) > 0);
+ TagSetAssign(&linePtr->tagoffPtr, data->newAddTagoffPtr);
+ } else {
+ data->addTagoffPtr = linePtr->tagoffPtr;
+ linePtr->tagoffPtr = TagSetAdd(linePtr->tagoffPtr, tagPtr);
+ data->newAddTagoffPtr = linePtr->tagoffPtr;
}
- prevPtr->nextPtr = segPtr->nextPtr;
+ flags |= HAS_TAGOFF;
+ } else {
+ linePtr->tagoffPtr = TagSetErase(linePtr->tagoffPtr, tagPtr);
}
- if (segPtr->body.toggle.inNodeCounts) {
- ChangeNodeToggleCount(search.curIndex.linePtr->parentPtr,
- segPtr->body.toggle.tagPtr, -1);
- segPtr->body.toggle.inNodeCounts = 0;
- changed = 1;
+ flags |= AddRemoveTag(data, linePtr, segPtr1, segPtr2, TagSetAdd);
+ } else {
+ if (LineTestIfAnyIsTagged(linePtr->segPtr, segPtr, tagIndex)
+ || (segPtr2 && LineTestIfAnyIsTagged(segPtr2, NULL, tagIndex))) {
+ linePtr->tagoffPtr = TagSetAdd(linePtr->tagoffPtr, tagPtr);
+ flags |= HAS_TAGON | HAS_TAGOFF;
} else {
- changed = 0;
+ if (linePtr->tagonPtr == data->tagonPtr) {
+ assert(TkTextTagSetRefCount(data->newTagonPtr) > 0);
+ TagSetAssign(&linePtr->tagonPtr, data->newTagonPtr);
+ } else {
+ data->tagonPtr = linePtr->tagonPtr;
+ linePtr->tagonPtr = TagSetErase(linePtr->tagonPtr, tagPtr);
+ data->newTagonPtr = linePtr->tagonPtr;
+ }
+ if (linePtr->tagoffPtr == data->eraseTagoffPtr) {
+ assert(TkTextTagSetRefCount(data->newEraseTagoffPtr) > 0);
+ TagSetAssign(&linePtr->tagoffPtr, data->newEraseTagoffPtr);
+ } else {
+ data->eraseTagoffPtr = linePtr->tagoffPtr;
+ linePtr->tagoffPtr = TagSetErase(linePtr->tagoffPtr, tagPtr);
+ data->newEraseTagoffPtr = linePtr->tagoffPtr;
+ }
}
- ckfree(segPtr);
+ flags |= AddRemoveTag(data, linePtr, segPtr1, segPtr2, TagSetErase);
+ }
- /*
- * The code below is a bit tricky. After deleting a toggle we
- * eventually have to call CleanupLine, in order to allow character
- * segments to be merged together. To do this, we remember in
- * cleanupLinePtr a line that needs to be cleaned up, but we don't
- * clean it up until we've moved on to a different line. That way the
- * cleanup process won't goof up segPtr.
- */
+ return flags;
+}
+
+static unsigned
+TreeTagNode(
+ Node *nodePtr,
+ TreeTagData *data,
+ unsigned firstLineNo,
+ TkTextSegment *segPtr1,
+ TkTextSegment *segPtr2,
+ bool redraw)
+{
+ TkTextTag *tagPtr;
+ bool add;
+ unsigned flags;
+ unsigned nchilds;
+ unsigned endLineNo = firstLineNo + nodePtr->numLines - 1;
- if (cleanupLinePtr != search.curIndex.linePtr) {
- CleanupLine(cleanupLinePtr);
- cleanupLinePtr = search.curIndex.linePtr;
+ if (endLineNo < data->lineNo1 || data->lineNo2 < firstLineNo) {
+ return DID_SKIP;
+ }
+
+ tagPtr = data->tagPtr;
+ add = data->add;
+
+ assert(tagPtr);
+
+ if (NodeTestAllSegments(nodePtr, tagPtr->index, add)) {
+ if (!data->firstSegPtr) {
+ data->firstSegPtr = nodePtr->linePtr->segPtr;
}
+ data->lastSegPtr = nodePtr->lastPtr->prevPtr->lastPtr;
+ data->lastOffset = data->lastSegPtr->size;
+ return add ? HAS_TAGON : 0;
+ }
+
+ flags = nchilds = 0;
+
+ if ((segPtr1 ? data->lineNo1 < firstLineNo : data->lineNo1 <= firstLineNo)
+ && (segPtr2 ? endLineNo < data->lineNo2 : endLineNo <= data->lineNo2)) {
+ const TkSharedText *sharedTextPtr = tagPtr->sharedTextPtr;
+ bool delegateRedraw = redraw && NodeTestAnySegment(nodePtr, tagPtr->index, add);
+ TkTextIndex index1, index2;
+
+ if (delegateRedraw) {
+ redraw = false;
+ }
+
+ TkTextIndexClear2(&index1, NULL, sharedTextPtr->tree);
+ TkTextIndexClear2(&index2, NULL, sharedTextPtr->tree);
/*
- * Quick hack. ChangeNodeToggleCount may move the tag's root location
- * around and leave the search in the void. This resets the search.
+ * Whole node is affected.
*/
- if (changed) {
- TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
+ if (nodePtr->level > 0) {
+ Node *childPtr;
+
+ for (childPtr = nodePtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ flags |= TreeTagNode(childPtr, data, firstLineNo, NULL, NULL, delegateRedraw);
+ firstLineNo += childPtr->numLines;
+ }
+ } else {
+ TkTextLine *linePtr = nodePtr->linePtr;
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+
+ for ( ; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ if (!LineTestAllSegments(linePtr, tagPtr, add)) {
+ if (add) {
+ flags |= AddRemoveTag(data, linePtr, NULL, NULL, TagSetAdd);
+ if (linePtr->tagonPtr == data->tagonPtr) {
+ assert(TkTextTagSetRefCount(data->newTagonPtr) > 0);
+ TagSetAssign(&linePtr->tagonPtr, data->newTagonPtr);
+ } else {
+ data->tagonPtr = linePtr->tagonPtr;
+ linePtr->tagonPtr = TagSetAdd(linePtr->tagonPtr, tagPtr);
+ data->newTagonPtr = linePtr->tagonPtr;
+ }
+ } else {
+ flags |= AddRemoveTag(data, linePtr, NULL, NULL, TagSetErase);
+ if (linePtr->tagonPtr == data->tagonPtr) {
+ assert(TkTextTagSetRefCount(data->newTagonPtr) > 0);
+ TagSetAssign(&linePtr->tagonPtr, data->newTagonPtr);
+ } else {
+ data->tagonPtr = linePtr->tagonPtr;
+ linePtr->tagonPtr = TagSetErase(linePtr->tagonPtr, tagPtr);
+ data->newTagonPtr = linePtr->tagonPtr;
+ }
+ }
+ if (linePtr->tagoffPtr == data->eraseTagoffPtr) {
+ assert(TkTextTagSetRefCount(data->newEraseTagoffPtr) > 0);
+ TagSetAssign(&linePtr->tagoffPtr, data->newEraseTagoffPtr);
+ } else {
+ data->eraseTagoffPtr = linePtr->tagoffPtr;
+ linePtr->tagoffPtr = TagSetErase(linePtr->tagoffPtr, tagPtr);
+ data->newEraseTagoffPtr = linePtr->tagoffPtr;
+ }
+ if (delegateRedraw) {
+ TkTextIndexSetToStartOfLine2(&index1, linePtr);
+ TkTextIndexSetToEndOfLine2(&index2, linePtr);
+ data->changedProc(sharedTextPtr, data->textPtr, &index1, &index2,
+ tagPtr, false);
+ }
+ if (!data->firstSegPtr) {
+ data->firstSegPtr = linePtr->segPtr;
+ }
+ data->lastSegPtr = linePtr->lastPtr;
+ data->lastOffset = linePtr->lastPtr->size;
+ } else if (data->undoInfo) {
+ SubLength(data, linePtr->size);
+ }
+ }
+ }
+
+ if (redraw) {
+ TkTextIndexSetToStartOfLine2(&index1, nodePtr->linePtr);
+ TkTextIndexSetToEndOfLine2(&index2, nodePtr->lastPtr);
+ data->changedProc(sharedTextPtr, data->textPtr, &index1, &index2, tagPtr, false);
+ }
+
+ if (add) {
+ flags = HAS_TAGON;
+ nchilds = nodePtr->numChildren;
+ }
+ } else {
+ unsigned tagIndex = tagPtr->index;
+ unsigned myFlags;
+
+ if (nodePtr->level > 0) {
+ Node *childPtr;
+
+ for (childPtr = nodePtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ myFlags = TreeTagNode(childPtr, data, firstLineNo, segPtr1, segPtr2, redraw);
+ if (myFlags == DID_SKIP) {
+ if (TkTextTagSetTest(childPtr->tagonPtr, tagIndex)) {
+ if (!tagPtr->rootPtr) {
+ tagPtr->rootPtr = childPtr;
+ }
+ myFlags |= HAS_TAGON;
+ }
+ if (TkTextTagSetTest(childPtr->tagoffPtr, tagIndex)) {
+ myFlags |= HAS_TAGOFF;
+ }
+ }
+ if (myFlags & HAS_TAGON) { nchilds += 1; }
+ flags |= myFlags;
+ firstLineNo += childPtr->numLines;
+ }
+ } else {
+ const TkSharedText *sharedTextPtr = tagPtr->sharedTextPtr;
+ TkTextLine *linePtr = nodePtr->linePtr;
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+ TkTextIndex index1, index2;
+
+ if (redraw) {
+ TkTextIndexClear2(&index1, NULL, sharedTextPtr->tree);
+ TkTextIndexClear2(&index2, NULL, sharedTextPtr->tree);
+ }
+
+ for ( ; firstLineNo < data->lineNo1; ++firstLineNo, linePtr = linePtr->nextPtr) {
+ assert(linePtr);
+ myFlags = 0;
+ if (TkTextTagSetTest(linePtr->tagonPtr, tagIndex)) { myFlags |= HAS_TAGON; }
+ if (TkTextTagSetTest(linePtr->tagoffPtr, tagIndex)) { myFlags |= HAS_TAGOFF; }
+ if (myFlags & HAS_TAGON) { nchilds += 1; }
+ flags |= myFlags;
+ if (data->undoInfo) {
+ SubLength(data, linePtr->size);
+ }
+ }
+ for ( ; firstLineNo <= data->lineNo2 && linePtr != lastPtr;
+ linePtr = linePtr->nextPtr, ++firstLineNo) {
+ if (!LineTestAllSegments(linePtr, tagPtr, add)) {
+ TkTextSegment *startSegPtr, *stopSegPtr;
+
+ startSegPtr = (firstLineNo == data->lineNo1) ? segPtr1 : NULL;
+ stopSegPtr = (firstLineNo == data->lineNo2) ? segPtr2 : NULL;
+ myFlags = TreeTagLine(data, linePtr, startSegPtr, stopSegPtr);
+
+ if (myFlags == DID_SKIP) {
+ if (TkTextTagSetTest(linePtr->tagonPtr, tagIndex)) { myFlags |= HAS_TAGON; }
+ if (TkTextTagSetTest(linePtr->tagoffPtr, tagIndex)) { myFlags |= HAS_TAGOFF; }
+ }
+ if (myFlags & HAS_TAGON) { nchilds += 1; }
+ flags |= myFlags;
+
+ if (redraw) {
+ TkTextIndexSetToStartOfLine2(&index1, linePtr);
+ TkTextIndexSetToEndOfLine2(&index2, linePtr);
+ data->changedProc(sharedTextPtr, data->textPtr, &index1, &index2, tagPtr, false);
+ }
+ } else {
+ if (add) {
+ flags |= HAS_TAGON;
+ nchilds += 1;
+ }
+ if (data->undoInfo) {
+ SubLength(data, linePtr->size);
+ }
+ }
+ }
+ for ( ; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ assert(linePtr);
+ myFlags = 0;
+ if (TkTextTagSetTest(linePtr->tagonPtr, tagIndex)) { myFlags |= HAS_TAGON; }
+ if (TkTextTagSetTest(linePtr->tagoffPtr, tagIndex)) { myFlags |= HAS_TAGOFF; }
+ if (myFlags & HAS_TAGON) { nchilds += 1; }
+ flags |= myFlags;
+ if (data->undoInfo) {
+ SubLength(data, linePtr->size);
+ }
+ }
}
}
- if ((add != 0) ^ oldState) {
- segPtr = ckalloc(TSEG_SIZE);
- segPtr->typePtr = (add) ? &tkTextToggleOffType : &tkTextToggleOnType;
- prevPtr = SplitSeg(index2Ptr);
- if (prevPtr == NULL) {
- segPtr->nextPtr = index2Ptr->linePtr->segPtr;
- index2Ptr->linePtr->segPtr = segPtr;
+
+ if (!(flags & HAS_TAGON)) {
+ flags &= ~HAS_TAGOFF;
+ } else if (nchilds < nodePtr->numChildren) {
+ flags |= HAS_TAGOFF;
+ }
+ if (nchilds > (nodePtr->level > 0 ? 1 : 0)) {
+ tagPtr->rootPtr = nodePtr;
+ }
+
+ nodePtr->tagonPtr = TagSetAddOrErase(nodePtr->tagonPtr, tagPtr, !!(flags & HAS_TAGON));
+ nodePtr->tagoffPtr = TagSetAddOrErase(nodePtr->tagoffPtr, tagPtr, !!(flags & HAS_TAGOFF));
+
+ return flags;
+}
+
+static bool
+FindSplitPoints(
+ TkSharedText *sharedTextPtr,
+ const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2,
+ const TkTextTag *tagPtr, /* can be NULL */
+ bool add,
+ TkTextSegment **segPtr1,
+ TkTextSegment **segPtr2)
+{
+ TkTextLine *linePtr1 = TkTextIndexGetLine(indexPtr1);
+ TkTextLine *linePtr2 = TkTextIndexGetLine(indexPtr2);
+ TkTextIndex end;
+ bool needSplit1;
+ bool needSplit2;
+
+ assert(tagPtr || !add);
+
+ TkTextIndexBackChars(NULL, indexPtr2, 1, &end, COUNT_INDICES);
+
+ needSplit1 = (TkBTreeCharTagged(indexPtr1, tagPtr) != add);
+ needSplit2 = (TkBTreeCharTagged(&end, tagPtr) != add);
+
+ if (!needSplit1 && !needSplit2) {
+ if (tagPtr) {
+ TkTextSearch search;
+
+ TkBTreeStartSearch(indexPtr1, indexPtr2, tagPtr, &search, SEARCH_EITHER_TAGON_TAGOFF);
+ if (!TkBTreeNextTag(&search)) {
+ return false; /* whole range is already tagged/untagged */
+ }
} else {
- segPtr->nextPtr = prevPtr->nextPtr;
- prevPtr->nextPtr = segPtr;
+ if (!TkBTreeFindNextTagged(indexPtr1, indexPtr2, NULL)) {
+ return false; /* whole range is already untagged */
+ }
+ }
+ }
+
+ if (needSplit1) {
+ if ((*segPtr1 = SplitSeg(indexPtr1, NULL))) {
+ SplitSection((*segPtr1)->sectionPtr);
}
- segPtr->size = 0;
- segPtr->body.toggle.tagPtr = tagPtr;
- segPtr->body.toggle.inNodeCounts = 0;
- anyChanges = 1;
+ TkTextIndexToByteIndex((TkTextIndex *) indexPtr2); /* mutable due to concept */
+ } else {
+ *segPtr1 = NULL;
+ }
+ if (!*segPtr1) {
+ *segPtr1 = TkTextIndexGetContentSegment(indexPtr1, NULL);
+ } else if (!(*segPtr1 = (*segPtr1)->nextPtr)) {
+ assert((*segPtr1)->sectionPtr->linePtr->nextPtr);
+ linePtr1 = (*segPtr1)->sectionPtr->linePtr->nextPtr;
+ *segPtr1 = linePtr1->segPtr;
}
/*
- * Cleanup cleanupLinePtr and the last line of the range, if these are
- * different.
+ * The next split may invalidate '*segPtr1', so we are inserting temporarily
+ * a protection mark, this avoids the invalidation.
*/
- if (anyChanges) {
- CleanupLine(cleanupLinePtr);
- if (cleanupLinePtr != index2Ptr->linePtr) {
- CleanupLine(index2Ptr->linePtr);
+ assert(!sharedTextPtr->protectionMark[0]->sectionPtr); /* this protection mark is unused? */
+ LinkSegment(linePtr1, (*segPtr1)->prevPtr, sharedTextPtr->protectionMark[0]);
+
+ if (!needSplit2) {
+ *segPtr2 = NULL;
+ } else if ((*segPtr2 = SplitSeg(indexPtr2, NULL))) {
+ SplitSection((*segPtr2)->sectionPtr);
+ }
+ if (!*segPtr2) {
+ *segPtr2 = TkTextIndexGetContentSegment(indexPtr2, NULL);
+ } else if (!(*segPtr2 = (*segPtr2)->nextPtr)) {
+ assert((*segPtr2)->sectionPtr->linePtr->nextPtr);
+ linePtr2 = (*segPtr2)->sectionPtr->linePtr->nextPtr;
+ *segPtr2 = linePtr2->segPtr;
+ }
+
+ *segPtr1 = sharedTextPtr->protectionMark[0]->nextPtr;
+ UnlinkSegment(sharedTextPtr->protectionMark[0]);
+
+ return true;
+}
+
+bool
+TkBTreeTag(
+ TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ TkText *textPtr, /* Information about text widget, can be NULL. */
+ const TkTextIndex *indexPtr1, /* Indicates first character in range. */
+ const TkTextIndex *indexPtr2, /* Indicates character just after the last one in range. */
+ TkTextTag *tagPtr, /* Tag to add or remove. */
+ bool add, /* 'true' means add tag to the given range of characters;
+ * 'false' means remove the tag from the range. */
+ TkTextUndoInfo *undoInfo, /* Store undo information, can be NULL. */
+ TkTextTagChangedProc changedProc) /* Trigger this callback when any tag will be added/removed. */
+{
+ TkTextLine *linePtr1;
+ TkTextLine *linePtr2;
+ TkTextSegment *segPtr1, *segPtr2;
+ TkTextSegment *firstPtr, *lastPtr;
+ TreeTagData data;
+ Node *rootPtr;
+
+ assert(tagPtr);
+ assert(indexPtr1);
+ assert(indexPtr2);
+ assert(TkTextIndexCompare(indexPtr1, indexPtr2) <= 0);
+ assert(changedProc);
+
+ if (!add && !tagPtr->rootPtr) {
+ return false;
+ }
+ if (TkTextIndexIsEqual(indexPtr1, indexPtr2)) {
+ return false;
+ }
+ if (!add) {
+ if (!tagPtr->rootPtr) {
+ return false;
}
- ((BTree *)index1Ptr->tree)->stateEpoch++;
+ if (TkBTreeGetRoot(sharedTextPtr->tree)->tagonPtr == sharedTextPtr->emptyTagInfoPtr) {
+ return false;
+ }
+ }
+ if (!FindSplitPoints(sharedTextPtr, indexPtr1, indexPtr2, tagPtr, add, &segPtr1, &segPtr2)) {
+ return false;
}
- if (tkBTreeDebug) {
- TkBTreeCheck(index1Ptr->tree);
+ segPtr1->protectionFlag = true;
+ segPtr2->protectionFlag = true;
+
+ if (!add && tagPtr->elideString) {
+ /*
+ * In case of elision we have to inspect each segment, because a
+ * Branch or a Link segment has to be inserted/removed if required.
+ *
+ * NOTE: Currently, when using elision (tag option -elide), TkBTreeTag
+ * can be considerably slower than without. In return the lookup, whether
+ * a segment is elided, is super-fast now, and this has more importance -
+ * in general inserting/removing an elided range will be done only once,
+ * but the lookup for the elision option is a frequent use case.
+ *
+ * Note that UpdateElideInfo needs the old state when removing the tag,
+ * so we are doing this before eliminating the tag.
+ */
+
+ UpdateElideInfo(sharedTextPtr, tagPtr, segPtr1, segPtr2, ELISION_WILL_BE_REMOVED);
+ }
+
+ if (undoInfo) {
+ memset(undoInfo, 0, sizeof(*undoInfo));
}
- return anyChanges;
+
+ firstPtr = TkTextIndexIsStartOfLine(indexPtr1) ? NULL : segPtr1;
+ lastPtr = TkTextIndexIsStartOfLine(indexPtr2) ? NULL : segPtr2;
+ linePtr1 = segPtr1->sectionPtr->linePtr;
+ linePtr2 = segPtr2->sectionPtr->linePtr;
+ rootPtr = TkBTreeGetRoot(sharedTextPtr->tree); /* we must start at top level */
+ tagPtr->rootPtr = NULL; /* will be recomputed */
+
+ memset(&data, 0, sizeof(data));
+ data.tagPtr = tagPtr;
+ data.add = add;
+ data.changedProc = changedProc;
+ data.undoInfo = tagPtr->undo ? undoInfo : NULL;
+ data.firstSegPtr = NULL;
+ data.lastSegPtr = NULL;
+ data.textPtr = textPtr;
+ data.lineNo1 = TkTextIndexGetLineNumber(indexPtr1, NULL);
+ data.lineNo2 = linePtr1 == linePtr2 ?
+ data.lineNo1 : TkTextIndexGetLineNumber(indexPtr2, NULL) - (lastPtr ? 0 : 1);
+ data.lengths = data.lengthsBuf;
+ data.capacityOfLengths = sizeof(data.lengthsBuf)/sizeof(data.lengthsBuf[0]);
+
+ TreeTagNode(rootPtr, &data, 0, firstPtr, lastPtr, tagPtr->affectsDisplay);
+
+ if (add && tagPtr->elideString) {
+ /*
+ * In case of elision we have to inspect each segment, because a
+ * Branch or a Link segment has to be inserted/removed if required.
+ *
+ * NOTE: Currently, when using elision (tag option -elide), TkBTreeTag
+ * can be considerably slower than without. In return the lookup, whether
+ * a segment is elided, is super-fast now, and this has more importance -
+ * in general inserting/removing an elided range will be done only once,
+ * but the lookup for the elision option is a frequent use case.
+ *
+ * Note that UpdateElideInfo needs the new state when adding the tag,
+ * so we are doing this after the tag has been added.
+ */
+
+ UpdateElideInfo(sharedTextPtr, tagPtr, segPtr1, segPtr2, ELISION_HAS_BEEN_ADDED);
+ }
+
+ if (undoInfo && (data.sizeOfLengths > 0 || data.currLength > 0)) {
+ TkTextIndex index1 = *indexPtr1;
+ TkTextIndex index2 = *indexPtr2;
+
+ assert(data.firstSegPtr);
+ assert(data.lastSegPtr);
+
+ /*
+ * Setup the undo information.
+ */
+
+ assert(data.lastSegPtr->size >= data.lastOffset);
+ data.lastOffset = data.lastSegPtr->size - data.lastOffset;
+
+ if (data.lastSegPtr->nextPtr) {
+ data.lastSegPtr = data.lastSegPtr->nextPtr;
+ } else if (data.lastSegPtr->sectionPtr->linePtr->nextPtr) {
+ data.lastSegPtr = data.lastSegPtr->sectionPtr->linePtr->nextPtr->segPtr;
+ }
+ if (data.lastSegPtr->sectionPtr->linePtr == GetLastLine(sharedTextPtr, textPtr)) {
+ data.lastSegPtr = textPtr->endMarker;
+ }
+ TkTextIndexSetSegment(&index1, data.firstSegPtr);
+ TkTextIndexSetSegment(&index2, data.lastSegPtr);
+ TkTextIndexForwBytes(textPtr, &index1, data.firstOffset, &index1);
+ TkTextIndexBackBytes(textPtr, &index2, data.lastOffset, &index2);
+ assert(TkTextIndexCompare(&index1, &index2) < 0);
+
+ if (data.sizeOfLengths > 0) {
+ assert(data.currLength != 0);
+ if (data.currLength > 0 && data.sizeOfLengths > 1) {
+ SaveLength(&data);
+ }
+ if (data.sizeOfLengths == 1) {
+ data.sizeOfLengths = 0;
+ } else if (data.lengths[data.sizeOfLengths - 1] > 0) {
+ data.lengths[data.sizeOfLengths - 1] = 0;
+ } else {
+ data.currLength = 0;
+ SaveLength(&data);
+ }
+ }
+
+ switch (MergeTagUndoToken(sharedTextPtr, &index1, &index2, &data)) {
+ case UNDO_NEEDED: {
+ UndoTokenTagChange *undoToken;
+
+ if (tagPtr->recentTagAddRemoveToken && !tagPtr->recentTagAddRemoveTokenIsNull) {
+ undoInfo->token = (TkTextUndoToken *) tagPtr->recentTagAddRemoveToken;
+ undoInfo->byteSize = 0;
+ tagPtr->recentTagAddRemoveToken = NULL;
+ }
+ if (!tagPtr->recentTagAddRemoveToken) {
+ tagPtr->recentTagAddRemoveToken = malloc(sizeof(UndoTokenTagChange));
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ }
+
+ tagPtr->recentTagAddRemoveTokenIsNull = false;
+ undoToken = (UndoTokenTagChange *) tagPtr->recentTagAddRemoveToken;
+ undoToken->undoType = &undoTokenTagType;
+ undoToken->tagPtr = tagPtr;
+ if (!add) {
+ MARK_POINTER(undoToken->tagPtr);
+ }
+ MakeUndoIndex(sharedTextPtr, &index1, &undoToken->startIndex, GRAVITY_LEFT);
+ MakeUndoIndex(sharedTextPtr, &index2, &undoToken->endIndex, GRAVITY_RIGHT);
+ if (data.sizeOfLengths > 0) {
+ if (data.lengths == data.lengthsBuf) {
+ data.lengths = malloc(data.sizeOfLengths * sizeof(data.lengths[0]));
+ memcpy(data.lengths, data.lengthsBuf, data.sizeOfLengths * sizeof(data.lengths[0]));
+ } else {
+ data.lengths = realloc(data.lengths, data.sizeOfLengths * sizeof(data.lengths[0]));
+ }
+ undoToken->lengths = data.lengths;
+ data.lengths = data.lengthsBuf;
+ } else {
+ undoToken->lengths = NULL;
+ }
+ TkTextTagAddRetainedUndo(sharedTextPtr, tagPtr);
+ break;
+ }
+ case UNDO_MERGED:
+ /* no action required */
+ break;
+ case UNDO_ANNIHILATED:
+ tagPtr->recentTagAddRemoveTokenIsNull = true;
+ break;
+ }
+
+ if (data.lengths != data.lengthsBuf) {
+ free(data.lengths);
+ }
+ }
+
+ assert(data.lengths == data.lengthsBuf);
+
+ CleanupSplitPoint(segPtr1, sharedTextPtr);
+ CleanupSplitPoint(segPtr2, sharedTextPtr);
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+
+ TK_BTREE_DEBUG(TkBTreeCheck(indexPtr1->tree));
+
+ return !!data.firstSegPtr;
}
/*
*----------------------------------------------------------------------
*
- * ChangeNodeToggleCount --
+ * TkBTreeClearTags --
*
- * This function increments or decrements the toggle count for a
- * particular tag in a particular node and all its ancestors up to the
- * per-tag root node.
+ * Turn all tags off inside a given range. Note that the special
+ * selection tag is an exception, and may not be removed if not
+ * wanted.
*
* Results:
- * None.
+ * True if the tags on any characters were changed, and false otherwise.
*
* Side effects:
- * The toggle count for tag is adjusted up or down by "delta" in nodePtr.
- * This routine maintains the tagRootPtr that identifies the root node
- * for the tag, moving it up or down the tree as needed.
+ * Some branches and links may be removed.
*
*----------------------------------------------------------------------
*/
+typedef struct ClearTagsData {
+ unsigned skip;
+ unsigned capacity;
+ TkTextTagSet *tagonPtr;
+ TkTextTagSet *tagoffPtr;
+ TkTextTagSet *newTagonPtr;
+ TkTextTagSet *newTagoffPtr;
+ UndoTagChange *tagChangePtr;
+ TkTextSegment *firstSegPtr;
+ TkTextSegment *lastSegPtr;
+} ClearTagsData;
+
+static Node *
+FindCommonParent(
+ Node *nodePtr1,
+ Node *nodePtr2)
+{
+ while (nodePtr1->level > nodePtr2->level) {
+ nodePtr1 = nodePtr1->parentPtr;
+ }
+ while (nodePtr2->level > nodePtr1->level) {
+ nodePtr2 = nodePtr2->parentPtr;
+ }
+ return nodePtr2;
+}
+
+static bool
+TestIfAnySegmentIsAffected(
+ TkSharedText *sharedTextPtr,
+ const TkTextTagSet *tagInfoPtr,
+ bool discardSelection)
+{
+ if (discardSelection) {
+ return !TkTextTagBitContainsSet(sharedTextPtr->selectionTags, tagInfoPtr);
+ }
+ return tagInfoPtr != sharedTextPtr->emptyTagInfoPtr;
+}
+
+static bool
+TestIfDisplayGeometryIsAffected(
+ TkSharedText *sharedTextPtr,
+ const TkTextTagSet *tagInfoPtr,
+ bool discardSelection)
+{
+ unsigned i;
+
+ i = TkTextTagSetFindFirstInIntersection(
+ tagInfoPtr, discardSelection ? sharedTextPtr->affectGeometryNonSelTags
+ : sharedTextPtr->affectGeometryTags);
+ return i != TK_TEXT_TAG_SET_NPOS && sharedTextPtr->tagLookup[i]->affectsDisplayGeometry;
+}
+
+static TkTextTagSet *
+ClearTagsFromLine(
+ TkSharedText *sharedTextPtr,
+ TkTextLine *linePtr,
+ TkTextSegment *firstPtr,
+ TkTextSegment *lastPtr,
+ TkTextTagSet *affectedTagInfoPtr,
+ UndoTokenTagClear *undoToken,
+ ClearTagsData *data,
+ bool discardSelection,
+ bool redraw,
+ TkTextTagChangedProc changedProc,
+ TkText *textPtr)
+{
+ TkTextTagSet *emptyTagInfoPtr = sharedTextPtr->emptyTagInfoPtr;
+ TkTextTagSet *myAffectedTagInfoPtr;
+ TkTextSegment *segPtr;
+ TkTextSegment *prevPtr;
+ bool anyChanges;
+
+ if (linePtr->tagonPtr == emptyTagInfoPtr) {
+ /*
+ * Nothing to do.
+ */
+ if (undoToken) {
+ data->skip += linePtr->size;
+ }
+ return affectedTagInfoPtr;
+ }
+
+ if (discardSelection || redraw) {
+ TkTextTagSetIncrRefCount(myAffectedTagInfoPtr = emptyTagInfoPtr);
+ } else {
+ myAffectedTagInfoPtr = affectedTagInfoPtr;
+ }
+
+ segPtr = firstPtr ? firstPtr : linePtr->segPtr;
+ prevPtr = NULL;
+ anyChanges = false;
+
+ if (undoToken && firstPtr) {
+ TkTextIndex index;
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&index, firstPtr);
+ data->skip = TkTextSegToIndex(firstPtr);
+ }
+
+ while (segPtr != lastPtr) {
+ TkTextSegment *nextPtr = segPtr->nextPtr;
+
+ if (segPtr->tagInfoPtr) {
+ if (segPtr->tagInfoPtr != emptyTagInfoPtr
+ && (!discardSelection
+ || !TkTextTagBitContainsSet(sharedTextPtr->selectionTags, segPtr->tagInfoPtr))) {
+ if (!data->firstSegPtr) {
+ data->firstSegPtr = segPtr;
+ }
+ data->lastSegPtr = segPtr;
+
+ if (myAffectedTagInfoPtr) {
+ myAffectedTagInfoPtr = TkTextTagSetJoin(myAffectedTagInfoPtr, segPtr->tagInfoPtr);
+ }
+
+ if (undoToken) {
+ TkTextTagSet *tagInfoPtr;
+
+ TkTextTagSetIncrRefCount(tagInfoPtr = segPtr->tagInfoPtr);
+ tagInfoPtr = TagSetRemoveBits(segPtr->tagInfoPtr,
+ sharedTextPtr->dontUndoTags, sharedTextPtr);
+
+ if (tagInfoPtr == sharedTextPtr->emptyTagInfoPtr) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ data->skip += segPtr->size;
+ if (data->firstSegPtr == segPtr) {
+ data->firstSegPtr = data->lastSegPtr = NULL;
+ }
+ } else {
+ UndoTagChange *tagChangePtr;
+
+ if (data->skip == 0
+ && data->tagChangePtr
+ && TkTextTagSetIsEqual(data->tagChangePtr->tagInfoPtr, tagInfoPtr)) {
+ data->tagChangePtr->size += segPtr->size;
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ } else {
+ if (undoToken->changeListSize == data->capacity) {
+ data->capacity = MAX(2*data->capacity, 50);
+ undoToken->changeList = realloc(undoToken->changeList,
+ data->capacity * sizeof(undoToken->changeList[0]));
+ }
+ tagChangePtr = undoToken->changeList + undoToken->changeListSize++;
+ tagChangePtr->tagInfoPtr = tagInfoPtr;
+ tagChangePtr->size = segPtr->size;
+ tagChangePtr->skip = data->skip;
+ data->tagChangePtr = tagChangePtr;
+ data->skip = 0;
+ }
+ }
+ }
+
+ if (discardSelection) {
+ segPtr->tagInfoPtr = TagSetIntersectBits(segPtr->tagInfoPtr,
+ sharedTextPtr->selectionTags, sharedTextPtr);
+ } else {
+ TagSetAssign(&segPtr->tagInfoPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+ anyChanges = true;
+ } else if (undoToken) {
+ data->skip += segPtr->size;
+ }
+ if (segPtr->typePtr == &tkTextCharType && !segPtr->protectionFlag) {
+ if (prevPtr && TkTextTagSetIsEqual(segPtr->tagInfoPtr, prevPtr->tagInfoPtr)) {
+ TkTextSegment *pPtr = prevPtr;
+
+ prevPtr = JoinCharSegments(sharedTextPtr, prevPtr);
+ if (data->firstSegPtr == pPtr || data->firstSegPtr == segPtr) {
+ data->firstSegPtr = prevPtr;
+ }
+ if (data->lastSegPtr == pPtr || data->lastSegPtr == segPtr) {
+ data->lastSegPtr = prevPtr;
+ }
+ } else {
+ prevPtr = segPtr;
+ }
+ } else {
+ prevPtr = NULL;
+ }
+ } else {
+ prevPtr = NULL;
+ }
+
+ segPtr = nextPtr;
+ }
+
+ if (anyChanges) {
+ if (redraw
+ && TkTextTagSetIntersectsBits(myAffectedTagInfoPtr,
+ discardSelection
+ ? sharedTextPtr->affectDisplayNonSelTags
+ : sharedTextPtr->affectDisplayTags)) {
+ bool affectsDisplayGeometry = TestIfDisplayGeometryIsAffected(
+ sharedTextPtr, myAffectedTagInfoPtr, discardSelection);
+ TkTextIndex index1, index2;
+
+ TkTextIndexClear2(&index1, NULL, sharedTextPtr->tree);
+ TkTextIndexClear2(&index2, NULL, sharedTextPtr->tree);
+ TkTextIndexSetToStartOfLine2(&index1, linePtr);
+ TkTextIndexSetToEndOfLine2(&index2, linePtr);
+ changedProc(sharedTextPtr, textPtr, &index1, &index2, NULL, affectsDisplayGeometry);
+ }
+
+ if (discardSelection) {
+ myAffectedTagInfoPtr = TagSetRemoveBits(myAffectedTagInfoPtr,
+ sharedTextPtr->selectionTags, sharedTextPtr);
+ }
+
+ if (firstPtr || lastPtr) {
+ TkTextTagSet *tagonPtr, *tagoffPtr;
+
+ if (linePtr->tagonPtr == data->tagonPtr && linePtr->tagoffPtr == data->tagoffPtr) {
+ TagSetReplace(&linePtr->tagonPtr, data->newTagonPtr);
+ TagSetReplace(&linePtr->tagoffPtr, data->newTagoffPtr);
+ } else {
+ data->tagonPtr = linePtr->tagonPtr;
+ data->tagoffPtr = linePtr->tagoffPtr;
+
+ TkTextTagSetIncrRefCount(tagonPtr = sharedTextPtr->emptyTagInfoPtr);
+ tagoffPtr = NULL;
+
+ for (segPtr = linePtr->segPtr; segPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->tagInfoPtr) {
+ tagonPtr = TkTextTagSetJoin(tagonPtr, segPtr->tagInfoPtr);
+ tagoffPtr = TagSetIntersect(tagoffPtr, segPtr->tagInfoPtr, sharedTextPtr);
+ }
+ }
+
+ TagSetReplace(&linePtr->tagonPtr, tagonPtr);
+
+ if (tagoffPtr) {
+ tagoffPtr = TagSetComplementTo(tagoffPtr, linePtr->tagonPtr, sharedTextPtr);
+ TagSetReplace(&linePtr->tagoffPtr, tagoffPtr);
+ } else {
+ TagSetAssign(&linePtr->tagoffPtr, linePtr->tagonPtr);
+ }
+
+ data->newTagonPtr = linePtr->tagonPtr;
+ data->newTagoffPtr = linePtr->tagoffPtr;
+ }
+ } else if (discardSelection) {
+ linePtr->tagonPtr = TagSetRemove(linePtr->tagonPtr, myAffectedTagInfoPtr, sharedTextPtr);
+ linePtr->tagoffPtr = TagSetRemove(linePtr->tagoffPtr, myAffectedTagInfoPtr, sharedTextPtr);
+ } else {
+ TagSetAssign(&linePtr->tagonPtr, sharedTextPtr->emptyTagInfoPtr);
+ TagSetAssign(&linePtr->tagoffPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+
+ if (discardSelection) {
+ if (affectedTagInfoPtr) {
+ affectedTagInfoPtr = TkTextTagSetJoin(affectedTagInfoPtr, myAffectedTagInfoPtr);
+ }
+ TkTextTagSetDecrRefCount(myAffectedTagInfoPtr);
+ } else if (redraw && affectedTagInfoPtr) {
+ affectedTagInfoPtr = TkTextTagSetJoin(affectedTagInfoPtr, myAffectedTagInfoPtr);
+ TkTextTagSetDecrRefCount(myAffectedTagInfoPtr);
+ }
+ }
+
+ return affectedTagInfoPtr;
+}
+
static void
-ChangeNodeToggleCount(
- register Node *nodePtr, /* Node whose toggle count for a tag must be
- * changed. */
- TkTextTag *tagPtr, /* Information about tag. */
- int delta) /* Amount to add to current toggle count for
- * tag (may be negative). */
-{
- register Summary *summaryPtr, *prevPtr;
- register Node *node2Ptr;
- int rootLevel; /* Level of original tag root. */
-
- tagPtr->toggleCount += delta;
- if (tagPtr->tagRootPtr == NULL) {
- tagPtr->tagRootPtr = nodePtr;
- return;
+ClearTagRoots(
+ const TkSharedText *sharedTextPtr,
+ const TkTextTagSet *affectedTags)
+{
+ unsigned i;
+
+ for (i = TkTextTagSetFindFirst(affectedTags);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(affectedTags, i)) {
+ TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
+
+ assert(tagPtr);
+ tagPtr->rootPtr = NULL;
}
+}
+static void
+ClearTagsFromAllNodes(
+ TkSharedText *sharedTextPtr,
+ Node *nodePtr,
+ ClearTagsData *data,
+ bool discardSelection,
+ TkTextTagChangedProc changedProc,
+ TkText *textPtr)
+{
/*
- * Note the level of the existing root for the tag so we can detect if it
- * needs to be moved because of the toggle count change.
+ * This is a very fast way to clear all tags, but this function only works
+ * if all the tags in the widget will be cleared.
*/
- rootLevel = tagPtr->tagRootPtr->level;
+ if (!TestIfAnySegmentIsAffected(sharedTextPtr, nodePtr->tagonPtr, discardSelection)) {
+ return; /* nothing to do */
+ }
- /*
- * Iterate over the node and its ancestors up to the tag root, adjusting
- * summary counts at each node and moving the tag's root upwards if
- * necessary.
- */
+ if (nodePtr->level > 0) {
+ Node *childPtr;
+
+ for (childPtr = nodePtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ ClearTagsFromAllNodes(sharedTextPtr, childPtr, data, discardSelection, changedProc, textPtr);
+ }
+ } else {
+ TkTextLine *linePtr = nodePtr->linePtr;
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+
+ for ( ; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ if (TestIfAnySegmentIsAffected(sharedTextPtr, linePtr->tagonPtr, discardSelection)) {
+ ClearTagsFromLine(sharedTextPtr, linePtr, NULL, NULL, NULL, NULL, data,
+ discardSelection, false, changedProc, textPtr);
+ } else if (data->firstSegPtr) {
+ data->skip += linePtr->size;
+ }
+ }
+ }
+
+ if (discardSelection) {
+ nodePtr->tagonPtr = TagSetIntersectBits(nodePtr->tagonPtr,
+ sharedTextPtr->selectionTags, sharedTextPtr);
+ nodePtr->tagoffPtr = TagSetIntersectBits(nodePtr->tagoffPtr,
+ sharedTextPtr->selectionTags, sharedTextPtr);
+ } else {
+ TagSetAssign(&nodePtr->tagonPtr, sharedTextPtr->emptyTagInfoPtr);
+ TagSetAssign(&nodePtr->tagoffPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+}
- for ( ; nodePtr != tagPtr->tagRootPtr; nodePtr = nodePtr->parentPtr) {
+static TkTextTagSet *
+ClearTagsFromNode(
+ TkSharedText *sharedTextPtr,
+ Node *nodePtr,
+ unsigned firstLineNo,
+ unsigned lineNo1,
+ unsigned lineNo2,
+ TkTextSegment *segPtr1, /* will not be free'd! */
+ TkTextSegment *segPtr2, /* will not be free'd! */
+ TkTextTagSet *affectedTagInfoPtr,
+ UndoTokenTagClear *undoToken,
+ ClearTagsData *data,
+ bool discardSelection,
+ bool redraw,
+ TkTextTagChangedProc changedProc,
+ TkText *textPtr)
+{
+ TkTextTagSet *emptyTagInfoPtr = sharedTextPtr->emptyTagInfoPtr;
+ unsigned endLineNo = firstLineNo + nodePtr->numLines - 1;
+ TkTextTagSet *additionalTagoffPtr, *tagInfoPtr, *tagRootInfoPtr;
+ unsigned i;
+
+ if (endLineNo < lineNo1
+ || lineNo2 < firstLineNo
+ || !TestIfAnySegmentIsAffected(sharedTextPtr, nodePtr->tagonPtr, discardSelection)) {
/*
- * See if there's already an entry for this tag for this node. If so,
- * perhaps all we have to do is adjust its count.
+ * Nothing to do for this node.
*/
- for (prevPtr = NULL, summaryPtr = nodePtr->summaryPtr;
- summaryPtr != NULL;
- prevPtr = summaryPtr, summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->tagPtr == tagPtr) {
- break;
- }
+ if (undoToken) {
+ data->skip += nodePtr->size;
}
- if (summaryPtr != NULL) {
- summaryPtr->toggleCount += delta;
- if (summaryPtr->toggleCount > 0 &&
- summaryPtr->toggleCount < tagPtr->toggleCount) {
- continue;
- }
- if (summaryPtr->toggleCount != 0) {
- /*
- * Should never find a node with max toggle count at this
- * point (there shouldn't have been a summary entry in the
- * first place).
- */
+ return affectedTagInfoPtr;
+ }
+
+ additionalTagoffPtr = NULL;
+ tagRootInfoPtr = NULL;
+ TkTextTagSetIncrRefCount(tagInfoPtr = nodePtr->tagonPtr);
- Tcl_Panic("ChangeNodeToggleCount: bad toggle count (%d) max (%d)",
- summaryPtr->toggleCount, tagPtr->toggleCount);
+ if ((segPtr1 ? lineNo1 < firstLineNo : lineNo1 <= firstLineNo)
+ && (segPtr2 ? endLineNo < lineNo2 : endLineNo <= lineNo2)) {
+ bool delegateRedraw = redraw
+ && (discardSelection
+ ? TkTextTagSetIntersectionIsEqual(nodePtr->tagonPtr, nodePtr->tagoffPtr,
+ sharedTextPtr->selectionTags)
+ : !TkTextTagSetIsEqual(nodePtr->tagonPtr, nodePtr->tagoffPtr));
+ TkTextIndex index1, index2;
+
+ TkTextIndexClear2(&index1, NULL, sharedTextPtr->tree);
+ TkTextIndexClear2(&index2, NULL, sharedTextPtr->tree);
+
+ if (delegateRedraw) {
+ redraw = false;
+ }
+
+ /*
+ * Whole node is affected.
+ */
+
+ if (affectedTagInfoPtr) {
+ affectedTagInfoPtr = TkTextTagSetJoin(affectedTagInfoPtr, nodePtr->tagonPtr);
+ affectedTagInfoPtr = TagSetRemoveBits(affectedTagInfoPtr,
+ sharedTextPtr->selectionTags, sharedTextPtr);
+ }
+
+ if (discardSelection) {
+ nodePtr->tagonPtr = TagSetIntersectBits(
+ nodePtr->tagonPtr, sharedTextPtr->selectionTags, sharedTextPtr);
+ nodePtr->tagoffPtr = TagSetIntersectBits(
+ nodePtr->tagoffPtr, sharedTextPtr->selectionTags, sharedTextPtr);
+ } else {
+ TagSetAssign(&nodePtr->tagonPtr, emptyTagInfoPtr);
+ TagSetAssign(&nodePtr->tagoffPtr, emptyTagInfoPtr);
+ }
+
+ if (nodePtr->level > 0) {
+ Node *childPtr;
+
+ for (childPtr = nodePtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ ClearTagsFromNode(sharedTextPtr, childPtr, firstLineNo, lineNo1, lineNo2,
+ NULL, NULL, NULL, undoToken, data, discardSelection, delegateRedraw,
+ changedProc, textPtr);
+ firstLineNo += childPtr->numLines;
}
+ } else {
+ TkTextLine *linePtr = nodePtr->linePtr;
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+
+ for ( ; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ if (TestIfAnySegmentIsAffected(sharedTextPtr, linePtr->tagonPtr, discardSelection)) {
+ ClearTagsFromLine(sharedTextPtr, linePtr, NULL, NULL, NULL, undoToken, data,
+ discardSelection, delegateRedraw, changedProc, textPtr);
+ } else if (data->firstSegPtr) {
+ data->skip += linePtr->size;
+ }
+ }
+ }
- /*
- * Zero toggle count; must remove this tag from the list.
- */
+ if (redraw) {
+ bool affectsDisplayGeometry = TestIfDisplayGeometryIsAffected(sharedTextPtr,
+ nodePtr->tagonPtr, discardSelection);
+ TkTextIndexSetToStartOfLine2(&index1, nodePtr->linePtr);
+ TkTextIndexSetToEndOfLine2(&index2,
+ nodePtr->lastPtr->nextPtr ? nodePtr->lastPtr: nodePtr->lastPtr->prevPtr);
+ changedProc(sharedTextPtr, textPtr, &index1, &index2, NULL, affectsDisplayGeometry);
+ }
+ } else {
+ TagSetAssign(&nodePtr->tagonPtr, emptyTagInfoPtr);
+ TagSetAssign(&nodePtr->tagoffPtr, emptyTagInfoPtr);
- if (prevPtr == NULL) {
- nodePtr->summaryPtr = summaryPtr->nextPtr;
- } else {
- prevPtr->nextPtr = summaryPtr->nextPtr;
+ if (nodePtr->level > 0) {
+ Node *childPtr;
+
+ TkTextTagSetIncrRefCount(tagRootInfoPtr = emptyTagInfoPtr);
+
+ for (childPtr = nodePtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ affectedTagInfoPtr = ClearTagsFromNode(sharedTextPtr, childPtr, firstLineNo,
+ lineNo1, lineNo2, segPtr1, segPtr2, affectedTagInfoPtr, undoToken, data,
+ discardSelection, redraw, changedProc, textPtr);
+ tagRootInfoPtr = TagSetJoinOfDifferences(
+ tagRootInfoPtr, childPtr->tagonPtr, nodePtr->tagonPtr, sharedTextPtr);
+ nodePtr->tagonPtr = TkTextTagSetJoin(nodePtr->tagonPtr, childPtr->tagonPtr);
+ nodePtr->tagoffPtr = TkTextTagSetJoin(nodePtr->tagoffPtr, childPtr->tagoffPtr);
+ additionalTagoffPtr = TagSetIntersect(additionalTagoffPtr,
+ childPtr->tagonPtr, sharedTextPtr);
+ firstLineNo += childPtr->numLines;
}
- ckfree(summaryPtr);
+
+ tagRootInfoPtr = TkTextTagSetComplementTo(tagRootInfoPtr, nodePtr->tagonPtr);
} else {
- /*
- * This tag isn't currently in the summary information list.
- */
+ TkTextLine *linePtr = nodePtr->linePtr;
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+ TkTextIndex index1, index2;
+
+ TkTextIndexClear2(&index1, NULL, sharedTextPtr->tree);
+ TkTextIndexClear2(&index2, NULL, sharedTextPtr->tree);
+
+ for ( ; linePtr != lastPtr; linePtr = linePtr->nextPtr, ++firstLineNo) {
+ if (firstLineNo >= lineNo1 && firstLineNo <= lineNo2) {
+ if (TestIfAnySegmentIsAffected(sharedTextPtr, linePtr->tagonPtr,
+ discardSelection)) {
+ TkTextSegment *startSegPtr = (firstLineNo == lineNo1) ? segPtr1 : NULL;
+ TkTextSegment *stopSegPtr = (firstLineNo == lineNo2) ? segPtr2 : NULL;
+
+ affectedTagInfoPtr = ClearTagsFromLine(sharedTextPtr, linePtr, startSegPtr,
+ stopSegPtr, affectedTagInfoPtr, undoToken, data, discardSelection,
+ redraw, changedProc, textPtr);
+ } else if (data->firstSegPtr) {
+ data->skip += linePtr->size;
+ }
+ }
- if (rootLevel == nodePtr->level) {
- /*
- * The old tag root is at the same level in the tree as this
- * node, but it isn't at this node. Move the tag root up a
- * level, in the hopes that it will now cover this node as
- * well as the old root (if not, we'll move it up again the
- * next time through the loop). To push it up one level we
- * copy the original toggle count into the summary information
- * at the old root and change the root to its parent node.
- */
+ nodePtr->tagonPtr = TkTextTagSetJoin(nodePtr->tagonPtr, linePtr->tagonPtr);
+ nodePtr->tagoffPtr = TkTextTagSetJoin(nodePtr->tagoffPtr, linePtr->tagoffPtr);
+ additionalTagoffPtr = TagSetIntersect(additionalTagoffPtr,
+ linePtr->tagonPtr, sharedTextPtr);
+ }
+ }
+ }
+
+ if (additionalTagoffPtr) {
+ nodePtr->tagoffPtr = TagSetJoinComplementTo(
+ nodePtr->tagoffPtr, additionalTagoffPtr, nodePtr->tagonPtr, sharedTextPtr);
+ TkTextTagSetDecrRefCount(additionalTagoffPtr);
+ } else {
+ TagSetAssign(&nodePtr->tagoffPtr, nodePtr->tagonPtr);
+ }
+
+ /*
+ * Update tag roots.
+ */
+
+ if (tagRootInfoPtr) {
+ for (i = TkTextTagSetFindFirst(tagInfoPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
- Node *rootNodePtr = tagPtr->tagRootPtr;
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
- summaryPtr = ckalloc(sizeof(Summary));
- summaryPtr->tagPtr = tagPtr;
- summaryPtr->toggleCount = tagPtr->toggleCount - delta;
- summaryPtr->nextPtr = rootNodePtr->summaryPtr;
- rootNodePtr->summaryPtr = summaryPtr;
- rootNodePtr = rootNodePtr->parentPtr;
- rootLevel = rootNodePtr->level;
- tagPtr->tagRootPtr = rootNodePtr;
+ if (TkTextTagSetTest(tagRootInfoPtr, i)) {
+ tagPtr->rootPtr = nodePtr;
+ } else if (tagPtr->rootPtr == nodePtr) {
+ tagPtr->rootPtr = NULL;
}
- summaryPtr = ckalloc(sizeof(Summary));
- summaryPtr->tagPtr = tagPtr;
- summaryPtr->toggleCount = delta;
- summaryPtr->nextPtr = nodePtr->summaryPtr;
- nodePtr->summaryPtr = summaryPtr;
+ }
+
+ TkTextTagSetDecrRefCount(tagRootInfoPtr);
+ } else {
+ tagInfoPtr = TkTextTagSetRemove(tagInfoPtr, nodePtr->tagonPtr);
+
+ for (i = TkTextTagSetFindFirst(tagInfoPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+ tagPtr->rootPtr = NULL;
}
}
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ return affectedTagInfoPtr;
+}
+
+static bool
+CheckIfAnyTagIsAffected(
+ TkSharedText *sharedTextPtr,
+ const TkTextTagSet *tagInfoPtr,
+ bool discardSelection)
+{
+ unsigned i;
+
+ for (i = TkTextTagSetFindFirst(tagInfoPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+
+ if (!discardSelection || !TkBitTest(sharedTextPtr->selectionTags, tagPtr->index)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+TkTextTag *
+TkBTreeClearTags(
+ TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ TkText *textPtr, /* Information about text widget, can be NULL. */
+ const TkTextIndex *indexPtr1, /* Start clearing tags here. */
+ const TkTextIndex *indexPtr2, /* Stop clearing tags here. */
+ TkTextUndoInfo *undoInfo, /* Store undo information, can be NULL. */
+ bool discardSelection, /* Discard the special selection tag (do not delete)? */
+ TkTextTagChangedProc changedProc) /* Trigger this callback when any tag will be added/removed. */
+{
+ TkTextTag *chainPtr;
+ UndoTokenTagClear *undoToken;
+ TkTextSegment *segPtr1, *segPtr2;
+ TkTextTagSet *affectedTagInfoPtr;
+ TkTextLine *linePtr1, *linePtr2;
+ TkTextIndex startIndex, endIndex;
+ Node *rootPtr;
+ bool wholeText;
+ unsigned i;
+
+ assert(TkTextIndexCompare(indexPtr1, indexPtr2) <= 0);
+ assert(changedProc);
+
+ if (TkTextIndexIsEqual(indexPtr1, indexPtr2)) {
+ return NULL;
+ }
+
+ linePtr1 = TkTextIndexGetLine(indexPtr1);
+ linePtr2 = TkTextIndexGetLine(indexPtr2);
+ rootPtr = FindCommonParent(linePtr1->parentPtr, linePtr2->parentPtr);
+
+ if (discardSelection
+ ? TkTextTagBitContainsSet(sharedTextPtr->selectionTags, rootPtr->tagonPtr)
+ : rootPtr->tagonPtr == sharedTextPtr->emptyTagInfoPtr) {
+ return NULL; /* there is nothing to do */
+ }
+
/*
- * If we've decremented the toggle count, then it may be necessary to push
- * the tag root down one or more levels.
+ * Try to restrict the range, because in general we have to process all the segments
+ * inside the range, and this is a bit expensive. The search for smaller bounds is
+ * quite fast because it uses the B-Tree. But if the range is small, then it's not
+ * worth to search for smaller bounds.
*/
- if (delta >= 0) {
- return;
+ if (linePtr1->parentPtr != linePtr2->parentPtr) {
+ const TkTextSegment *segPtr;
+ TkTextIndex oneBack;
+
+ segPtr = TkBTreeFindNextTagged(indexPtr1, indexPtr2,
+ discardSelection ? sharedTextPtr->selectionTags : NULL);
+ if (!segPtr) {
+ return NULL;
+ }
+ TkTextIndexClear2(&startIndex, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&startIndex, (TkTextSegment *) segPtr);
+ TkTextIndexBackChars(textPtr, indexPtr1, 1, &oneBack, COUNT_DISPLAY_INDICES);
+ segPtr = TkBTreeFindPrevTagged(&oneBack, indexPtr1, discardSelection);
+ assert(segPtr);
+ TkTextIndexClear2(&endIndex, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&endIndex, (TkTextSegment *) segPtr);
+ assert(TkTextIndexCompare(&startIndex, &endIndex) <= 0);
+ } else {
+ startIndex = *indexPtr1;
+ endIndex = *indexPtr2;
}
- if (tagPtr->toggleCount == 0) {
- tagPtr->tagRootPtr = NULL;
- return;
+
+ if (!FindSplitPoints(sharedTextPtr, &startIndex, &endIndex, NULL, false, &segPtr1, &segPtr2)) {
+ return NULL;
}
- nodePtr = tagPtr->tagRootPtr;
- while (nodePtr->level > 0) {
- /*
- * See if a single child node accounts for all of the tag's toggles.
- * If so, push the root down one level.
- */
- for (node2Ptr = nodePtr->children.nodePtr;
- node2Ptr != NULL ;
- node2Ptr = node2Ptr->nextPtr) {
- for (prevPtr = NULL, summaryPtr = node2Ptr->summaryPtr;
- summaryPtr != NULL;
- prevPtr = summaryPtr, summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->tagPtr == tagPtr) {
- break;
+ linePtr1 = TkTextIndexGetLine(&startIndex);
+ linePtr2 = TkTextIndexGetLine(&endIndex);
+ segPtr1->protectionFlag = true;
+ segPtr2->protectionFlag = true;
+ undoToken = NULL;
+ chainPtr = NULL;
+ wholeText = false;
+
+ /*
+ * Now we will test whether we can accelerate a frequent case: all tagged segments
+ * will be cleared. But if the range is small, then it's not worth to test for this
+ * case.
+ */
+
+ if (!undoInfo) {
+ if (TkTextIndexIsStartOfText(indexPtr1) && TkTextIndexIsEndOfText(indexPtr2)) {
+ wholeText = true;
+ } else if (linePtr1->parentPtr != linePtr2->parentPtr) {
+ TkTextIndex index1, index2;
+
+ wholeText = true;
+
+ if (TkTextIndexBackChars(textPtr, indexPtr1, 1, &index1, COUNT_DISPLAY_INDICES)) {
+ TkTextIndexSetupToStartOfText(&index2, textPtr, sharedTextPtr->tree);
+ if (TkBTreeFindPrevTagged(&index1, &index2, discardSelection)) {
+ wholeText = false;
}
}
- if (summaryPtr == NULL) {
- continue;
+
+ if (wholeText && !TkTextIndexIsEndOfText(indexPtr2)) {
+ TkTextIndexSetupToEndOfText(&index2, textPtr, sharedTextPtr->tree);
+ if (TkBTreeFindNextTagged(indexPtr2, &index2,
+ discardSelection ? sharedTextPtr->selectionTags : NULL)) {
+ wholeText = false;
+ }
}
- if (summaryPtr->toggleCount != tagPtr->toggleCount) {
- /*
- * No node has all toggles, so the root is still valid.
- */
+ }
+ }
- return;
+ TkTextTagSetIncrRefCount(affectedTagInfoPtr = sharedTextPtr->emptyTagInfoPtr);
+
+ if (!wholeText || CheckIfAnyTagIsAffected(sharedTextPtr, rootPtr->tagonPtr, discardSelection)) {
+ bool anyChanges = wholeText; /* already checked for this case */
+ ClearTagsData data;
+
+ memset(&data, 0, sizeof(data));
+ rootPtr = TkBTreeGetRoot(sharedTextPtr->tree); /* we must start at top level */
+
+ if (TkBTreeHaveElidedSegments(sharedTextPtr)) {
+ UpdateElideInfo(sharedTextPtr, NULL, segPtr1, segPtr2, ELISION_WILL_BE_REMOVED);
+ }
+
+ if (wholeText) {
+ assert(!undoInfo);
+ TagSetAssign(&affectedTagInfoPtr, rootPtr->tagonPtr);
+ ClearTagsFromAllNodes(sharedTextPtr, rootPtr, &data, discardSelection, changedProc, textPtr);
+ ClearTagRoots(sharedTextPtr, affectedTagInfoPtr);
+ if (TkTextTagSetIntersectsBits(affectedTagInfoPtr, sharedTextPtr->affectDisplayTags)) {
+ /* TODO: probably it's better to search for all affected ranges. */
+ /* TODO: probably it's better to delegate the redraw to ClearTagsFromAllNodes,
+ * especially because of 'affectsDisplayGeometry'. */
+ bool affectsDisplayGeometry = TestIfDisplayGeometryIsAffected(sharedTextPtr,
+ affectedTagInfoPtr, discardSelection);
+ changedProc(sharedTextPtr, textPtr, &startIndex, &endIndex,
+ NULL, affectsDisplayGeometry);
+ }
+ } else {
+ TkTextSegment *firstPtr, *lastPtr;
+ int lineNo1, lineNo2;
+
+ if (undoInfo) {
+ undoToken = malloc(sizeof(UndoTokenTagClear));
+ undoInfo->token = (TkTextUndoToken *) undoToken;
+ undoInfo->byteSize = 0;
+ undoToken->undoType = &undoTokenClearTagsType;
+ undoToken->changeList = NULL;
+ undoToken->changeListSize = 0;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ }
+
+ firstPtr = segPtr1;
+ if (TkTextIndexIsStartOfLine(&endIndex)) {
+ lastPtr = NULL;
+ linePtr2 = linePtr2->prevPtr;
+ } else {
+ lastPtr = segPtr2;
+ }
+ lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr1, NULL);
+ lineNo2 = (linePtr1 == linePtr2) ?
+ lineNo1 : TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr2, NULL);
+
+ affectedTagInfoPtr = ClearTagsFromNode(sharedTextPtr, rootPtr, 0, lineNo1, lineNo2,
+ firstPtr, lastPtr, affectedTagInfoPtr, undoToken, &data, discardSelection,
+ true, changedProc, textPtr);
+ anyChanges = CheckIfAnyTagIsAffected(sharedTextPtr, affectedTagInfoPtr, discardSelection);
+
+ if (undoToken) {
+ if (anyChanges
+ && !TkTextTagBitContainsSet(sharedTextPtr->selectionTags, affectedTagInfoPtr)) {
+ TkTextIndex index1 = startIndex;
+ TkTextIndex index2 = endIndex;
+
+ assert(data.lastSegPtr);
+ TkTextIndexSetSegment(&index1, data.firstSegPtr);
+ if (data.lastSegPtr->nextPtr) {
+ data.lastSegPtr = data.lastSegPtr->nextPtr;
+ } else if (data.lastSegPtr->sectionPtr->linePtr->nextPtr) {
+ data.lastSegPtr = data.lastSegPtr->sectionPtr->linePtr->nextPtr->segPtr;
+ }
+ if (data.lastSegPtr->sectionPtr->linePtr == GetLastLine(sharedTextPtr, textPtr)) {
+ data.lastSegPtr = textPtr->endMarker;
+ }
+ MakeUndoIndex(sharedTextPtr, &index1, &undoToken->startIndex, GRAVITY_LEFT);
+ MakeUndoIndex(sharedTextPtr, &index2, &undoToken->endIndex, GRAVITY_RIGHT);
+ } else {
+ undoToken->changeListSize = 0;
+ }
+ }
+ }
+
+ if (anyChanges) {
+ if (!wholeText) {
+ if (!TkTextIndexIsStartOfLine(&startIndex)) {
+ RecomputeLineTagInfo(linePtr1, NULL, sharedTextPtr);
+ if (linePtr1 == linePtr2) {
+ linePtr2 = NULL;
+ }
+ }
+ if (linePtr2 && !TkTextIndexIsStartOfLine(&endIndex)) {
+ RecomputeLineTagInfo(linePtr2, NULL, sharedTextPtr);
+ }
}
/*
- * This node has all the toggles, so push down the root.
+ * Build a chain of all affected tags.
*/
- if (prevPtr == NULL) {
- node2Ptr->summaryPtr = summaryPtr->nextPtr;
- } else {
- prevPtr->nextPtr = summaryPtr->nextPtr;
+ for (i = TkTextTagSetFindFirst(affectedTagInfoPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(affectedTagInfoPtr, i)) {
+ TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+
+ tagPtr->nextPtr = chainPtr;
+ tagPtr->epoch = 0;
+ chainPtr = tagPtr;
}
- ckfree(summaryPtr);
- tagPtr->tagRootPtr = node2Ptr;
- break;
+ TkTextTagSetDecrRefCount(affectedTagInfoPtr);
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+ }
+ }
+
+ if (undoToken) {
+ if (undoToken->changeListSize == 0) {
+ free(undoToken->changeList);
+ free(undoToken);
+ undoInfo->token = NULL;
+ DEBUG_ALLOC(tkTextCountNewUndoToken--);
+ } else {
+ undoToken->changeList = realloc(undoToken->changeList,
+ undoToken->changeListSize * sizeof(undoToken->changeList[0]));
}
- nodePtr = tagPtr->tagRootPtr;
}
+
+ CleanupSplitPoint(segPtr1, sharedTextPtr);
+ CleanupSplitPoint(segPtr2, sharedTextPtr);
+
+ TK_BTREE_DEBUG(TkBTreeCheck(indexPtr1->tree));
+ return chainPtr;
}
/*
@@ -2452,9 +10282,8 @@ ChangeNodeToggleCount(
*
* Results:
* The return value is a pointer to the first tag toggle segment for the
- * tag. This can be either a tagon or tagoff segments because of the way
- * TkBTreeAdd removes a tag. Sets *indexPtr to be the index of the tag
- * toggle.
+ * tag. This can be either a tagon or tagoff segment. Sets *indexPtr to be
+ * the index of the tag toggle.
*
* Side effects:
* None.
@@ -2463,68 +10292,218 @@ ChangeNodeToggleCount(
*/
static TkTextSegment *
-FindTagStart(
- TkTextBTree tree, /* Tree to search within. */
- TkTextTag *tagPtr, /* Tag to search for. */
- TkTextIndex *indexPtr) /* Return - index information. */
-{
- register Node *nodePtr;
- register TkTextLine *linePtr;
- register TkTextSegment *segPtr;
- register Summary *summaryPtr;
- int offset;
+FindTagStartInLine(
+ TkTextSearch *searchPtr,
+ TkTextLine *linePtr,
+ TkTextSegment *segPtr,
+ bool testTagon)
+{
+ TkTextIndex *indexPtr = &searchPtr->curIndex;
+ const TkTextTag *tagPtr = searchPtr->tagPtr;
+ const TkTextSegment *lastPtr;
+ int byteOffset;
- nodePtr = tagPtr->tagRootPtr;
- if (nodePtr == NULL) {
- return NULL;
+ assert(tagPtr);
+
+ if (LineTestAllSegments(linePtr, tagPtr, testTagon)) {
+ if (!segPtr) {
+ TkTextIndexSetToStartOfLine2(indexPtr, linePtr);
+ } else {
+ TkTextIndexSetSegment(indexPtr, segPtr);
+ }
+ segPtr = TkTextIndexGetContentSegment(indexPtr, NULL);
+ return segPtr;
}
- /*
- * Search from the root of the subtree that contains the tag down to the
- * level 0 node.
- */
+ if (segPtr) {
+ byteOffset = TkTextIndexGetByteIndex(indexPtr);
+ } else {
+ assert(!searchPtr->textPtr || linePtr != searchPtr->textPtr->startMarker->sectionPtr->linePtr);
+ segPtr = linePtr->segPtr;
+ byteOffset = 0;
+ }
+ lastPtr = (linePtr == searchPtr->lastLinePtr) ? searchPtr->lastPtr : NULL;
- while (nodePtr && nodePtr->level > 0) {
- for (nodePtr = nodePtr->children.nodePtr ; nodePtr != NULL;
- nodePtr = nodePtr->nextPtr) {
- for (summaryPtr = nodePtr->summaryPtr ; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->tagPtr == tagPtr) {
- goto gotNodeWithTag;
+ while (segPtr != lastPtr) {
+ if (segPtr->tagInfoPtr) {
+ if (TkTextTagSetTest(segPtr->tagInfoPtr, tagPtr->index) == testTagon) {
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, byteOffset);
+ return segPtr;
+ }
+ byteOffset += segPtr->size;
+ }
+ segPtr = segPtr->nextPtr;
+ }
+
+ return NULL;
+}
+
+static const Node *
+FindTagStartInSubtree(
+ const Node *nodePtr,
+ unsigned startLineNo,
+ unsigned endLineNo,
+ unsigned lineNumber,
+ const Node *excludePtr, /* we don't want this result */
+ unsigned tagIndex)
+{
+ assert(nodePtr->level > 0);
+
+ for (nodePtr = nodePtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (nodePtr != excludePtr && startLineNo < lineNumber + nodePtr->numLines) {
+ bool testTagon = !LineTestIfToggleIsOpen(nodePtr->linePtr->prevPtr, tagIndex);
+
+ if (NodeTestToggleFwd(nodePtr, tagIndex, testTagon)) {
+ const Node *nPtr;
+
+ if (nodePtr->level == 0) {
+ return nodePtr;
+ }
+ nPtr = FindTagStartInSubtree(
+ nodePtr, startLineNo, endLineNo, lineNumber, excludePtr, tagIndex);
+ if (nPtr) {
+ return nPtr;
}
}
}
- gotNodeWithTag:
- continue;
+ if ((lineNumber += nodePtr->numLines) > endLineNo) {
+ return NULL;
+ }
}
- if (nodePtr == NULL) {
+ return NULL;
+}
+
+static TkTextSegment *
+FindTagStart(
+ TkTextSearch *searchPtr,
+ const TkTextIndex *stopIndex)
+{
+ TkTextIndex *indexPtr = &searchPtr->curIndex;
+ const TkTextTag *tagPtr = searchPtr->tagPtr;
+ TkTextLine *linePtr;
+ const TkTextLine *lastLinePtr;
+ const TkTextLine *lastPtr;
+ TkTextSegment *segPtr;
+ bool testTagon;
+ const Node *nodePtr;
+ const Node *rootPtr;
+ unsigned startLineNumber;
+ unsigned endLineNumber;
+ unsigned lineNumber;
+ unsigned tagIndex;
+
+ assert(tagPtr);
+
+ if (!tagPtr->rootPtr) {
return NULL;
}
- /*
- * Work through the lines attached to the level-0 node.
- */
+ tagIndex = tagPtr->index;
+ linePtr = TkTextIndexGetLine(indexPtr);
+ lastLinePtr = searchPtr->lastLinePtr;
+ testTagon = !LineTestIfToggleIsOpen(linePtr->prevPtr, tagIndex);
- for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
- linePtr = linePtr->nextPtr) {
- for (offset = 0, segPtr = linePtr->segPtr ; segPtr != NULL;
- offset += segPtr->size, segPtr = segPtr->nextPtr) {
- if (((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType))
- && (segPtr->body.toggle.tagPtr == tagPtr)) {
- /*
- * It is possible that this is a tagoff tag, but that gets
- * cleaned up later.
- */
+ if (LineTestToggleFwd(linePtr, tagIndex, testTagon)) {
+ TkTextSegment *sPtr;
- indexPtr->tree = tree;
- indexPtr->linePtr = linePtr;
- indexPtr->byteIndex = offset;
- return segPtr;
+ segPtr = TkTextIndexGetContentSegment(&searchPtr->curIndex, NULL);
+
+ if (!TkTextTagSetTest(testTagon ? linePtr->tagoffPtr : linePtr->tagonPtr, tagIndex)) {
+ return segPtr;
+ }
+ if (searchPtr->mode == SEARCH_EITHER_TAGON_TAGOFF) {
+ sPtr = GetFirstTagInfoSegment(searchPtr->textPtr, linePtr);
+ for ( ; sPtr != segPtr; sPtr = sPtr->nextPtr) {
+ if (sPtr->tagInfoPtr && TkTextTagSetTest(sPtr->tagInfoPtr, tagIndex) == testTagon) {
+ testTagon = !testTagon;
+ }
+ }
+ }
+ if ((segPtr = FindTagStartInLine(searchPtr, linePtr, segPtr, testTagon))) {
+ return segPtr;
+ }
+ if (linePtr == lastLinePtr) {
+ return NULL;
+ }
+ testTagon = !LineTestIfToggleIsOpen(linePtr, tagIndex);
+ } else if (linePtr == lastLinePtr) {
+ return NULL;
+ }
+
+ nodePtr = linePtr->parentPtr;
+ if (TkTextTagSetTest(testTagon ? nodePtr->tagonPtr : nodePtr->tagoffPtr, tagIndex)) {
+ lastPtr = nodePtr->lastPtr->nextPtr;
+
+ while ((linePtr = linePtr->nextPtr) != lastPtr) {
+ if (LineTestToggleFwd(linePtr, tagIndex, testTagon)) {
+ return FindTagStartInLine(searchPtr, linePtr, NULL, testTagon);
+ }
+ if (linePtr == lastLinePtr) {
+ return NULL;
}
}
}
+
+ rootPtr = tagPtr->rootPtr;
+ if (rootPtr == nodePtr) {
+ if (!nodePtr->nextPtr) {
+ Node *parentPtr = nodePtr->parentPtr;
+
+ while (parentPtr && !parentPtr->nextPtr) {
+ parentPtr = parentPtr->parentPtr;
+ }
+ if (!parentPtr) {
+ return NULL;
+ }
+ nodePtr = parentPtr->nextPtr;
+ }
+ linePtr = nodePtr->nextPtr->linePtr;
+ lineNumber = TkBTreeLinesTo(indexPtr->tree, NULL, linePtr, NULL);
+ if (lineNumber > TkTextIndexGetLineNumber(stopIndex, NULL)) {
+ return NULL;
+ }
+ segPtr = linePtr->segPtr;
+ while (!segPtr->tagInfoPtr && segPtr != searchPtr->lastPtr) {
+ segPtr = segPtr->nextPtr;
+ }
+ return segPtr == searchPtr->lastPtr ? NULL : segPtr;
+ }
+
+ startLineNumber = TkTextIndexGetLineNumber(indexPtr, NULL);
+ endLineNumber = TkTextIndexGetLineNumber(stopIndex, NULL);
+ lineNumber = TkBTreeLinesTo(indexPtr->tree, NULL, rootPtr->linePtr, NULL);
+
+ if (lineNumber > endLineNumber || startLineNumber >= lineNumber + rootPtr->numLines) {
+ return NULL;
+ }
+
+ if (rootPtr->level == 0) {
+ nodePtr = rootPtr;
+ } else {
+ nodePtr = FindTagStartInSubtree(
+ rootPtr, startLineNumber, endLineNumber, lineNumber, nodePtr, tagPtr->index);
+ if (!nodePtr) {
+ return NULL;
+ }
+ lineNumber = TkBTreeLinesTo(indexPtr->tree, NULL, nodePtr->linePtr, NULL);
+ }
+
+ assert(nodePtr->level == 0);
+ assert(lineNumber >= startLineNumber);
+
+ lastPtr = nodePtr->lastPtr->nextPtr;
+ testTagon = !LineTestIfToggleIsOpen(linePtr->prevPtr, tagIndex);
+
+ for (linePtr = nodePtr->linePtr; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ if (LineTestToggleFwd(linePtr, tagIndex, testTagon)) {
+ return FindTagStartInLine(searchPtr, linePtr, NULL, testTagon);
+ }
+ if (linePtr == lastLinePtr) {
+ return NULL;
+ }
+ }
+
return NULL;
}
@@ -2537,9 +10516,8 @@ FindTagStart(
*
* Results:
* The return value is a pointer to the last tag toggle segment for the
- * tag. This can be either a tagon or tagoff segments because of the way
- * TkBTreeAdd removes a tag. Sets *indexPtr to be the index of the tag
- * toggle.
+ * tag. This can be either a tagon or tagoff segment. Sets *indexPtr to be
+ * the index of the tag toggle.
*
* Side effects:
* None.
@@ -2547,75 +10525,283 @@ FindTagStart(
*----------------------------------------------------------------------
*/
+static bool
+HasLeftNode(
+ const Node *nodePtr)
+{
+ assert(nodePtr);
+ return nodePtr->parentPtr && nodePtr->parentPtr->childPtr != nodePtr;
+}
+
static TkTextSegment *
-FindTagEnd(
- TkTextBTree tree, /* Tree to search within. */
- TkTextTag *tagPtr, /* Tag to search for. */
- TkTextIndex *indexPtr) /* Return - index information. */
-{
- register Node *nodePtr, *lastNodePtr;
- register TkTextLine *linePtr ,*lastLinePtr;
- register TkTextSegment *segPtr, *lastSegPtr, *last2SegPtr;
- register Summary *summaryPtr;
- int lastoffset, lastoffset2, offset;
-
- nodePtr = tagPtr->tagRootPtr;
- if (nodePtr == NULL) {
- return NULL;
+FindTagEndInLine(
+ TkTextSearch *searchPtr,
+ TkTextLine *linePtr,
+ TkTextSegment *segPtr,
+ bool testTagon)
+{
+ TkTextIndex *indexPtr = &searchPtr->curIndex;
+ const TkTextTag *tagPtr = searchPtr->tagPtr;
+ TkTextSegment *lastPtr;
+ TkTextSegment *firstPtr;
+ TkTextSegment *prevPtr;
+ int byteOffset, offset = 0;
+
+ assert(tagPtr);
+
+ if (LineTestAllSegments(linePtr, tagPtr, testTagon)) {
+ if (!segPtr || linePtr != searchPtr->lastLinePtr) {
+ TkTextIndexSetToStartOfLine2(indexPtr, linePtr);
+ } else {
+ lastPtr = searchPtr->lastPtr;
+ while (segPtr && segPtr != lastPtr) {
+ segPtr = segPtr->prevPtr;
+ }
+ TkTextIndexSetSegment(indexPtr, segPtr);
+ }
+ segPtr = TkTextIndexGetContentSegment(indexPtr, NULL);
+ return segPtr;
}
- /*
- * Search from the root of the subtree that contains the tag down to the
- * level 0 node.
- */
+ if (segPtr) {
+ byteOffset = TkTextIndexGetByteIndex(indexPtr);
+ } else if (searchPtr->textPtr && linePtr == searchPtr->textPtr->endMarker->sectionPtr->linePtr) {
+ segPtr = searchPtr->textPtr->endMarker;
+ byteOffset = TkTextSegToIndex(segPtr);
+ } else {
+ segPtr = linePtr->lastPtr;
+ byteOffset = linePtr->size - segPtr->size;
+ }
+ lastPtr = (linePtr == searchPtr->lastLinePtr) ? searchPtr->lastPtr : NULL;
+ firstPtr = prevPtr = NULL;
+
+ while (segPtr) {
+ if (segPtr->tagInfoPtr) {
+ if (TkTextTagSetTest(segPtr->tagInfoPtr, tagPtr->index)) {
+ if (prevPtr) {
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, offset);
+ return prevPtr;
+ }
+ if (testTagon) {
+ prevPtr = segPtr;
+ }
+ firstPtr = segPtr;
+ } else if (firstPtr) {
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, offset);
+ return firstPtr;
+ } else if (!testTagon) {
+ prevPtr = segPtr;
+ }
+ offset = byteOffset;
+ }
+ if (segPtr == lastPtr) {
+ break;
+ }
+ if ((segPtr = segPtr->prevPtr)) {
+ byteOffset -= segPtr->size;
+ }
+ }
- while (nodePtr && nodePtr->level > 0) {
- for (lastNodePtr = NULL, nodePtr = nodePtr->children.nodePtr ;
- nodePtr != NULL; nodePtr = nodePtr->nextPtr) {
- for (summaryPtr = nodePtr->summaryPtr ; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->tagPtr == tagPtr) {
- lastNodePtr = nodePtr;
- break;
+ if (firstPtr
+ && firstPtr == GetFirstTagInfoSegment(searchPtr->textPtr, linePtr)
+ && !LineTestIfToggleIsOpen(linePtr->prevPtr, tagPtr->index)) {
+ TkTextIndexSetByteIndex2(&searchPtr->curIndex, linePtr, offset);
+ return firstPtr;
+ }
+
+ return NULL;
+}
+
+static const Node *
+FindTagEndInSubtree(
+ const Node *nodePtr,
+ unsigned startLineNo, /* start of search interval */
+ unsigned endLineNo, /* end of search interval */
+ unsigned lineNumber, /* line number of last line in this node */
+ const Node *excludePtr, /* we don't want this result */
+ unsigned tagIndex)
+{
+ const Node *stack[MAX_CHILDREN];
+ unsigned count = 0;
+
+ assert(nodePtr->level > 0);
+
+ lineNumber -= nodePtr->numLines - 1; /* now it's the line number of first line in this node */
+
+ for (nodePtr = nodePtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ stack[count++] = nodePtr;
+ lineNumber += nodePtr->numLines;
+ if (startLineNo < lineNumber) {
+ break;
+ }
+ }
+
+ lineNumber -= 1; /* now it's the line number of the last line in last node */
+
+ while (count > 0) {
+ nodePtr = stack[--count];
+ if (nodePtr != excludePtr && startLineNo >= lineNumber - nodePtr->numLines + 1) {
+ bool testTagon = !LineTestIfToggleIsClosed(nodePtr->lastPtr->nextPtr, tagIndex);
+
+ if (NodeTestToggleBack(nodePtr, tagIndex, testTagon)) {
+ const Node *nPtr;
+
+ if (nodePtr->level == 0) {
+ return nodePtr;
+ }
+ nPtr = FindTagEndInSubtree(
+ nodePtr, startLineNo, endLineNo, lineNumber, excludePtr, tagIndex);
+ if (nPtr) {
+ return nPtr;
}
}
}
- nodePtr = lastNodePtr;
+ if ((lineNumber -= nodePtr->numLines) + 1 <= endLineNo) {
+ return NULL;
+ }
}
- if (nodePtr == NULL) {
+ return NULL;
+}
+
+static TkTextSegment *
+FindTagEnd(
+ TkTextSearch *searchPtr,
+ const TkTextIndex *stopIndex)
+{
+ TkTextIndex *indexPtr = &searchPtr->curIndex;
+ const TkTextTag *tagPtr = searchPtr->tagPtr;
+ TkTextLine *linePtr;
+ const TkTextLine *lastLinePtr, *lastPtr;
+ TkTextSegment *segPtr;
+ bool testTagon;
+ const Node *nodePtr;
+ const Node *rootPtr;
+ unsigned startLineNumber;
+ unsigned endLineNumber;
+ unsigned lineNumber;
+ unsigned tagIndex;
+
+ assert(tagPtr);
+
+ if (!tagPtr->rootPtr) {
return NULL;
}
+ tagIndex = tagPtr->index;
+ linePtr = TkTextIndexGetLine(indexPtr);
+ lastLinePtr = searchPtr->lastLinePtr;
+ testTagon = !LineTestIfToggleIsClosed(linePtr->nextPtr, tagIndex);
+
/*
- * Work through the lines attached to the level-0 node.
+ * Here testTagon == true means: test for the segment which starts the tagged region.
*/
- last2SegPtr = NULL;
- lastoffset2 = 0;
- lastoffset = 0;
- for (lastLinePtr = NULL, linePtr = nodePtr->children.linePtr;
- linePtr != NULL; linePtr = linePtr->nextPtr) {
- for (offset = 0, lastSegPtr = NULL, segPtr = linePtr->segPtr ;
- segPtr != NULL;
- offset += segPtr->size, segPtr = segPtr->nextPtr) {
- if (((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType))
- && (segPtr->body.toggle.tagPtr == tagPtr)) {
- lastSegPtr = segPtr;
- lastoffset = offset;
+ if (LineTestToggleBack(linePtr, tagIndex, testTagon)) {
+ TkTextSegment *sPtr;
+
+ segPtr = TkTextIndexGetContentSegment(&searchPtr->curIndex, NULL);
+
+ for (sPtr = linePtr->lastPtr; sPtr != segPtr; sPtr = sPtr->prevPtr) {
+ if (sPtr->tagInfoPtr && TkTextTagSetTest(sPtr->tagInfoPtr, tagIndex) != testTagon) {
+ testTagon = !testTagon;
+ }
+ }
+ if ((segPtr = FindTagEndInLine(searchPtr, linePtr, segPtr, testTagon))) {
+ return segPtr;
+ }
+ if (linePtr == lastLinePtr) {
+ return NULL;
+ }
+ testTagon = !LineTestIfToggleIsClosed(linePtr, tagIndex);
+ } else if (linePtr == lastLinePtr) {
+ return NULL;
+ }
+
+ nodePtr = linePtr->parentPtr;
+ if (TkTextTagSetTest(testTagon ? nodePtr->tagonPtr : nodePtr->tagoffPtr, tagIndex)) {
+ lastPtr = nodePtr->linePtr->prevPtr;
+
+ while ((linePtr = linePtr->prevPtr) != lastPtr) {
+ if (LineTestToggleBack(linePtr, tagIndex, testTagon)) {
+ return FindTagEndInLine(searchPtr, linePtr, NULL, testTagon);
+ }
+ if (linePtr == lastLinePtr) {
+ return NULL;
+ }
+ }
+ }
+
+ rootPtr = tagPtr->rootPtr;
+ if (rootPtr == nodePtr) {
+ const Node *nPtr, *prevPtr = NULL;
+
+ if (!HasLeftNode(nodePtr)) {
+ Node *parentPtr = nodePtr->parentPtr;
+
+ while (parentPtr && !HasLeftNode(parentPtr)) {
+ parentPtr = parentPtr->parentPtr;
}
+ if (!parentPtr) {
+ return NULL;
+ }
+ nodePtr = parentPtr;
+ }
+ for (nPtr = nodePtr->parentPtr->childPtr; nPtr != nodePtr; nPtr = nPtr->nextPtr) {
+ prevPtr = nPtr;
+ }
+ if (!prevPtr || !(linePtr = prevPtr->lastPtr->prevPtr)) {
+ return NULL;
+ }
+ lineNumber = TkBTreeLinesTo(indexPtr->tree, NULL, linePtr, NULL);
+ if (lineNumber < TkTextIndexGetLineNumber(stopIndex, NULL)) {
+ return NULL;
+ }
+ return linePtr->lastPtr == searchPtr->lastPtr ? NULL : linePtr->lastPtr;
+ }
+
+ startLineNumber = TkTextIndexGetLineNumber(indexPtr, NULL);
+ endLineNumber = TkTextIndexGetLineNumber(stopIndex, NULL);
+ lineNumber = TkBTreeLinesTo(indexPtr->tree, NULL, rootPtr->lastPtr, NULL);
+
+ if (endLineNumber > lineNumber || lineNumber >= startLineNumber + rootPtr->numLines) {
+ return NULL;
+ }
+
+ if (rootPtr->level == 0) {
+ nodePtr = rootPtr;
+ } else {
+ nodePtr = FindTagEndInSubtree(rootPtr, startLineNumber, endLineNumber,
+ lineNumber, nodePtr, tagPtr->index);
+ if (!nodePtr) {
+ return NULL;
+ }
+ lineNumber = TkBTreeLinesTo(indexPtr->tree, NULL, nodePtr->lastPtr, NULL);
+ }
+
+ assert(nodePtr->level == 0);
+ assert(lineNumber <= startLineNumber);
+
+ if (!testTagon && NodeTestAllSegments(nodePtr, tagIndex, true)) {
+ linePtr = nodePtr->lastPtr;
+ if (linePtr->nextPtr) { linePtr = linePtr->nextPtr; }
+ TkTextIndexSetToStartOfLine2(&searchPtr->curIndex, linePtr);
+ return linePtr->segPtr;
+ }
+
+ lastPtr = nodePtr->linePtr->prevPtr;
+ testTagon = !LineTestIfToggleIsClosed(linePtr, tagIndex);
+
+ for (linePtr = nodePtr->lastPtr; linePtr != lastPtr; linePtr = linePtr->prevPtr) {
+ if (LineTestToggleBack(linePtr, tagIndex, testTagon)) {
+ return FindTagEndInLine(searchPtr, linePtr, NULL, testTagon);
}
- if (lastSegPtr != NULL) {
- lastLinePtr = linePtr;
- last2SegPtr = lastSegPtr;
- lastoffset2 = lastoffset;
+ if (linePtr == lastLinePtr) {
+ return NULL;
}
}
- indexPtr->tree = tree;
- indexPtr->linePtr = lastLinePtr;
- indexPtr->byteIndex = lastoffset2;
- return last2SegPtr;
+
+ return NULL;
}
/*
@@ -2624,7 +10810,7 @@ FindTagEnd(
* TkBTreeStartSearch --
*
* This function sets up a search for tag transitions involving a given
- * tag (or all tags) in a given range of the text.
+ * tag in a given range of the text.
*
* Results:
* None.
@@ -2635,81 +10821,154 @@ FindTagEnd(
* locations of tag transitions. Note that TkBTreeNextTag or
* TkBTreePrevTag must be called to get the first transition. Note:
* unlike TkBTreeNextTag and TkBTreePrevTag, this routine does not
- * guarantee that searchPtr->curIndex is equal to *index1Ptr. It may be
- * greater than that if *index1Ptr is less than the first tag transition.
+ * guarantee that searchPtr->curIndex is equal to *indexPtr1. It may be
+ * greater than that if *indexPtr1 is less than the first tag transition.
*
*----------------------------------------------------------------------
*/
+static bool
+TestPrevSegmentIsTagged(
+ const TkTextIndex *indexPtr,
+ const TkTextTag *tagPtr)
+{
+ const TkTextLine *linePtr = TkTextIndexGetLine(indexPtr);
+ const TkTextLine *startLinePtr = indexPtr->textPtr ? TkBTreeGetStartLine(indexPtr->textPtr) : NULL;
+ const TkTextSegment *segPtr = NULL; /* avoid compiler warning */
+
+ if (linePtr == startLinePtr) {
+ if (!(segPtr = GetPrevTagInfoSegment(indexPtr->textPtr->startMarker))) {
+ return false;
+ }
+ } else if (linePtr->prevPtr) {
+ const TkTextLine *endLinePtr = indexPtr->textPtr ? TkBTreeGetStartLine(indexPtr->textPtr) : NULL;
+
+ if (linePtr->prevPtr == endLinePtr) {
+ if (TkTextIsDeadPeer(indexPtr->textPtr)) {
+ return false;
+ }
+ segPtr = GetPrevTagInfoSegment(indexPtr->textPtr->endMarker);
+ } else {
+ segPtr = linePtr->prevPtr->lastPtr;
+ }
+ }
+
+ return TkTextTagSetTest(segPtr->tagInfoPtr, tagPtr->index);
+}
+
void
TkBTreeStartSearch(
- TkTextIndex *index1Ptr, /* Search starts here. Tag toggles at this
- * position will not be returned. */
- TkTextIndex *index2Ptr, /* Search stops here. Tag toggles at this
- * position *will* be returned. */
- TkTextTag *tagPtr, /* Tag to search for. NULL means search for
- * any tag. */
- register TkTextSearch *searchPtr)
- /* Where to store information about search's
- * progress. */
+ const TkTextIndex *indexPtr1,
+ /* Search starts here. Tag toggles at this position will be returned. */
+ const TkTextIndex *indexPtr2,
+ /* Search stops here. Tag toggles at this position *will* not be
+ * returned. */
+ const TkTextTag *tagPtr, /* Tag to search for. */
+ TkTextSearch *searchPtr, /* Where to store information about search's progress. */
+ TkTextSearchMode mode) /* The search mode, see definition of TkTextSearchMode. */
{
- int offset;
- TkTextIndex index0; /* First index of the tag. */
- TkTextSegment *seg0Ptr; /* First segment of the tag. */
+ TkTextSegment *segPtr;
+ int offset, nlines, lineNo;
+
+ assert(tagPtr);
/*
* Find the segment that contains the first toggle for the tag. This may
* become the starting point in the search.
*/
- seg0Ptr = FindTagStart(index1Ptr->tree, tagPtr, &index0);
- if (seg0Ptr == NULL) {
- /*
- * Even though there are no toggles, the display code still uses the
- * search curIndex, so initialize that anyway.
- */
+ searchPtr->textPtr = indexPtr1->textPtr;
+ searchPtr->curIndex = *indexPtr1;
+ searchPtr->tagPtr = tagPtr;
+ searchPtr->segPtr = NULL;
+ searchPtr->tagon = true;
+ searchPtr->endOfText = false;
+ searchPtr->linesLeft = 0;
+ searchPtr->resultPtr = NULL;
+ searchPtr->mode = mode;
- searchPtr->linesLeft = 0;
- searchPtr->curIndex = *index1Ptr;
- searchPtr->segPtr = NULL;
- searchPtr->nextPtr = NULL;
+ if (TkTextIndexCompare(indexPtr1, indexPtr2) >= 0) {
return;
}
- if (TkTextIndexCmp(index1Ptr, &index0) < 0) {
- /*
- * Adjust start of search up to the first range of the tag.
- */
- searchPtr->curIndex = index0;
- searchPtr->segPtr = NULL;
- searchPtr->nextPtr = seg0Ptr; /* Will be returned by NextTag. */
- index1Ptr = &index0;
+ segPtr = TkTextIndexGetContentSegment(indexPtr1, &offset);
+ if (offset > 0) {
+ if (segPtr->nextPtr) {
+ int byteOffset = TkTextIndexGetByteIndex(indexPtr1);
+
+ TkTextIndexSetPosition(&searchPtr->curIndex,
+ byteOffset + segPtr->size - offset, segPtr->nextPtr);
+ segPtr = segPtr->nextPtr;
+ } else {
+ TkTextLine *linePtr = segPtr->sectionPtr->linePtr;
+
+ if (linePtr == TkTextIndexGetLine(indexPtr2)
+ || (linePtr = linePtr->nextPtr) == TkTextIndexGetLine(indexPtr2)) {
+ return;
+ }
+ TkTextIndexSetToStartOfLine2(&searchPtr->curIndex, linePtr);
+ segPtr = GetFirstTagInfoSegment(NULL, linePtr);
+ }
+ }
+
+ if (indexPtr2->textPtr && TkTextIndexIsEndOfText(indexPtr2)) {
+ /* In this case indexPtr2 points to start of last line, but we need end marker. */
+ searchPtr->lastPtr = indexPtr2->textPtr->endMarker;
+ offset = 0;
} else {
- searchPtr->curIndex = *index1Ptr;
- searchPtr->segPtr = NULL;
- searchPtr->nextPtr = TkTextIndexToSeg(index1Ptr, &offset);
- searchPtr->curIndex.byteIndex -= offset;
+ searchPtr->lastPtr = TkTextIndexGetContentSegment(indexPtr2, &offset);
}
- searchPtr->lastPtr = TkTextIndexToSeg(index2Ptr, NULL);
- searchPtr->tagPtr = tagPtr;
- searchPtr->linesLeft = TkBTreeLinesTo(NULL, index2Ptr->linePtr) + 1
- - TkBTreeLinesTo(NULL, index1Ptr->linePtr);
- searchPtr->allTags = (tagPtr == NULL);
- if (searchPtr->linesLeft == 1) {
+ searchPtr->lastLinePtr = searchPtr->lastPtr->sectionPtr->linePtr;
+ if (offset > 0) {
+ searchPtr->lastPtr = searchPtr->lastPtr->nextPtr;
+ }
+ if (segPtr == searchPtr->lastPtr) {
+ return;
+ }
+ if (TkTextIndexIsEndOfText(indexPtr2)) {
+ searchPtr->endOfText = true;
+ }
+
+ if (mode == SEARCH_NEXT_TAGON
+ && TkTextIndexIsStartOfText(indexPtr1)
+ && TkTextTagSetTest(segPtr->tagInfoPtr, tagPtr->index)) {
/*
- * Starting and stopping segments are in the same line; mark the
- * search as over immediately if the second segment is before the
- * first. A search does not return a toggle at the very start of the
- * range, unless the range is artificially moved up to index0.
+ * We must find start of text.
*/
-
- if (((index1Ptr == &index0) &&
- (index1Ptr->byteIndex > index2Ptr->byteIndex)) ||
- ((index1Ptr != &index0) &&
- (index1Ptr->byteIndex >= index2Ptr->byteIndex))) {
+ searchPtr->segPtr = segPtr;
+ searchPtr->resultPtr = segPtr;
+ } else if (!(searchPtr->resultPtr = FindTagStart(searchPtr, indexPtr2))) {
+ if (mode == SEARCH_EITHER_TAGON_TAGOFF
+ && searchPtr->endOfText
+ && TestPrevSegmentIsTagged(indexPtr2, tagPtr)) {
+ /*
+ * We must find end of text.
+ */
+ searchPtr->resultPtr = TkTextIndexGetContentSegment(indexPtr2, NULL);
+ searchPtr->curIndex = *indexPtr2;
+ searchPtr->segPtr = NULL;
searchPtr->linesLeft = 0;
+ searchPtr->tagon = false;
+ }
+ return;
+ } else if (!TkTextTagSetTest(searchPtr->resultPtr->tagInfoPtr, tagPtr->index)) {
+ searchPtr->tagon = false;
+ if (mode == SEARCH_NEXT_TAGON) {
+ /*
+ * We have found tagoff, but we are searching tagon, so we have no
+ * result yet: force TkBTreeNextTag to continue the search.
+ */
+ searchPtr->segPtr = searchPtr->resultPtr;
+ TkTextIndexSetSegment(&searchPtr->curIndex, searchPtr->segPtr);
+ searchPtr->resultPtr = NULL;
}
}
+
+ indexPtr1 = &searchPtr->curIndex;
+ lineNo = TkTextIndexGetLineNumber(indexPtr2, indexPtr1->textPtr);
+ searchPtr->linesLeft = lineNo - TkTextIndexGetLineNumber(indexPtr1, indexPtr1->textPtr) + 1;
+ nlines = TkBTreeNumLines(indexPtr1->tree, indexPtr1->textPtr);
+ searchPtr->linesToEndOfText = nlines - lineNo + 1;
}
/*
@@ -2719,8 +10978,8 @@ TkBTreeStartSearch(
*
* This function sets up a search backwards for tag transitions involving
* a given tag (or all tags) in a given range of the text. In the normal
- * case the first index (*index1Ptr) is beyond the second index
- * (*index2Ptr).
+ * case the first index (*indexPtr1) is beyond the second index
+ * (*indexPtr2).
*
* Results:
* None.
@@ -2731,7 +10990,7 @@ TkBTreeStartSearch(
* transitions. Note that TkBTreePrevTag must be called to get the first
* transition. Note: unlike TkBTreeNextTag and TkBTreePrevTag, this
* routine does not guarantee that searchPtr->curIndex is equal to
- * *index1Ptr. It may be less than that if *index1Ptr is greater than the
+ * *indexPtr1. It may be less than that if *indexPtr1 is greater than the
* last tag transition.
*
*----------------------------------------------------------------------
@@ -2739,84 +10998,147 @@ TkBTreeStartSearch(
void
TkBTreeStartSearchBack(
- TkTextIndex *index1Ptr, /* Search starts here. Tag toggles at this
- * position will not be returned. */
- TkTextIndex *index2Ptr, /* Search stops here. Tag toggles at this
- * position *will* be returned. */
- TkTextTag *tagPtr, /* Tag to search for. NULL means search for
- * any tag. */
- register TkTextSearch *searchPtr)
- /* Where to store information about search's
- * progress. */
+ const TkTextIndex *indexPtr1,
+ /* Search starts here. Tag toggles at this position will not be
+ * returned iff mode is SEARCH_NEXT_TAGON. */
+ const TkTextIndex *indexPtr2,
+ /* Search stops here. Tag toggles at this position *will* be returned. */
+ const TkTextTag *tagPtr, /* Tag to search for. */
+ TkTextSearch *searchPtr, /* Where to store information about search's progress. */
+ TkTextSearchMode mode) /* The search mode, see definition of TkTextSearchMode. */
{
+ TkTextSegment *segPtr;
+ TkTextSegment *lastPtr;
int offset;
- TkTextIndex index0; /* Last index of the tag. */
- TkTextIndex backOne; /* One character before starting index. */
- TkTextSegment *seg0Ptr; /* Last segment of the tag. */
+ int lineNo;
+
+ assert(tagPtr);
/*
* Find the segment that contains the last toggle for the tag. This may
* become the starting point in the search.
*/
- seg0Ptr = FindTagEnd(index1Ptr->tree, tagPtr, &index0);
- if (seg0Ptr == NULL) {
- /*
- * Even though there are no toggles, the display code still uses the
- * search curIndex, so initialize that anyway.
- */
+ searchPtr->textPtr = indexPtr1->textPtr;
+ searchPtr->curIndex = *indexPtr1;
+ searchPtr->tagPtr = tagPtr;
+ searchPtr->segPtr = NULL;
+ searchPtr->tagon = true;
+ searchPtr->endOfText = false;
+ searchPtr->linesLeft = 0;
+ searchPtr->resultPtr = NULL;
+ searchPtr->mode = mode;
- searchPtr->linesLeft = 0;
- searchPtr->curIndex = *index1Ptr;
- searchPtr->segPtr = NULL;
- searchPtr->nextPtr = NULL;
+ if (TkTextIndexCompare(indexPtr1, indexPtr2) <= 0) {
return;
}
- /*
- * Adjust the start of the search so it doesn't find any tag toggles
- * that are right at the index specified by the user.
- */
-
- if (TkTextIndexCmp(index1Ptr, &index0) > 0) {
- searchPtr->curIndex = index0;
- index1Ptr = &index0;
+ if (indexPtr1->textPtr && TkTextIndexIsEndOfText(indexPtr1)) {
+ /*
+ * In this case indexPtr2 points to start of last line, but we need
+ * next content segment after end marker.
+ */
+ segPtr = GetNextTagInfoSegment(indexPtr1->textPtr->endMarker);
+ offset = 0;
} else {
- TkTextIndexBackChars(NULL, index1Ptr, 1, &searchPtr->curIndex,
- COUNT_INDICES);
+ segPtr = TkTextIndexGetContentSegment(indexPtr1, &offset);
}
- searchPtr->segPtr = NULL;
- searchPtr->nextPtr = TkTextIndexToSeg(&searchPtr->curIndex, &offset);
- searchPtr->curIndex.byteIndex -= offset;
- /*
- * Adjust the end of the search so it does find toggles that are right at
- * the second index specified by the user.
- */
-
- if ((TkBTreeLinesTo(NULL, index2Ptr->linePtr) == 0) &&
- (index2Ptr->byteIndex == 0)) {
- backOne = *index2Ptr;
- searchPtr->lastPtr = NULL; /* Signals special case for 1.0. */
+ if (offset == 0) {
+ segPtr = GetPrevTagInfoSegment(segPtr);
+ TkTextIndexSetSegment(&searchPtr->curIndex, segPtr);
} else {
- TkTextIndexBackChars(NULL, index2Ptr, 1, &backOne, COUNT_INDICES);
- searchPtr->lastPtr = TkTextIndexToSeg(&backOne, NULL);
+ TkTextIndexAddToByteIndex(&searchPtr->curIndex, -offset);
}
- searchPtr->tagPtr = tagPtr;
- searchPtr->linesLeft = TkBTreeLinesTo(NULL, index1Ptr->linePtr) + 1
- - TkBTreeLinesTo(NULL, backOne.linePtr);
- searchPtr->allTags = (tagPtr == NULL);
- if (searchPtr->linesLeft == 1) {
+
+ lastPtr = searchPtr->lastPtr = TkTextIndexGetContentSegment(indexPtr2, &offset);
+ if (offset == 0) {
+ if (searchPtr->lastPtr->prevPtr) {
+ searchPtr->lastPtr = searchPtr->lastPtr->prevPtr;
+ } else {
+ assert(searchPtr->lastPtr->sectionPtr->linePtr->prevPtr);
+ searchPtr->lastPtr = searchPtr->lastPtr->sectionPtr->linePtr->prevPtr->lastPtr;
+ }
+ } else if (segPtr == searchPtr->lastPtr) {
+ return;
+ }
+ searchPtr->lastLinePtr = searchPtr->lastPtr->sectionPtr->linePtr;
+ if (TkTextIndexIsStartOfText(indexPtr2)) {
+ searchPtr->endOfText = true;
+ }
+
+ if (mode == SEARCH_EITHER_TAGON_TAGOFF
+ && TkTextIndexIsEndOfText(indexPtr1)
+ && TestPrevSegmentIsTagged(indexPtr1, tagPtr)) {
/*
- * Starting and stopping segments are in the same line; mark the
- * search as over immediately if the second segment is after the
- * first.
+ * We must find end of text.
*/
-
- if (index1Ptr->byteIndex <= backOne.byteIndex) {
+ searchPtr->curIndex = *indexPtr1;
+ searchPtr->segPtr = TkTextIndexGetContentSegment(indexPtr1, NULL);
+ searchPtr->resultPtr = segPtr;
+ searchPtr->tagon = false;
+ } else if (!(searchPtr->resultPtr = FindTagEnd(searchPtr, indexPtr2))) {
+ if (searchPtr->endOfText
+ && TkTextTagSetTest(lastPtr->tagInfoPtr, tagPtr->index)
+ && TestPrevSegmentIsTagged(indexPtr2, tagPtr)) {
+ /*
+ * We must find start of text.
+ */
+ searchPtr->resultPtr = TkTextIndexGetContentSegment(indexPtr2, NULL);
+ searchPtr->curIndex = *indexPtr2;
+ searchPtr->segPtr = NULL;
searchPtr->linesLeft = 0;
+ searchPtr->tagon = true;
+ }
+ return;
+ } else if (!TkTextTagSetTest(searchPtr->resultPtr->tagInfoPtr, tagPtr->index)) {
+ searchPtr->tagon = false;
+ if (mode == SEARCH_NEXT_TAGON) {
+ /*
+ * We have found tagoff, but we are searching tagon, so we have no
+ * result yet: force TkBTreePrevTag to continue the search.
+ */
+ searchPtr->segPtr = searchPtr->resultPtr;
+ TkTextIndexSetSegment(&searchPtr->curIndex, searchPtr->segPtr);
+ searchPtr->resultPtr = NULL;
}
}
+
+ indexPtr1 = &searchPtr->curIndex;
+ searchPtr->linesToEndOfText = TkTextIndexGetLineNumber(indexPtr2, indexPtr1->textPtr);
+ lineNo = TkTextIndexGetLineNumber(indexPtr1, indexPtr1->textPtr);
+ searchPtr->linesLeft = lineNo - searchPtr->linesToEndOfText + 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeLiftSearch --
+ *
+ * This function "lifts" the search, next TkBTreeNextTag (or TkBTreePrevTag)
+ * will search without a limitation of the range, this is especially required
+ * if we search for tagoff of a corresponding tagon.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The information at *searchPtr is set up so that subsequent calls to
+ * TkBTreeNextTag/TkBTreePrevTag will search outside of the specified
+ * range.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkBTreeLiftSearch(
+ TkTextSearch *searchPtr)
+{
+ TkText *textPtr = searchPtr->curIndex.textPtr;
+
+ searchPtr->lastPtr = textPtr ?
+ textPtr->endMarker : TkTextIndexGetShared(&searchPtr->curIndex)->endMarker;
+ searchPtr->linesLeft += searchPtr->linesToEndOfText;
}
/*
@@ -2830,12 +11152,12 @@ TkBTreeStartSearchBack(
* the call to TkBTreeStartSearch.
*
* Results:
- * The return value is 1 if another toggle was found that met the
+ * The return value is 'true' if another toggle was found that met the
* criteria specified in the call to TkBTreeStartSearch; in this case
* searchPtr->curIndex gives the toggle's position and
- * searchPtr->curTagPtr points to its segment. 0 is returned if no more
- * matching tag transitions were found; in this case searchPtr->curIndex
- * is the same as searchPtr->stopIndex.
+ * searchPtr->segPtr points to its segment. 'false' is returned if no
+ * more matching tag transitions were found; in this case
+ * searchPtr->curIndex is the same as searchPtr->stopIndex.
*
* Side effects:
* Information in *searchPtr is modified to update the state of the
@@ -2844,47 +11166,104 @@ TkBTreeStartSearchBack(
*----------------------------------------------------------------------
*/
-int
-TkBTreeNextTag(
- register TkTextSearch *searchPtr)
- /* Information about search in progress; must
- * have been set up by call to
- * TkBTreeStartSearch. */
+static const Node *
+NextTagFindNextNode(
+ const Node *nodePtr,
+ TkTextSearch *searchPtr,
+ bool tagon)
{
- register TkTextSegment *segPtr;
- register Node *nodePtr;
- register Summary *summaryPtr;
+ const Node *parentPtr;
+ const TkTextTag *tagPtr = searchPtr->tagPtr;
- if (searchPtr->linesLeft <= 0) {
- goto searchOver;
- }
+ assert(tagPtr);
/*
- * The outermost loop iterates over lines that may potentially contain a
- * relevant tag transition, starting from the current segment in the
- * current line.
+ * Search forward across and up through the B-tree's node hierarchy looking for the
+ * next node that has a relevant tag transition somewhere in its subtree. Be sure to
+ * update linesLeft as we skip over large chunks of lines.
*/
- segPtr = searchPtr->nextPtr;
- while (1) {
- /*
- * Check for more tags on the current line.
- */
+ parentPtr = nodePtr->parentPtr;
- for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
- if (segPtr == searchPtr->lastPtr) {
- goto searchOver;
+ while (true) {
+ if (!parentPtr || nodePtr == tagPtr->rootPtr) {
+ if (tagon) {
+ return NULL;
}
- if (((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType))
- && (searchPtr->allTags
- || (segPtr->body.toggle.tagPtr == searchPtr->tagPtr))) {
- searchPtr->segPtr = segPtr;
- searchPtr->nextPtr = segPtr->nextPtr;
- searchPtr->tagPtr = segPtr->body.toggle.tagPtr;
- return 1;
+ searchPtr->linesLeft = 0;
+ return nodePtr;
+ }
+ if (!(nodePtr = nodePtr->nextPtr)) {
+ nodePtr = parentPtr;
+ parentPtr = nodePtr->parentPtr;
+ } else if (NodeTestToggleFwd(nodePtr, tagPtr->index, tagon)) {
+ return nodePtr;
+ } else if ((searchPtr->linesLeft -= nodePtr->numLines) <= 0) {
+ return NULL;
+ }
+ }
+
+ return NULL; /* never reached */
+}
+
+static bool
+NextTag(
+ TkTextSearch *searchPtr) /* Information about search in progress; must
+ * have been set up by call to TkBTreeStartSearch. */
+{
+ TkTextSegment *segPtr;
+ const TkTextTag *tagPtr;
+ const Node *nodePtr;
+ TkTextLine *linePtr;
+ bool tagon;
+
+ assert(searchPtr->tagPtr);
+ assert(searchPtr->segPtr);
+
+ TkTextIndexAddToByteIndex(&searchPtr->curIndex, searchPtr->segPtr->size);
+ linePtr = searchPtr->segPtr->sectionPtr->linePtr;
+ tagPtr = searchPtr->tagPtr;
+ segPtr = searchPtr->segPtr->nextPtr;
+ searchPtr->segPtr = NULL;
+ tagon = !searchPtr->tagon;
+
+ /*
+ * The outermost loop iterates over lines that may potentially contain a relevant
+ * tag transition, starting from the current segment in the current line.
+ */
+
+ while (true) {
+ const TkTextLine *lastPtr;
+
+ if (segPtr) {
+ bool wholeLine;
+
+ /*
+ * Check for more tags on the current line.
+ */
+
+ wholeLine = LineTestAllSegments(linePtr, tagPtr, tagon);
+
+ while (segPtr) {
+ if (segPtr == searchPtr->lastPtr) {
+ searchPtr->linesLeft = 0;
+ return false;
+ }
+ if (segPtr->tagInfoPtr) {
+ if (wholeLine || TkTextTagSetTest(segPtr->tagInfoPtr, tagPtr->index) == tagon) {
+ searchPtr->segPtr = segPtr;
+ searchPtr->tagon = tagon;
+ return true;
+ }
+ if (!TkTextIndexAddToByteIndex(&searchPtr->curIndex, segPtr->size)) {
+ segPtr = TkTextIndexGetFirstSegment(&searchPtr->curIndex, NULL);
+ } else {
+ segPtr = segPtr->nextPtr;
+ }
+ } else {
+ segPtr = segPtr->nextPtr;
+ }
}
- searchPtr->curIndex.byteIndex += segPtr->size;
}
/*
@@ -2892,45 +11271,33 @@ TkBTreeNextTag(
* node. If so, go back to the top of the loop to search the next one.
*/
- nodePtr = searchPtr->curIndex.linePtr->parentPtr;
- searchPtr->curIndex.linePtr = searchPtr->curIndex.linePtr->nextPtr;
- searchPtr->linesLeft--;
- if (searchPtr->linesLeft <= 0) {
- goto searchOver;
- }
- if (searchPtr->curIndex.linePtr != NULL) {
- segPtr = searchPtr->curIndex.linePtr->segPtr;
- searchPtr->curIndex.byteIndex = 0;
- continue;
- }
- if (nodePtr == searchPtr->tagPtr->tagRootPtr) {
- goto searchOver;
+ nodePtr = linePtr->parentPtr;
+ lastPtr = nodePtr->lastPtr->nextPtr;
+
+ do {
+ if (--searchPtr->linesLeft == 0) {
+ return false;
+ }
+ linePtr = linePtr->nextPtr;
+ } while (linePtr != lastPtr && !LineTestToggleFwd(linePtr, tagPtr->index, tagon));
+
+ if (linePtr != lastPtr) {
+ segPtr = linePtr->segPtr;
+ TkTextIndexSetToStartOfLine2(&searchPtr->curIndex, linePtr);
+ continue; /* go back to outer loop */
}
- /*
- * Search across and up through the B-tree's node hierarchy looking
- * for the next node that has a relevant tag transition somewhere in
- * its subtree. Be sure to update linesLeft as we skip over large
- * chunks of lines.
- */
+ if (!(nodePtr = NextTagFindNextNode(nodePtr, searchPtr, tagon))) {
+ searchPtr->linesLeft = 0;
+ return false;
+ }
- while (1) {
- while (nodePtr->nextPtr == NULL) {
- if (nodePtr->parentPtr == NULL ||
- nodePtr->parentPtr == searchPtr->tagPtr->tagRootPtr) {
- goto searchOver;
- }
- nodePtr = nodePtr->parentPtr;
- }
- nodePtr = nodePtr->nextPtr;
- for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if ((searchPtr->allTags) ||
- (summaryPtr->tagPtr == searchPtr->tagPtr)) {
- goto gotNodeWithTag;
- }
- }
- searchPtr->linesLeft -= nodePtr->numLines;
+ if (searchPtr->linesLeft == 0) {
+ assert(nodePtr->lastPtr->nextPtr);
+ TkTextIndexSetToStartOfLine2(&searchPtr->curIndex, nodePtr->lastPtr->nextPtr);
+ searchPtr->segPtr = TkTextIndexGetContentSegment(&searchPtr->curIndex, NULL);
+ searchPtr->tagon = tagon;
+ return true;
}
/*
@@ -2939,49 +11306,74 @@ TkBTreeNextTag(
* find the first level-0 node that has a relevant tag transition.
*/
- gotNodeWithTag:
while (nodePtr->level > 0) {
- for (nodePtr = nodePtr->children.nodePtr; ;
- nodePtr = nodePtr->nextPtr) {
- for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if ((searchPtr->allTags)
- || (summaryPtr->tagPtr == searchPtr->tagPtr)) {
- /*
- * Would really like a multi-level continue here...
- */
-
- goto nextChild;
- }
- }
- searchPtr->linesLeft -= nodePtr->numLines;
- if (nodePtr->nextPtr == NULL) {
- Tcl_Panic("TkBTreeNextTag found incorrect tag summary info");
+ nodePtr = nodePtr->childPtr;
+ while (!NodeTestToggleFwd(nodePtr, tagPtr->index, tagon)) {
+ if ((searchPtr->linesLeft -= nodePtr->numLines) <= 0) {
+ return false;
}
+ nodePtr = nodePtr->nextPtr;
+ assert(nodePtr);
}
- nextChild:
- continue;
}
/*
* Now we're down to a level-0 node that contains a line that contains
- * a relevant tag transition. Set up line information and go back to
- * the beginning of the loop to search through lines.
+ * a relevant tag transition.
+ */
+
+ linePtr = nodePtr->linePtr;
+ DEBUG(lastPtr = nodePtr->lastPtr->nextPtr);
+
+ /*
+ * Now search through the lines.
*/
- searchPtr->curIndex.linePtr = nodePtr->children.linePtr;
- searchPtr->curIndex.byteIndex = 0;
- segPtr = searchPtr->curIndex.linePtr->segPtr;
- if (searchPtr->linesLeft <= 0) {
- goto searchOver;
+ while (!LineTestToggleFwd(linePtr, tagPtr->index, tagon)) {
+ if (--searchPtr->linesLeft == 0) {
+ return false;
+ }
+ linePtr = linePtr->nextPtr;
+ assert(linePtr != lastPtr);
}
- continue;
+
+ TkTextIndexSetToStartOfLine2(&searchPtr->curIndex, linePtr);
+ segPtr = linePtr->segPtr;
}
- searchOver:
- searchPtr->linesLeft = 0;
- searchPtr->segPtr = NULL;
- return 0;
+ return false; /* never reached */
+}
+
+bool
+TkBTreeNextTag(
+ TkTextSearch *searchPtr) /* Information about search in progress; must
+ * have been set up by call to TkBTreeStartSearch. */
+{
+ if (searchPtr->resultPtr) {
+ searchPtr->segPtr = searchPtr->resultPtr;
+ searchPtr->resultPtr = NULL;
+ return true;
+ }
+
+ if (searchPtr->linesLeft <= 0) {
+ searchPtr->segPtr = NULL;
+ return false;
+ }
+
+ if (NextTag(searchPtr)) {
+ return true;
+ }
+
+ if (searchPtr->endOfText && searchPtr->tagon) {
+ /* we must find end of text in this case */
+ TkTextIndexSetupToEndOfText(&searchPtr->curIndex,
+ searchPtr->curIndex.textPtr, searchPtr->curIndex.tree);
+ searchPtr->segPtr = TkTextIndexGetContentSegment(&searchPtr->curIndex, NULL);
+ searchPtr->tagon = false;
+ return true;
+ }
+
+ return false;
}
/*
@@ -2995,12 +11387,12 @@ TkBTreeNextTag(
* from the B-tree since the call to TkBTreeStartSearch.
*
* Results:
- * The return value is 1 if another toggle was found that met the
+ * The return value is 'true' if another toggle was found that met the
* criteria specified in the call to TkBTreeStartSearch; in this case
* searchPtr->curIndex gives the toggle's position and
- * searchPtr->curTagPtr points to its segment. 0 is returned if no more
- * matching tag transitions were found; in this case searchPtr->curIndex
- * is the same as searchPtr->stopIndex.
+ * searchPtr->segPtr points to its segment. 'false' is returned if no
+ * more matching tag transitions were found; in this case
+ * 'searchPtr->curIndex' is the same as 'searchPtr->stopIndex'.
*
* Side effects:
* Information in *searchPtr is modified to update the state of the
@@ -3009,213 +11401,279 @@ TkBTreeNextTag(
*----------------------------------------------------------------------
*/
-int
-TkBTreePrevTag(
- register TkTextSearch *searchPtr)
- /* Information about search in progress; must
- * have been set up by call to
- * TkBTreeStartSearch. */
-{
- register TkTextSegment *segPtr, *prevPtr;
- register TkTextLine *linePtr, *prevLinePtr;
- register Node *nodePtr, *node2Ptr, *prevNodePtr;
- register Summary *summaryPtr;
- int byteIndex, linesSkipped;
- int pastLast; /* Saw last marker during scan. */
+static const Node *
+PrevTagFindPrevNode(
+ const Node *nodePtr,
+ TkTextSearch *searchPtr,
+ bool tagon)
+{
+ const TkTextTag *tagPtr = searchPtr->tagPtr;
+ const Node *parentPtr;
+ const Node *rootPtr;
- if (searchPtr->linesLeft <= 0) {
- goto searchOver;
- }
+ assert(tagPtr);
/*
- * The outermost loop iterates over lines that may potentially contain a
- * relevant tag transition, starting from the current segment in the
- * current line. "nextPtr" is maintained as the last segment in a line
- * that we can look at.
+ * Search backward across and up through the B-tree's node hierarchy looking for the
+ * next node that has a relevant tag transition somewhere in its subtree. Be sure to
+ * update linesLeft as we skip over large chunks of lines.
*/
- while (1) {
- /*
- * Check for the last toggle before the current segment on this line.
- */
+ if (nodePtr == tagPtr->rootPtr) {
+ return NULL;
+ }
- byteIndex = 0;
- if (searchPtr->lastPtr == NULL) {
- /*
- * Search back to the very beginning, so pastLast is irrelevent.
- */
+ parentPtr = nodePtr->parentPtr;
+ rootPtr = tagPtr->rootPtr;
- pastLast = 1;
- } else {
- pastLast = 0;
- }
+ do {
+ const Node *nodeStack[MAX_CHILDREN];
+ const Node *lastPtr = nodePtr;
+ int idx = 0;
- for (prevPtr = NULL, segPtr = searchPtr->curIndex.linePtr->segPtr ;
- segPtr != NULL && segPtr != searchPtr->nextPtr;
- segPtr = segPtr->nextPtr) {
- if (((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType))
- && (searchPtr->allTags
- || (segPtr->body.toggle.tagPtr == searchPtr->tagPtr))) {
- prevPtr = segPtr;
- searchPtr->curIndex.byteIndex = byteIndex;
+ for (nodePtr = parentPtr->childPtr; nodePtr != lastPtr; nodePtr = nodePtr->nextPtr) {
+ if (nodePtr == rootPtr) {
+ if (!tagon) {
+ return NULL;
+ }
+ return nodePtr;
}
- if (segPtr == searchPtr->lastPtr) {
- prevPtr = NULL; /* Segments earlier than last don't
- * count. */
- pastLast = 1;
+ nodeStack[idx++] = nodePtr;
+ }
+ for (--idx; idx >= 0; --idx) {
+ if (NodeTestToggleBack(nodePtr = nodeStack[idx], tagPtr->index, tagon)) {
+ return nodePtr;
+ }
+ if ((searchPtr->linesLeft -= nodePtr->numLines) <= 0) {
+ return NULL;
}
- byteIndex += segPtr->size;
}
- if (prevPtr != NULL) {
- if (searchPtr->linesLeft == 1 && !pastLast) {
- /*
- * We found a segment that is before the stopping index. Note
- * that it is OK if prevPtr == lastPtr.
- */
+ nodePtr = parentPtr;
+ parentPtr = parentPtr->parentPtr;
+ } while (parentPtr);
+
+ searchPtr->linesLeft = 0;
+ return NULL;
+}
+
+static bool
+PrevTag(
+ TkTextSearch *searchPtr) /* Information about search in progress; must
+ * have been set up by call to TkBTreeStartSearch. */
+{
+ TkTextSegment *segPtr;
+ const TkTextTag *tagPtr;
+ const Node *nodePtr;
+ bool tagon;
+
+ assert(searchPtr->tagPtr);
+ assert(searchPtr->segPtr);
+
+ tagPtr = searchPtr->tagPtr;
+ segPtr = searchPtr->segPtr->prevPtr;
+ searchPtr->segPtr = NULL;
+ tagon = !searchPtr->tagon;
+
+ if (segPtr) {
+ TkTextIndexAddToByteIndex(&searchPtr->curIndex, -segPtr->size);
+ }
+
+ /*
+ * The outermost loop iterates over lines that may potentially contain a relevant
+ * tag transition, starting from the current segment in the current line.
+ */
- goto searchOver;
+ while (true) {
+ TkTextLine *linePtr;
+ const TkTextLine *lastPtr;
+
+ if (segPtr) {
+ TkTextSegment *prevPtr;
+ TkTextSegment *firstPtr;
+ int byteOffset, offset = 0;
+
+ /*
+ * Check for more tags in the current line.
+ */
+
+ linePtr = segPtr->sectionPtr->linePtr;
+
+ if (LineTestAllSegments(linePtr, tagPtr, tagon)) {
+ if (searchPtr->lastPtr->sectionPtr->linePtr == linePtr) {
+ TkTextIndexSetSegment(&searchPtr->curIndex, searchPtr->lastPtr);
+ searchPtr->segPtr = searchPtr->lastPtr;
+ } else {
+ TkTextIndexSetToStartOfLine2(&searchPtr->curIndex, linePtr);
+ searchPtr->segPtr = linePtr->segPtr;
+ }
+ searchPtr->tagon = tagon;
+ return true;
}
- searchPtr->segPtr = prevPtr;
- searchPtr->nextPtr = prevPtr;
- searchPtr->tagPtr = prevPtr->body.toggle.tagPtr;
- return 1;
- }
- searchPtr->linesLeft--;
- if (searchPtr->linesLeft <= 0) {
- goto searchOver;
+ prevPtr = firstPtr = NULL;
+ byteOffset = TkTextIndexGetByteIndex(&searchPtr->curIndex);
+
+ while (true) {
+ if (segPtr->tagInfoPtr) {
+ if (TkTextTagSetTest(segPtr->tagInfoPtr, tagPtr->index)) {
+ if (prevPtr) {
+ TkTextIndexSetByteIndex(&searchPtr->curIndex, offset);
+ searchPtr->tagon = tagon;
+ return true;
+ }
+ firstPtr = segPtr;
+ } else if (firstPtr) {
+ TkTextIndexSetByteIndex(&searchPtr->curIndex, offset);
+ searchPtr->segPtr = firstPtr;
+ searchPtr->tagon = tagon;
+ return true;
+ } else if (!tagon) {
+ prevPtr = segPtr;
+ }
+ offset = byteOffset;
+ }
+ if (segPtr == searchPtr->lastPtr) {
+ if (firstPtr
+ && firstPtr == GetFirstTagInfoSegment(searchPtr->textPtr, linePtr)
+ && !LineTestIfToggleIsOpen(linePtr->prevPtr, tagPtr->index)) {
+ TkTextIndexSetByteIndex(&searchPtr->curIndex, offset);
+ searchPtr->segPtr = firstPtr;
+ searchPtr->tagon = tagon;
+ return true;
+ }
+ searchPtr->linesLeft = 0;
+ return false;
+ }
+ if (!(segPtr = segPtr->prevPtr)) {
+ break;
+ }
+ byteOffset -= segPtr->size;
+ }
+ if (firstPtr && !LineTestIfToggleIsOpen(linePtr->prevPtr, tagPtr->index)) {
+ TkTextIndexSetByteIndex(&searchPtr->curIndex, offset);
+ searchPtr->segPtr = firstPtr;
+ searchPtr->tagon = tagon;
+ return true;
+ }
+ } else {
+ linePtr = TkTextIndexGetLine(&searchPtr->curIndex);
}
/*
* See if there are more lines associated with the current parent
- * node. If so, go back to the top of the loop to search the previous
- * one.
+ * node. If so, go back to the top of the loop to search the previous one.
*/
- nodePtr = searchPtr->curIndex.linePtr->parentPtr;
- for (prevLinePtr = NULL, linePtr = nodePtr->children.linePtr;
- linePtr != NULL && linePtr != searchPtr->curIndex.linePtr;
- prevLinePtr = linePtr, linePtr = linePtr->nextPtr) {
- /* empty loop body */ ;
- }
- if (prevLinePtr != NULL) {
- searchPtr->curIndex.linePtr = prevLinePtr;
- searchPtr->nextPtr = NULL;
- continue;
- }
- if (nodePtr == searchPtr->tagPtr->tagRootPtr) {
- goto searchOver;
- }
+ nodePtr = linePtr->parentPtr;
+ lastPtr = nodePtr->linePtr->prevPtr;
- /*
- * Search across and up through the B-tree's node hierarchy looking
- * for the previous node that has a relevant tag transition somewhere
- * in its subtree. The search and line counting is trickier with/out
- * back pointers. We'll scan all the nodes under a parent up to the
- * current node, searching all of them for tag state. The last one we
- * find, if any, is recorded in prevNodePtr, and any nodes past
- * prevNodePtr that don't have tag state increment linesSkipped.
- */
+ do {
+ if (--searchPtr->linesLeft == 0) {
+ return false;
+ }
+ linePtr = linePtr->prevPtr;
+ } while (linePtr != lastPtr && !LineTestToggleBack(linePtr, tagPtr->index, tagon));
- while (1) {
- for (prevNodePtr = NULL, linesSkipped = 0,
- node2Ptr = nodePtr->parentPtr->children.nodePtr ;
- node2Ptr != nodePtr; node2Ptr = node2Ptr->nextPtr) {
- for (summaryPtr = node2Ptr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if ((searchPtr->allTags) ||
- (summaryPtr->tagPtr == searchPtr->tagPtr)) {
- prevNodePtr = node2Ptr;
- linesSkipped = 0;
- goto keepLooking;
- }
- }
- linesSkipped += node2Ptr->numLines;
+ if (linePtr != lastPtr) {
+ TkTextIndexSetSegment(&searchPtr->curIndex, segPtr = linePtr->lastPtr);
+ continue; /* go back to outer loop */
+ }
- keepLooking:
- continue;
- }
- if (prevNodePtr != NULL) {
- nodePtr = prevNodePtr;
- searchPtr->linesLeft -= linesSkipped;
- goto gotNodeWithTag;
- }
- nodePtr = nodePtr->parentPtr;
- if (nodePtr->parentPtr == NULL ||
- nodePtr == searchPtr->tagPtr->tagRootPtr) {
- goto searchOver;
- }
+ if (!(nodePtr = PrevTagFindPrevNode(nodePtr, searchPtr, tagon))) {
+ searchPtr->linesLeft = 0;
+ return false;
}
/*
* At this point we've found a subtree that has a relevant tag
* transition. Now search down (and across) through that subtree to
- * find the last level-0 node that has a relevant tag transition.
+ * find the first level-0 node that has a relevant tag transition.
*/
- gotNodeWithTag:
while (nodePtr->level > 0) {
- for (linesSkipped = 0, prevNodePtr = NULL,
- nodePtr = nodePtr->children.nodePtr; nodePtr != NULL ;
- nodePtr = nodePtr->nextPtr) {
- for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if ((searchPtr->allTags)
- || (summaryPtr->tagPtr == searchPtr->tagPtr)) {
- prevNodePtr = nodePtr;
- linesSkipped = 0;
- goto keepLooking2;
- }
- }
- linesSkipped += nodePtr->numLines;
+ const Node *nodeStack[MAX_CHILDREN];
+ int idx = 0;
- keepLooking2:
- continue;
+ for (nodePtr = nodePtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ nodeStack[idx++] = nodePtr;
}
- if (prevNodePtr == NULL) {
- Tcl_Panic("TkBTreePrevTag found incorrect tag summary info");
+ assert(idx > 0);
+ nodePtr = nodeStack[--idx];
+ while (!NodeTestToggleBack(nodePtr, tagPtr->index, tagon)) {
+ if ((searchPtr->linesLeft -= nodePtr->numLines) <= 0) {
+ return false;
+ }
+ assert(idx > 0);
+ nodePtr = nodeStack[--idx];
}
- searchPtr->linesLeft -= linesSkipped;
- nodePtr = prevNodePtr;
}
/*
- * Now we're down to a level-0 node that contains a line that contains
- * a relevant tag transition. Set up line information and go back to
- * the beginning of the loop to search through lines. We start with
- * the last line below the node.
+ * We're down to a level-0 node that contains a line that has a relevant tag transition.
*/
- for (prevLinePtr = NULL, linePtr = nodePtr->children.linePtr;
- linePtr != NULL ;
- prevLinePtr = linePtr, linePtr = linePtr->nextPtr) {
- /* empty loop body */ ;
- }
- searchPtr->curIndex.linePtr = prevLinePtr;
- searchPtr->curIndex.byteIndex = 0;
- if (searchPtr->linesLeft <= 0) {
- goto searchOver;
+ linePtr = nodePtr->lastPtr;
+ DEBUG(lastPtr = nodePtr->linePtr->prevPtr);
+
+ /*
+ * Now search through the lines.
+ */
+
+ while (!LineTestToggleBack(linePtr, tagPtr->index, tagon)) {
+ if (--searchPtr->linesLeft == 0) {
+ return false;
+ }
+ linePtr = linePtr->prevPtr;
+ assert(linePtr != lastPtr);
}
- continue;
+
+ TkTextIndexSetSegment(&searchPtr->curIndex, segPtr = linePtr->lastPtr);
}
- searchOver:
- searchPtr->linesLeft = 0;
- searchPtr->segPtr = NULL;
- return 0;
+ return false; /* never reached */
+}
+
+bool
+TkBTreePrevTag(
+ TkTextSearch *searchPtr) /* Information about search in progress; must
+ * have been set up by call to TkBTreeStartSearch. */
+{
+ if (searchPtr->resultPtr) {
+ searchPtr->segPtr = searchPtr->resultPtr;
+ searchPtr->resultPtr = NULL;
+ return true;
+ }
+
+ if (searchPtr->linesLeft <= 0) {
+ searchPtr->segPtr = NULL;
+ return false;
+ }
+
+ if (PrevTag(searchPtr)) {
+ return true;
+ }
+
+ if (searchPtr->endOfText && !searchPtr->tagon) {
+ /* we must find start of text in this case */
+ TkTextIndexSetupToStartOfText(&searchPtr->curIndex,
+ searchPtr->curIndex.textPtr, searchPtr->curIndex.tree);
+ searchPtr->segPtr = TkTextIndexGetContentSegment(&searchPtr->curIndex, NULL);
+ searchPtr->tagon = true;
+ return true;
+ }
+
+ return false;
}
/*
*----------------------------------------------------------------------
*
- * TkBTreeCharTagged --
+ * TkBTreeFindNextTagged --
*
- * Determine whether a particular character has a particular tag.
+ * Find next segment which contains any tag inside given range.
*
* Results:
- * The return value is 1 if the given tag is in effect at the character
- * given by linePtr and ch, and 0 otherwise.
+ * The return value is the next segment containing any tag.
*
* Side effects:
* None.
@@ -3223,109 +11681,156 @@ TkBTreePrevTag(
*----------------------------------------------------------------------
*/
-int
-TkBTreeCharTagged(
- const TkTextIndex *indexPtr,/* Indicates a character position at which to
- * check for a tag. */
- TkTextTag *tagPtr) /* Tag of interest. */
+TkTextSegment *
+FindNextTaggedSegInLine(
+ TkTextSegment *segPtr,
+ const TkTextSegment *lastPtr,
+ const TkBitField *discardTags)
{
- register Node *nodePtr;
- register TkTextLine *siblingLinePtr;
- register TkTextSegment *segPtr;
- TkTextSegment *toggleSegPtr;
- int toggles, index;
+ if (lastPtr->sectionPtr->linePtr != segPtr->sectionPtr->linePtr) {
+ lastPtr = NULL;
+ }
- /*
- * Check for toggles for the tag in indexPtr's line but before indexPtr.
- * If there is one, its type indicates whether or not the character is
- * tagged.
- */
+ for ( ; segPtr != lastPtr; segPtr = segPtr->nextPtr) {
+ const TkTextTagSet *tagInfoPtr = segPtr->tagInfoPtr;
- toggleSegPtr = NULL;
- for (index = 0, segPtr = indexPtr->linePtr->segPtr;
- (index + segPtr->size) <= indexPtr->byteIndex;
- index += segPtr->size, segPtr = segPtr->nextPtr) {
- if (((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType))
- && (segPtr->body.toggle.tagPtr == tagPtr)) {
- toggleSegPtr = segPtr;
+ if (tagInfoPtr) {
+ if (TagSetTestBits(tagInfoPtr, discardTags)) {
+ return segPtr;
+ }
}
}
- if (toggleSegPtr != NULL) {
- return (toggleSegPtr->typePtr == &tkTextToggleOnType);
+
+ return NULL;
+}
+
+TkTextSegment *
+FindNextTaggedSegInNode(
+ const TkTextSegment *lastPtr,
+ const TkTextLine *linePtr,
+ const TkBitField *discardTags) /* can be NULL */
+{
+ const TkTextLine *lastLinePtr = lastPtr->sectionPtr->linePtr;
+ const TkTextLine *endLinePtr = linePtr->parentPtr->lastPtr;
+
+ while (linePtr) {
+ if (TagSetTestBits(linePtr->tagonPtr, discardTags)) {
+ return FindNextTaggedSegInLine(linePtr->segPtr, lastPtr, discardTags);
+ }
+ if (linePtr == lastLinePtr || linePtr == endLinePtr) {
+ return NULL;
+ }
+ linePtr = linePtr->nextPtr;
}
- /*
- * No toggle in this line. Look for toggles for the tag in lines that are
- * predecessors of indexPtr->linePtr but under the same level-0 node.
- */
+ return NULL;
+}
- for (siblingLinePtr = indexPtr->linePtr->parentPtr->children.linePtr;
- siblingLinePtr != indexPtr->linePtr;
- siblingLinePtr = siblingLinePtr->nextPtr) {
- for (segPtr = siblingLinePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- if (((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType))
- && (segPtr->body.toggle.tagPtr == tagPtr)) {
- toggleSegPtr = segPtr;
+static const Node *
+FindNextTaggedNode(
+ const Node *nodePtr,
+ const TkBitField *discardTags) /* can be NULL */
+{
+ while (nodePtr) {
+ const Node *startNodePtr = nodePtr;
+
+ for (nodePtr = nodePtr->nextPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TagSetTestBits(nodePtr->tagonPtr, discardTags)) {
+ while (nodePtr->level > 0) {
+ for (nodePtr = nodePtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TagSetTestBits(nodePtr->tagonPtr, discardTags)) {
+ return nodePtr;
+ }
+ }
+ }
+ return nodePtr;
}
}
+
+ nodePtr = startNodePtr->parentPtr;
}
- if (toggleSegPtr != NULL) {
- return (toggleSegPtr->typePtr == &tkTextToggleOnType);
+
+ return NULL;
+}
+
+TkTextSegment *
+TkBTreeFindNextTagged(
+ const TkTextIndex *indexPtr1,
+ /* Search starts here. Tag toggles at this position will be returned. */
+ const TkTextIndex *indexPtr2,
+ /* Search stops here. Tag toggles at this position will not be
+ * returned. */
+ const struct TkBitField *discardTags)
+ /* Discard these tags when searching, can be NULL. */
+{
+ const TkSharedText *sharedTextPtr = TkTextIndexGetShared(indexPtr1);
+ const TkTextLine *linePtr = TkTextIndexGetLine(indexPtr1);
+ const TkTextSegment *lastPtr = TkTextIndexGetFirstSegment(indexPtr2, NULL);
+ const TkText *textPtr;
+ const Node *nodePtr;
+
+ /*
+ * At first, search for next segment in first line.
+ */
+
+ if (TagSetTestBits(linePtr->tagonPtr, discardTags)) {
+ TkTextSegment *segPtr = TkTextIndexGetContentSegment(indexPtr1, NULL);
+
+ if ((segPtr = FindNextTaggedSegInLine(segPtr, lastPtr, discardTags))) {
+ return segPtr;
+ }
}
/*
- * No toggle in this node. Scan upwards through the ancestors of this
- * node, counting the number of toggles of the given tag in siblings that
- * precede that node.
+ * At second, search for line containing any tag in current node.
*/
- toggles = 0;
- for (nodePtr = indexPtr->linePtr->parentPtr; nodePtr->parentPtr != NULL;
- nodePtr = nodePtr->parentPtr) {
- register Node *siblingPtr;
- register Summary *summaryPtr;
+ textPtr = indexPtr1->textPtr;
+ nodePtr = linePtr->parentPtr;
- for (siblingPtr = nodePtr->parentPtr->children.nodePtr;
- siblingPtr != nodePtr; siblingPtr = siblingPtr->nextPtr) {
- for (summaryPtr = siblingPtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->tagPtr == tagPtr) {
- toggles += summaryPtr->toggleCount;
- }
- }
+ if (linePtr != nodePtr->lastPtr && TagSetTestBits(nodePtr->tagonPtr, discardTags)) {
+ TkTextSegment *segPtr = FindNextTaggedSegInNode(lastPtr, linePtr->nextPtr, discardTags);
+
+ if (segPtr) {
+ return segPtr;
}
- if (nodePtr == tagPtr->tagRootPtr) {
- break;
+ }
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which contains any tag.
+ */
+
+ if (!(nodePtr = FindNextTaggedNode(nodePtr, discardTags))) {
+ return NULL;
+ }
+
+ if (textPtr && textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
+ int lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, nodePtr->linePtr, NULL);
+ int lineNo2 = TkTextIndexGetLineNumber(indexPtr2, NULL);
+
+ if (lineNo1 > lineNo2) {
+ /* We've found a node after text end, so return NULL. */
+ return NULL;
}
}
/*
- * An odd number of toggles means that the tag is present at the given
- * point.
+ * Final search of segment containing any tag.
*/
- return toggles & 1;
+ return FindNextTaggedSegInNode(lastPtr, nodePtr->linePtr, discardTags);
}
/*
*----------------------------------------------------------------------
*
- * TkBTreeGetTags --
+ * TkBTreeFindNextUntagged --
*
- * Return information about all of the tags that are associated with a
- * particular character in a B-tree of text.
+ * Find next segment which does not contain any tag.
*
* Results:
- * The return value is a malloc-ed array containing pointers to
- * information for each of the tags that is associated with the character
- * at the position given by linePtr and ch. The word at *numTagsPtr is
- * filled in with the number of pointers in the array. It is up to the
- * caller to free the array by passing it to free. If there are no tags
- * at the given character then a NULL pointer is returned and *numTagsPtr
- * will be set to 0.
+ * The return value is the next segment not containing any tag.
*
* Side effects:
* None.
@@ -3333,145 +11838,159 @@ TkBTreeCharTagged(
*----------------------------------------------------------------------
*/
- /* ARGSUSED */
-TkTextTag **
-TkBTreeGetTags(
- const TkTextIndex *indexPtr,/* Indicates a particular position in the
- * B-tree. */
- const TkText *textPtr, /* If non-NULL, then only return tags for this
- * text widget (when there are peer
- * widgets). */
- int *numTagsPtr) /* Store number of tags found at this
- * location. */
+TkTextSegment *
+FindNextUntaggedSegInLine(
+ TkTextSegment *segPtr,
+ const TkTextSegment *lastPtr,
+ const TkBitField *discardTags)
{
- register Node *nodePtr;
- register TkTextLine *siblingLinePtr;
- register TkTextSegment *segPtr;
- TkTextLine *linePtr;
- int src, dst, index;
- TagInfo tagInfo;
-#define NUM_TAG_INFOS 10
-
- tagInfo.numTags = 0;
- tagInfo.arraySize = NUM_TAG_INFOS;
- tagInfo.tagPtrs = ckalloc(NUM_TAG_INFOS * sizeof(TkTextTag *));
- tagInfo.counts = ckalloc(NUM_TAG_INFOS * sizeof(int));
+ if (lastPtr->sectionPtr->linePtr != segPtr->sectionPtr->linePtr) {
+ lastPtr = NULL;
+ }
- /*
- * Record tag toggles within the line of indexPtr but preceding indexPtr.
- */
+ for ( ; segPtr != lastPtr; segPtr = segPtr->nextPtr) {
+ const TkTextTagSet *tagInfoPtr = segPtr->tagInfoPtr;
- linePtr = indexPtr->linePtr;
- index = 0;
- segPtr = linePtr->segPtr;
- while ((index + segPtr->size) <= indexPtr->byteIndex) {
- if ((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType)) {
- IncCount(segPtr->body.toggle.tagPtr, 1, &tagInfo);
+ if (tagInfoPtr) {
+ if (!TagSetTestDisjunctiveBits(tagInfoPtr, discardTags)) {
+ return segPtr;
+ }
}
- index += segPtr->size;
- segPtr = segPtr->nextPtr;
+ }
- if (segPtr == NULL) {
- /*
- * Two logical lines merged into one display line through eliding
- * of a newline.
- */
+ return NULL;
+}
- linePtr = TkBTreeNextLine(NULL, linePtr);
- segPtr = linePtr->segPtr;
+TkTextSegment *
+FindNextUntaggedSegInNode(
+ const TkTextSegment *lastPtr,
+ const TkTextLine *linePtr,
+ const TkBitField *discardTags) /* can be NULL */
+{
+ const TkTextLine *lastLinePtr = lastPtr->sectionPtr->linePtr;
+ const TkTextLine *endLinePtr = linePtr->parentPtr->lastPtr;
+
+ while (linePtr) {
+ if (TagSetTestDontContainsAny(linePtr->tagonPtr, linePtr->tagoffPtr, discardTags)) {
+ return FindNextUntaggedSegInLine(linePtr->segPtr, lastPtr, discardTags);
+ }
+ if (linePtr == lastLinePtr || linePtr == endLinePtr) {
+ return NULL;
}
+ linePtr = linePtr->nextPtr;
}
- /*
- * Record toggles for tags in lines that are predecessors of
- * indexPtr->linePtr but under the same level-0 node.
- */
+ return NULL;
+}
- for (siblingLinePtr = indexPtr->linePtr->parentPtr->children.linePtr;
- siblingLinePtr != indexPtr->linePtr;
- siblingLinePtr = siblingLinePtr->nextPtr) {
- for (segPtr = siblingLinePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- if ((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType)) {
- IncCount(segPtr->body.toggle.tagPtr, 1, &tagInfo);
+static const Node *
+FindNextUntaggedNode(
+ const Node *nodePtr,
+ const TkBitField *discardTags) /* can be NULL */
+{
+ while (nodePtr) {
+ const Node *startNodePtr = nodePtr;
+
+ for (nodePtr = nodePtr->nextPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TagSetTestDontContainsAny(nodePtr->tagonPtr, nodePtr->tagoffPtr, discardTags)) {
+ while (nodePtr->level > 0) {
+ for (nodePtr = nodePtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TagSetTestDontContainsAny(nodePtr->tagonPtr, nodePtr->tagoffPtr,
+ discardTags)) {
+ return nodePtr;
+ }
+ }
+ }
+ return nodePtr;
}
}
+
+ nodePtr = startNodePtr->parentPtr;
}
+ return NULL;
+}
+
+TkTextSegment *
+TkBTreeFindNextUntagged(
+ const TkTextIndex *indexPtr1,
+ /* Search starts here. Tag toggles at this position will be
+ * returned. */
+ const TkTextIndex *indexPtr2,
+ /* Search stops here. Tag toggles at this position will not be
+ * returned. */
+ const struct TkBitField *discardTags)
+ /* Discard these tags when searching, can be NULL. */
+{
+ const TkSharedText *sharedTextPtr = TkTextIndexGetShared(indexPtr1);
+ const TkTextLine *linePtr = TkTextIndexGetLine(indexPtr1);
+ const TkTextSegment *lastPtr = TkTextIndexGetFirstSegment(indexPtr2, NULL);
+ const TkText *textPtr;
+ const Node *nodePtr;
+
/*
- * For each node in the ancestry of this line, record tag toggles for all
- * siblings that precede that node.
+ * At first, search for next segment in first line.
*/
- for (nodePtr = indexPtr->linePtr->parentPtr; nodePtr->parentPtr != NULL;
- nodePtr = nodePtr->parentPtr) {
- register Node *siblingPtr;
- register Summary *summaryPtr;
+ if (TagSetTestDontContainsAny(linePtr->tagonPtr, linePtr->tagoffPtr, discardTags)) {
+ TkTextSegment *segPtr = TkTextIndexGetContentSegment(indexPtr1, NULL);
- for (siblingPtr = nodePtr->parentPtr->children.nodePtr;
- siblingPtr != nodePtr; siblingPtr = siblingPtr->nextPtr) {
- for (summaryPtr = siblingPtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->toggleCount & 1) {
- IncCount(summaryPtr->tagPtr, summaryPtr->toggleCount,
- &tagInfo);
- }
- }
+ if ((segPtr = FindNextUntaggedSegInLine(segPtr, lastPtr, discardTags))) {
+ return segPtr;
}
}
/*
- * Go through the tag information and squash out all of the tags that have
- * even toggle counts (these tags exist before the point of interest, but
- * not at the desired character itself). Also squash out all tags that
- * don't belong to the requested widget.
+ * At second, search for line containing any tag in current node.
*/
- for (src = 0, dst = 0; src < tagInfo.numTags; src++) {
- if (tagInfo.counts[src] & 1) {
- const TkText *tagTextPtr = tagInfo.tagPtrs[src]->textPtr;
+ textPtr = indexPtr1->textPtr;
+ nodePtr = linePtr->parentPtr;
+
+ if (linePtr != nodePtr->lastPtr
+ && (TagSetTestDontContainsAny(nodePtr->tagonPtr, nodePtr->tagoffPtr, discardTags))) {
+ TkTextSegment *segPtr = FindNextUntaggedSegInNode(lastPtr, linePtr->nextPtr, discardTags);
- if (tagTextPtr==NULL || textPtr==NULL || tagTextPtr==textPtr) {
- tagInfo.tagPtrs[dst] = tagInfo.tagPtrs[src];
- dst++;
- }
+ if (segPtr) {
+ return segPtr;
}
}
- *numTagsPtr = dst;
- ckfree(tagInfo.counts);
- if (dst == 0) {
- ckfree(tagInfo.tagPtrs);
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which don't contains any tag.
+ */
+
+ if (!(nodePtr = FindNextUntaggedNode(nodePtr, discardTags))) {
return NULL;
}
- return tagInfo.tagPtrs;
+
+ if (textPtr && textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
+ int lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, nodePtr->linePtr, NULL);
+ int lineNo2 = TkTextIndexGetLineNumber(indexPtr2, NULL);
+
+ if (lineNo1 > lineNo2) {
+ /* We've found a node after text end, so return NULL. */
+ return NULL;
+ }
+ }
+
+ /*
+ * Final search of segment not containing any tag.
+ */
+
+ return FindNextUntaggedSegInNode(lastPtr, nodePtr->linePtr, discardTags);
}
/*
*----------------------------------------------------------------------
*
- * TkTextIsElided --
- *
- * Special case to just return information about elided attribute.
- * Specialized from TkBTreeGetTags(indexPtr, textPtr, numTagsPtr) and
- * GetStyle(textPtr, indexPtr). Just need to keep track of invisibility
- * settings for each priority, pick highest one active at end.
- *
- * Note that this returns all elide information up to and including the
- * given index (quite obviously). However, this does mean that if
- * indexPtr is a line-start and one then iterates from the beginning of
- * that line forwards, one will actually revisit the segPtrs of size zero
- * (for tag toggling, for example) which have already been seen here.
+ * TkBTreeFindPrevTagged --
*
- * For this reason we fill in the fields 'segPtr' and 'segOffset' of
- * elideInfo, enabling our caller easily to calculate incremental changes
- * from where we left off.
+ * Starting at given index, find previous segment which contains any tag.
*
* Results:
- * Returns whether this text should be elided or not.
- *
- * Optionally returns more detailed information in elideInfo.
+ * The return value is the previous segment containing any tag.
*
* Side effects:
* None.
@@ -3479,258 +11998,292 @@ TkBTreeGetTags(
*----------------------------------------------------------------------
*/
- /* ARGSUSED */
-int
-TkTextIsElided(
- const TkText *textPtr, /* Overall information about text widget. */
- const TkTextIndex *indexPtr,/* The character in the text for which display
- * information is wanted. */
- TkTextElideInfo *elideInfo) /* NULL or a pointer to a structure in which
- * indexPtr's elide state will be stored and
- * returned. */
+TkTextSegment *
+FindPrevTaggedSegInLine(
+ TkTextSegment *segPtr,
+ const TkTextSegment *firstPtr,
+ const TkBitField *selTags)
{
- register Node *nodePtr;
- register TkTextLine *siblingLinePtr;
- register TkTextSegment *segPtr;
- register TkTextTag *tagPtr = NULL;
- register int i, index;
- register TkTextElideInfo *infoPtr;
- TkTextLine *linePtr;
- int elide;
+ const TkTextLine *linePtr = segPtr->sectionPtr->linePtr;
- if (elideInfo == NULL) {
- infoPtr = ckalloc(sizeof(TkTextElideInfo));
- } else {
- infoPtr = elideInfo;
+ firstPtr = (linePtr == firstPtr->sectionPtr->linePtr) ? firstPtr->prevPtr : NULL;
+
+ for ( ; segPtr != firstPtr; segPtr = segPtr->prevPtr) {
+ const TkTextTagSet *tagInfoPtr = segPtr->tagInfoPtr;
+
+ if (tagInfoPtr) {
+ if (TagSetTestBits(tagInfoPtr, selTags)) {
+ return segPtr;
+ }
+ }
}
- infoPtr->elide = 0; /* If nobody says otherwise, it's visible. */
- infoPtr->tagCnts = infoPtr->deftagCnts;
- infoPtr->tagPtrs = infoPtr->deftagPtrs;
- infoPtr->numTags = textPtr->sharedTextPtr->numTags;
+ return NULL;
+}
- /*
- * Almost always avoid malloc, so stay out of system calls.
- */
+TkTextSegment *
+FindPrevTaggedSegInNode(
+ TkTextSegment *firstPtr,
+ const TkTextLine *linePtr,
+ const TkBitField *selTags) /* can be NULL */
+{
+ const TkTextLine *firstLinePtr = firstPtr->sectionPtr->linePtr;
+ const TkTextLine *startLinePtr = linePtr->parentPtr->linePtr;
- if (LOTSA_TAGS < infoPtr->numTags) {
- infoPtr->tagCnts = ckalloc(sizeof(int) * infoPtr->numTags);
- infoPtr->tagPtrs = ckalloc(sizeof(TkTextTag *) * infoPtr->numTags);
- }
+ while (true) {
+ if (TagSetTestBits(linePtr->tagonPtr, selTags)) {
+ return FindPrevTaggedSegInLine(linePtr->lastPtr, firstPtr, selTags);
+ }
+ if (linePtr == startLinePtr || linePtr == firstLinePtr) {
+ return NULL;
+ }
+ linePtr = linePtr->prevPtr;
+ };
- for (i=0; i<infoPtr->numTags; i++) {
- infoPtr->tagCnts[i] = 0;
- }
+ return NULL;
+}
- /*
- * Record tag toggles within the line of indexPtr but preceding indexPtr.
- */
+static const Node *
+FindPrevTaggedNode(
+ const Node *nodePtr,
+ const TkBitField *selTags) /* can be NULL */
+{
+ assert(nodePtr);
- index = 0;
- linePtr = indexPtr->linePtr;
- segPtr = linePtr->segPtr;
- while ((index + segPtr->size) <= indexPtr->byteIndex) {
- if ((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType)) {
- tagPtr = segPtr->body.toggle.tagPtr;
- if (tagPtr->elideString != NULL) {
- infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
- infoPtr->tagCnts[tagPtr->priority]++;
+ while (nodePtr->parentPtr) {
+ const Node *startNodePtr = nodePtr;
+ const Node *lastNodePtr = NULL;
+
+ nodePtr = nodePtr->parentPtr->childPtr;
+
+ for ( ; nodePtr != startNodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TagSetTestBits(nodePtr->tagonPtr, selTags)) {
+ lastNodePtr = nodePtr;
}
}
+ if (lastNodePtr) {
+ nodePtr = lastNodePtr;
- index += segPtr->size;
- segPtr = segPtr->nextPtr;
- if (segPtr == NULL) {
- /*
- * Two logical lines merged into one display line through eliding
- * of a newline.
- */
+ while (nodePtr->level > 0) {
+ DEBUG(lastNodePtr = NULL);
- linePtr = TkBTreeNextLine(NULL, linePtr);
- segPtr = linePtr->segPtr;
+ for (nodePtr = nodePtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (TagSetTestBits(nodePtr->tagonPtr, selTags)) {
+ lastNodePtr = nodePtr;
+ }
+ }
+
+ assert(lastNodePtr);
+ nodePtr = lastNodePtr;
+ }
+
+ return lastNodePtr;
}
+
+ nodePtr = startNodePtr->parentPtr;
}
- /*
- * Store the first segPtr we haven't examined completely so that our
- * caller knows where to start.
- */
+ return NULL;
+}
- infoPtr->segPtr = segPtr;
- infoPtr->segOffset = index;
+TkTextSegment *
+TkBTreeFindPrevTagged(
+ const TkTextIndex *indexPtr1,
+ /* Search starts here. Tag toggles at this position will be returned. */
+ const TkTextIndex *indexPtr2,
+ /* Search stops here. Tag toggles at this position will be returned. */
+ bool discardSelection) /* Discard selection tags? */
+{
+ const TkSharedText *sharedTextPtr = TkTextIndexGetShared(indexPtr1);
+ const TkBitField *selTags = discardSelection ? sharedTextPtr->selectionTags : NULL;
+ const TkTextLine *linePtr = TkTextIndexGetLine(indexPtr1);
+ TkTextSegment *firstPtr = TkTextIndexGetFirstSegment(indexPtr2, NULL);
+ const TkText *textPtr;
+ const Node *nodePtr;
/*
- * Record toggles for tags in lines that are predecessors of
- * indexPtr->linePtr but under the same level-0 node.
+ * At first, search for previous segment in first line.
*/
- for (siblingLinePtr = indexPtr->linePtr->parentPtr->children.linePtr;
- siblingLinePtr != indexPtr->linePtr;
- siblingLinePtr = siblingLinePtr->nextPtr) {
- for (segPtr = siblingLinePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- if ((segPtr->typePtr == &tkTextToggleOnType)
- || (segPtr->typePtr == &tkTextToggleOffType)) {
- tagPtr = segPtr->body.toggle.tagPtr;
- if (tagPtr->elideString != NULL) {
- infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
- infoPtr->tagCnts[tagPtr->priority]++;
- }
- }
+ if (TagSetTestBits(linePtr->tagonPtr, selTags)) {
+ TkTextSegment *segPtr = TkTextIndexGetContentSegment(indexPtr1, NULL);
+
+ if ((segPtr = FindPrevTaggedSegInLine(segPtr, firstPtr, selTags))) {
+ return segPtr;
}
}
/*
- * For each node in the ancestry of this line, record tag toggles for all
- * siblings that precede that node.
+ * At second, search for line containing any tag in current node.
*/
- for (nodePtr = indexPtr->linePtr->parentPtr; nodePtr->parentPtr != NULL;
- nodePtr = nodePtr->parentPtr) {
- register Node *siblingPtr;
- register Summary *summaryPtr;
-
- for (siblingPtr = nodePtr->parentPtr->children.nodePtr;
- siblingPtr != nodePtr; siblingPtr = siblingPtr->nextPtr) {
- for (summaryPtr = siblingPtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->toggleCount & 1) {
- tagPtr = summaryPtr->tagPtr;
- if (tagPtr->elideString != NULL) {
- infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
- infoPtr->tagCnts[tagPtr->priority] +=
- summaryPtr->toggleCount;
- }
- }
- }
+ textPtr = indexPtr1->textPtr;
+ nodePtr = linePtr->parentPtr;
+
+ if (linePtr != nodePtr->linePtr && TagSetTestBits(nodePtr->tagonPtr, selTags)) {
+ TkTextSegment *segPtr = FindPrevTaggedSegInNode(firstPtr, linePtr->prevPtr, selTags);
+
+ if (segPtr) {
+ return segPtr;
}
}
/*
- * Now traverse from highest priority to lowest, take elided value from
- * first odd count (= on).
+ * We couldn't find a line, so search inside B-Tree for previous level-0
+ * node which contains any tag.
*/
- infoPtr->elidePriority = -1;
- for (i = infoPtr->numTags-1; i >=0; i--) {
- if (infoPtr->tagCnts[i] & 1) {
- infoPtr->elide = infoPtr->tagPtrs[i]->elide;
-
- /*
- * Note: i == infoPtr->tagPtrs[i]->priority
- */
-
- infoPtr->elidePriority = i;
- break;
- }
+ if (!(nodePtr = FindPrevTaggedNode(nodePtr, selTags))) {
+ return NULL;
}
- elide = infoPtr->elide;
+ if (textPtr && textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
+ int lineNo1 = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, nodePtr->lastPtr, NULL);
+ int lineNo2 = TkTextIndexGetLineNumber(indexPtr2, NULL);
- if (elideInfo == NULL) {
- if (LOTSA_TAGS < infoPtr->numTags) {
- ckfree(infoPtr->tagCnts);
- ckfree(infoPtr->tagPtrs);
+ if (lineNo1 < lineNo2) {
+ /* We've found a node before text start, so return NULL. */
+ return NULL;
}
-
- ckfree(infoPtr);
}
- return elide;
+ /*
+ * Final search of segment containing any tag.
+ */
+
+ return FindPrevTaggedSegInNode(firstPtr, nodePtr->lastPtr, selTags);
}
/*
*----------------------------------------------------------------------
*
- * TkTextFreeElideInfo --
+ * TkBTreeCharTagged --
*
- * This is a utility function used to free up any memory allocated by the
- * TkTextIsElided function above.
+ * Determine whether a particular character has a particular tag.
*
* Results:
- * None.
+ * The return value is 1 if the given tag is in effect at the character
+ * given by linePtr and ch, and 0 otherwise.
*
* Side effects:
- * Memory may be freed.
+ * None.
*
*----------------------------------------------------------------------
*/
-void
-TkTextFreeElideInfo(
- TkTextElideInfo *elideInfo) /* Free any allocated memory in this
- * structure. */
+bool
+TkBTreeCharTagged(
+ const TkTextIndex *indexPtr,/* Indicates a character position at which to check for a tag. */
+ const TkTextTag *tagPtr) /* Tag of interest, can be NULL. */
{
- if (LOTSA_TAGS < elideInfo->numTags) {
- ckfree(elideInfo->tagCnts);
- ckfree(elideInfo->tagPtrs);
- }
+ const TkTextTagSet *tagInfoPtr = TkTextIndexGetContentSegment(indexPtr, NULL)->tagInfoPtr;
+ return tagPtr ? TkTextTagSetTest(tagInfoPtr, tagPtr->index) : !TkTextTagSetIsEmpty(tagInfoPtr);
}
/*
*----------------------------------------------------------------------
*
- * IncCount --
+ * TkBTreeGetSegmentTags --
*
- * This is a utility function used by TkBTreeGetTags. It increments the
- * count for a particular tag, adding a new entry for that tag if there
- * wasn't one previously.
+ * Return information about all of the tags that are associated with a
+ * particular char segment in a B-tree of text.
*
* Results:
- * None.
+ * The return value is the root of the tag chain, containing all tags
+ * associated with the given char segment. If there are no tags in this
+ * segment, then a NULL pointer is returned.
*
* Side effects:
- * The information at *tagInfoPtr may be modified, and the arrays may be
- * reallocated to make them larger.
+ * The attribute nextPtr of TkTextTag will be modified for any tag.
*
*----------------------------------------------------------------------
*/
-static void
-IncCount(
- TkTextTag *tagPtr, /* Handle for tag. */
- int inc, /* Amount by which to increment tag count. */
- TagInfo *tagInfoPtr) /* Holds cumulative information about tags;
- * increment count here. */
+TkTextTag *
+TkBTreeGetSegmentTags(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *segPtr, /* Get tags from this segment. */
+ const TkText *textPtr) /* If non-NULL, then only return tags for this text widget
+ * (when there are peer widgets). */
{
- register TkTextTag **tagPtrPtr;
- int count;
+ const TkTextTagSet *tagInfoPtr;
+ TkTextTag *chainPtr = NULL;
- for (tagPtrPtr = tagInfoPtr->tagPtrs, count = tagInfoPtr->numTags;
- count > 0; tagPtrPtr++, count--) {
- if (*tagPtrPtr == tagPtr) {
- tagInfoPtr->counts[tagInfoPtr->numTags-count] += inc;
- return;
+ assert(segPtr->tagInfoPtr);
+
+ tagInfoPtr = segPtr->tagInfoPtr;
+
+ if (tagInfoPtr != sharedTextPtr->emptyTagInfoPtr) {
+ unsigned i = TkTextTagSetFindFirst(tagInfoPtr);
+
+ for ( ; i != TK_TEXT_TAG_SET_NPOS; i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+
+ if (!textPtr || !tagPtr->textPtr || tagPtr->textPtr == textPtr) {
+ tagPtr->nextPtr = chainPtr;
+ tagPtr->epoch = 0;
+ chainPtr = tagPtr;
+ }
}
}
- /*
- * There isn't currently an entry for this tag, so we have to make a new
- * one. If the arrays are full, then enlarge the arrays first.
- */
+ return chainPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeGetLang --
+ *
+ * Return the language information of given segment.
+ *
+ * Results:
+ * The return value is the language string belonging to given segment.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- if (tagInfoPtr->numTags == tagInfoPtr->arraySize) {
- TkTextTag **newTags;
- int *newCounts, newSize;
+const char *
+TkBTreeGetLang(
+ const TkText *textPtr, /* Relative to this client of the B-tree. */
+ const TkTextSegment *segPtr) /* Get tags from this segment. */
+{
+ const TkTextTagSet *tagInfoPtr;
+ const TkSharedText *sharedTextPtr;
+ const char *langPtr;
+
+ assert(textPtr);
+ assert(segPtr->tagInfoPtr);
+ assert(segPtr->sectionPtr->linePtr->nextPtr);
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ tagInfoPtr = segPtr->tagInfoPtr;
+ langPtr = textPtr->lang;
+
+ if (tagInfoPtr != sharedTextPtr->emptyTagInfoPtr) {
+ unsigned i = TkTextTagSetFindFirst(tagInfoPtr);
+ int highestPriority = -1;
+
+ for ( ; i != TK_TEXT_TAG_SET_NPOS; i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ const TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
- newSize = 2 * tagInfoPtr->arraySize;
- newTags = ckalloc(newSize * sizeof(TkTextTag *));
- memcpy(newTags, tagInfoPtr->tagPtrs,
- tagInfoPtr->arraySize * sizeof(TkTextTag *));
- ckfree(tagInfoPtr->tagPtrs);
- tagInfoPtr->tagPtrs = newTags;
- newCounts = ckalloc(newSize * sizeof(int));
- memcpy(newCounts, tagInfoPtr->counts,
- tagInfoPtr->arraySize * sizeof(int));
- ckfree(tagInfoPtr->counts);
- tagInfoPtr->counts = newCounts;
- tagInfoPtr->arraySize = newSize;
+ if (tagPtr->lang[0] && tagPtr->priority > highestPriority) {
+ langPtr = tagPtr->lang;
+ highestPriority = tagPtr->priority;
+ }
+ }
}
- tagInfoPtr->tagPtrs[tagInfoPtr->numTags] = tagPtr;
- tagInfoPtr->counts[tagInfoPtr->numTags] = inc;
- tagInfoPtr->numTags++;
+ return langPtr;
}
/*
@@ -3756,69 +12309,173 @@ TkBTreeCheck(
TkTextBTree tree) /* Tree to check. */
{
BTree *treePtr = (BTree *) tree;
- register Summary *summaryPtr;
- register Node *nodePtr;
- register TkTextLine *linePtr;
- register TkTextSegment *segPtr;
- register TkTextTag *tagPtr;
+ const Node *nodePtr;
+ const TkTextLine *linePtr, *prevLinePtr;
+ const TkTextSegment *segPtr;
+ const TkText *peer;
+ unsigned numBranches = 0;
+ unsigned numLinks = 0;
Tcl_HashEntry *entryPtr;
Tcl_HashSearch search;
- int count;
+ const char *s;
+
+ if (treePtr->sharedTextPtr->refCount == 0) {
+ Tcl_Panic("TkBTreeCheck: tree is destroyed");
+ }
+
+ nodePtr = treePtr->rootPtr;
+ while (nodePtr->level > 0) {
+ nodePtr = nodePtr->childPtr;
+ if (!nodePtr) {
+ Tcl_Panic("TkBTreeCheck: no level 0 node in tree");
+ }
+ }
/*
- * Make sure that the tag toggle counts and the tag root pointers are OK.
+ * Check line pointers.
*/
- for (entryPtr=Tcl_FirstHashEntry(&treePtr->sharedTextPtr->tagTable,&search);
- entryPtr != NULL ; entryPtr = Tcl_NextHashEntry(&search)) {
- tagPtr = Tcl_GetHashValue(entryPtr);
- nodePtr = tagPtr->tagRootPtr;
- if (nodePtr == NULL) {
- if (tagPtr->toggleCount != 0) {
- Tcl_Panic("TkBTreeCheck found \"%s\" with toggles (%d) but no root",
- tagPtr->name, tagPtr->toggleCount);
- }
- continue; /* No ranges for the tag. */
- } else if (tagPtr->toggleCount == 0) {
- Tcl_Panic("TkBTreeCheck found root for \"%s\" with no toggles",
- tagPtr->name);
- } else if (tagPtr->toggleCount & 1) {
- Tcl_Panic("TkBTreeCheck found odd toggle count for \"%s\" (%d)",
- tagPtr->name, tagPtr->toggleCount);
+ prevLinePtr = NULL;
+ for (linePtr = nodePtr->linePtr;
+ linePtr;
+ prevLinePtr = linePtr, linePtr = linePtr->nextPtr) {
+ if (!linePtr->segPtr) {
+ Tcl_Panic("TkBTreeCheck: line has no segments");
+ }
+ if (linePtr->size == 0) {
+ Tcl_Panic("TkBTreeCheck: line has size zero");
+ }
+ if (!linePtr->lastPtr) {
+ Tcl_Panic("TkBTreeCheck: line has no last pointer");
+ }
+ if (linePtr->prevPtr != prevLinePtr) {
+ Tcl_Panic("TkBTreeCheck: line has wrong predecessor");
+ }
+ if (!linePtr->tagoffPtr || !linePtr->tagonPtr) {
+ Tcl_Panic("TkBTreeCheck: line tag information is incomplete");
+ }
+ if (TkTextTagSetRefCount(linePtr->tagonPtr) == 0) {
+ Tcl_Panic("TkBTreeCheck: unreferenced tag info (tagon)");
}
- for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->tagPtr == tagPtr) {
- Tcl_Panic("TkBTreeCheck found root node with summary info");
+ if (TkTextTagSetRefCount(linePtr->tagonPtr) > 0x3fffffff) {
+ Tcl_Panic("TkBTreeCheck: negative reference count in tagon info");
+ }
+ if (TkTextTagSetRefCount(linePtr->tagoffPtr) == 0) {
+ Tcl_Panic("TkBTreeCheck: unreferenced tag info (tagoff)");
+ }
+ if (TkTextTagSetRefCount(linePtr->tagoffPtr) > 0x3fffffff) {
+ Tcl_Panic("TkBTreeCheck: negative reference count in tagoff info");
+ }
+ if (!TkTextTagSetContains(linePtr->tagonPtr, linePtr->tagoffPtr)) {
+ Tcl_Panic("TkBTreeCheck: line tagoff not included in tagon");
+ }
+ if (TkTextTagSetIsEmpty(linePtr->tagonPtr)
+ && linePtr->tagonPtr != treePtr->sharedTextPtr->emptyTagInfoPtr) {
+ Tcl_Panic("TkBTreeCheck: should use shared resource if tag info is empty");
+ }
+ if (TkTextTagSetIsEmpty(linePtr->tagoffPtr)
+ && linePtr->tagoffPtr != treePtr->sharedTextPtr->emptyTagInfoPtr) {
+ Tcl_Panic("TkBTreeCheck: should use shared resource if tag info is empty");
+ }
+ if (TkTextTagSetRefCount(linePtr->tagonPtr) == 0) {
+ Tcl_Panic("TkBTreeCheck: reference count of line tagon is zero");
+ }
+ if (TkTextTagSetRefCount(linePtr->tagoffPtr) == 0) {
+ Tcl_Panic("TkBTreeCheck: reference count of line tagoff is zero");
+ }
+ if (linePtr->logicalLine ==
+ (linePtr->prevPtr && HasElidedNewline(treePtr->sharedTextPtr, linePtr->prevPtr))) {
+ Tcl_Panic("TkBTreeCheck: wrong logicalLine flag");
+ }
+ numBranches += linePtr->numBranches;
+ numLinks += linePtr->numLinks;
+ }
+
+ if (numBranches != treePtr->rootPtr->numBranches) {
+ Tcl_Panic("TkBTreeCheck: wrong branch count %u (expected is %u)",
+ numBranches, treePtr->rootPtr->numBranches);
+ }
+ if (numLinks != numBranches) {
+ Tcl_Panic("TkBTreeCheck: mismatch in number of links (%d) and branches (%d)",
+ numLinks, numBranches);
+ }
+
+ /*
+ * Check the special markers.
+ */
+
+ if (!treePtr->sharedTextPtr->startMarker->sectionPtr) {
+ Tcl_Panic("TkBTreeCheck: start marker of shared resource is not linked");
+ }
+ if (!treePtr->sharedTextPtr->endMarker->sectionPtr) {
+ Tcl_Panic("TkBTreeCheck: end marker of shared resource is not linked");
+ }
+ if (treePtr->sharedTextPtr->startMarker->sectionPtr->linePtr->prevPtr) {
+ Tcl_Panic("TkBTreeCheck: start marker of shared resource is not in first line");
+ }
+ if (treePtr->sharedTextPtr->endMarker->sectionPtr->linePtr->nextPtr) {
+ Tcl_Panic("TkBTreeCheck: end marker of shared resource is not in last line");
+ }
+ if (!SegIsAtStartOfLine(treePtr->sharedTextPtr->startMarker)) {
+ Tcl_Panic("TkBTreeCheck: start marker of shared resource is not at start of line");
+ }
+ if (!SegIsAtStartOfLine(treePtr->sharedTextPtr->endMarker)) {
+ Tcl_Panic("TkBTreeCheck: end marker of shared resource is not at start of line");
+ }
+
+ for (peer = treePtr->sharedTextPtr->peers; peer; peer = peer->next) {
+ if (peer->currentMarkPtr && peer->currentMarkPtr->sectionPtr) {
+ if ((peer->currentMarkPtr->prevPtr && !peer->currentMarkPtr->prevPtr->typePtr)
+ || (peer->currentMarkPtr->nextPtr && !peer->currentMarkPtr->nextPtr->typePtr)
+ || (peer->currentMarkPtr->sectionPtr
+ && (!peer->currentMarkPtr->sectionPtr->linePtr
+ || !peer->currentMarkPtr->sectionPtr->linePtr->parentPtr))) {
+ Tcl_Panic("TkBTreeCheck: current mark is expired");
}
}
- count = 0;
- if (nodePtr->level > 0) {
- for (nodePtr = nodePtr->children.nodePtr ; nodePtr != NULL ;
- nodePtr = nodePtr->nextPtr) {
- for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->tagPtr == tagPtr) {
- count += summaryPtr->toggleCount;
- }
- }
+ if (peer->insertMarkPtr && peer->insertMarkPtr->sectionPtr) {
+ if ((peer->insertMarkPtr->prevPtr && !peer->insertMarkPtr->prevPtr->typePtr)
+ || (peer->insertMarkPtr->nextPtr && !peer->insertMarkPtr->nextPtr->typePtr)
+ || (peer->insertMarkPtr->sectionPtr
+ && (!peer->insertMarkPtr->sectionPtr->linePtr
+ || !peer->insertMarkPtr->sectionPtr->linePtr->parentPtr))) {
+ Tcl_Panic("TkBTreeCheck: insert mark is expired");
}
- } else {
- for (linePtr = nodePtr->children.linePtr ; linePtr != NULL ;
- linePtr = linePtr->nextPtr) {
- for (segPtr = linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- if ((segPtr->typePtr == &tkTextToggleOnType ||
- segPtr->typePtr == &tkTextToggleOffType) &&
- segPtr->body.toggle.tagPtr == tagPtr) {
- count++;
- }
- }
+ }
+#if 0 /* cannot be used, because also TkBTreeUnlinkSegment is calling TreeCheck */
+ if (peer->startMarker != treePtr->sharedTextPtr->startMarker) {
+ if (!peer->startMarker->sectionPtr) {
+ Tcl_Panic("TkBTreeCheck: start marker is not linked");
+ }
+ if (!peer->endMarker->sectionPtr) {
+ Tcl_Panic("TkBTreeCheck: end marker is not linked");
}
}
- if (count != tagPtr->toggleCount) {
- Tcl_Panic("TkBTreeCheck toggleCount (%d) wrong for \"%s\" should be (%d)",
- tagPtr->toggleCount, tagPtr->name, count);
+#endif
+ if (!peer->startMarker->sectionPtr) {
+ Tcl_Panic("TkBTreeCheck: start marker of is not linked");
+ }
+ if (!peer->endMarker->sectionPtr) {
+ Tcl_Panic("TkBTreeCheck: end marker of is not linked");
+ }
+ if (!peer->startMarker->sectionPtr->linePtr->nextPtr) {
+ Tcl_Panic("TkBTreeCheck: start marker is on very last line");
+ }
+ if (peer->startMarker->sectionPtr->linePtr == peer->endMarker->sectionPtr->linePtr) {
+ const TkTextSegment *segPtr = peer->startMarker;
+ while (segPtr && segPtr != peer->endMarker) {
+ segPtr = segPtr->prevPtr;
+ }
+ if (segPtr == peer->endMarker) {
+ Tcl_Panic("TkBTreeCheck: end marker segment is before start marker segment");
+ }
+ } else {
+ int startLineNo = TkBTreeLinesTo(tree, NULL, peer->startMarker->sectionPtr->linePtr, NULL);
+ int endLineNo = TkBTreeLinesTo(tree, NULL, peer->endMarker->sectionPtr->linePtr, NULL);
+
+ if (startLineNo > endLineNo) {
+ Tcl_Panic("TkBTreeCheck: end marker line is before start marker line");
+ }
}
}
@@ -3826,51 +12483,104 @@ TkBTreeCheck(
* Call a recursive function to do the main body of checks.
*/
- nodePtr = treePtr->rootPtr;
- CheckNodeConsistency(treePtr->rootPtr, treePtr->pixelReferences);
+ CheckNodeConsistency(treePtr->sharedTextPtr, treePtr->rootPtr,
+ treePtr->rootPtr, treePtr->numPixelReferences);
/*
* Make sure that there are at least two lines in the text and that the
* last line has no characters except a newline.
*/
+ nodePtr = treePtr->rootPtr;
if (nodePtr->numLines < 2) {
Tcl_Panic("TkBTreeCheck: less than 2 lines in tree");
}
+ if (!nodePtr->linePtr->logicalLine) {
+ Tcl_Panic("TkBTreeCheck: first line must be a logical line");
+ }
+#if 0 /* TODO: is it really allowed that the last line is not a logical line? */
+ if (!nodePtr->lastPtr->logicalLine) {
+ Tcl_Panic("TkBTreeCheck: last line must be a logical line");
+ }
+#endif
while (nodePtr->level > 0) {
- nodePtr = nodePtr->children.nodePtr;
- while (nodePtr->nextPtr != NULL) {
+ nodePtr = nodePtr->childPtr;
+ while (nodePtr->nextPtr) {
nodePtr = nodePtr->nextPtr;
}
}
- linePtr = nodePtr->children.linePtr;
- while (linePtr->nextPtr != NULL) {
- linePtr = linePtr->nextPtr;
- }
+ linePtr = nodePtr->lastPtr;
segPtr = linePtr->segPtr;
- while ((segPtr->typePtr == &tkTextToggleOffType)
- || (segPtr->typePtr == &tkTextRightMarkType)
- || (segPtr->typePtr == &tkTextLeftMarkType)) {
- /*
- * It's OK to toggle a tag off in the last line, but not to start a
- * new range. It's also OK to have marks in the last line.
- */
-
+ if (segPtr->typePtr == &tkTextLinkType) {
+ /* It's OK to have one link in the last line. */
+ segPtr = segPtr->nextPtr;
+ }
+ while (segPtr->typePtr->group == SEG_GROUP_MARK) {
+ /* It's OK to have marks or breaks in the last line. */
segPtr = segPtr->nextPtr;
}
if (segPtr->typePtr != &tkTextCharType) {
Tcl_Panic("TkBTreeCheck: last line has bogus segment type");
}
- if (segPtr->nextPtr != NULL) {
+ if (segPtr->nextPtr) {
Tcl_Panic("TkBTreeCheck: last line has too many segments");
}
if (segPtr->size != 1) {
- Tcl_Panic("TkBTreeCheck: last line has wrong # characters: %d",
- segPtr->size);
+ Tcl_Panic("TkBTreeCheck: last line has wrong # characters: %d", segPtr->size);
+ }
+
+ s = segPtr->body.chars; /* this avoids warnings */
+ if (s[0] != '\n' || s[1] != '\0') {
+ Tcl_Panic("TkBTreeCheck: last line had bad value: %s", segPtr->body.chars);
+ }
+
+ for (entryPtr = Tcl_FirstHashEntry(&treePtr->sharedTextPtr->tagTable, &search);
+ entryPtr;
+ entryPtr = Tcl_NextHashEntry(&search)) {
+ const TkTextTag *tagPtr = Tcl_GetHashValue(entryPtr);
+
+ assert(tagPtr->index < treePtr->sharedTextPtr->tagInfoSize);
+
+ if (TkBitTest(treePtr->sharedTextPtr->selectionTags, tagPtr->index) && tagPtr->elideString) {
+ Tcl_Panic("TkBTreeCheck: the selection tag '%s' is not allowed to elide (or un-elide)",
+ tagPtr->name);
+ }
+
+ if ((nodePtr = tagPtr->rootPtr)) {
+ assert(nodePtr->linePtr); /* still unfree'd? */
+
+ if (!TkTextTagSetTest(nodePtr->tagonPtr, tagPtr->index)) {
+ if (nodePtr->level == 0) {
+ Tcl_Panic("TkBTreeCheck: level zero node is not root for tag '%s'",
+ tagPtr->name);
+ } else {
+ Tcl_Panic("TkBTreeCheck: node is not root for tag '%s'", tagPtr->name);
+ }
+ }
+
+ if (nodePtr->level > 0 && CountChildsWithTag(nodePtr, tagPtr->index) < 2) {
+ Tcl_Panic("TkBTreeCheck: node is not root for tag '%s', it has less "
+ "than two childs containing this tag", tagPtr->name);
+ }
+
+ while ((nodePtr = nodePtr->parentPtr)) {
+ if (CountChildsWithTag(nodePtr, tagPtr->index) > 1) {
+ Tcl_Panic("TkBTreeCheck: found higher node as root for tag '%s'", tagPtr->name);
+ }
+ }
+ } else if (TkTextTagSetTest(treePtr->rootPtr->tagonPtr, tagPtr->index)) {
+ Tcl_Panic("TkBTreeCheck: tag '%s' is used, but has no root", tagPtr->name);
+ }
}
- if ((segPtr->body.chars[0] != '\n') || (segPtr->body.chars[1] != 0)) {
- Tcl_Panic("TkBTreeCheck: last line had bad value: %s",
- segPtr->body.chars);
+
+ if (tkTextDebug) {
+ for (peer = treePtr->sharedTextPtr->peers; peer; peer = peer->next) {
+ /*
+ * Check display stuff.
+ */
+ TkTextCheckDisplayLineConsistency(peer);
+ TkTextCheckLineMetricUpdate(peer);
+ }
}
}
@@ -3895,172 +12605,325 @@ TkBTreeCheck(
static void
CheckNodeConsistency(
- register Node *nodePtr, /* Node whose subtree should be checked. */
- int references) /* Number of referring widgets which have
- * pixel counts. */
-{
- register Node *childNodePtr;
- register Summary *summaryPtr, *summaryPtr2;
- register TkTextLine *linePtr;
- register TkTextSegment *segPtr;
- int numChildren, numLines, toggleCount, minChildren, i;
- int *numPixels;
- int pixels[PIXEL_CLIENTS];
-
- if (nodePtr->parentPtr != NULL) {
- minChildren = MIN_CHILDREN;
- } else if (nodePtr->level > 0) {
- minChildren = 2;
- } else {
- minChildren = 1;
+ const TkSharedText *sharedTextPtr,/* Handle to shared text resource. */
+ const Node *rootPtr, /* The root node. */
+ const Node *nodePtr, /* Node whose subtree should be checked. */
+ unsigned references) /* Number of referring widgets which have pixel counts. */
+{
+ const Node *childNodePtr;
+ const TkTextLine *linePtr;
+ const TkTextLine *prevLinePtr;
+ int numChildren, numLines, numLogicalLines, numBranches;
+ int minChildren, size, i;
+ NodePixelInfo *pixelInfo = NULL;
+ NodePixelInfo pixelInfoBuf[PIXEL_CLIENTS];
+ TkTextTagSet *tagonPtr = NULL;
+ TkTextTagSet *tagoffPtr = NULL;
+ TkTextTagSet *additionalTagoffPtr = NULL;
+ unsigned memsize;
+
+ if (nodePtr->level == 0 && !nodePtr->linePtr) {
+ Tcl_Panic("CheckNodeConsistency: this node is freed");
}
- if ((nodePtr->numChildren < minChildren)
- || (nodePtr->numChildren > MAX_CHILDREN)) {
- Tcl_Panic("CheckNodeConsistency: bad child count (%d)",
- nodePtr->numChildren);
+
+ minChildren = nodePtr->parentPtr ? MIN_CHILDREN : (nodePtr->level > 0 ? 2 : 1);
+ if (nodePtr->numChildren < minChildren || nodePtr->numChildren > MAX_CHILDREN) {
+ Tcl_Panic("CheckNodeConsistency: bad child count (%d)", nodePtr->numChildren);
}
- numChildren = 0;
- numLines = 0;
- if (references > PIXEL_CLIENTS) {
- numPixels = ckalloc(sizeof(int) * references);
- } else {
- numPixels = pixels;
+ if (!nodePtr->linePtr) {
+ Tcl_Panic("CheckNodeConsistency: first pointer is NULL");
+ }
+ if (!nodePtr->lastPtr) {
+ Tcl_Panic("CheckNodeConsistency: last pointer is NULL");
+ }
+ if (!nodePtr->tagonPtr || !nodePtr->tagoffPtr) {
+ Tcl_Panic("CheckNodeConsistency: tag information is NULL");
+ }
+ if (TkTextTagSetRefCount(nodePtr->tagonPtr) == 0) {
+ Tcl_Panic("CheckNodeConsistency: unreferenced tag info (tagon)");
+ }
+ if (TkTextTagSetRefCount(nodePtr->tagonPtr) > 0x3fffffff) {
+ Tcl_Panic("CheckNodeConsistency: negative reference count in tagon info");
+ }
+ if (TkTextTagSetRefCount(nodePtr->tagoffPtr) == 0) {
+ Tcl_Panic("CheckNodeConsistency: unreferenced tag info (tagoff)");
+ }
+ if (TkTextTagSetRefCount(nodePtr->tagoffPtr) > 0x3fffffff) {
+ Tcl_Panic("CheckNodeConsistency: negative reference count in tagoff info");
+ }
+ if (TkTextTagSetIsEmpty(nodePtr->tagonPtr)
+ && nodePtr->tagonPtr != sharedTextPtr->emptyTagInfoPtr) {
+ Tcl_Panic("CheckNodeConsistency: should use shared resource if tag info is empty");
}
- for (i = 0; i<references; i++) {
- numPixels[i] = 0;
+ if (TkTextTagSetIsEmpty(nodePtr->tagoffPtr)
+ && nodePtr->tagoffPtr != sharedTextPtr->emptyTagInfoPtr) {
+ Tcl_Panic("CheckNodeConsistency: should use shared resource if tag info is empty");
}
+ if (!TkTextTagSetContains(nodePtr->tagonPtr, nodePtr->tagoffPtr)) {
+ Tcl_Panic("CheckNodeConsistency: node tagoff not included in tagon");
+ }
+ if (!TkTextTagSetContains(rootPtr->tagonPtr, nodePtr->tagonPtr)) {
+ Tcl_Panic("CheckNodeConsistency: tagon not propagated to root");
+ }
+ if (!TkTextTagSetContains(rootPtr->tagoffPtr, nodePtr->tagoffPtr)) {
+ Tcl_Panic("CheckNodeConsistency: tagoff not propagated to root");
+ }
+
+ numChildren = numLines = numLogicalLines = numBranches = size = 0;
+
+ memsize = sizeof(pixelInfo[0])*references;
+ pixelInfo = (references > PIXEL_CLIENTS) ? (NodePixelInfo *) malloc(memsize) : pixelInfoBuf;
+ memset(pixelInfo, 0, memsize);
+
+ TkTextTagSetIncrRefCount(tagonPtr = sharedTextPtr->emptyTagInfoPtr);
+ TkTextTagSetIncrRefCount(tagoffPtr = sharedTextPtr->emptyTagInfoPtr);
+ additionalTagoffPtr = NULL;
if (nodePtr->level == 0) {
- for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
- linePtr = linePtr->nextPtr) {
+ prevLinePtr = NULL;
+ linePtr = nodePtr->linePtr;
+ for (linePtr = nodePtr->linePtr;
+ numChildren < nodePtr->numChildren;
+ ++numChildren, ++numLines, linePtr = linePtr->nextPtr) {
+ if (!linePtr) {
+ Tcl_Panic("CheckNodeConsistency: unexpected end of line chain");
+ }
if (linePtr->parentPtr != nodePtr) {
- Tcl_Panic("CheckNodeConsistency: line doesn't point to parent");
+ Tcl_Panic("CheckNodeConsistency: line has wrong parent pointer");
}
- if (linePtr->segPtr == NULL) {
- Tcl_Panic("CheckNodeConsistency: line has no segments");
+ CheckSegments(sharedTextPtr, linePtr);
+ CheckSegmentItems(sharedTextPtr, linePtr);
+ CheckSections(linePtr);
+ for (i = 0; i < references; ++i) {
+ pixelInfo[i].pixels += linePtr->pixelInfo[i].height;
+ pixelInfo[i].numDispLines += GetDisplayLines(linePtr, i);
}
- for (segPtr = linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- if (segPtr->typePtr->checkProc != NULL) {
- segPtr->typePtr->checkProc(segPtr, linePtr);
- }
- if ((segPtr->size == 0) && (!segPtr->typePtr->leftGravity)
- && (segPtr->nextPtr != NULL)
- && (segPtr->nextPtr->size == 0)
- && (segPtr->nextPtr->typePtr->leftGravity)) {
- Tcl_Panic("CheckNodeConsistency: wrong segment order for gravity");
- }
- if ((segPtr->nextPtr == NULL)
- && (segPtr->typePtr != &tkTextCharType)) {
- Tcl_Panic("CheckNodeConsistency: line ended with wrong type");
+ if (tagonPtr) {
+ tagonPtr = TkTextTagSetJoin(tagonPtr, linePtr->tagonPtr);
+ tagoffPtr = TkTextTagSetJoin(tagoffPtr, linePtr->tagoffPtr);
+ if (additionalTagoffPtr) {
+ additionalTagoffPtr = TkTextTagSetIntersect(additionalTagoffPtr, linePtr->tagonPtr);
+ } else {
+ TkTextTagSetIncrRefCount(additionalTagoffPtr = linePtr->tagonPtr);
}
}
- numChildren++;
- numLines++;
- for (i = 0; i<references; i++) {
- numPixels[i] += linePtr->pixels[2 * i];
- }
+ prevLinePtr = linePtr;
+ numLogicalLines += linePtr->logicalLine;
+ numBranches += linePtr->numBranches;
+ size += linePtr->size;
+ }
+ if (prevLinePtr != nodePtr->lastPtr) {
+ Tcl_Panic("CheckNodeConsistency: wrong pointer to last line");
}
} else {
- for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL;
- childNodePtr = childNodePtr->nextPtr) {
+ TkTextLine *startLinePtr = nodePtr->linePtr;
+
+ for (childNodePtr = nodePtr->childPtr; childNodePtr; childNodePtr = childNodePtr->nextPtr) {
if (childNodePtr->parentPtr != nodePtr) {
Tcl_Panic("CheckNodeConsistency: node doesn't point to parent");
}
- if (childNodePtr->level != (nodePtr->level-1)) {
+ if (childNodePtr->level != nodePtr->level - 1) {
Tcl_Panic("CheckNodeConsistency: level mismatch (%d %d)",
nodePtr->level, childNodePtr->level);
}
- CheckNodeConsistency(childNodePtr, references);
- for (summaryPtr = childNodePtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- for (summaryPtr2 = nodePtr->summaryPtr; ;
- summaryPtr2 = summaryPtr2->nextPtr) {
- if (summaryPtr2 == NULL) {
- if (summaryPtr->tagPtr->tagRootPtr == nodePtr) {
- break;
- }
- Tcl_Panic("CheckNodeConsistency: node tag \"%s\" not %s",
- summaryPtr->tagPtr->name,
- "present in parent summaries");
- }
- if (summaryPtr->tagPtr == summaryPtr2->tagPtr) {
- break;
- }
+ if (childNodePtr->linePtr != startLinePtr) {
+ const Node *nodePtr = childNodePtr;
+ while (nodePtr->level > 0) {
+ nodePtr = nodePtr->childPtr;
+ }
+ if (nodePtr->linePtr != startLinePtr) {
+ Tcl_Panic("CheckNodeConsistency: pointer to first line is wrong");
+ } else {
+ Tcl_Panic("CheckNodeConsistency: pointer to last line is wrong");
}
}
- numChildren++;
+ startLinePtr = childNodePtr->lastPtr->nextPtr;
+ CheckNodeConsistency(sharedTextPtr, rootPtr, childNodePtr, references);
+ numChildren += 1;
numLines += childNodePtr->numLines;
- for (i = 0; i<references; i++) {
- numPixels[i] += childNodePtr->numPixels[i];
+ numLogicalLines += childNodePtr->numLogicalLines;
+ numBranches += childNodePtr->numBranches;
+ size += childNodePtr->size;
+ if (tagonPtr) {
+ tagonPtr = TkTextTagSetJoin(tagonPtr, nodePtr->tagonPtr);
+ tagoffPtr = TkTextTagSetJoin(tagoffPtr, nodePtr->tagoffPtr);
+ if (additionalTagoffPtr) {
+ additionalTagoffPtr = TkTextTagSetIntersect(additionalTagoffPtr, nodePtr->tagonPtr);
+ } else {
+ TkTextTagSetIncrRefCount(additionalTagoffPtr = nodePtr->tagonPtr);
+ }
+ }
+ for (i = 0; i < references; i++) {
+ pixelInfo[i].pixels += childNodePtr->pixelInfo[i].pixels;
+ pixelInfo[i].numDispLines += childNodePtr->pixelInfo[i].numDispLines;
}
}
}
+ if (size != nodePtr->size) {
+ Tcl_Panic("CheckNodeConsistency: sum of size (%d) at level %d is wrong (%d is expected)",
+ nodePtr->size, nodePtr->level, size);
+ }
if (numChildren != nodePtr->numChildren) {
- Tcl_Panic("CheckNodeConsistency: mismatch in numChildren (%d %d)",
+ Tcl_Panic("CheckNodeConsistency: mismatch in numChildren (expected: %d, counted: %d)",
numChildren, nodePtr->numChildren);
}
if (numLines != nodePtr->numLines) {
- Tcl_Panic("CheckNodeConsistency: mismatch in numLines (%d %d)",
+ Tcl_Panic("CheckNodeConsistency: mismatch in numLines (expected: %d, counted: %d)",
numLines, nodePtr->numLines);
}
- for (i = 0; i<references; i++) {
- if (numPixels[i] != nodePtr->numPixels[i]) {
- Tcl_Panic("CheckNodeConsistency: mismatch in numPixels (%d %d) for widget (%d)",
- numPixels[i], nodePtr->numPixels[i], i);
- }
+ if (numLogicalLines != nodePtr->numLogicalLines) {
+ Tcl_Panic("CheckNodeConsistency: mismatch in numLogicalLines (expected: %d, counted: %d)",
+ numLogicalLines, nodePtr->numLogicalLines);
}
- if (references > PIXEL_CLIENTS) {
- ckfree(numPixels);
+ if (numBranches != nodePtr->numBranches) {
+ Tcl_Panic("CheckNodeConsistency: mismatch in numBranches (expected: %d, counted: %d)",
+ numLogicalLines, nodePtr->numLogicalLines);
}
-
- for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr->tagPtr->toggleCount == summaryPtr->toggleCount) {
- Tcl_Panic("CheckNodeConsistency: found unpruned root for \"%s\"",
- summaryPtr->tagPtr->name);
+ if (tagonPtr) {
+ if (!TkTextTagSetIsEqual(tagonPtr, nodePtr->tagonPtr)) {
+ Tcl_Panic("CheckNodeConsistency: sum of node tag information is wrong (tagon)");
}
- toggleCount = 0;
- if (nodePtr->level == 0) {
- for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
- linePtr = linePtr->nextPtr) {
- for (segPtr = linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- if ((segPtr->typePtr != &tkTextToggleOnType)
- && (segPtr->typePtr != &tkTextToggleOffType)) {
- continue;
- }
- if (segPtr->body.toggle.tagPtr == summaryPtr->tagPtr) {
- toggleCount++;
- }
- }
+ assert(additionalTagoffPtr);
+ additionalTagoffPtr = TkTextTagSetComplementTo(additionalTagoffPtr, tagonPtr);
+ tagoffPtr = TkTextTagSetJoin(tagoffPtr, additionalTagoffPtr);
+ if (!TkTextTagSetIsEqual(tagoffPtr, nodePtr->tagoffPtr)) {
+ Tcl_Panic("CheckNodeConsistency: sum of node tag information is wrong (tagoff)");
+ }
+ for (i = TkTextTagSetFindFirst(tagonPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagonPtr, i)) {
+ if (!sharedTextPtr->tagLookup[i]) {
+ Tcl_Panic("CheckNodeConsistency: node tagon contains deleted tag %d", i);
}
- } else {
- for (childNodePtr = nodePtr->children.nodePtr;
- childNodePtr != NULL;
- childNodePtr = childNodePtr->nextPtr) {
- for (summaryPtr2 = childNodePtr->summaryPtr;
- summaryPtr2 != NULL;
- summaryPtr2 = summaryPtr2->nextPtr) {
- if (summaryPtr2->tagPtr == summaryPtr->tagPtr) {
- toggleCount += summaryPtr2->toggleCount;
- }
- }
+ if (sharedTextPtr->tagLookup[i]->isDisabled) {
+ Tcl_Panic("CheckNodeConsistency: node tagon contains disabled tag %d", i);
}
}
- if (toggleCount != summaryPtr->toggleCount) {
- Tcl_Panic("CheckNodeConsistency: mismatch in toggleCount (%d %d)",
- toggleCount, summaryPtr->toggleCount);
+
+ TkTextTagSetDecrRefCount(tagonPtr);
+ TkTextTagSetDecrRefCount(tagoffPtr);
+ TkTextTagSetDecrRefCount(additionalTagoffPtr);
+ }
+ for (i = 0; i < references; i++) {
+ if (pixelInfo[i].pixels != nodePtr->pixelInfo[i].pixels) {
+ Tcl_Panic("CheckNodeConsistency: mismatch in pixel count "
+ "(expected: %d, counted: %d) for widget (%d) at level %d",
+ pixelInfo[i].pixels, nodePtr->pixelInfo[i].pixels, i, nodePtr->level);
}
- for (summaryPtr2 = summaryPtr->nextPtr; summaryPtr2 != NULL;
- summaryPtr2 = summaryPtr2->nextPtr) {
- if (summaryPtr2->tagPtr == summaryPtr->tagPtr) {
- Tcl_Panic("CheckNodeConsistency: duplicated node tag: %s",
- summaryPtr->tagPtr->name);
- }
+ if (pixelInfo[i].numDispLines != nodePtr->pixelInfo[i].numDispLines) {
+ Tcl_Panic("CheckNodeConsistency: mismatch in number of display lines "
+ "(expected: %d, counted: %d) for widget (%d) at level %d",
+ pixelInfo[i].numDispLines, nodePtr->pixelInfo[i].numDispLines,
+ i, nodePtr->level);
+ }
+ }
+ if (pixelInfo != pixelInfoBuf) {
+ free(pixelInfo);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DeleteEmptyNode --
+ *
+ * This function is deleting a level-0 node from the B-tree.
+ * It is also deleting the parents recursively upwards until
+ * a non-empty node is found.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The internal structure of treePtr will change. The pixel counts
+ * will be updated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DeleteEmptyNode(
+ BTree *treePtr, /* B-tree that is being modified. */
+ Node *nodePtr) /* Level-0 node that will be deleted. */
+{
+ TkTextLine *linePtr, *lastPtr, *nextPtr, *prevPtr;
+ Node *parentPtr;
+ NodePixelInfo *changeToPixelInfo;
+ unsigned ref;
+
+ assert(nodePtr->level == 0);
+ assert(nodePtr->numChildren == 0);
+ assert(nodePtr->linePtr);
+
+ changeToPixelInfo = treePtr->pixelInfoBuffer;
+ memset(changeToPixelInfo, 0, treePtr->numPixelReferences * sizeof(changeToPixelInfo[0]));
+
+ /*
+ * The pixel count of this node is going to zero.
+ */
+
+ for (linePtr = nodePtr->linePtr, lastPtr = nodePtr->lastPtr->nextPtr;
+ linePtr != lastPtr;
+ linePtr = linePtr->nextPtr) {
+ NodePixelInfo *dst = changeToPixelInfo;
+
+ for (ref = 0; ref < treePtr->numPixelReferences; ++ref, ++dst) {
+ dst->pixels += linePtr->pixelInfo[ref].height;
+ dst->numDispLines += GetDisplayLines(linePtr, ref);
}
}
+ SubtractPixelCount2(treePtr, nodePtr->parentPtr, nodePtr->numLines, nodePtr->numLogicalLines,
+ nodePtr->numBranches, nodePtr->size, changeToPixelInfo);
+
+ lastPtr = nodePtr->lastPtr;
+ prevPtr = nodePtr->linePtr->prevPtr;
+ parentPtr = nodePtr->parentPtr;
+ for ( ; parentPtr && parentPtr->lastPtr == lastPtr; parentPtr = parentPtr->parentPtr) {
+ parentPtr->lastPtr = prevPtr;
+ }
+
+ linePtr = nodePtr->linePtr;
+ nextPtr = nodePtr->lastPtr->nextPtr;
+ parentPtr = nodePtr->parentPtr;
+ for ( ; parentPtr && parentPtr->linePtr == linePtr; parentPtr = parentPtr->parentPtr) {
+ parentPtr->linePtr = nextPtr;
+ }
+
+ do {
+ TkTextTagSet *tagonPtr;
+ unsigned i;
+
+ parentPtr = nodePtr->parentPtr;
+
+ if (parentPtr->childPtr == nodePtr) {
+ parentPtr->childPtr = nodePtr->nextPtr;
+ } else {
+ Node *prevNodePtr = parentPtr->childPtr;
+
+ while (prevNodePtr->nextPtr != nodePtr) {
+ prevNodePtr = prevNodePtr->nextPtr;
+ }
+ prevNodePtr->nextPtr = nodePtr->nextPtr;
+ }
+ parentPtr->numChildren -= 1;
+
+ /*
+ * Remove all tags from this node.
+ */
+
+ tagonPtr = nodePtr->tagonPtr;
+ TkTextTagSetIncrRefCount(tagonPtr);
+ for (i = TkTextTagSetFindFirst(tagonPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagonPtr, i)) {
+ RemoveTagFromNode(nodePtr, treePtr->sharedTextPtr->tagLookup[i]);
+ }
+ TkTextTagSetDecrRefCount(tagonPtr);
+
+ FreeNode(nodePtr);
+ nodePtr = parentPtr;
+ } while (nodePtr->numChildren == 0);
}
/*
@@ -4082,120 +12945,511 @@ CheckNodeConsistency(
*/
static void
+RebalanceAssignNewParentToChildren(
+ Node *nodePtr)
+{
+ if (nodePtr->level == 0) {
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+ TkTextLine *linePtr;
+
+ for (linePtr = nodePtr->linePtr; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ linePtr->parentPtr = nodePtr;
+ }
+ } else {
+ Node *childPtr = nodePtr->childPtr;
+
+ for (childPtr = nodePtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ childPtr->parentPtr = nodePtr;
+ }
+ }
+}
+
+static void
+RebalanceAddLinePixels(
+ NodePixelInfo *dstPixels,
+ const TkTextLine *linePtr,
+ unsigned numRefs)
+{
+ const TkTextPixelInfo *srcPixelInfo = linePtr->pixelInfo;
+ const TkTextPixelInfo *e = srcPixelInfo + numRefs;
+
+ for ( ; srcPixelInfo < e; ++srcPixelInfo, ++dstPixels) {
+ dstPixels->pixels += srcPixelInfo->height;
+ dstPixels->numDispLines += TkBTreeGetNumberOfDisplayLines(srcPixelInfo);
+ }
+}
+
+static void
+RebalanceAddNodePixels(
+ NodePixelInfo *dstPixels,
+ const NodePixelInfo *srcPixels,
+ unsigned numRefs)
+{
+ const NodePixelInfo *e = srcPixels + numRefs;
+
+ for ( ; srcPixels < e; ++srcPixels, ++dstPixels) {
+ dstPixels->pixels += srcPixels->pixels;
+ dstPixels->numDispLines += srcPixels->numDispLines;
+ }
+}
+
+static void
+RebalanceSubtractNodePixels(
+ NodePixelInfo *dstPixels,
+ const NodePixelInfo *srcPixels,
+ unsigned numRefs)
+{
+ const NodePixelInfo *e = srcPixels + numRefs;
+
+ for ( ; srcPixels < e; ++srcPixels, ++dstPixels) {
+ dstPixels->pixels -= srcPixels->pixels;
+ dstPixels->numDispLines -= srcPixels->numDispLines;
+ }
+}
+
+static void
+RebalanceRecomputeNodeTagInfo(
+ Node *nodePtr,
+ TkSharedText *sharedTextPtr)
+{
+ TkTextTagSet *additionalTagoffPtr = NULL;
+
+ assert(TkTextTagSetIsEmpty(nodePtr->tagonPtr));
+ assert(TkTextTagSetIsEmpty(nodePtr->tagoffPtr));
+
+ if (nodePtr->level == 0) {
+ TkTextLine *linePtr = nodePtr->linePtr;
+ TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+
+ for ( ; linePtr != lastPtr; linePtr = linePtr->nextPtr) {
+ nodePtr->tagonPtr = TkTextTagSetJoin(nodePtr->tagonPtr, linePtr->tagonPtr);
+ nodePtr->tagoffPtr = TkTextTagSetJoin(nodePtr->tagoffPtr, linePtr->tagoffPtr);
+ if (additionalTagoffPtr) {
+ additionalTagoffPtr = TkTextTagSetIntersect(additionalTagoffPtr, linePtr->tagonPtr);
+ } else {
+ TkTextTagSetIncrRefCount(additionalTagoffPtr = linePtr->tagonPtr);
+ }
+ }
+ } else {
+ Node *childPtr = nodePtr->childPtr;
+
+ for ( ; childPtr; childPtr = childPtr->nextPtr) {
+ nodePtr->tagonPtr = TkTextTagSetJoin(nodePtr->tagonPtr, childPtr->tagonPtr);
+ nodePtr->tagoffPtr = TkTextTagSetJoin(nodePtr->tagoffPtr, childPtr->tagoffPtr);
+ if (additionalTagoffPtr) {
+ additionalTagoffPtr = TkTextTagSetIntersect(additionalTagoffPtr, nodePtr->tagonPtr);
+ } else {
+ TkTextTagSetIncrRefCount(additionalTagoffPtr = nodePtr->tagonPtr);
+ }
+ }
+ }
+
+ assert(additionalTagoffPtr);
+
+ /*
+ * Finally add any tag to tagoff, if it is contained in at least one child, but not in all.
+ */
+
+ nodePtr->tagoffPtr = TagSetJoinComplementTo(
+ nodePtr->tagoffPtr, additionalTagoffPtr, nodePtr->tagonPtr, sharedTextPtr);
+ TkTextTagSetDecrRefCount(additionalTagoffPtr);
+}
+
+static Node *
+RebalanceFindSiblingForTag(
+ Node *parentPtr,
+ unsigned tagIndex)
+{
+ Node *childPtr;
+ Node *nodePtr = NULL;
+
+ for (childPtr = parentPtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ if (TkTextTagSetTest(childPtr->tagonPtr, tagIndex)) {
+ if (nodePtr) {
+ return NULL;
+ }
+ nodePtr = childPtr;
+ }
+ }
+
+ return nodePtr;
+}
+
+static void
+RebalanceRecomputeTagRootsAfterSplit(
+ Node *parentPtr,
+ TkSharedText *sharedTextPtr)
+{
+ const TkTextTagSet *tagInfoPtr = parentPtr->tagonPtr;
+ unsigned childLevel = parentPtr->level - 1;
+ unsigned i;
+
+ for (i = TkTextTagSetFindFirst(tagInfoPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
+ const Node *rootPtr;
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+
+ rootPtr = tagPtr->rootPtr;
+
+ if (rootPtr == parentPtr || rootPtr->level == childLevel) {
+ Node *nodePtr;
+
+ /*
+ * Either we have a sibling which has collected all occurrences, so move
+ * the root to this node, or more than one sibling contains this tag,
+ * so the parent is the root.
+ */
+
+ nodePtr = RebalanceFindSiblingForTag(parentPtr, i);
+ tagPtr->rootPtr = nodePtr ? nodePtr : parentPtr;
+ }
+ }
+}
+
+static bool
+RebalanceHasCollectedAll(
+ const Node *nodePtr,
+ const Node *excludePtr, /* don't test this node */
+ unsigned tagIndex)
+{
+ for ( ; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (nodePtr != excludePtr && TkTextTagSetTest(nodePtr->tagonPtr, tagIndex)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void
+RebalanceRecomputeTagRootsAfterMerge(
+ Node *resultPtr, /* The node as the result of the merge. */
+ const Node *mergePtr, /* The node which has been merged into resultPtr. */
+ TkSharedText *sharedTextPtr)
+{
+ unsigned i;
+
+ assert(resultPtr->parentPtr);
+
+ for (i = TkTextTagSetFindFirst(resultPtr->tagonPtr);
+ i != TK_TEXT_TAG_SET_NPOS;
+ i = TkTextTagSetFindNext(resultPtr->tagonPtr, i)) {
+ TkTextTag *tagPtr = sharedTextPtr->tagLookup[i];
+ const Node *tagRootPtr;
+
+ assert(tagPtr);
+ assert(!tagPtr->isDisabled);
+
+ tagRootPtr = tagPtr->rootPtr;
+
+ /*
+ * We have three cases:
+ *
+ * 1. mergePtr is the root of this tag; simply move the root to resultPtr.
+ *
+ * 2. The parent of these nodes is root of this tag, and resultPtr now has
+ * collected all occurrences of this tag; simply move the root one level
+ * down to resultPtr.
+ *
+ * 3. Otherwise, simply do nothing.
+ */
+
+ if (tagRootPtr == mergePtr) {
+ tagPtr->rootPtr = resultPtr;
+ } else if (tagRootPtr == resultPtr->parentPtr) {
+ if (RebalanceHasCollectedAll(resultPtr->parentPtr->childPtr, resultPtr, i)) {
+ tagPtr->rootPtr = resultPtr;
+ }
+ }
+ }
+}
+
+static Node *
+RebalanceDivideChildren(
+ Node *nodePtr,
+ Node *otherPtr, /* can be NULL */
+ unsigned minChildren, /* split after this number of children */
+ unsigned numRefs)
+{
+ Node *childPtr = nodePtr->childPtr;
+ Node *divideChildPtr = NULL;
+
+ assert(nodePtr->level > 0);
+ assert(minChildren > 0);
+
+ nodePtr->numLines = 0;
+ nodePtr->numLogicalLines = 0;
+ nodePtr->numBranches = 0;
+ nodePtr->size = 0;
+
+ for ( ; childPtr->nextPtr; childPtr = childPtr->nextPtr) {
+ if (!divideChildPtr) {
+ nodePtr->numLines += childPtr->numLines;
+ nodePtr->numLogicalLines += childPtr->numLogicalLines;
+ nodePtr->numBranches += childPtr->numBranches;
+ nodePtr->size += childPtr->size;
+ RebalanceAddNodePixels(nodePtr->pixelInfo, childPtr->pixelInfo, numRefs);
+ }
+ if (--minChildren == 0) {
+ if (!otherPtr) {
+ return childPtr;
+ }
+ divideChildPtr = childPtr;
+ }
+ }
+
+ assert(otherPtr);
+
+ childPtr->nextPtr = otherPtr->childPtr;
+
+ if (!divideChildPtr) {
+ assert(minChildren > 1);
+ nodePtr->numLines += childPtr->numLines;
+ nodePtr->numLogicalLines += childPtr->numLogicalLines;
+ nodePtr->size += childPtr->size;
+ RebalanceAddNodePixels(nodePtr->pixelInfo, childPtr->pixelInfo, numRefs);
+ for ( ; minChildren > 1; --minChildren) {
+ childPtr = childPtr->nextPtr;
+ nodePtr->numLines += childPtr->numLines;
+ nodePtr->numLogicalLines += childPtr->numLogicalLines;
+ nodePtr->numBranches += childPtr->numBranches;
+ nodePtr->size += childPtr->size;
+ RebalanceAddNodePixels(nodePtr->pixelInfo, childPtr->pixelInfo, numRefs);
+ }
+ assert(childPtr);
+ divideChildPtr = childPtr;
+ }
+
+ return divideChildPtr;
+}
+
+static TkTextLine *
+RebalanceDivideLines(
+ Node *nodePtr,
+ unsigned minLines,
+ unsigned numRefs)
+{
+ TkTextLine *divideLinePtr = nodePtr->linePtr;
+
+ assert(nodePtr->level == 0);
+ assert(minLines > 0);
+
+ RebalanceAddLinePixels(nodePtr->pixelInfo, divideLinePtr, numRefs);
+ nodePtr->size = divideLinePtr->size;
+ nodePtr->numLogicalLines = divideLinePtr->logicalLine;
+ nodePtr->numBranches = divideLinePtr->numBranches;
+
+ for ( ; minLines > 1; --minLines) {
+ divideLinePtr = divideLinePtr->nextPtr;
+ nodePtr->size += divideLinePtr->size;
+ nodePtr->numLogicalLines += divideLinePtr->logicalLine;
+ nodePtr->numBranches += divideLinePtr->numBranches;
+ RebalanceAddLinePixels(nodePtr->pixelInfo, divideLinePtr, numRefs);
+ }
+
+ return divideLinePtr;
+}
+
+static void
+RebalanceFinalizeNodeSplits(
+ Node **firstNodePtr,
+ Node *lastNodePtr, /* inclusive this node */
+ TkSharedText *sharedTextPtr)
+{
+ Node *nodePtr;
+
+ if (!*firstNodePtr) {
+ return;
+ }
+
+ lastNodePtr = lastNodePtr->nextPtr;
+
+ for (nodePtr = *firstNodePtr; nodePtr != lastNodePtr; nodePtr = nodePtr->nextPtr) {
+ TagSetAssign(&nodePtr->tagonPtr, sharedTextPtr->emptyTagInfoPtr);
+ TagSetAssign(&nodePtr->tagoffPtr, sharedTextPtr->emptyTagInfoPtr);
+ RebalanceAssignNewParentToChildren(nodePtr);
+ RebalanceRecomputeNodeTagInfo(nodePtr, sharedTextPtr);
+ }
+
+ RebalanceRecomputeTagRootsAfterSplit((*firstNodePtr)->parentPtr, sharedTextPtr);
+ *firstNodePtr = NULL;
+}
+
+static void
+RebalanceNodeJoinTagInfo(
+ Node *dstPtr,
+ Node *srcPtr,
+ const TkSharedText *sharedTextPtr)
+{
+ assert(dstPtr);
+ assert(srcPtr);
+ assert(sharedTextPtr);
+
+ if (srcPtr->tagonPtr == dstPtr->tagonPtr && srcPtr->tagoffPtr == dstPtr->tagoffPtr) {
+ return;
+ }
+
+ if (dstPtr->tagonPtr == sharedTextPtr->emptyTagInfoPtr) {
+ dstPtr->tagoffPtr = TkTextTagSetJoin2(dstPtr->tagoffPtr, srcPtr->tagoffPtr, srcPtr->tagonPtr);
+ } else if (srcPtr->tagonPtr == sharedTextPtr->emptyTagInfoPtr) {
+ dstPtr->tagoffPtr = TkTextTagSetJoin2(dstPtr->tagoffPtr, srcPtr->tagoffPtr, dstPtr->tagonPtr);
+ } else {
+#if !TK_TEXT_DONT_USE_BITFIELDS
+ unsigned size1 = TkTextTagSetSize(dstPtr->tagonPtr);
+ unsigned size2 = TkTextTagSetSize(srcPtr->tagonPtr);
+ unsigned minSize = MAX(TkTextTagSetSize(srcPtr->tagoffPtr), MAX(size1, size2));
+
+ if (TkTextTagSetSize(dstPtr->tagoffPtr) < minSize) {
+ dstPtr->tagoffPtr = TkTextTagSetResize(dstPtr->tagoffPtr, sharedTextPtr->tagInfoSize);
+ }
+ if (size1 < size2) {
+ dstPtr->tagonPtr = TkTextTagSetResize(dstPtr->tagonPtr, size2);
+ } else if (size2 < size1) {
+ srcPtr->tagonPtr = TkTextTagSetResize(srcPtr->tagonPtr, size1);
+ }
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+ dstPtr->tagoffPtr = TkTextTagSetJoin2ComplementToIntersection(
+ dstPtr->tagoffPtr, srcPtr->tagoffPtr, dstPtr->tagonPtr, srcPtr->tagonPtr);
+ }
+ if (TkTextTagSetIsEmpty(dstPtr->tagoffPtr)) {
+ TagSetAssign(&dstPtr->tagoffPtr, sharedTextPtr->emptyTagInfoPtr);
+ }
+ dstPtr->tagonPtr = TkTextTagSetJoin(dstPtr->tagonPtr, srcPtr->tagonPtr);
+}
+
+static void
Rebalance(
- BTree *treePtr, /* Tree that is being rebalanced. */
- register Node *nodePtr) /* Node that may be out of balance. */
+ BTree *treePtr, /* Tree that is being rebalanced. */
+ Node *nodePtr) /* Node that may be out of balance. */
{
+ unsigned numRefs = treePtr->numPixelReferences;
+ unsigned pixelSize = sizeof(nodePtr->pixelInfo[0])*numRefs;
+
/*
* Loop over the entire ancestral chain of the node, working up through
* the tree one node at a time until the root node has been processed.
*/
- for ( ; nodePtr != NULL; nodePtr = nodePtr->parentPtr) {
- register Node *newPtr, *childPtr;
- register TkTextLine *linePtr;
- int i;
+ for ( ; nodePtr; nodePtr = nodePtr->parentPtr) {
+ Node *firstNodePtr = NULL;
+ Node *lastNodePtr = NULL;
/*
- * Check to see if the node has too many children. If it does, then
- * split off all but the first MIN_CHILDREN into a separate node
- * following the original one. Then repeat until the node has a decent
- * size.
+ * Check to see if the node has too many children. If it does, then split off
+ * all but the first MIN_CHILDREN into a separate node following the original
+ * one. Then repeat until the node has a decent size.
*/
if (nodePtr->numChildren > MAX_CHILDREN) {
- while (1) {
+ firstNodePtr = nodePtr;
+
+ do {
+ Node *newPtr;
+
/*
- * If the node being split is the root node, then make a new
- * root node above it first.
+ * If the node being split is the root node, then make a new root node above it first.
*/
- if (nodePtr->parentPtr == NULL) {
- newPtr = ckalloc(sizeof(Node));
- newPtr->parentPtr = NULL;
- newPtr->nextPtr = NULL;
- newPtr->summaryPtr = NULL;
- newPtr->level = nodePtr->level + 1;
- newPtr->children.nodePtr = nodePtr;
- newPtr->numChildren = 1;
- newPtr->numLines = nodePtr->numLines;
- newPtr->numPixels =
- ckalloc(sizeof(int) * treePtr->pixelReferences);
- for (i=0; i<treePtr->pixelReferences; i++) {
- newPtr->numPixels[i] = nodePtr->numPixels[i];
- }
- RecomputeNodeCounts(treePtr, newPtr);
- treePtr->rootPtr = newPtr;
- }
- newPtr = ckalloc(sizeof(Node));
- newPtr->numPixels =
- ckalloc(sizeof(int) * treePtr->pixelReferences);
- for (i=0; i<treePtr->pixelReferences; i++) {
- newPtr->numPixels[i] = 0;
+ if (!nodePtr->parentPtr) {
+ Node *newRootPtr = malloc(sizeof(Node));
+ newRootPtr->parentPtr = NULL;
+ newRootPtr->nextPtr = NULL;
+ newRootPtr->childPtr = nodePtr;
+ newRootPtr->linePtr = nodePtr->linePtr;
+ newRootPtr->lastPtr = nodePtr->lastPtr;
+ TkTextTagSetIncrRefCount(newRootPtr->tagonPtr = nodePtr->tagonPtr);
+ TkTextTagSetIncrRefCount(newRootPtr->tagoffPtr = nodePtr->tagoffPtr);
+ newRootPtr->numChildren = 1;
+ newRootPtr->numLines = nodePtr->numLines;
+ newRootPtr->numLogicalLines = nodePtr->numLogicalLines;
+ newRootPtr->numBranches = nodePtr->numBranches;
+ newRootPtr->level = nodePtr->level + 1;
+ newRootPtr->size = nodePtr->size;
+ newRootPtr->pixelInfo = memcpy(malloc(pixelSize), nodePtr->pixelInfo, pixelSize);
+ nodePtr->parentPtr = newRootPtr;
+ treePtr->rootPtr = newRootPtr;
+ DEBUG_ALLOC(tkTextCountNewNode++);
+ DEBUG_ALLOC(tkTextCountNewPixelInfo++);
}
+
+ newPtr = malloc(sizeof(Node));
newPtr->parentPtr = nodePtr->parentPtr;
newPtr->nextPtr = nodePtr->nextPtr;
- nodePtr->nextPtr = newPtr;
- newPtr->summaryPtr = NULL;
- newPtr->level = nodePtr->level;
+ newPtr->lastPtr = nodePtr->lastPtr;
+ newPtr->tagonPtr = treePtr->sharedTextPtr->emptyTagInfoPtr;
+ newPtr->tagoffPtr = treePtr->sharedTextPtr->emptyTagInfoPtr;
+ TkTextTagSetIncrRefCount(newPtr->tagonPtr);
+ TkTextTagSetIncrRefCount(newPtr->tagoffPtr);
newPtr->numChildren = nodePtr->numChildren - MIN_CHILDREN;
+ newPtr->level = nodePtr->level;
+ newPtr->size = nodePtr->size;
+ newPtr->pixelInfo = nodePtr->pixelInfo;
+ newPtr->numLines = nodePtr->numLines;
+ newPtr->numLogicalLines = nodePtr->numLogicalLines;
+ newPtr->numBranches = nodePtr->numBranches;
+ nodePtr->nextPtr = newPtr;
+ nodePtr->numChildren = MIN_CHILDREN;
+ nodePtr->pixelInfo = memset(malloc(pixelSize), 0, pixelSize);
+ TagSetAssign(&nodePtr->tagonPtr, treePtr->sharedTextPtr->emptyTagInfoPtr);
+ TagSetAssign(&nodePtr->tagoffPtr, treePtr->sharedTextPtr->emptyTagInfoPtr);
+ DEBUG_ALLOC(tkTextCountNewNode++);
+ DEBUG_ALLOC(tkTextCountNewPixelInfo++);
if (nodePtr->level == 0) {
- for (i = MIN_CHILDREN-1,
- linePtr = nodePtr->children.linePtr;
- i > 0; i--, linePtr = linePtr->nextPtr) {
- /* Empty loop body. */
- }
- newPtr->children.linePtr = linePtr->nextPtr;
- linePtr->nextPtr = NULL;
+ TkTextLine *linePtr = RebalanceDivideLines(nodePtr, MIN_CHILDREN, numRefs);
+ assert(linePtr->nextPtr);
+ newPtr->childPtr = NULL;
+ newPtr->linePtr = linePtr->nextPtr;
+ newPtr->numLines = newPtr->numChildren;
+ nodePtr->lastPtr = linePtr;
+ nodePtr->numLines = MIN_CHILDREN;
} else {
- for (i = MIN_CHILDREN-1,
- childPtr = nodePtr->children.nodePtr;
- i > 0; i--, childPtr = childPtr->nextPtr) {
- /* Empty loop body. */
- }
- newPtr->children.nodePtr = childPtr->nextPtr;
+ Node *childPtr = RebalanceDivideChildren(nodePtr, NULL, MIN_CHILDREN, numRefs);
+ newPtr->childPtr = childPtr->nextPtr;
+ newPtr->linePtr = childPtr->nextPtr->linePtr;
+ newPtr->numLines -= nodePtr->numLines;
+ nodePtr->lastPtr = childPtr->lastPtr;
childPtr->nextPtr = NULL;
}
- RecomputeNodeCounts(treePtr, nodePtr);
- nodePtr->parentPtr->numChildren++;
- nodePtr = newPtr;
- if (nodePtr->numChildren <= MAX_CHILDREN) {
- RecomputeNodeCounts(treePtr, nodePtr);
- break;
- }
- }
+ RebalanceSubtractNodePixels(newPtr->pixelInfo, nodePtr->pixelInfo, numRefs);
+ newPtr->size -= nodePtr->size;
+ newPtr->numLogicalLines -= nodePtr->numLogicalLines;
+ newPtr->numBranches -= nodePtr->numBranches;
+ nodePtr->parentPtr->numChildren += 1;
+ lastNodePtr = nodePtr = newPtr;
+ } while (nodePtr->numChildren > MAX_CHILDREN);
}
while (nodePtr->numChildren < MIN_CHILDREN) {
- register Node *otherPtr;
- Node *halfwayNodePtr = NULL; /* Initialization needed only */
- TkTextLine *halfwayLinePtr = NULL; /* to prevent cc warnings. */
- int totalChildren, firstChildren, i;
+ Node *otherPtr;
+ unsigned totalChildren;
/*
- * Too few children for this node. If this is the root then, it's
- * OK for it to have less than MIN_CHILDREN children as long as
- * it's got at least two. If it has only one (and isn't at level
- * 0), then chop the root node out of the tree and use its child
- * as the new root.
+ * Too few children for this node. If this is the root then, it's OK
+ * for it to have less than MIN_CHILDREN children as long as it's got
+ * at least two. If it has only one (and isn't at level 0), then chop
+ * the root node out of the tree and use its child as the new root.
*/
- if (nodePtr->parentPtr == NULL) {
- if ((nodePtr->numChildren == 1) && (nodePtr->level > 0)) {
- treePtr->rootPtr = nodePtr->children.nodePtr;
+ if (!nodePtr->parentPtr) {
+ if (nodePtr->numChildren == 1 && nodePtr->level > 0) {
+ treePtr->rootPtr = nodePtr->childPtr;
treePtr->rootPtr->parentPtr = NULL;
- DeleteSummaries(nodePtr->summaryPtr);
- ckfree(nodePtr);
+ FreeNode(nodePtr);
}
return;
}
/*
- * Not the root. Make sure that there are siblings to balance
- * with.
+ * Not the root. Make sure that there are siblings to balance with.
*/
if (nodePtr->parentPtr->numChildren < 2) {
+ /* Do the finalization of previous splits. */
+ RebalanceFinalizeNodeSplits(&firstNodePtr, lastNodePtr, treePtr->sharedTextPtr);
Rebalance(treePtr, nodePtr->parentPtr);
continue;
}
@@ -4205,8 +13459,8 @@ Rebalance(
* to be the earlier of the pair.
*/
- if (nodePtr->nextPtr == NULL) {
- for (otherPtr = nodePtr->parentPtr->children.nodePtr;
+ if (!nodePtr->nextPtr) {
+ for (otherPtr = nodePtr->parentPtr->childPtr;
otherPtr->nextPtr != nodePtr;
otherPtr = otherPtr->nextPtr) {
/* Empty loop body. */
@@ -4217,260 +13471,1197 @@ Rebalance(
/*
* We're going to either merge the two siblings together into one
- * node or redivide the children among them to balance their
- * loads. As preparation, join their two child lists into a single
- * list and remember the half-way point in the list.
+ * node or redivide the children among them to balance their loads.
*/
totalChildren = nodePtr->numChildren + otherPtr->numChildren;
- firstChildren = totalChildren/2;
- if (nodePtr->children.nodePtr == NULL) {
- nodePtr->children = otherPtr->children;
- otherPtr->children.nodePtr = NULL;
- otherPtr->children.linePtr = NULL;
- }
- if (nodePtr->level == 0) {
- register TkTextLine *linePtr;
-
- for (linePtr = nodePtr->children.linePtr, i = 1;
- linePtr->nextPtr != NULL;
- linePtr = linePtr->nextPtr, i++) {
- if (i == firstChildren) {
- halfwayLinePtr = linePtr;
+
+ /*
+ * The successor node will contain the sum of both pixel counts.
+ */
+
+ RebalanceAddNodePixels(otherPtr->pixelInfo, nodePtr->pixelInfo, numRefs);
+
+ if (!nodePtr->childPtr) {
+ nodePtr->childPtr = otherPtr->childPtr;
+ otherPtr->childPtr = NULL;
+ }
+
+ if (totalChildren <= MAX_CHILDREN) {
+ NodePixelInfo *pixelInfo;
+ Node *childPtr;
+
+ /*
+ * Do the finalization of previous splits.
+ */
+
+ RebalanceFinalizeNodeSplits(&firstNodePtr, lastNodePtr, treePtr->sharedTextPtr);
+
+ /*
+ * Simply merge the two siblings. At first join their two child
+ * lists into a single list.
+ */
+
+ if (nodePtr->level > 0) {
+ for (childPtr = nodePtr->childPtr; childPtr->nextPtr; childPtr = childPtr->nextPtr) {
+ /* empty loop body */
}
+ childPtr->nextPtr = otherPtr->childPtr;
}
- linePtr->nextPtr = otherPtr->children.linePtr;
- while (i <= firstChildren) {
- halfwayLinePtr = linePtr;
- linePtr = linePtr->nextPtr;
- i++;
- }
+
+ nodePtr->lastPtr = otherPtr->lastPtr;
+ nodePtr->nextPtr = otherPtr->nextPtr;
+ nodePtr->numChildren = totalChildren;
+ nodePtr->numLines += otherPtr->numLines;
+ nodePtr->numLogicalLines += otherPtr->numLogicalLines;
+ nodePtr->numBranches += otherPtr->numBranches;
+ nodePtr->parentPtr->numChildren -= 1;
+ nodePtr->size += otherPtr->size;
+ /* swap pixel count */
+ pixelInfo = nodePtr->pixelInfo;
+ nodePtr->pixelInfo = otherPtr->pixelInfo;
+ otherPtr->pixelInfo = pixelInfo;
+
+ RebalanceAssignNewParentToChildren(nodePtr);
+ RebalanceNodeJoinTagInfo(nodePtr, otherPtr, treePtr->sharedTextPtr);
+ RebalanceRecomputeTagRootsAfterMerge(nodePtr, otherPtr, treePtr->sharedTextPtr);
+ FreeNode(otherPtr);
} else {
- register Node *childPtr;
-
- for (childPtr = nodePtr->children.nodePtr, i = 1;
- childPtr->nextPtr != NULL;
- childPtr = childPtr->nextPtr, i++) {
- if (i <= firstChildren) {
- if (i == firstChildren) {
- halfwayNodePtr = childPtr;
- }
- }
+ /*
+ * The siblings can't be merged, so just divide their children evenly between them.
+ */
+
+ unsigned firstChildren = totalChildren/2;
+
+ /*
+ * Remember this node for finalization.
+ */
+
+ if (!firstNodePtr) {
+ firstNodePtr = nodePtr;
}
- childPtr->nextPtr = otherPtr->children.nodePtr;
- while (i <= firstChildren) {
- halfwayNodePtr = childPtr;
- childPtr = childPtr->nextPtr;
- i++;
+ lastNodePtr = otherPtr;
+
+ otherPtr->size += nodePtr->size;
+ otherPtr->numLogicalLines += nodePtr->numLogicalLines;
+ otherPtr->numBranches += nodePtr->numBranches;
+
+ /* Prepare pixel count in nodePtr, DivideLines/DivideChildren will do the count. */
+ memset(nodePtr->pixelInfo, 0, pixelSize);
+
+ nodePtr->numChildren = firstChildren;
+ otherPtr->numChildren = totalChildren - firstChildren;
+
+ if (nodePtr->level == 0) {
+ TkTextLine *halfwayLinePtr = RebalanceDivideLines(nodePtr, firstChildren, numRefs);
+
+ nodePtr->numLines = nodePtr->numChildren;
+ nodePtr->lastPtr = halfwayLinePtr;
+ otherPtr->linePtr = halfwayLinePtr->nextPtr;
+ otherPtr->numLines = otherPtr->numChildren;
+ } else {
+ unsigned totalLines = nodePtr->numLines + otherPtr->numLines;
+ Node *halfwayNodePtr;
+
+ halfwayNodePtr = RebalanceDivideChildren(nodePtr, otherPtr, firstChildren, numRefs);
+ nodePtr->lastPtr = halfwayNodePtr->lastPtr;
+ otherPtr->numLines = totalLines - nodePtr->numLines;
+ otherPtr->linePtr = halfwayNodePtr->nextPtr->linePtr;
+ otherPtr->childPtr = halfwayNodePtr->nextPtr;
+ halfwayNodePtr->nextPtr = NULL;
}
+
+ otherPtr->size -= nodePtr->size;
+ otherPtr->numLogicalLines -= nodePtr->numLogicalLines;
+ otherPtr->numBranches -= nodePtr->numBranches;
+ RebalanceSubtractNodePixels(otherPtr->pixelInfo, nodePtr->pixelInfo, numRefs);
}
+ }
- /*
- * If the two siblings can simply be merged together, do it.
- */
+ /*
+ * Do the finalization of previous splits.
+ */
- if (totalChildren <= MAX_CHILDREN) {
- RecomputeNodeCounts(treePtr, nodePtr);
- nodePtr->nextPtr = otherPtr->nextPtr;
- nodePtr->parentPtr->numChildren--;
- DeleteSummaries(otherPtr->summaryPtr);
- ckfree(otherPtr);
- continue;
+ RebalanceFinalizeNodeSplits(&firstNodePtr, lastNodePtr, treePtr->sharedTextPtr);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeGetLogicalLine --
+ *
+ * Given a line, this function is searching in B-Tree for the first
+ * line which belongs logically to given line due to elided newlines.
+ *
+ * Results:
+ * The return value is the first logical line belonging to given
+ * line, in most cases this will be the given line itself.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static const Node *
+PrevLogicalNode(
+ const Node *nodePtr)
+{
+ assert(nodePtr);
+
+ while (nodePtr->parentPtr) {
+ const Node *startNodePtr = nodePtr;
+ const Node *lastNodePtr = NULL;
+
+ nodePtr = nodePtr->parentPtr->childPtr;
+
+ for ( ; nodePtr != startNodePtr; nodePtr = nodePtr->nextPtr) {
+ if (nodePtr->numLogicalLines > 0) {
+ lastNodePtr = nodePtr;
}
+ }
+ if (lastNodePtr) {
+ nodePtr = lastNodePtr;
- /*
- * The siblings can't be merged, so just divide their children
- * evenly between them.
- */
+ while (nodePtr->level > 0) {
+ DEBUG(lastNodePtr = NULL);
- if (nodePtr->level == 0) {
- CLANG_ASSERT(halfwayLinePtr);
- otherPtr->children.linePtr = halfwayLinePtr->nextPtr;
- halfwayLinePtr->nextPtr = NULL;
- } else {
- CLANG_ASSERT(halfwayNodePtr);
- otherPtr->children.nodePtr = halfwayNodePtr->nextPtr;
- halfwayNodePtr->nextPtr = NULL;
+ for (nodePtr = nodePtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (nodePtr->numLogicalLines > 0) {
+ lastNodePtr = nodePtr;
+ }
+ }
+
+ assert(lastNodePtr);
+ nodePtr = lastNodePtr;
}
- RecomputeNodeCounts(treePtr, nodePtr);
- RecomputeNodeCounts(treePtr, otherPtr);
+
+ return lastNodePtr;
}
+
+ nodePtr = startNodePtr->parentPtr;
}
+
+ return NULL;
+}
+
+TkTextLine *
+TkBTreeGetLogicalLine(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ TkTextLine *linePtr)
+{
+ const Node *nodePtr;
+ TkTextLine *startLinePtr;
+
+ assert(linePtr);
+
+ if (linePtr->logicalLine || linePtr == GetStartLine(sharedTextPtr, textPtr)) {
+ return linePtr;
+ }
+
+ nodePtr = linePtr->parentPtr;
+ startLinePtr = GetStartLine(sharedTextPtr, textPtr);
+
+ /*
+ * At first, search for logical line in current node.
+ */
+
+ while (linePtr->parentPtr == nodePtr) {
+ if (linePtr->logicalLine || linePtr == startLinePtr) {
+ return linePtr;
+ }
+ linePtr = linePtr->prevPtr;
+ }
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which contains the logical line.
+ */
+
+ if (!(nodePtr = PrevLogicalNode(nodePtr))) {
+ return startLinePtr;
+ }
+
+ if (textPtr && textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
+ int lineNo1 = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, nodePtr->lastPtr, NULL);
+ int lineNo2 = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, startLinePtr, NULL);
+
+ if (lineNo1 <= lineNo2) {
+ /*
+ * We've found a node before text start, so return text start.
+ */
+ return startLinePtr;
+ }
+ }
+
+ /*
+ * Final search of logical line.
+ */
+
+ linePtr = nodePtr->lastPtr;
+ while (!linePtr->logicalLine && linePtr != startLinePtr) {
+ linePtr = linePtr->prevPtr;
+ }
+ return linePtr;
}
/*
*----------------------------------------------------------------------
*
- * RecomputeNodeCounts --
+ * TkBTreeNextLogicalLine --
*
- * This function is called to recompute all the counts in a node (tags,
- * child information, etc.) by scanning the information in its
- * descendants. This function is called during rebalancing when a node's
- * child structure has changed.
+ * Given a line, this function is searching in the B-Tree for the
+ * next logical line, which don't has a predecessing line with
+ * elided newline. If the search reaches the end of the text, then
+ * the last line will be returned, even if it's not a logical line
+ * (the latter can only happen in peers with restricted ranges).
*
* Results:
- * None.
+ * The return value is the next logical line, in most cases this
+ * will be simply the next line.
*
* Side effects:
- * The tag counts for nodePtr are modified to reflect its current child
- * structure, as are its numChildren and numLines fields. Also, all of
- * the childrens' parentPtr fields are made to point to nodePtr.
+ * None.
*
*----------------------------------------------------------------------
*/
-static void
-RecomputeNodeCounts(
- register BTree *treePtr, /* The whole B-tree. */
- register Node *nodePtr) /* Node whose tag summary information must be
- * recomputed. */
-{
- register Summary *summaryPtr, *summaryPtr2;
- register Node *childPtr;
- register TkTextLine *linePtr;
- register TkTextSegment *segPtr;
- TkTextTag *tagPtr;
- int ref;
+static const Node *
+NextLogicalNode(
+ const Node *nodePtr)
+{
+ while (nodePtr) {
+ const Node *startNodePtr = nodePtr;
+
+ for (nodePtr = nodePtr->nextPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (nodePtr->numLogicalLines > 0) {
+ while (nodePtr->level > 0) {
+ for (nodePtr = nodePtr->childPtr; nodePtr; nodePtr = nodePtr->nextPtr) {
+ if (nodePtr->numLogicalLines > 0) {
+ return nodePtr;
+ }
+ }
+ }
+ return nodePtr;
+ }
+ }
+
+ nodePtr = startNodePtr->parentPtr;
+ }
+
+ return NULL;
+}
+
+TkTextLine *
+TkBTreeNextLogicalLine(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ TkTextLine *linePtr)
+{
+ const Node *nodePtr;
+ TkTextLine *endLinePtr;
+
+ assert(linePtr);
+ assert(linePtr->nextPtr);
+ assert(linePtr != GetLastLine(sharedTextPtr, textPtr));
+
+ if (linePtr->nextPtr->logicalLine) {
+ return linePtr->nextPtr;
+ }
/*
- * Zero out all the existing counts for the node, but don't delete the
- * existing Summary records (most of them will probably be reused).
+ * At first, search for logical line in current node.
*/
- for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
- summaryPtr = summaryPtr->nextPtr) {
- summaryPtr->toggleCount = 0;
+ nodePtr = linePtr->parentPtr;
+ linePtr = linePtr->nextPtr;
+ endLinePtr = GetLastLine(sharedTextPtr, textPtr);
+
+ while (linePtr && linePtr->parentPtr == nodePtr) {
+ if (linePtr->logicalLine || linePtr == endLinePtr) {
+ return linePtr;
+ }
+ linePtr = linePtr->nextPtr;
}
- nodePtr->numChildren = 0;
- nodePtr->numLines = 0;
- for (ref = 0; ref<treePtr->pixelReferences; ref++) {
- nodePtr->numPixels[ref] = 0;
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which contains the logical line.
+ */
+
+ if (!(nodePtr = NextLogicalNode(nodePtr))) {
+ return endLinePtr;
+ }
+
+ if (textPtr && textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
+ int lineNo1 = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, nodePtr->linePtr, NULL);
+ int lineNo2 = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, endLinePtr, NULL);
+
+ if (lineNo1 >= lineNo2) {
+ /*
+ * We've found a node after text end, so return text end.
+ */
+ return endLinePtr;
+ }
}
/*
- * Scan through the children, adding the childrens' tag counts into the
- * node's tag counts and adding new Summary structures if necessary.
+ * Final search of logical line.
*/
- if (nodePtr->level == 0) {
- for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
- linePtr = linePtr->nextPtr) {
- nodePtr->numChildren++;
- nodePtr->numLines++;
- for (ref = 0; ref<treePtr->pixelReferences; ref++) {
- nodePtr->numPixels[ref] += linePtr->pixels[2 * ref];
+ linePtr = nodePtr->linePtr;
+ while (!linePtr->logicalLine && linePtr != endLinePtr) {
+ linePtr = linePtr->nextPtr;
+ }
+ return linePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeNextDisplayLine --
+ *
+ * Given a logical line, and a display line number belonging to
+ * this logical line, find next display line 'offset' display lines
+ * ahead.
+ *
+ * Results:
+ * Returns the logcial line of the requested display line, and stores
+ * the display line number in 'dispLineNo'.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextLine *
+GetLastDisplayLine(
+ TkText *textPtr,
+ int *displayLineNo)
+{
+ TkTextLine *linePtr;
+
+ linePtr = textPtr->endMarker->sectionPtr->linePtr;
+ linePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ *displayLineNo = GetDisplayLines(linePtr, textPtr->pixelReference);
+ return linePtr;
+}
+
+TkTextLine *
+TkBTreeNextDisplayLine(
+ TkText *textPtr, /* Information about text widget. */
+ TkTextLine *linePtr, /* Start at this logical line. */
+ int *displayLineNo, /* IN: Start at this display line number in given logical line.
+ * OUT: Store display line number of requested display line. */
+ unsigned offset) /* Offset to requested display line. */
+{
+ const Node *nodePtr;
+ const Node *parentPtr;
+ int lineNo, numLines;
+ unsigned numDispLines;
+ unsigned ref;
+
+ assert(textPtr);
+ assert(linePtr->logicalLine || linePtr == TkBTreeGetStartLine(textPtr));
+ assert(*displayLineNo >= 0);
+ assert(*displayLineNo < GetDisplayLines(linePtr, textPtr->pixelReference));
+
+ if (offset == 0) {
+ return linePtr;
+ }
+
+ ref = textPtr->pixelReference;
+ nodePtr = linePtr->parentPtr;
+ parentPtr = nodePtr->parentPtr;
+ offset += *displayLineNo;
+
+ if (linePtr != nodePtr->linePtr || !parentPtr || HasLeftNode(nodePtr)) {
+ TkTextLine *lastPtr;
+
+ /*
+ * At first, search for display line in current node.
+ */
+
+ lastPtr = nodePtr->lastPtr->nextPtr;
+
+ while (linePtr != lastPtr) {
+ numDispLines = GetDisplayLines(linePtr, ref);
+ if (numDispLines > offset) {
+ assert(linePtr->logicalLine);
+ *displayLineNo = offset;
+ return linePtr;
}
- linePtr->parentPtr = nodePtr;
- for (segPtr = linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- if (((segPtr->typePtr != &tkTextToggleOnType)
- && (segPtr->typePtr != &tkTextToggleOffType))
- || !(segPtr->body.toggle.inNodeCounts)) {
- continue;
- }
- tagPtr = segPtr->body.toggle.tagPtr;
- for (summaryPtr = nodePtr->summaryPtr; ;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr == NULL) {
- summaryPtr = ckalloc(sizeof(Summary));
- summaryPtr->tagPtr = tagPtr;
- summaryPtr->toggleCount = 1;
- summaryPtr->nextPtr = nodePtr->summaryPtr;
- nodePtr->summaryPtr = summaryPtr;
- break;
+ offset -= numDispLines;
+ if (!(linePtr = TkBTreeNextLine(textPtr, linePtr))) {
+ return GetLastDisplayLine(textPtr, displayLineNo);
+ }
+ }
+
+ nodePtr = nodePtr->nextPtr;
+ }
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which contains the display line.
+ */
+
+ lineNo = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, linePtr, NULL);
+ numLines = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, TkBTreeGetLastLine(textPtr), NULL);
+
+ while (parentPtr) {
+ if (!nodePtr || (!HasLeftNode(nodePtr) && offset >= parentPtr->pixelInfo[ref].numDispLines)) {
+ offset -= parentPtr->pixelInfo[ref].numDispLines;
+ nodePtr = parentPtr->nextPtr;
+ parentPtr = parentPtr->parentPtr;
+ } else {
+ while (nodePtr) {
+ numDispLines = nodePtr->pixelInfo[ref].numDispLines;
+ if (offset < numDispLines) {
+ if (nodePtr->level > 0) {
+ nodePtr = nodePtr->childPtr;
+ continue;
}
- if (summaryPtr->tagPtr == tagPtr) {
- summaryPtr->toggleCount++;
- break;
+ /*
+ * We've found the right node, now search for the line.
+ */
+ linePtr = nodePtr->linePtr;
+ while (true) {
+ numDispLines = GetDisplayLines(linePtr, ref);
+ if (offset < numDispLines) {
+ *displayLineNo = offset;
+ assert(linePtr->logicalLine);
+ return linePtr;
+ }
+ offset -= numDispLines;
+ if (!(linePtr = TkBTreeNextLine(textPtr, linePtr))) {
+ return GetLastDisplayLine(textPtr, displayLineNo);
+ }
}
}
+ if ((lineNo += nodePtr->numLines) >= numLines) {
+ parentPtr = NULL;
+ break;
+ }
+ offset -= numDispLines;
+ nodePtr = nodePtr->nextPtr;
+ }
+ }
+ }
+
+ return GetLastDisplayLine(textPtr, displayLineNo);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreePrevDisplayLine --
+ *
+ * Given a logical line, and a display line number belonging to
+ * this logical line, find previous display line 'offset' display lines
+ * back.
+ *
+ * Results:
+ * Returns the logcial line of the requested display line, and stores
+ * the display line number in 'dispLineNo'.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextLine *
+GetFirstDisplayLine(
+ TkText *textPtr,
+ int *displayLineNo)
+{
+ *displayLineNo = 0;
+ return textPtr->startMarker->sectionPtr->linePtr;
+}
+
+TkTextLine *
+TkBTreePrevDisplayLine(
+ TkText *textPtr, /* Information about text widget. */
+ TkTextLine *linePtr, /* Start at this logical line. */
+ int *displayLineNo, /* IN: Start at this display line number in given logical line.
+ * OUT: Store display line number of requested display line. */
+ unsigned offset) /* Offset to requested display line. */
+{
+ const Node *nodeStack[MAX_CHILDREN];
+ const Node *nodePtr;
+ const Node *parentPtr;
+ const Node *nPtr;
+ unsigned numDispLines;
+ unsigned ref;
+ unsigned idx;
+ int lineNo;
+
+ assert(textPtr);
+ assert(linePtr->logicalLine || linePtr == TkBTreeGetStartLine(textPtr));
+ assert(*displayLineNo >= 0);
+ assert(*displayLineNo < GetDisplayLines(linePtr, textPtr->pixelReference));
+
+ if (offset == 0) {
+ return linePtr;
+ }
+
+ ref = textPtr->pixelReference;
+ nodePtr = linePtr->parentPtr;
+ parentPtr = nodePtr->parentPtr;
+ numDispLines = GetDisplayLines(linePtr, ref);
+ offset += numDispLines - *displayLineNo - 1;
+
+ if (linePtr != nodePtr->lastPtr || !parentPtr || nodePtr->nextPtr) {
+ TkTextLine *lastPtr;
+
+ /*
+ * At first, search for display line in current node.
+ */
+
+ lastPtr = nodePtr->linePtr->prevPtr;
+
+ while (linePtr != lastPtr) {
+ numDispLines = GetDisplayLines(linePtr, ref);
+ if (offset < numDispLines) {
+ assert(linePtr->logicalLine);
+ *displayLineNo = numDispLines - offset - 1;
+ return linePtr;
+ }
+ offset -= numDispLines;
+ if (!(linePtr = TkBTreePrevLine(textPtr, linePtr))) {
+ return GetFirstDisplayLine(textPtr, displayLineNo);
}
}
} else {
- for (childPtr = nodePtr->children.nodePtr; childPtr != NULL;
- childPtr = childPtr->nextPtr) {
- nodePtr->numChildren++;
- nodePtr->numLines += childPtr->numLines;
- for (ref = 0; ref<treePtr->pixelReferences; ref++) {
- nodePtr->numPixels[ref] += childPtr->numPixels[ref];
+ nodePtr = nodePtr->nextPtr;
+ }
+
+ for (nPtr = parentPtr->childPtr, idx = 0; nPtr != nodePtr; nPtr = nPtr->nextPtr) {
+ nodeStack[idx++] = nPtr;
+ }
+ nodePtr = idx ? nodeStack[--idx] : NULL;
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which contains the display line.
+ */
+
+ lineNo = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, linePtr, NULL);
+
+ while (parentPtr) {
+ if (!nodePtr || (!nodePtr->nextPtr && offset >= parentPtr->pixelInfo[ref].numDispLines)) {
+ nodePtr = parentPtr;
+ if ((parentPtr = parentPtr->parentPtr)) {
+ for (nPtr = parentPtr->childPtr, idx = 0; nPtr != nodePtr; nPtr = nPtr->nextPtr) {
+ nodeStack[idx++] = nPtr;
+ }
+ nodePtr = idx ? nodeStack[--idx] : NULL;
}
- childPtr->parentPtr = nodePtr;
- for (summaryPtr2 = childPtr->summaryPtr; summaryPtr2 != NULL;
- summaryPtr2 = summaryPtr2->nextPtr) {
- for (summaryPtr = nodePtr->summaryPtr; ;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr == NULL) {
- summaryPtr = ckalloc(sizeof(Summary));
- summaryPtr->tagPtr = summaryPtr2->tagPtr;
- summaryPtr->toggleCount = summaryPtr2->toggleCount;
- summaryPtr->nextPtr = nodePtr->summaryPtr;
- nodePtr->summaryPtr = summaryPtr;
- break;
+ } else {
+ while (nodePtr) {
+ numDispLines = nodePtr->pixelInfo[ref].numDispLines;
+ if (offset < numDispLines) {
+ if (nodePtr->level > 0) {
+ parentPtr = nodePtr;
+ idx = 0;
+ for (nPtr = nodePtr->childPtr; nPtr; nPtr = nPtr->nextPtr) {
+ nodeStack[idx++] = nPtr;
+ }
+ nodePtr = idx ? nodeStack[--idx] : NULL;
+ continue;
}
- if (summaryPtr->tagPtr == summaryPtr2->tagPtr) {
- summaryPtr->toggleCount += summaryPtr2->toggleCount;
- break;
+ /*
+ * We've found the right node, now search for the line.
+ */
+ linePtr = nodePtr->lastPtr;
+ while (true) {
+ numDispLines = GetDisplayLines(linePtr, ref);
+ if (offset < numDispLines) {
+ assert(linePtr->logicalLine);
+ *displayLineNo = numDispLines - offset - 1;
+ return linePtr;
+ }
+ offset -= numDispLines;
+ if (!(linePtr = TkBTreePrevLine(textPtr, linePtr))) {
+ return GetFirstDisplayLine(textPtr, displayLineNo);
+ }
}
}
+ if ((lineNo -= nodePtr->numLines) < 0) {
+ parentPtr = NULL;
+ break;
+ }
+ offset -= numDispLines;
+ nodePtr = idx ? nodeStack[--idx] : NULL;
}
}
}
+ return GetFirstDisplayLine(textPtr, displayLineNo);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeFindStartOfElidedRange --
+ *
+ * Given an elided segment, this function is searching for the
+ * first segment which is spanning the range containing the
+ * given segment. Normally this is a branch segment, but in
+ * case of restricted peers it may be a start marker.
+ *
+ * Results:
+ * The return value is a corresponding branch segment (or the
+ * start marker of this peer).
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextSegment *
+SearchBranchInLine(
+ TkTextSegment *segPtr,
+ TkTextSegment *startMarker)
+{
+ TkTextSection *sectionPtr = segPtr->sectionPtr;
+ TkTextSection *startSectionPtr;
+
/*
- * Scan through the node's tag records again and delete any Summary
- * records that still have a zero count, or that have all the toggles.
- * The node with the children that account for all the tags toggles have
- * no summary information, and they become the tagRootPtr for the tag.
+ * Note that a branch is always at the end of a section.
*/
- summaryPtr2 = NULL;
- for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; ) {
- if (summaryPtr->toggleCount > 0 &&
- summaryPtr->toggleCount < summaryPtr->tagPtr->toggleCount) {
- if (nodePtr->level == summaryPtr->tagPtr->tagRootPtr->level) {
- /*
- * The tag's root node split and some toggles left. The tag
- * root must move up a level.
- */
+ while (segPtr->nextPtr && segPtr->size == 0 && segPtr->nextPtr->sectionPtr == sectionPtr) {
+ segPtr = segPtr->nextPtr;
+ }
+
+ if (segPtr->typePtr == &tkTextBranchType) {
+ return segPtr;
+ }
+
+ startSectionPtr = startMarker ? startMarker->sectionPtr : NULL;
+
+ if (sectionPtr == startSectionPtr) {
+ return startMarker;
+ }
+
+ for ( ; sectionPtr->prevPtr; sectionPtr = sectionPtr->prevPtr) {
+ if (sectionPtr->segPtr->prevPtr->typePtr == &tkTextBranchType) {
+ return sectionPtr->segPtr->prevPtr;
+ }
+ if (sectionPtr == startSectionPtr) {
+ return startMarker;
+ }
+ }
+
+ return NULL;
+}
- summaryPtr->tagPtr->tagRootPtr = nodePtr->parentPtr;
+static const Node *
+FindNodeWithBranch(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ const Node *nodePtr)
+{
+ const Node *parentPtr;
+
+ assert(nodePtr);
+
+ for (parentPtr = nodePtr->parentPtr; parentPtr; parentPtr = parentPtr->parentPtr) {
+ const Node *resultPtr = NULL;
+ const Node *childPtr;
+
+ if (parentPtr->numBranches > 0) {
+ for (childPtr = parentPtr->childPtr; childPtr != nodePtr; childPtr = childPtr->nextPtr) {
+ if (childPtr->numBranches > 0) {
+ resultPtr = childPtr;
+ }
}
- summaryPtr2 = summaryPtr;
- summaryPtr = summaryPtr->nextPtr;
- continue;
+ if (resultPtr) {
+ while (resultPtr->level > 0) {
+ for (childPtr = resultPtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ if (childPtr->numBranches > 0) {
+ resultPtr = childPtr;
+ }
+ }
+ }
+ return resultPtr;
+ }
+ }
+ nodePtr = parentPtr;
+ }
+
+ return TkBTreeGetRoot(sharedTextPtr->tree)->linePtr->parentPtr;
+}
+
+static TkTextSegment *
+FindBranchSegment(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ const TkTextSegment *segPtr,
+ TkTextSegment *startMarker)
+{
+ const Node *nodePtr;
+ TkTextLine *firstLinePtr;
+ TkTextLine *linePtr;
+
+ assert(segPtr);
+ assert(segPtr->tagInfoPtr);
+ assert(TkBTreeHaveElidedSegments(sharedTextPtr));
+ assert(SegmentIsElided(sharedTextPtr, segPtr, textPtr));
+
+ linePtr = segPtr->sectionPtr->linePtr;
+ nodePtr = linePtr->parentPtr;
+ firstLinePtr = startMarker ? GetStartLine(sharedTextPtr, textPtr) : NULL;
+
+ /*
+ * At first, search for branch in current line.
+ */
+
+ if (linePtr->numBranches > 0) {
+ TkTextSegment *branchPtr = SearchBranchInLine((TkTextSegment *) segPtr, startMarker);
+
+ if (branchPtr) {
+ return branchPtr;
}
- if (summaryPtr->toggleCount == summaryPtr->tagPtr->toggleCount) {
+ }
+
+ /*
+ * At second, search for line with a branch in current node.
+ */
+
+ linePtr = linePtr->prevPtr;
+ while (linePtr && linePtr->parentPtr == nodePtr) {
+ TkTextLine *prevPtr = linePtr->prevPtr;
+
+ if (linePtr->numBranches > 0) {
+ return SearchBranchInLine(linePtr->lastPtr, startMarker);
+ }
+ if (prevPtr == firstLinePtr) {
+ return startMarker;
+ }
+ linePtr = prevPtr;
+ }
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which contains a branch.
+ */
+
+ nodePtr = FindNodeWithBranch(sharedTextPtr, textPtr, nodePtr);
+
+ if (startMarker && startMarker != sharedTextPtr->startMarker) {
+ int lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, nodePtr->lastPtr, NULL);
+ int lineNo2 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, startMarker->sectionPtr->linePtr, NULL);
+
+ if (lineNo1 <= lineNo2) {
/*
- * A node merge has collected all the toggles under one node. Push
- * the root down to this level.
+ * We've found a node before text start, so return text start.
*/
+ return startMarker;
+ }
+ }
+
+ /*
+ * Final search of branch segment.
+ */
+
+ linePtr = nodePtr->lastPtr;
+ while (linePtr->numBranches == 0) {
+ if (linePtr == firstLinePtr) {
+ return startMarker;
+ }
+ linePtr = linePtr->prevPtr;
+ assert(linePtr);
+ }
+
+ return SearchBranchInLine(linePtr->lastPtr, startMarker);
+}
+
+TkTextSegment *
+TkBTreeFindStartOfElidedRange(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr);
+ assert(TkBTreeHaveElidedSegments(sharedTextPtr));
+ assert(SegmentIsElided(sharedTextPtr, segPtr, textPtr));
+
+ return FindBranchSegment(sharedTextPtr, textPtr, segPtr,
+ textPtr ? textPtr->startMarker : sharedTextPtr->startMarker);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeFindEndOfElidedRange --
+ *
+ * Given an elided segment, this function is searching for the
+ * last segment which is spanning the range containing the
+ * given segment. Normally this is a link segment, but in
+ * case of restricted peers it may be an end marker.
+ *
+ * Results:
+ * The return value is a corresponding link segment (or the end
+ * marker of this peer).
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextSegment *
+SearchLinkInLine(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ TkTextSegment *segPtr)
+{
+ TkTextSegment *endMarker = textPtr ? textPtr->endMarker : sharedTextPtr->endMarker;
+ TkTextSection *sectionPtr = segPtr->sectionPtr;
+ TkTextSection *endSectionPtr;
+
+ assert(endMarker);
+
+ /*
+ * Note that a link is always at the start of a section.
+ */
+
+ if (segPtr->typePtr == &tkTextLinkType) {
+ return segPtr;
+ }
+
+ endSectionPtr = endMarker->sectionPtr;
+
+ if (sectionPtr == endSectionPtr) {
+ return endMarker;
+ }
+
+ for (sectionPtr = sectionPtr->nextPtr; sectionPtr; sectionPtr = sectionPtr->nextPtr) {
+ if (sectionPtr->segPtr->typePtr == &tkTextLinkType) {
+ return sectionPtr->segPtr;
+ }
+ if (sectionPtr == endSectionPtr) {
+ return endMarker;
+ }
+ }
+
+ return NULL;
+}
+
+TkTextSegment *
+TkBTreeFindEndOfElidedRange(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ const TkTextSegment *segPtr)
+{
+ TkTextSegment *branchPtr;
+ TkTextSegment *linkPtr;
+
+ assert(segPtr);
+ assert(SegmentIsElided(sharedTextPtr, segPtr, textPtr));
+
+ if (segPtr->sectionPtr->linePtr->numLinks > 0) {
+ if ((linkPtr = SearchLinkInLine(sharedTextPtr, textPtr, (TkTextSegment *) segPtr))) {
+ return linkPtr;
+ }
+ }
+
+ branchPtr = FindBranchSegment(sharedTextPtr, textPtr, segPtr, NULL);
+
+ assert(branchPtr);
+ assert(branchPtr->typePtr == &tkTextBranchType);
+
+ linkPtr = branchPtr->body.branch.nextPtr;
+
+ if (textPtr && textPtr->endMarker != sharedTextPtr->endMarker) {
+ TkTextLine *lastLinePtr = textPtr->endMarker->sectionPtr->linePtr;
+ TkTextLine *linePtr = linkPtr->sectionPtr->linePtr;
+ int lineNo1, lineNo2;
+
+ if (linePtr == lastLinePtr) {
+ return SearchLinkInLine(sharedTextPtr, textPtr, linePtr->segPtr);
+ }
+
+ lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, linkPtr->sectionPtr->linePtr, NULL);
+ lineNo2 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, lastLinePtr, NULL);
+
+ if (lineNo1 > lineNo2) {
+ /* we've found a node after text end, so return text end */
+ return textPtr->endMarker;
+ }
+ }
+
+ return linkPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeSize --
+ *
+ * This function returns the byte size over all lines in given client.
+ * If the client is NULL then count over all lines in the B-Tree.
+ *
+ * Results:
+ * The return value is either the total number of bytes in given client,
+ * or the total number of bytes in the B-Tree if the client is NULL.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+unsigned
+TkBTreeSize(
+ const TkTextBTree tree, /* The B-tree. */
+ const TkText *textPtr) /* Relative to this client of the B-tree, can be NULL. */
+{
+ assert(tree);
+
+ if (!textPtr) {
+ return TkBTreeGetRoot(tree)->size - 1;
+ }
+ return TkBTreeCountSize(tree, textPtr, TkBTreeGetStartLine(textPtr), TkBTreeGetLastLine(textPtr));
+
+}
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeCountSize --
+ *
+ * This function returns the byte size over all lines in given range.
+ *
+ * Results:
+ * The return value is the total number of bytes in given line range.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static unsigned
+CountSize(
+ const Node *nodePtr,
+ unsigned lineNo,
+ unsigned firstLineNo,
+ unsigned lastLineNo)
+{
+ unsigned endLineNo = lineNo + nodePtr->numLines - 1;
+ unsigned size;
+
+ if (firstLineNo <= lineNo && endLineNo <= lastLineNo) {
+ return nodePtr->size;
+ }
+
+ if (endLineNo < firstLineNo || lastLineNo < lineNo) {
+ return 0;
+ }
+
+ size = 0;
+
+ if (nodePtr->level == 0) {
+ const TkTextLine *linePtr = nodePtr->linePtr;
+ const TkTextLine *lastPtr = nodePtr->lastPtr->nextPtr;
+
+ endLineNo = MIN(endLineNo, lastLineNo);
+
+ for ( ; lineNo < firstLineNo; ++lineNo, linePtr = linePtr->nextPtr) {
+ assert(linePtr);
+ }
+ for ( ; lineNo <= endLineNo && linePtr != lastPtr; ++lineNo, linePtr = linePtr->nextPtr) {
+ size += linePtr->size;
+ }
+ } else {
+ const Node *childPtr;
+
+ for (childPtr = nodePtr->childPtr; childPtr; childPtr = childPtr->nextPtr) {
+ size += CountSize(childPtr, lineNo, firstLineNo, lastLineNo);
+ lineNo += childPtr->numLines;
+ }
+ }
+
+ return size;
+}
+
+unsigned
+TkBTreeCountSize(
+ const TkTextBTree tree,
+ const TkText *textPtr, /* Relative to this client, can be NULL. */
+ const TkTextLine *linePtr1, /* Start counting at this line. */
+ const TkTextLine *linePtr2) /* Stop counting at this line (don't count this line). */
+{
+ const BTree *treePtr = (const BTree *) tree;
+ unsigned numBytes;
+
+ if (linePtr1 == linePtr2) {
+ return 0;
+ }
+
+ assert(tree);
+ assert(linePtr1);
+ assert(linePtr2);
+ assert(TkBTreeLinesTo(tree, NULL, linePtr1, NULL) <= TkBTreeLinesTo(tree, NULL, linePtr2, NULL));
+
+ if (linePtr1 == treePtr->rootPtr->linePtr && linePtr2 == treePtr->rootPtr->lastPtr) {
+ numBytes = treePtr->rootPtr->size - 1;
+ } else {
+ unsigned firstLineNo = TkBTreeLinesTo(tree, NULL, linePtr1, NULL);
+ unsigned lastLineNo = TkBTreeLinesTo(tree, NULL, linePtr2, NULL) - 1;
+
+ numBytes = CountSize(treePtr->rootPtr, 0, firstLineNo, lastLineNo);
+ }
+
+ if (textPtr) {
+ const TkSharedText *sharedTextPtr = treePtr->sharedTextPtr;
+
+ if (textPtr->startMarker != sharedTextPtr->startMarker) {
+ if (linePtr1 == textPtr->startMarker->sectionPtr->linePtr) {
+ assert(TkTextSegToIndex(textPtr->startMarker) <= numBytes);
+ numBytes -= TkTextSegToIndex(textPtr->startMarker);
+ }
+ }
+ if (textPtr->endMarker != sharedTextPtr->endMarker) {
+ if (!SegIsAtStartOfLine(textPtr->endMarker)) {
+ const TkTextLine *linePtr = textPtr->endMarker->sectionPtr->linePtr;
+ assert(linePtr->size - TkTextSegToIndex(textPtr->endMarker) - 1 <= numBytes);
+ numBytes -= linePtr->size - TkTextSegToIndex(textPtr->endMarker) - 1;
+ }
+ }
+ }
+
+ return numBytes;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeMoveForward --
+ *
+ * Given an index for a text widget, this function creates a new index
+ * that points 'byteCount' bytes ahead of the source index.
+ *
+ * Results:
+ * 'dstPtr' is modified to refer to the character 'byteCount' bytes after
+ * 'srcPtr', or to the last character in the TkText if there aren't 'byteCount'
+ * bytes left.
+ *
+ * In this latter case, the function returns 'false' to indicate that not all
+ * of 'byteCount' could be used.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkBTreeMoveForward(
+ TkTextIndex *indexPtr,
+ unsigned byteCount)
+{
+ TkTextLine *linePtr;
+ const Node *nodePtr;
+ const Node *parentPtr;
+ int byteIndex;
+
+ if (byteCount == 0) {
+ return true;
+ }
+
+ byteIndex = byteCount + TkTextIndexGetByteIndex(indexPtr);
+ linePtr = TkTextIndexGetLine(indexPtr);
+ nodePtr = linePtr->parentPtr;
+ parentPtr = nodePtr->parentPtr;
+
+ if (linePtr != nodePtr->linePtr || !parentPtr || HasLeftNode(nodePtr)) {
+ TkTextLine *lastPtr;
+
+ /*
+ * At first, search for byte offset in current node.
+ */
- summaryPtr->tagPtr->tagRootPtr = nodePtr;
+ lastPtr = nodePtr->lastPtr->nextPtr;
+
+ while (linePtr != lastPtr) {
+ if (byteIndex < linePtr->size) {
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, byteIndex);
+ return TkTextIndexRestrictToEndRange(indexPtr) <= 0;
+ }
+ byteIndex -= linePtr->size;
+ if (!(linePtr = TkBTreeNextLine(indexPtr->textPtr, linePtr))) {
+ TkTextIndexSetupToEndOfText(indexPtr, indexPtr->textPtr, indexPtr->tree);
+ return false;
+ }
}
- if (summaryPtr2 != NULL) {
- summaryPtr2->nextPtr = summaryPtr->nextPtr;
- ckfree(summaryPtr);
- summaryPtr = summaryPtr2->nextPtr;
+
+ nodePtr = nodePtr->nextPtr;
+ }
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which contains the byte offset.
+ */
+
+ while (parentPtr) {
+ if (!nodePtr || (!HasLeftNode(nodePtr) && byteIndex >= parentPtr->size)) {
+ nodePtr = parentPtr->nextPtr;
+ parentPtr = parentPtr->parentPtr;
} else {
- nodePtr->summaryPtr = summaryPtr->nextPtr;
- ckfree(summaryPtr);
- summaryPtr = nodePtr->summaryPtr;
+ while (nodePtr) {
+ if (byteIndex < nodePtr->size) {
+ if (nodePtr->level > 0) {
+ nodePtr = nodePtr->childPtr;
+ continue;
+ }
+ /*
+ * We've found the right node, now search for the line.
+ */
+ linePtr = nodePtr->linePtr;
+ while (true) {
+ if (byteIndex < linePtr->size) {
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, byteIndex);
+ return TkTextIndexRestrictToEndRange(indexPtr) <= 0;
+ }
+ byteIndex -= linePtr->size;
+ if (!(linePtr = TkBTreeNextLine(indexPtr->textPtr, linePtr))) {
+ TkTextIndexSetupToEndOfText(indexPtr, indexPtr->textPtr, indexPtr->tree);
+ return false;
+ }
+ }
+ }
+ byteIndex -= nodePtr->size;
+ nodePtr = nodePtr->nextPtr;
+ }
}
}
+
+ TkTextIndexSetupToEndOfText(indexPtr, indexPtr->textPtr, indexPtr->tree);
+ return false;
}
/*
*----------------------------------------------------------------------
*
- * TkBTreeNumLines --
+ * TkBTreeMoveBackward --
*
- * This function returns a count of the number of logical lines of text
- * present in a given B-tree.
+ * Given an index for a text widget, this function creates a new index
+ * that points 'byteCount' bytes earlier of the source index.
*
* Results:
- * The return value is a count of the number of usable lines in tree
- * (i.e. it doesn't include the dummy line that is just used to mark the
- * end of the tree).
+ * 'dstPtr' is modified to refer to the character 'byteCount' bytes before
+ * 'srcPtr', or to the first character in the TkText if there aren't 'byteCount'
+ * bytes earlier.
+ *
+ * In this latter case, the function returns true to indicate that not all
+ * of 'byteCount' could be used.
*
* Side effects:
* None.
@@ -4478,24 +14669,201 @@ RecomputeNodeCounts(
*----------------------------------------------------------------------
*/
-int
-TkBTreeNumLines(
- TkTextBTree tree, /* Information about tree. */
- const TkText *textPtr) /* Relative to this client of the B-tree. */
+bool
+TkBTreeMoveBackward(
+ TkTextIndex *indexPtr,
+ unsigned byteCount)
{
- BTree *treePtr = (BTree *) tree;
- int count;
+ const Node *nodeStack[MAX_CHILDREN];
+ const Node *nodePtr;
+ const Node *parentPtr;
+ const Node *nPtr;
+ TkTextLine *linePtr;
+ unsigned idx;
+ int byteIndex;
+
+ if (byteCount == 0) {
+ return true;
+ }
+
+ linePtr = TkTextIndexGetLine(indexPtr);
+ nodePtr = linePtr->parentPtr;
+ parentPtr = nodePtr->parentPtr;
+ byteIndex = byteCount + (linePtr->size - TkTextIndexGetByteIndex(indexPtr));
- if (textPtr != NULL && textPtr->end != NULL) {
- count = TkBTreeLinesTo(NULL, textPtr->end);
+ if (linePtr != nodePtr->lastPtr || !parentPtr || nodePtr->nextPtr) {
+ TkTextLine *lastPtr;
+
+ /*
+ * At first, search for byte offset in current node.
+ */
+
+ lastPtr = nodePtr->linePtr->prevPtr;
+
+ while (linePtr != lastPtr) {
+ if ((byteIndex -= linePtr->size) <= 0) {
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, -byteIndex);
+ return TkTextIndexRestrictToStartRange(indexPtr) >= 0;
+ }
+ if (!(linePtr = TkBTreePrevLine(indexPtr->textPtr, linePtr))) {
+ TkTextIndexSetupToStartOfText(indexPtr, indexPtr->textPtr, indexPtr->tree);
+ return false;
+ }
+ }
} else {
- count = treePtr->rootPtr->numLines - 1;
+ nodePtr = NULL;
}
- if (textPtr != NULL && textPtr->start != NULL) {
- count -= TkBTreeLinesTo(NULL, textPtr->start);
+
+ /*
+ * We couldn't find a line, so search inside B-Tree for next level-0
+ * node which contains the byte offset.
+ */
+
+ for (nPtr = parentPtr->childPtr, idx = 0; nPtr != nodePtr; nPtr = nPtr->nextPtr) {
+ nodeStack[idx++] = nPtr;
}
+ nodePtr = idx ? nodeStack[--idx] : NULL;
- return count;
+ while (parentPtr) {
+ if (!nodePtr || (!nodePtr->nextPtr && byteIndex >= parentPtr->size)) {
+ nodePtr = parentPtr;
+ if ((parentPtr = parentPtr->parentPtr)) {
+ for (nPtr = parentPtr->childPtr, idx = 0; nPtr != nodePtr; nPtr = nPtr->nextPtr) {
+ nodeStack[idx++] = nPtr;
+ }
+ nodePtr = idx ? nodeStack[--idx] : NULL;
+ }
+ } else {
+ while (nodePtr) {
+ if (byteIndex < nodePtr->size) {
+ if (nodePtr->level > 0) {
+ parentPtr = nodePtr;
+ idx = 0;
+ for (nPtr = nodePtr->childPtr; nPtr; nPtr = nPtr->nextPtr) {
+ nodeStack[idx++] = nPtr;
+ }
+ nodePtr = idx ? nodeStack[--idx] : NULL;
+ continue;
+ }
+ /*
+ * We've found the right node, now search for the line.
+ */
+ linePtr = nodePtr->lastPtr;
+ while (true) {
+ if ((byteIndex -= linePtr->size) <= 0) {
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, -byteIndex);
+ return TkTextIndexRestrictToStartRange(indexPtr) >= 0;
+ }
+ if (!(linePtr = TkBTreePrevLine(indexPtr->textPtr, linePtr))) {
+ TkTextIndexSetupToStartOfText(indexPtr, indexPtr->textPtr, indexPtr->tree);
+ return false;
+ }
+ }
+ }
+ byteIndex -= nodePtr->size;
+ nodePtr = idx ? nodeStack[--idx] : NULL;
+ }
+ }
+ }
+
+ TkTextIndexSetupToStartOfText(indexPtr, indexPtr->textPtr, indexPtr->tree);
+ return false;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeRootTagInfo --
+ *
+ * This function returns the tag information of root node.
+ *
+ * Results:
+ * The tag information of root node.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+const TkTextTagSet *
+TkBTreeRootTagInfo(
+ const TkTextBTree tree)
+{
+ return ((BTree *) tree)->rootPtr->tagonPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeLinesPerNode --
+ *
+ * This function returns the minimal number of lines per node.
+ *
+ * Results:
+ * The minimal number of lines per node.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+unsigned
+TkBTreeLinesPerNode(
+ const TkTextBTree tree)
+{
+ return MIN_CHILDREN;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeChildNumber --
+ *
+ * This function returns the number of level-0 node which contains the
+ * given line. If 'depth' is given then also the depth of this node
+ * will be returned (in 'depth').
+ *
+ * Results:
+ * The number of the child for given line, and also the depth of this
+ * child if 'depth' is given.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+unsigned
+TkBTreeChildNumber(
+ const TkTextBTree tree,
+ const TkTextLine *linePtr,
+ unsigned *depth)
+{
+ const Node *childPtr;
+ const Node *nodePtr;
+ unsigned number = 0;
+
+ assert(linePtr);
+
+ nodePtr = linePtr->parentPtr;
+ childPtr = nodePtr->parentPtr->childPtr;
+
+ for ( ; childPtr != nodePtr; childPtr = childPtr->nextPtr) {
+ number += 1;
+ }
+
+ if (depth) {
+ *depth = 0;
+
+ while (nodePtr) {
+ nodePtr = nodePtr->parentPtr;
+ *depth += 1;
+ }
+ }
+
+ return number;
}
/*
@@ -4510,8 +14878,8 @@ TkBTreeNumLines(
* The return value is a count of the number of usable pixels in tree
* (since the dummy line used to mark the end of the B-tree is maintained
* with zero height, as are any lines that are before or after the
- * '-start -end' range of the text widget in question, the number stored
- * at the root is the number we want).
+ * '-startindex -endindex' range of the text widget in question, the number
+ * stored at the root is the number we want).
*
* Side effects:
* None.
@@ -4519,60 +14887,133 @@ TkBTreeNumLines(
*----------------------------------------------------------------------
*/
-int
+unsigned
TkBTreeNumPixels(
- TkTextBTree tree, /* The B-tree. */
const TkText *textPtr) /* Relative to this client of the B-tree. */
{
- BTree *treePtr = (BTree *) tree;
- return treePtr->rootPtr->numPixels[textPtr->pixelReference];
+ assert(textPtr);
+ assert(textPtr->pixelReference != -1);
+
+ return TkBTreeGetRoot(textPtr->sharedTextPtr->tree)->pixelInfo[textPtr->pixelReference].pixels;
}
/*
*--------------------------------------------------------------
*
- * CharSplitProc --
+ * CleanupSplitPoint --
*
- * This function implements splitting for character segments.
+ * This function merges adjacent character segments into a single
+ * character segment, if possible, and removes the protection flag.
*
* 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.
+ * None.
*
* Side effects:
- * Storage for segPtr is freed.
+ * Storage for the segments may be allocated and freed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+CleanupSplitPoint(
+ TkTextSegment *segPtr,
+ TkSharedText *sharedTextPtr)
+{
+ if (!segPtr || !segPtr->protectionFlag) {
+ return;
+ }
+
+ segPtr->protectionFlag = false;
+
+ if (segPtr->typePtr == &tkTextCharType) {
+ if (segPtr->prevPtr && segPtr->prevPtr->typePtr == &tkTextCharType) {
+ TkTextSegment *prevPtr = segPtr->prevPtr;
+ if ((segPtr = CleanupCharSegments(sharedTextPtr, prevPtr)) == prevPtr) {
+ segPtr = segPtr->nextPtr;
+ }
+ }
+ if (segPtr->nextPtr && segPtr->nextPtr->typePtr == &tkTextCharType) {
+ CleanupCharSegments(sharedTextPtr, segPtr);
+ }
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * JoinCharSegments --
+ *
+ * This function merges adjacent character segments into a single
+ * character segment.
+ *
+ * This functions assumes that the successor of given segment is
+ * a joinable char segment.
+ *
+ * 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.
*
*--------------------------------------------------------------
*/
static TkTextSegment *
-CharSplitProc(
- TkTextSegment *segPtr, /* Pointer to segment to split. */
- int index) /* Position within segment at which to
- * split. */
+JoinCharSegments(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ TkTextSegment *segPtr) /* Pointer to first of two adjacent segments to join. */
{
- TkTextSegment *newPtr1, *newPtr2;
+ TkTextSegment *nextPtr, *newPtr;
+
+ assert(segPtr);
+ assert(segPtr->typePtr == &tkTextCharType);
+ assert(!segPtr->protectionFlag);
+ assert(segPtr->nextPtr);
+ assert(!segPtr->nextPtr->protectionFlag);
+ assert(segPtr->nextPtr->typePtr == &tkTextCharType);
+ assert(TkTextTagSetIsEqual(segPtr->tagInfoPtr, segPtr->nextPtr->tagInfoPtr));
+
+ nextPtr = segPtr->nextPtr;
+ newPtr = CopyCharSeg(segPtr, 0, segPtr->size, segPtr->size + nextPtr->size);
+ memcpy(newPtr->body.chars + segPtr->size, nextPtr->body.chars, nextPtr->size);
+ newPtr->nextPtr = nextPtr->nextPtr;
+ newPtr->prevPtr = segPtr->prevPtr;
+
+ if (segPtr->prevPtr) {
+ segPtr->prevPtr->nextPtr = newPtr;
+ } else {
+ segPtr->sectionPtr->linePtr->segPtr = newPtr;
+ }
+ if (nextPtr->nextPtr) {
+ nextPtr->nextPtr->prevPtr = newPtr;
+ }
+ if (segPtr->sectionPtr->segPtr == segPtr) {
+ segPtr->sectionPtr->segPtr = newPtr;
+ }
+ if (nextPtr->sectionPtr->segPtr == nextPtr) {
+ nextPtr->sectionPtr->segPtr = nextPtr->nextPtr;
+ }
+ if (newPtr->sectionPtr->linePtr->lastPtr == nextPtr) {
+ newPtr->sectionPtr->linePtr->lastPtr = newPtr;
+ }
+ nextPtr->sectionPtr->length -= 1;
+ if (segPtr->sectionPtr != nextPtr->sectionPtr) {
+ segPtr->sectionPtr->size += nextPtr->size;
+ nextPtr->sectionPtr->size -= nextPtr->size;
+ JoinSections(nextPtr->sectionPtr);
+ }
+ JoinSections(segPtr->sectionPtr);
+ TkBTreeFreeSegment(segPtr);
+ TkBTreeFreeSegment(nextPtr);
- newPtr1 = ckalloc(CSEG_SIZE(index));
- newPtr2 = ckalloc(CSEG_SIZE(segPtr->size - index));
- newPtr1->typePtr = &tkTextCharType;
- newPtr1->nextPtr = newPtr2;
- newPtr1->size = index;
- memcpy(newPtr1->body.chars, segPtr->body.chars, (size_t) index);
- newPtr1->body.chars[index] = 0;
- newPtr2->typePtr = &tkTextCharType;
- newPtr2->nextPtr = segPtr->nextPtr;
- newPtr2->size = segPtr->size - index;
- memcpy(newPtr2->body.chars, segPtr->body.chars + index, newPtr2->size);
- newPtr2->body.chars[newPtr2->size] = 0;
- ckfree(segPtr);
- return newPtr1;
+ return newPtr;
}
/*
*--------------------------------------------------------------
*
- * CharCleanupProc --
+ * CleanupCharSegments --
*
* This function merges adjacent character segments into a single
* character segment, if possible.
@@ -4587,29 +15028,27 @@ CharSplitProc(
*--------------------------------------------------------------
*/
- /* ARGSUSED */
static TkTextSegment *
-CharCleanupProc(
- TkTextSegment *segPtr, /* Pointer to first of two adjacent segments
- * to join. */
- TkTextLine *linePtr) /* Line containing segments (not used). */
+CleanupCharSegments(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ TkTextSegment *segPtr) /* Pointer to first of two adjacent segments to join. */
{
- TkTextSegment *segPtr2, *newPtr;
+ TkTextSegment *nextPtr;
- segPtr2 = segPtr->nextPtr;
- if ((segPtr2 == NULL) || (segPtr2->typePtr != &tkTextCharType)) {
+ assert(segPtr);
+ assert(segPtr->typePtr == &tkTextCharType);
+
+ if (segPtr->protectionFlag) {
return segPtr;
}
- newPtr = ckalloc(CSEG_SIZE(segPtr->size + segPtr2->size));
- newPtr->typePtr = &tkTextCharType;
- newPtr->nextPtr = segPtr2->nextPtr;
- newPtr->size = segPtr->size + segPtr2->size;
- memcpy(newPtr->body.chars, segPtr->body.chars, segPtr->size);
- memcpy(newPtr->body.chars + segPtr->size, segPtr2->body.chars, segPtr2->size);
- newPtr->body.chars[newPtr->size] = 0;
- ckfree(segPtr);
- ckfree(segPtr2);
- return newPtr;
+ nextPtr = segPtr->nextPtr;
+ if (!nextPtr
+ || nextPtr->protectionFlag
+ || nextPtr->typePtr != &tkTextCharType
+ || !TkTextTagSetIsEqual(segPtr->tagInfoPtr, nextPtr->tagInfoPtr)) {
+ return segPtr;
+ }
+ return JoinCharSegments(sharedTextPtr, segPtr);
}
/*
@@ -4620,7 +15059,8 @@ CharCleanupProc(
* This function is invoked to delete a character segment.
*
* Results:
- * Always returns 0 to indicate that the segment was deleted.
+ * Always returns true to indicate that the segment was
+ * (virtually) deleted.
*
* Side effects:
* Storage for the segment is freed.
@@ -4628,17 +15068,45 @@ CharCleanupProc(
*--------------------------------------------------------------
*/
- /* ARGSUSED */
-static int
+static bool
CharDeleteProc(
+ TkTextBTree tree,
TkTextSegment *segPtr, /* Segment to delete. */
- TkTextLine *linePtr, /* Line containing segment. */
- int treeGone) /* Non-zero means the entire tree is being
- * deleted, so everything must get cleaned
- * up. */
+ int flags) /* Flags controlling the deletion. */
+{
+ TkBTreeFreeSegment(segPtr);
+ return true;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * CharInspectProc --
+ *
+ * This function is invoked to build the information for
+ * "inspect".
+ *
+ * Results:
+ * Return a TCL object containing the information for
+ * "inspect".
+ *
+ * Side effects:
+ * Storage is allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+CharInspectProc(
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr)
{
- ckfree(segPtr);
- return 0;
+ Tcl_Obj *objPtr = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(segPtr->typePtr->name, -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(segPtr->body.chars, segPtr->size));
+ Tcl_ListObjAppendElement(NULL, objPtr, MakeTagInfoObj(sharedTextPtr, segPtr->tagInfoPtr));
+ return objPtr;
}
/*
@@ -4658,17 +15126,16 @@ CharDeleteProc(
*--------------------------------------------------------------
*/
- /* ARGSUSED */
static void
CharCheckProc(
- TkTextSegment *segPtr, /* Segment to check. */
- TkTextLine *linePtr) /* Line containing segment. */
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *segPtr) /* Segment to check. */
{
/*
* Make sure that the segment contains the number of characters indicated
* by its header, and that the last segment in a line ends in a newline.
- * Also make sure that there aren't ever two character segments adjacent
- * to each other: they should be merged together.
+ * Also make sure that there aren't ever two character segments with same
+ * tagging adjacent to each other: they should be merged together.
*/
if (segPtr->size <= 0) {
@@ -4677,11 +15144,12 @@ CharCheckProc(
if (strlen(segPtr->body.chars) != (size_t) segPtr->size) {
Tcl_Panic("CharCheckProc: segment has wrong size");
}
- if (segPtr->nextPtr == NULL) {
- if (segPtr->body.chars[segPtr->size-1] != '\n') {
+ if (!segPtr->nextPtr) {
+ if (segPtr->body.chars[segPtr->size - 1] != '\n') {
Tcl_Panic("CharCheckProc: line doesn't end with newline");
}
- } else if (segPtr->nextPtr->typePtr == &tkTextCharType) {
+ } else if (segPtr->nextPtr->typePtr == &tkTextCharType
+ && TkTextTagSetIsEqual(segPtr->tagInfoPtr, segPtr->nextPtr->tagInfoPtr)) {
Tcl_Panic("CharCheckProc: adjacent character segments weren't merged");
}
}
@@ -4689,154 +15157,373 @@ CharCheckProc(
/*
*--------------------------------------------------------------
*
- * ToggleDeleteProc --
+ * HyphenDeleteProc --
*
- * This function is invoked to delete toggle segments.
+ * This function is invoked to delete hyphen segments.
*
* Results:
- * Returns 1 to indicate that the segment may not be deleted, unless the
- * entire B-tree is going away.
+ * Returns always true to indicate that the segment has been
+ * deleted (virtually).
*
* Side effects:
- * If the tree is going away then the toggle's memory is freed; otherwise
- * the toggle counts in nodes above the segment get updated.
+ * None.
*
*--------------------------------------------------------------
*/
-static int
-ToggleDeleteProc(
+static bool
+HyphenDeleteProc(
+ TkTextBTree tree,
TkTextSegment *segPtr, /* Segment to check. */
- TkTextLine *linePtr, /* Line containing segment. */
- int treeGone) /* Non-zero means the entire tree is being
- * deleted, so everything must get cleaned
- * up. */
+ int flags) /* Flags controlling the deletion. */
{
- if (treeGone) {
- ckfree(segPtr);
- return 0;
- }
+ TkBTreeFreeSegment(segPtr);
+ return true;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * HyphenInspectProc --
+ *
+ * This function is invoked to build the information for
+ * "inspect".
+ *
+ * Results:
+ * Return a TCL object containing the information for
+ * "inspect".
+ *
+ * Side effects:
+ * Storage is allocated.
+ *
+ *--------------------------------------------------------------
+ */
- /*
- * 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 function will be called later. Decrement node toggle
- * counts here, and set a flag so we'll re-increment them in the cleanup
- * function.
- */
+static Tcl_Obj *
+HyphenInspectProc(
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(segPtr->typePtr->name, -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, MakeTagInfoObj(sharedTextPtr, segPtr->tagInfoPtr));
+ return objPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * HyphenCheckProc --
+ *
+ * This function is invoked to perform consistency checks on hyphen
+ * segments.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If a consistency problem is found the function panics.
+ *
+ *--------------------------------------------------------------
+ */
- if (segPtr->body.toggle.inNodeCounts) {
- ChangeNodeToggleCount(linePtr->parentPtr,
- segPtr->body.toggle.tagPtr, -1);
- segPtr->body.toggle.inNodeCounts = 0;
+static void
+HyphenCheckProc(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *segPtr) /* Segment to check. */
+{
+ if (segPtr->size != 1) {
+ Tcl_Panic("HyphenCheckProc: hyphen has size %d", segPtr->size);
}
- return 1;
}
/*
*--------------------------------------------------------------
*
- * ToggleCleanupProc --
+ * BranchDeleteProc --
*
- * This function 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.
+ * This function is invoked to delete branch segments.
*
* 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
- * function to delete or modify segPtr.
+ * Returns always true to indicate that the segment has been
+ * deleted (virtually).
*
* Side effects:
- * Toggle counts in the nodes 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.
+ * None.
*
*--------------------------------------------------------------
*/
-static TkTextSegment *
-ToggleCleanupProc(
+static bool
+BranchDeleteProc(
+ TkTextBTree tree,
TkTextSegment *segPtr, /* Segment to check. */
- TkTextLine *linePtr) /* Line that now contains segment. */
+ int flags) /* Flags controlling the deletion. */
{
- TkTextSegment *segPtr2, *prevPtr;
- int counts;
+ if (flags & TREE_GONE) {
+ FREE_SEGMENT(segPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ return true;
+ }
- /*
- * 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 (flags & DELETE_BRANCHES) {
+ TkBTreeFreeSegment(segPtr);
+ return true;
+ }
- if (segPtr->typePtr == &tkTextToggleOffType) {
- for (prevPtr = segPtr, segPtr2 = prevPtr->nextPtr;
- (segPtr2 != NULL) && (segPtr2->size == 0);
- prevPtr = segPtr2, segPtr2 = prevPtr->nextPtr) {
- if (segPtr2->typePtr != &tkTextToggleOnType) {
- continue;
- }
- if (segPtr2->body.toggle.tagPtr != segPtr->body.toggle.tagPtr) {
- continue;
- }
- counts = segPtr->body.toggle.inNodeCounts
- + segPtr2->body.toggle.inNodeCounts;
- if (counts != 0) {
- ChangeNodeToggleCount(linePtr->parentPtr,
- segPtr->body.toggle.tagPtr, -counts);
- }
- prevPtr->nextPtr = segPtr2->nextPtr;
- ckfree(segPtr2);
- segPtr2 = segPtr->nextPtr;
- ckfree(segPtr);
- return segPtr2;
+ /* Save old relationships for undo (we misuse an unused pointer). */
+ segPtr->tagInfoPtr = (TkTextTagSet *) segPtr->body.branch.nextPtr;
+ return false;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * BranchRestoreProc --
+ *
+ * This function is called when a branch will be restored from the
+ * undo chain.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+BranchRestoreProc(
+ TkTextSegment *segPtr) /* Segment to reuse. */
+{
+ /* Restore old relationship. */
+ segPtr->body.branch.nextPtr = (TkTextSegment *) segPtr->tagInfoPtr;
+ assert(segPtr->body.branch.nextPtr->typePtr == &tkTextLinkType);
+ segPtr->tagInfoPtr = NULL;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * BranchInspectProc --
+ *
+ * This function is invoked to build the information for
+ * "inspect".
+ *
+ * Results:
+ * Return a TCL object containing the information for
+ * "inspect".
+ *
+ * Side effects:
+ * Storage is allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+BranchInspectProc(
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(segPtr->typePtr->name, -1));
+ return objPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * BranchCheckProc --
+ *
+ * This function is invoked to perform consistency checks on branch
+ * segments.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If a consistency problem is found the function panics.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+BranchCheckProc(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *segPtr) /* Segment to check. */
+{
+ const TkTextSegment *prevPtr, *nextPtr;
+ const TkTextLine *linePtr;
+
+ if (segPtr->size != 0) {
+ Tcl_Panic("BranchCheckProc: branch has size %d", segPtr->size);
+ }
+ if (!segPtr->nextPtr) {
+ Tcl_Panic("BranchCheckProc: branch cannot be at end of line");
+ }
+ if (segPtr->sectionPtr->nextPtr
+ ? segPtr->sectionPtr->nextPtr->segPtr->prevPtr != segPtr
+ : !!segPtr->nextPtr) {
+ Tcl_Panic("BranchCheckProc: branch is not at end of section");
+ }
+ if (!segPtr->body.branch.nextPtr) {
+ Tcl_Panic("BranchCheckProc: missing fork");
+ }
+ if (segPtr->nextPtr == segPtr->body.branch.nextPtr) {
+ Tcl_Panic("BranchCheckProc: bad fork");
+ }
+ if (!segPtr->body.branch.nextPtr->sectionPtr) {
+ Tcl_Panic("BranchCheckProc: connection is not linked");
+ }
+ if (segPtr->nextPtr->typePtr->group == SEG_GROUP_MARK) {
+ Tcl_Panic("BranchCheckProc: branch shouldn't be followed by marks");
+ }
+
+ assert(segPtr->body.branch.nextPtr);
+ assert(segPtr->body.branch.nextPtr->typePtr);
+
+ if (segPtr->body.branch.nextPtr->typePtr != &tkTextLinkType) {
+ Tcl_Panic("BranchCheckProc: branch is not pointing to a link");
+ }
+ if (segPtr->body.branch.nextPtr->body.link.prevPtr != segPtr) {
+ Tcl_Panic("BranchCheckProc: related link is not pointing to this branch");
+ }
+ if (segPtr->nextPtr->typePtr == &tkTextLinkType) {
+ Tcl_Panic("BranchCheckProc: elided section is empty");
+ }
+
+ linePtr = segPtr->sectionPtr->linePtr;
+ if (!(prevPtr = segPtr->prevPtr) && linePtr->prevPtr) {
+ prevPtr = linePtr->prevPtr->lastPtr;
+ }
+ while (prevPtr && !prevPtr->tagInfoPtr) {
+ if (prevPtr->typePtr->group == SEG_GROUP_BRANCH) {
+ Tcl_Panic("BranchCheckProc: invalid branch/link structure (%s before branch)",
+ prevPtr->typePtr->name);
+ }
+ if (!(prevPtr = prevPtr->prevPtr) && linePtr->prevPtr) {
+ prevPtr = linePtr->prevPtr->lastPtr;
}
}
+ nextPtr = segPtr->nextPtr;
+ while (nextPtr && !nextPtr->tagInfoPtr) {
+ if (nextPtr->typePtr->group == SEG_GROUP_BRANCH) {
+ Tcl_Panic("BranchCheckProc: invalid branch/link structure (%s after branch)",
+ nextPtr->typePtr->name);
+ }
+ nextPtr = nextPtr->nextPtr;
+ }
- if (!segPtr->body.toggle.inNodeCounts) {
- ChangeNodeToggleCount(linePtr->parentPtr,
- segPtr->body.toggle.tagPtr, 1);
- segPtr->body.toggle.inNodeCounts = 1;
+ if (prevPtr && SegmentIsElided(sharedTextPtr, prevPtr, NULL)) {
+ Tcl_Panic("BranchCheckProc: branch not at start of elided range");
+ }
+ if (nextPtr && !SegmentIsElided(sharedTextPtr, nextPtr, NULL)) {
+ Tcl_Panic("BranchCheckProc: misplaced branch");
}
- return segPtr;
}
/*
*--------------------------------------------------------------
*
- * ToggleLineChangeProc --
+ * LinkDeleteProc --
*
- * This function is invoked when a toggle segment is about to move from
- * one line to another.
+ * This function is invoked to delete link segments.
*
* Results:
- * None.
+ * Returns always 'true' to indicate that the segment has been
+ * deleted (virtually).
*
* Side effects:
- * Toggle counts are decremented in the nodes above the line.
+ * None.
*
*--------------------------------------------------------------
*/
-static void
-ToggleLineChangeProc(
+static bool
+LinkDeleteProc(
+ TkTextBTree tree,
TkTextSegment *segPtr, /* Segment to check. */
- TkTextLine *linePtr) /* Line that used to contain segment. */
+ int flags) /* Flags controlling the deletion. */
{
- if (segPtr->body.toggle.inNodeCounts) {
- ChangeNodeToggleCount(linePtr->parentPtr,
- segPtr->body.toggle.tagPtr, -1);
- segPtr->body.toggle.inNodeCounts = 0;
+ if (flags & TREE_GONE) {
+ FREE_SEGMENT(segPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ return true;
}
+
+ if (flags & DELETE_BRANCHES) {
+ TkBTreeFreeSegment(segPtr);
+ return true;
+ }
+
+ /* Save old relationships for undo (we have misused an unused pointer). */
+ segPtr->tagInfoPtr = (TkTextTagSet *) segPtr->body.link.prevPtr;
+ return false;
}
/*
*--------------------------------------------------------------
*
- * ToggleCheckProc --
+ * LinkRestoreProc --
+ *
+ * This function is called when a branch will be restored from the
+ * undo chain.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+LinkRestoreProc(
+ TkTextSegment *segPtr) /* Segment to reuse. */
+{
+ /* Restore old relationship (misuse of an unused pointer). */
+ segPtr->body.link.prevPtr = (TkTextSegment *) segPtr->tagInfoPtr;
+ assert(segPtr->body.link.prevPtr->typePtr == &tkTextBranchType);
+ segPtr->tagInfoPtr = NULL;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * LinkInspectProc --
+ *
+ * This function is invoked to build the information for
+ * "inspect".
*
- * This function is invoked to perform consistency checks on toggle
+ * Results:
+ * Return a TCL object containing the information for
+ * "inspect".
+ *
+ * Side effects:
+ * Storage is allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+LinkInspectProc(
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(segPtr->typePtr->name, -1));
+ return objPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * LinkCheckProc --
+ *
+ * This function is invoked to perform consistency checks on link
* segments.
*
* Results:
@@ -4849,42 +15536,436 @@ ToggleLineChangeProc(
*/
static void
-ToggleCheckProc(
- TkTextSegment *segPtr, /* Segment to check. */
- TkTextLine *linePtr) /* Line containing segment. */
+LinkCheckProc(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *segPtr) /* Segment to check. */
{
- register Summary *summaryPtr;
- int needSummary;
+ const TkTextSegment *prevPtr, *nextPtr;
+ const TkTextLine *linePtr;
if (segPtr->size != 0) {
- Tcl_Panic("ToggleCheckProc: segment had non-zero size");
+ Tcl_Panic("LinkCheckProc: link has size %d", segPtr->size);
+ }
+ if (segPtr->sectionPtr->segPtr != segPtr) {
+ Tcl_Panic("LinkCheckProc: link is not at start of section");
+ }
+ if (!segPtr->body.link.prevPtr) {
+ Tcl_Panic("LinkCheckProc: missing connection");
+ }
+ if (!segPtr->body.link.prevPtr->sectionPtr) {
+ Tcl_Panic("LinkCheckProc: connection is not linked");
+ }
+ if (segPtr->prevPtr == segPtr->body.link.prevPtr) {
+ Tcl_Panic("LinkCheckProc: bad link");
+ }
+
+ assert(segPtr->body.link.prevPtr);
+ assert(segPtr->body.link.prevPtr->typePtr);
+
+ if (segPtr->body.link.prevPtr->typePtr != &tkTextBranchType) {
+ Tcl_Panic("LinkCheckProc: link is not pointing to a branch");
}
- if (!segPtr->body.toggle.inNodeCounts) {
- Tcl_Panic("ToggleCheckProc: toggle counts not updated in nodes");
+ if (segPtr->body.link.prevPtr->body.branch.nextPtr != segPtr) {
+ Tcl_Panic("LinkCheckProc: related branch is not pointing to this link");
}
- needSummary = (segPtr->body.toggle.tagPtr->tagRootPtr!=linePtr->parentPtr);
- for (summaryPtr = linePtr->parentPtr->summaryPtr; ;
- summaryPtr = summaryPtr->nextPtr) {
- if (summaryPtr == NULL) {
- if (needSummary) {
- Tcl_Panic("ToggleCheckProc: tag not present in node");
+ if (segPtr->prevPtr && segPtr->prevPtr->typePtr->group == SEG_GROUP_MARK) {
+ Tcl_Panic("LinkCheckProc: link shouldn't be preceded by marks");
+ }
+
+ linePtr = segPtr->sectionPtr->linePtr;
+ if (!(prevPtr = segPtr->prevPtr) && linePtr->prevPtr) {
+ prevPtr = linePtr->prevPtr->lastPtr;
+ }
+ while (prevPtr && !prevPtr->tagInfoPtr) {
+ if (prevPtr->typePtr->group == SEG_GROUP_BRANCH) {
+ Tcl_Panic("LinkCheckProc: invalid branch/link structure (%s after link)",
+ prevPtr->typePtr->name);
+ }
+ if (!(prevPtr = prevPtr->prevPtr) && linePtr->prevPtr) {
+ prevPtr = linePtr->prevPtr->lastPtr;
+ }
+ }
+ nextPtr = segPtr->nextPtr;
+ while (nextPtr && !nextPtr->tagInfoPtr) {
+ if (nextPtr->typePtr->group == SEG_GROUP_BRANCH) {
+ Tcl_Panic("LinkCheckProc: invalid branch/link structure (%s after link)",
+ nextPtr->typePtr->name);
+ }
+ nextPtr = nextPtr->nextPtr;
+ }
+
+ if (prevPtr && !SegmentIsElided(sharedTextPtr, prevPtr, NULL)) {
+ Tcl_Panic("LinkCheckProc: misplaced link");
+ }
+ if (nextPtr && SegmentIsElided(sharedTextPtr, nextPtr, NULL)) {
+ Tcl_Panic("LinkCheckProc: link is not at end of elided range");
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * ProtectionMarkCheckProc --
+ *
+ * This function is invoked to perform consistency checks on the
+ * protection mark.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The function panics because the deletion markers are only
+ * temporary.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+ProtectionMarkCheckProc(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *segPtr) /* Segment to check. */
+{
+ Tcl_Panic("ProtectionMarkCheckProc: protection mark detected");
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * ProtectionMarkDeleteProc --
+ *
+ * This function is invoked to delete the protection markers.
+ *
+ * Results:
+ * Returns 'true' to indicate that the segment is (virtually) deleted.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+ProtectionMarkDeleteProc(
+ TkTextBTree tree,
+ TkTextSegment *segPtr, /* Segment to check. */
+ int flags) /* Flags controlling the deletion. */
+{
+ return true;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * CheckSegmentItems --
+ *
+ * This function is invoked to perform consistency checks on the
+ * segment items.
+ *
+ * Results:
+ * Returns always true for successful, because in case of an detected
+ * error the panic function will be called.
+ *
+ * Side effects:
+ * If a consistency problem is found the function panics.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+CheckSegmentItems(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextLine *linePtr) /* Pointer to line in B-tree */
+{
+ const TkTextSegment *segPtr;
+
+ for (segPtr = linePtr->segPtr; segPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr->checkProc) {
+ segPtr->typePtr->checkProc(sharedTextPtr, segPtr);
+ }
+ }
+
+ return true;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * CheckSegments --
+ *
+ * This function is invoked to perform consistency checks on the
+ * chain of segments.
+ *
+ * Results:
+ * Returns always true for successful, because in case of an detected
+ * error the panic function will be called.
+ *
+ * Side effects:
+ * If a consistency problem is found the function panics.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+CheckSegments(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextLine *linePtr) /* Pointer to line in B-tree */
+{
+ const TkTextSegment *segPtr;
+ TkTextTagSet *tagonPtr;
+ TkTextTagSet *tagoffPtr;
+ unsigned count = 0;
+ unsigned numBranches = 0;
+ unsigned numLinks = 0;
+ bool startsWithBranch = false;
+ bool startsWithLink = false;
+ bool endsWithBranch = false;
+ bool endsWithLink = false;
+
+ TkTextTagSetIncrRefCount(tagonPtr = sharedTextPtr->emptyTagInfoPtr);
+ TkTextTagSetIncrRefCount(tagoffPtr = linePtr->tagonPtr);
+
+ if (!linePtr->segPtr) {
+ Tcl_Panic("CheckSegments: line has no segments");
+ }
+ if (linePtr->segPtr->prevPtr) {
+ Tcl_Panic("CheckSegments: first segment has predecessor");
+ }
+
+ for (segPtr = linePtr->segPtr; segPtr; segPtr = segPtr->nextPtr) {
+ if (!segPtr->typePtr) {
+ Tcl_Panic("CheckSegments: segment has null type");
+ }
+ if (segPtr->refCount <= 0) {
+ Tcl_Panic("CheckSegments: reference count <= 0");
+ }
+ if (segPtr->protectionFlag) {
+ Tcl_Panic("CheckSegments: segment is protected");
+ }
+ if (segPtr != linePtr->segPtr && !segPtr->prevPtr) {
+ Tcl_Panic("CheckSegments: missing predecessor in segment");
+ }
+ if (segPtr->nextPtr && segPtr->nextPtr->prevPtr != segPtr) {
+ Tcl_Panic("CheckSegments: wrong successor in segment");
+ }
+ if (segPtr->prevPtr ? segPtr->prevPtr->nextPtr != segPtr
+ : segPtr != linePtr->segPtr) {
+ Tcl_Panic("CheckSegments: wrong predecessor in segment");
+ }
+ if (segPtr->typePtr->group != SEG_GROUP_MARK) {
+ if (segPtr->normalMarkFlag
+ || segPtr->privateMarkFlag
+ || segPtr->currentMarkFlag
+ || segPtr->insertMarkFlag
+ || segPtr->startEndMarkFlag) {
+ Tcl_Panic("CheckSegments: wrong mark flag in segment");
+ }
+ }
+ if (!sharedTextPtr->steadyMarks
+ && segPtr->typePtr->gravity == GRAVITY_RIGHT
+ && segPtr->nextPtr
+ && segPtr->nextPtr->typePtr->gravity == GRAVITY_LEFT) {
+ if (segPtr->typePtr == &tkTextBranchType && segPtr->nextPtr->typePtr == &tkTextLinkType) {
+ Tcl_Panic("CheckSegments: empty branch");
} else {
- break;
+ Tcl_Panic("CheckSegments: wrong segment order for gravity");
}
}
- if (summaryPtr->tagPtr == segPtr->body.toggle.tagPtr) {
- if (!needSummary) {
- Tcl_Panic("ToggleCheckProc: tag present in root node summary");
+ if (!segPtr->nextPtr && segPtr->typePtr != &tkTextCharType) {
+ Tcl_Panic("CheckSegments: line ended with wrong type");
+ }
+ if (!segPtr->sectionPtr) {
+ Tcl_Panic("CheckSegments: segment has no section");
+ }
+ if (segPtr->size > 0) {
+ if (!segPtr->tagInfoPtr) {
+ Tcl_Panic("CheckSegments: segment '%s' has no tag information", segPtr->typePtr->name);
}
- break;
+ if (TkTextTagSetIsEmpty(segPtr->tagInfoPtr)
+ && segPtr->tagInfoPtr != sharedTextPtr->emptyTagInfoPtr) {
+ Tcl_Panic("CheckSegments: should use shared resource if tag info is empty");
+ }
+ if (TkTextTagSetRefCount(segPtr->tagInfoPtr) == 0) {
+ Tcl_Panic("CheckSegments: unreferenced tag info");
+ }
+ if (TkTextTagSetRefCount(segPtr->tagInfoPtr) > 0x3fffffff) {
+ Tcl_Panic("CheckSegments: negative reference count in tag info");
+ }
+ tagonPtr = TkTextTagSetJoin(tagonPtr, segPtr->tagInfoPtr);
+ tagoffPtr = TkTextTagSetIntersect(tagoffPtr, segPtr->tagInfoPtr);
+ } else if (segPtr->tagInfoPtr) {
+ Tcl_Panic("CheckSegments: segment '%s' should not have tag information",
+ segPtr->typePtr->name);
+ }
+ if (segPtr->sectionPtr->linePtr != linePtr) {
+ Tcl_Panic("CheckSegments: segment has wrong line pointer");
+ }
+ if (!segPtr->nextPtr && linePtr->lastPtr != segPtr) {
+ Tcl_Panic("CheckSegments: wrong pointer to last segment");
}
+ if (segPtr->typePtr == &tkTextBranchType) {
+ numBranches += 1;
+ if (numLinks == 0) {
+ startsWithBranch = true;
+ }
+ endsWithBranch = true;
+ endsWithLink = false;
+ } else if (segPtr->typePtr == &tkTextLinkType) {
+ numLinks += 1;
+ if (numBranches == 0) {
+ startsWithLink = true;
+ }
+ endsWithBranch = false;
+ endsWithLink = true;
+ }
+ if (++count > 100000) {
+ Tcl_Panic("CheckSegments: infinite chain of segments");
+ }
+ }
+
+ tagoffPtr = TagSetComplementTo(tagoffPtr, tagonPtr, sharedTextPtr);
+
+ if (!TkTextTagSetIsEqual(linePtr->tagonPtr, tagonPtr)) {
+ Tcl_Panic("CheckSegments: line tagon information is wrong");
+ }
+ if (!TkTextTagSetIsEqual(linePtr->tagoffPtr, tagoffPtr)) {
+ Tcl_Panic("CheckSegments: line tagoff information is wrong");
+ }
+ if (numBranches != linePtr->numBranches) {
+ Tcl_Panic("CheckSegments: wrong branch count %u (expected is %u)",
+ numBranches, linePtr->numBranches);
+ }
+ if (numLinks != linePtr->numLinks) {
+ Tcl_Panic("CheckSegments: wrong link count %u (expected is %u)",
+ numLinks, linePtr->numLinks);
+ }
+ if (startsWithLink && linePtr->logicalLine) {
+ Tcl_Panic("CheckSegments: this line cannot be a logical line");
}
+ if (startsWithBranch && !linePtr->logicalLine) {
+ Tcl_Panic("CheckSegments: this line must be a logical line");
+ }
+ if (linePtr->nextPtr) {
+ if (endsWithBranch && linePtr->nextPtr->logicalLine) {
+ Tcl_Panic("CheckSegments: next line cannot be a logical line");
+ }
+ if (linePtr->logicalLine
+ && !linePtr->nextPtr->logicalLine
+ && (numBranches == 0 || endsWithLink)) {
+ Tcl_Panic("CheckSegments: next line must be a logical line");
+ }
+ }
+
+ TkTextTagSetDecrRefCount(tagonPtr);
+ TkTextTagSetDecrRefCount(tagoffPtr);
+ return true;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * CheckSections --
+ *
+ * This function is invoked to perform consistency checks on the
+ * chain of sections.
+ *
+ * Results:
+ * Returns always true for successful, because in case of an detected
+ * error the panic function will be called.
+ *
+ * Side effects:
+ * If a consistency problem is found the function panics.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+CheckSections(
+ const TkTextLine *linePtr) /* Pointer to line with sections. */
+{
+ const TkTextSection *sectionPtr = linePtr->segPtr->sectionPtr;
+ const TkTextSegment *segPtr;
+ unsigned numSegs, size, length, count, lineSize = 0;
+
+ if (!sectionPtr) {
+ Tcl_Panic("CheckSections: segment has no section");
+ }
+ if (linePtr->segPtr->sectionPtr->segPtr != linePtr->segPtr) {
+ Tcl_Panic("CheckSections: first segment has wrong section pointer");
+ }
+ if (linePtr->segPtr->sectionPtr->prevPtr) {
+ Tcl_Panic("CheckSections: first section has predecessor");
+ }
+
+ for ( ; sectionPtr; sectionPtr = sectionPtr->nextPtr) {
+ segPtr = sectionPtr->segPtr;
+ if (!sectionPtr->linePtr) {
+ Tcl_Panic("CheckSections: section has no line pointer");
+ }
+ if (sectionPtr->prevPtr
+ ? sectionPtr->prevPtr->nextPtr != sectionPtr
+ : sectionPtr->prevPtr != NULL) {
+ Tcl_Panic("CheckSections: wrong predecessor in section");
+ }
+ if (sectionPtr->nextPtr && sectionPtr->nextPtr->prevPtr != sectionPtr) {
+ Tcl_Panic("CheckSegments: wrong successor in segment");
+ }
+ numSegs = 0;
+ size = 0;
+ length = 0;
+ count = 0;
+ for ( ; segPtr && segPtr->sectionPtr == sectionPtr; segPtr = segPtr->nextPtr, numSegs++) {
+ size += segPtr->size;
+ length += 1;
+ if (++count > 4*MAX_TEXT_SEGS) {
+ Tcl_Panic("CheckSections: infinite chain of segments");
+ }
+ }
+ if (!sectionPtr->nextPtr && segPtr) {
+ Tcl_Panic("CheckSections: missing successor in section");
+ }
+ if (sectionPtr->nextPtr && sectionPtr->nextPtr->segPtr != segPtr) {
+ Tcl_Panic("CheckSections: wrong predecessor in section");
+ }
+ if (sectionPtr->length != length) {
+ Tcl_Panic("CheckSections: wrong segment count %d in section (expected is %d)",
+ sectionPtr->length, length);
+ }
+ if (sectionPtr->size != size) {
+ Tcl_Panic("CheckSections: wrong size %d in section (expected is %d)",
+ sectionPtr->size, size);
+ }
+ if (sectionPtr->linePtr != linePtr) {
+ Tcl_Panic("CheckSections: section has wrong line pointer");
+ }
+ if (numSegs < MIN_TEXT_SEGS
+ && sectionPtr->nextPtr
+ && (!sectionPtr->nextPtr
+ || sectionPtr->nextPtr->segPtr->prevPtr->typePtr != &tkTextBranchType
+ || (sectionPtr->prevPtr && sectionPtr->segPtr->typePtr != &tkTextLinkType))
+ && (!sectionPtr->nextPtr
+ || sectionPtr->nextPtr->segPtr->typePtr != &tkTextLinkType
+ || (sectionPtr->prevPtr
+ && sectionPtr->segPtr->prevPtr->typePtr != &tkTextBranchType))) {
+ Tcl_Panic("CheckSections: too few segments in section");
+ }
+ if (numSegs > MAX_TEXT_SEGS) {
+ Tcl_Panic("CheckSections: too many segments in section");
+ }
+ lineSize += sectionPtr->size;
+ }
+
+ if (linePtr->size != lineSize) {
+ Tcl_Panic("CheckSections: wrong size in line");
+ }
+
+ return true;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
- * fill-column: 78
+ * fill-column: 105
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c
index 0958986..48fbdc2 100644
--- a/generic/tkTextDisp.c
+++ b/generic/tkTextDisp.c
@@ -8,13 +8,16 @@
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
*/
-#include "tkInt.h"
#include "tkText.h"
+#include "tkTextTagSet.h"
+#include "tkRangeList.h"
+#include "tkInt.h"
#ifdef _WIN32
#include "tkWinInt.h"
@@ -26,6 +29,22 @@
#include "tkMacOSXInt.h"
#endif
+#include <stdlib.h>
+#include <assert.h>
+
+#ifndef MIN
+# define MIN(a,b) (a < b ? a : b)
+#endif
+#ifndef MAX
+# define MAX(a,b) (a < b ? b : a)
+#endif
+
+#if NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
+
/*
* "Calculations of line pixel heights and the size of the vertical
* scrollbar."
@@ -63,7 +82,7 @@
*
* For further details see the comments before and within the following
* functions below: LayoutDLine, AsyncUpdateLineMetrics, GetYView,
- * GetYPixelCount, TkTextUpdateOneLine, TkTextUpdateLineMetrics.
+ * GetYPixelCount, TkTextUpdateOneLine, UpdateLineMetrics.
*
* For details of the way in which the BTree keeps track of pixel heights, see
* tkTextBTree.c. Basically the BTree maintains two pieces of information: the
@@ -95,24 +114,130 @@
* for large stretches (> TCL_DSTRING_STATIC_SIZE == 200). We could
* reduce the overall memory footprint by copying the result to a plain
* char array after the line breaking process, but that would complicate
- * the code and make performance even worse speedwise. See also TODOs.
- *
- * TODOs:
- *
- * - Move the character collection process from the LayoutProc into
- * LayoutDLine(), so that the collection can be done before actual
- * layout. In this way measuring can look at the following text, too,
- * right from the beginning. Memory handling can also be improved with
- * this. Problem: We don't easily know which chunks are adjacent until
- * all the other chunks have calculated their width. Apparently marks
- * would return width==0. A separate char collection loop would have to
- * know these things.
- *
- * - Use a new context parameter to pass the context from LayoutDLine() to
- * the LayoutProc instead of using a global variable like now. Not
- * pressing until the previous point gets implemented.
+ * the code and make performance even worse speedwise.
+ */
+
+/*
+ * The following macro is used to compare two floating-point numbers to within
+ * a certain degree of scale. Direct comparison fails on processors where the
+ * processor and memory representations of FP numbers of a particular
+ * precision is different (e.g. Intel)
+ */
+
+#define FP_EQUAL_SCALE(double1, double2, scaleFactor) \
+ (fabs((double1) - (double2))*((scaleFactor) + 1.0) < 0.3)
+
+/*
+ * Macro to make debugging/testing logging a little easier.
+ */
+
+#define LOG(toVar,what) \
+ Tcl_SetVar2(textPtr->interp, toVar, NULL, what, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT)
+
+/*
+ * Speed up if the text content only contains monospaced line heights, and line wrapping
+ * is disabled.
+ *
+ * But this speed-up trial seems not to have any effect which is worth the effort,
+ * especially because it can be used only if no line wrapping will be done.
+ */
+
+#define SPEEDUP_MONOSPACED_LINE_HEIGHTS 0
+
+/*
+ * Structure for line break information:
+ */
+
+typedef struct BreakInfo {
+ uint32_t refCount; /* Reference counter, destroy if this counter is going to zero. */
+ char *brks; /* Array of break info, has exactly the char length of the logical line,
+ * each cell is one of LINEBREAK_NOBREAK, LINEBREAK_ALLOWBREAK,
+ * LINEBREAK_MUSTBREAK, or LINEBREAK_INSIDEACHAR. */
+ struct BreakInfo *nextPtr;
+ /* Pointer to break information of successor line. Will only be used
+ * when caching the break information while redrawing tags. */
+} BreakInfo;
+
+/*
+ * The following structure describes one line of the display, which may be
+ * either part or all of one line of the text.
+ */
+
+typedef struct DLine {
+ TkTextIndex index; /* Identifies first character in text that is displayed on this line. */
+ struct DLine *nextPtr; /* Next in list of all display lines for this window. The list is
+ * sorted in order from top to bottom. Note: the next DLine doesn't
+ * always correspond to the next line of text: (a) can have multiple
+ * DLines for one text line (wrapping), (b) can have elided newlines,
+ * and (c) can have gaps where DLine's have been deleted because
+ * they're out of date. */
+ struct DLine *prevPtr; /* Previous in list of all display lines for this window. */
+ TkTextDispChunk *chunkPtr; /* Pointer to first chunk in list of all of those that are displayed
+ * on this line of the screen. */
+ TkTextDispChunk *firstCharChunkPtr;
+ /* Pointer to first chunk in list containing chars, window, or image. */
+ TkTextDispChunk *lastChunkPtr;
+ /* Pointer to last chunk in list containing chars. */
+ TkTextDispChunk *cursorChunkPtr;
+ /* Pointer to chunk which displays the insert cursor. */
+ BreakInfo *breakInfo; /* Line break information of logical line. */
+ uint32_t displayLineNo; /* The number of this display line relative to the related logical
+ * line. */
+ uint32_t hyphenRule; /* Hyphenation rule applied to last char chunk (only if hyphenation
+ * has been applied). */
+ uint32_t byteCount; /* Number of bytes accounted for by this display line, including a
+ * trailing space or newline that isn't actually displayed. */
+ int32_t y; /* Y-position at which line is supposed to be drawn (topmost pixel
+ * of rectangular area occupied by line). */
+ int32_t oldY; /* Y-position at which line currently appears on display. This is
+ * used to move lines by scrolling rather than re-drawing. If 'flags'
+ * have the OLD_Y_INVALID bit set, then we will never examine this
+ * field (which means line isn't currently visible on display and
+ * must be redrawn). */
+ int32_t height; /* Height of line, in pixels. */
+ int32_t baseline; /* Offset of text baseline from y, in pixels. */
+ int32_t spaceAbove; /* How much extra space was added to the top of the line because of
+ * spacing options. This is included in height and baseline. */
+ int32_t spaceBelow; /* How much extra space was added to the bottom of the line because
+ * of spacing options. This is included in height. */
+ uint32_t length; /* Total length of line, in pixels. */
+ uint32_t flags; /* Various flag bits: see below for values. */
+} DLine;
+
+/*
+ * Flag bits for DLine structures:
+ *
+ * HAS_3D_BORDER - Non-zero means that at least one of the chunks in this line has
+ * a 3D border, so itpotentially interacts with 3D borders in
+ * neighboring lines (see DisplayLineBackground).
+ * NEW_LAYOUT - Non-zero means that the line has been re-layed out since the last
+ * time the display was updated.
+ * TOP_LINE - Non-zero means that this was the top line in in the window the last
+ * time that the window was laid out. This is important because a line
+ * may be displayed differently if its at the top or bottom than if
+ * it's in the middle (e.g. beveled edges aren't displayed for middle
+ * lines if the adjacent line has a similar background).
+ * BOTTOM_LINE - Non-zero means that this was the bottom line in the window the last
+ * time that the window was laid out.
+ * OLD_Y_INVALID - The value of oldY in the structure is not valid or useful and should
+ * not be examined. 'oldY' is only useful when the DLine is currently
+ * displayed at a different position and we wish to re-display it via
+ * scrolling, so this means the DLine needs redrawing.
+ * PARAGRAPH_START - We are on the first line of a paragraph (used to choose between
+ * lmargin1, lmargin2).
+ * CURSOR - This display line is displaying the cursor.
*/
+#define HAS_3D_BORDER (1 << 0)
+#define NEW_LAYOUT (1 << 1)
+#define TOP_LINE (1 << 2)
+#define BOTTOM_LINE (1 << 3)
+#define OLD_Y_INVALID (1 << 4)
+#define PARAGRAPH_START (1 << 5)
+#define DELETED (1 << 6) /* for debugging */
+#define LINKED (1 << 7) /* for debugging */
+#define CACHED (1 << 8) /* for debugging */
+
/*
* The following structure describes how to display a range of characters.
* The information is generated by scanning all of the tags associated with
@@ -123,42 +248,40 @@
typedef struct StyleValues {
Tk_3DBorder border; /* Used for drawing background under text.
* NULL means use widget background. */
- int borderWidth; /* Width of 3-D border for background. */
- int relief; /* 3-D relief for background. */
Pixmap bgStipple; /* Stipple bitmap for background. None means
* draw solid. */
XColor *fgColor; /* Foreground color for text. */
+ XColor *eolColor; /* Foreground color for end of line symbol, can be NULL. */
+ XColor *hyphenColor; /* Foreground color for soft hyphens, can be NULL. */
Tk_Font tkfont; /* Font for displaying text. */
- Pixmap fgStipple; /* Stipple bitmap for text and other
- * foreground stuff. None means draw solid.*/
- int justify; /* Justification style for text. */
- int lMargin1; /* Left margin, in pixels, for first display
- * line of each text line. */
- int lMargin2; /* Left margin, in pixels, for second and
- * later display lines of each text line. */
+ Pixmap fgStipple; /* Stipple bitmap for text and other foreground stuff. None means
+ * draw solid.*/
+ TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may be NULL). */
Tk_3DBorder lMarginColor; /* Color of left margins (1 and 2). */
- int offset; /* Offset in pixels of baseline, relative to
- * baseline of line. */
- int overstrike; /* Non-zero means draw overstrike through
- * text. */
- XColor *overstrikeColor; /* Foreground color for overstrike through
- * text. */
- int rMargin; /* Right margin, in pixels. */
Tk_3DBorder rMarginColor; /* Color of right margin. */
- int spacing1; /* Spacing above first dline in text line. */
- int spacing2; /* Spacing between lines of dline. */
- int spacing3; /* Spacing below last dline in text line. */
- TkTextTabArray *tabArrayPtr;/* Locations and types of tab stops (may be
- * NULL). */
- int tabStyle; /* One of TABULAR or WORDPROCESSOR. */
- int underline; /* Non-zero means draw underline underneath
- * text. */
- XColor *underlineColor; /* Foreground color for underline underneath
- * text. */
- int elide; /* Zero means draw text, otherwise not. */
- TkWrapMode wrapMode; /* How to handle wrap-around for this tag.
- * One of TEXT_WRAPMODE_CHAR,
- * TEXT_WRAPMODE_NONE or TEXT_WRAPMODE_WORD.*/
+ XColor *overstrikeColor; /* Foreground color for overstrike through text. */
+ XColor *underlineColor; /* Foreground color for underline underneath text. */
+ char const *lang; /* Language support (may be NULL). */
+ int hyphenRules; /* Hyphenation rules for spelling changes. */
+ int32_t borderWidth; /* Width of 3-D border for background. */
+ int32_t lMargin1; /* Left margin, in pixels, for first display line of each text line. */
+ int32_t lMargin2; /* Left margin, in pixels, for second and later display lines of
+ * each text line. */
+ int32_t offset; /* Offset in pixels of baseline, relative to baseline of line. */
+ int32_t rMargin; /* Right margin, in pixels. */
+ int32_t spacing1; /* Spacing above first dline in text line. */
+ int32_t spacing2; /* Spacing between lines of dline. */
+ int32_t spacing3; /* Spacing below last dline in text line. */
+ uint32_t wrapMode:3; /* How to handle wrap-around for this tag. One of TEXT_WRAPMODE_CHAR,
+ * TEXT_WRAPMODE_NONE, TEXT_WRAPMODE_WORD, or TEXT_WRAPMODE_CODEPOINT.*/
+ uint32_t tabStyle:3; /* One of TABULAR or WORDPROCESSOR. */
+ uint32_t justify:3; /* Justification style for text. */
+ uint32_t relief:3; /* 3-D relief for background. */
+ uint32_t indentBg:1; /* Background will be indented accordingly to the -lmargin1,
+ * and -lmargin2 options. */
+ uint32_t overstrike:1; /* Non-zero means draw overstrike through text. */
+ uint32_t underline:1; /* Non-zero means draw underline underneath text. */
+ uint32_t elide:1; /* Zero means draw text, otherwise not. */
} StyleValues;
/*
@@ -168,459 +291,1155 @@ typedef struct StyleValues {
*/
typedef struct TextStyle {
- int refCount; /* Number of times this structure is
- * referenced in Chunks. */
- GC bgGC; /* Graphics context for background. None means
- * use widget background. */
+ StyleValues *sValuePtr; /* Raw information from which GCs were derived. */
+ Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used to delete entry. */
+ GC bgGC; /* Graphics context for background. None means use widget background. */
GC fgGC; /* Graphics context for foreground. */
GC ulGC; /* Graphics context for underline. */
GC ovGC; /* Graphics context for overstrike. */
- StyleValues *sValuePtr; /* Raw information from which GCs were
- * derived. */
- Tcl_HashEntry *hPtr; /* Pointer to entry in styleTable. Used to
- * delete entry. */
+ GC eolGC; /* Graphics context for end of line symbol. */
+ GC hyphenGC; /* Graphics context for soft hyphen character. */
+ uint32_t refCount; /* Number of times this structure is referenced in Chunks. */
} TextStyle;
/*
- * The following macro determines whether two styles have the same background
- * so that, for example, no beveled border should be drawn between them.
+ * In TkTextDispChunk structures for character segments, the clientData field
+ * points to one of the following structures:
*/
-#define SAME_BACKGROUND(s1, s2) \
- (((s1)->sValuePtr->border == (s2)->sValuePtr->border) \
- && ((s1)->sValuePtr->borderWidth == (s2)->sValuePtr->borderWidth) \
- && ((s1)->sValuePtr->relief == (s2)->sValuePtr->relief) \
- && ((s1)->sValuePtr->bgStipple == (s2)->sValuePtr->bgStipple))
+typedef struct CharInfo {
+ union {
+ const char *chars; /* UTF characters to display. Actually points into the baseChars of
+ * it points points into the baseChars of the base chunk. */
+ struct CharInfo *next; /* Pointer to next free info struct. */
+ } u;
+ int32_t numBytes; /* Number of bytes that belong to this chunk. */
+ int32_t baseOffset; /* Starting offset in baseChars of base chunk; always zero if
+ * context drawing is not used. */
+ TkTextSegment *segPtr; /* Pointer to char segment containing the chars. */
+} CharInfo;
-/*
- * The following macro is used to compare two floating-point numbers to within
- * a certain degree of scale. Direct comparison fails on processors where the
- * processor and memory representations of FP numbers of a particular
- * precision is different (e.g. Intel)
- */
-
-#define FP_EQUAL_SCALE(double1, double2, scaleFactor) \
- (fabs((double1)-(double2))*((scaleFactor)+1.0) < 0.3)
+typedef struct PixelPos {
+ int32_t xFirst, xLast; /* Horizontal pixel position. */
+ int32_t yFirst, yLast; /* Vertical pixel position. */
+} PixelPos;
/*
- * Macro to make debugging/testing logging a little easier.
+ * Overall display information for a text widget:
*/
-#define LOG(toVar,what) \
- Tcl_SetVar2(textPtr->interp, toVar, NULL, (what), \
- TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT)
+typedef struct TextDInfo {
+ Tcl_HashTable styleTable; /* Hash table that maps from StyleValues to TextStyles for this
+ * widget. */
+ DLine *dLinePtr; /* First in list of all display lines for this widget, in order
+ * from top to bottom. */
+ DLine *lastDLinePtr; /* Pointer to last display line in this widget. */
+ TextStyle *defaultStyle; /* Default style. */
+ GC copyGC; /* Graphics context for copying from off-screen pixmaps onto screen. */
+ GC scrollGC; /* Graphics context for copying from one place in the window to
+ * another (scrolling): differs from copyGC in that we need to get
+ * GraphicsExpose events. */
+ GC insertFgGC; /* Graphics context for drawing text "behind" the insert cursor. */
+ double xScrollFirst, xScrollLast;
+ /* Most recent values reported to horizontal scrollbar; used to
+ * eliminate unnecessary reports. */
+ double yScrollFirst, yScrollLast;
+ /* Most recent values reported to vertical scrollbar; used to
+ * eliminate unnecessary reports. */
+ uint32_t firstLineNo; /* Line number of first line in text widget, needed for re-layout. */
+ uint32_t lastLineNo; /* Line number of last line in text widget, needed for re-layout. */
+ int32_t topPixelOffset; /* Identifies first pixel in top display line to display in window. */
+ int32_t newTopPixelOffset; /* Desired first pixel in top display line to display in window. */
+ int32_t x; /* First x-coordinate that may be used for actually displaying line
+ * information. Leaves space for border, etc. */
+ int32_t y; /* First y-coordinate that may be used for actually displaying line
+ * information. Leaves space for border, etc. */
+ int32_t maxX; /* First x-coordinate to right of available space for displaying
+ * lines. */
+ int32_t maxY; /* First y-coordinate below available space for displaying lines. */
+ int32_t topOfEof; /* Top-most pixel (lowest y-value) that has been drawn in the
+ * appropriate fashion for the portion of the window after the last
+ * line of the text. This field is used to figure out when to redraw
+ * part or all of the eof field. */
+ int32_t curYPixelOffset; /* Y offset of the current view. */
+ TkTextSegment *endOfLineSegPtr;
+ /* Holds the end of line symbol (option -showendOfline). */
-/*
- * The following structure describes one line of the display, which may be
- * either part or all of one line of the text.
- */
+ /*
+ * Cache for single lines:
+ */
-typedef struct DLine {
- TkTextIndex index; /* Identifies first character in text that is
- * displayed on this line. */
- int byteCount; /* Number of bytes accounted for by this
- * display line, including a trailing space or
- * newline that isn't actually displayed. */
- int logicalLinesMerged; /* Number of extra logical lines merged into
- * this one due to elided newlines. */
- int y; /* Y-position at which line is supposed to be
- * drawn (topmost pixel of rectangular area
- * occupied by line). */
- int oldY; /* Y-position at which line currently appears
- * on display. This is used to move lines by
- * scrolling rather than re-drawing. If
- * 'flags' have the OLD_Y_INVALID bit set,
- * then we will never examine this field
- * (which means line isn't currently visible
- * on display and must be redrawn). */
- int height; /* Height of line, in pixels. */
- int baseline; /* Offset of text baseline from y, in
- * pixels. */
- int spaceAbove; /* How much extra space was added to the top
- * of the line because of spacing options.
- * This is included in height and baseline. */
- int spaceBelow; /* How much extra space was added to the
- * bottom of the line because of spacing
- * options. This is included in height. */
- Tk_3DBorder lMarginColor; /* Background color of the area corresponding
- * to the left margin of the display line. */
- int lMarginWidth; /* Pixel width of the area corresponding to
- * the left margin. */
- Tk_3DBorder rMarginColor; /* Background color of the area corresponding
- * to the right margin of the display line. */
- int rMarginWidth; /* Pixel width of the area corresponding to
- * the right margin. */
- int length; /* Total length of line, in pixels. */
- TkTextDispChunk *chunkPtr; /* Pointer to first chunk in list of all of
- * those that are displayed on this line of
- * the screen. */
- struct DLine *nextPtr; /* Next in list of all display lines for this
- * window. The list is sorted in order from
- * top to bottom. Note: the next DLine doesn't
- * always correspond to the next line of text:
- * (a) can have multiple DLines for one text
- * line (wrapping), (b) can have elided newlines,
- * and (c) can have gaps where DLine's
- * have been deleted because they're out of
- * date. */
- int flags; /* Various flag bits: see below for values. */
-} DLine;
+ DLine *cachedDLinePtr; /* We are caching some computed display lines for speed enhancements. */
+ DLine *lastCachedDLinePtr; /* Pointer to last cached display line. */
+ unsigned numCachedLines; /* Number of cached display lines. */
+ DLine *lastMetricDLinePtr; /* We are caching the last computed display line in metric computation,
+ * one reason is speed up, but the main reason is to avoid that some
+ * cached data (i.e. brks) will be released to early. */
-/*
- * Flag bits for DLine structures:
- *
- * HAS_3D_BORDER - Non-zero means that at least one of the chunks
- * in this line has a 3D border, so it
- * potentially interacts with 3D borders in
- * neighboring lines (see DisplayLineBackground).
- * NEW_LAYOUT - Non-zero means that the line has been
- * re-layed out since the last time the display
- * was updated.
- * TOP_LINE - Non-zero means that this was the top line in
- * in the window the last time that the window
- * was laid out. This is important because a line
- * may be displayed differently if its at the top
- * or bottom than if it's in the middle
- * (e.g. beveled edges aren't displayed for
- * middle lines if the adjacent line has a
- * similar background).
- * BOTTOM_LINE - Non-zero means that this was the bottom line
- * in the window the last time that the window
- * was laid out.
- * OLD_Y_INVALID - The value of oldY in the structure is not
- * valid or useful and should not be examined.
- * 'oldY' is only useful when the DLine is
- * currently displayed at a different position
- * and we wish to re-display it via scrolling, so
- * this means the DLine needs redrawing.
- */
+ /*
+ * Storage for saved display lines. These lines has been computed for line metric information,
+ * and will be used for displaying afterwards.
+ */
-#define HAS_3D_BORDER 1
-#define NEW_LAYOUT 2
-#define TOP_LINE 4
-#define BOTTOM_LINE 8
-#define OLD_Y_INVALID 16
+ DLine *savedDLinePtr; /* First in list of saved display lines, in order from top to bottom. */
+ DLine *lastSavedDLinePtr;/* Pointer to last saved display line. */
+ int32_t savedDisplayLinesHeight;
+ /* Sum of saved display line heights. */
-/*
- * Overall display information for a text widget:
- */
+ /*
+ * Additional buffers:
+ */
-typedef struct TextDInfo {
- Tcl_HashTable styleTable; /* Hash table that maps from StyleValues to
- * TextStyles for this widget. */
- DLine *dLinePtr; /* First in list of all display lines for this
- * widget, in order from top to bottom. */
- int topPixelOffset; /* Identifies first pixel in top display line
- * to display in window. */
- int newTopPixelOffset; /* Desired first pixel in top display line to
- * display in window. */
- GC copyGC; /* Graphics context for copying from off-
- * screen pixmaps onto screen. */
- GC scrollGC; /* Graphics context for copying from one place
- * in the window to another (scrolling):
- * differs from copyGC in that we need to get
- * GraphicsExpose events. */
- int x; /* First x-coordinate that may be used for
- * actually displaying line information.
- * Leaves space for border, etc. */
- int y; /* First y-coordinate that may be used for
- * actually displaying line information.
- * Leaves space for border, etc. */
- int maxX; /* First x-coordinate to right of available
- * space for displaying lines. */
- int maxY; /* First y-coordinate below available space
- * for displaying lines. */
- int topOfEof; /* Top-most pixel (lowest y-value) that has
- * been drawn in the appropriate fashion for
- * the portion of the window after the last
- * line of the text. This field is used to
- * figure out when to redraw part or all of
- * the eof field. */
+ char *strBuffer; /* We need a string buffer for the line break algorithm. */
+ unsigned strBufferSize; /* Size of the line break string buffer. */
/*
* Information used for scrolling:
*/
- int newXPixelOffset; /* Desired x scroll position, measured as the
- * number of pixels off-screen to the left for
- * a line with no left margin. */
- int curXPixelOffset; /* Actual x scroll position, measured as the
- * number of pixels off-screen to the left. */
- int maxLength; /* Length in pixels of longest line that's
- * visible in window (length may exceed window
- * size). If there's no wrapping, this will be
- * zero. */
- double xScrollFirst, xScrollLast;
- /* Most recent values reported to horizontal
- * scrollbar; used to eliminate unnecessary
- * reports. */
- double yScrollFirst, yScrollLast;
- /* Most recent values reported to vertical
- * scrollbar; used to eliminate unnecessary
- * reports. */
+ int32_t newXPixelOffset; /* Desired x scroll position, measured as the number of pixels
+ * off-screen to the left for a line with no left margin. */
+ int32_t curXPixelOffset; /* Actual x scroll position, measured as the number of pixels
+ * off-screen to the left. */
+ int32_t maxLength; /* Length in pixels of longest line that's visible in window
+ * (length may exceed window size). If there's no wrapping, this
+ * will be zero. */
+ PixelPos curPixelPos; /* Most recent pixel position, used for the "watch" command. */
+ PixelPos prevPixelPos; /* Previous pixel position, used for the "watch" command. */
/*
* The following information is used to implement scanning:
*/
- int scanMarkXPixel; /* Pixel index of left edge of the window when
- * the scan started. */
- int scanMarkX; /* X-position of mouse at time scan started. */
- int scanTotalYScroll; /* Total scrolling (in screen pixels) that has
- * occurred since scanMarkY was set. */
- int scanMarkY; /* Y-position of mouse at time scan started. */
+ int32_t scanMarkXPixel; /* Pixel index of left edge of the window when the scan started. */
+ int32_t scanMarkX; /* X-position of mouse at time scan started. */
+ int32_t scanTotalYScroll; /* Total scrolling (in screen pixels) that has occurred since
+ * scanMarkY was set. */
+ int32_t scanMarkY; /* Y-position of mouse at time scan started. */
+
+ /*
+ * The following is caching the current chunk information:
+ */
+
+ TkTextIndex currChunkIndex; /* Index position of current chunk. */
+ TkTextDispChunk *currChunkPtr;
+ /* This is the chunk currently hovered by mouse. */
+ DLine *currDLinePtr; /* The DLine which contains the current chunk. */
+
+ /*
+ * Cache current y-view position:
+ */
+
+ int32_t topLineNo;
+ int32_t topByteIndex;
+
+ /*
+ * Pools for lines, chunks, char infos, and break infos:
+ */
+
+ DLine *dLinePoolPtr; /* Pointer to first free display line. */
+ TkTextDispChunk *chunkPoolPtr;
+ /* Pointer to first free chunk. */
+ struct TkTextDispChunkSection *sectionPoolPtr;
+ /* Pointer to first free section. */
+ CharInfo *charInfoPoolPtr; /* Pointer to first free char info. */
/*
* Miscellaneous information:
*/
- int dLinesInvalidated; /* This value is set to 1 whenever something
- * happens that invalidates information in
- * DLine structures; if a redisplay is in
- * progress, it will see this and abort the
- * redisplay. This is needed because, for
- * example, an embedded window could change
- * its size when it is first displayed,
- * invalidating the DLine that is currently
- * being displayed. If redisplay continues, it
- * will use freed memory and could dump
- * core. */
- int flags; /* Various flag values: see below for
- * definitions. */
+ bool dLinesInvalidated; /* This value is set to true whenever something happens that
+ * invalidates information in DLine structures; if a redisplay
+ * is in progress, it will see this and abort the redisplay. This
+ * is needed because, for example, an embedded window could change
+ * its size when it is first displayed, invalidating the DLine that
+ * is currently being displayed. If redisplay continues, it will
+ * use freed memory and could dump core. */
+ bool pendingUpdateLineMetricsFinished;
+ /* Did we add RunUpdateLineMetricsFinished to the idle loop? */
+ int32_t flags; /* Various flag values: see below for definitions. */
+ uint32_t countImages; /* Number of displayed images (currently unused except if
+ * SPEEDUP_MONOSPACED_LINE_HEIGHTS is set). */
+ uint32_t countWindows; /* Number of displayed windows. */
+ bool insideLineMetricUpdate;/* Line metric update is currently in progress. */
+
/*
* Information used to handle the asynchronous updating of the y-scrollbar
* and the vertical height calculations:
*/
- int lineMetricUpdateEpoch; /* Stores a number which is incremented each
- * time the text widget changes in a
- * significant way (e.g. resizing or
- * geometry-influencing tag changes). */
- int currentMetricUpdateLine;/* Stores a counter which is used to iterate
- * over the logical lines contained in the
- * widget and update their geometry
- * calculations, if they are out of date. */
- TkTextIndex metricIndex; /* If the current metric update line wraps
- * into very many display lines, then this is
- * used to keep track of what index we've got
+ int lineHeight; /* TkTextRelayoutWindow is using this value: the line height of
+ * monospaced lines, is zero of the line heights are not monospaced
+ * in last call of TkTextRelayoutWindow. */
+ uint32_t lineMetricUpdateEpoch;
+ /* Stores a number which is incremented each time the text widget
+ * changes in a significant way (e.g. resizing or geometry-influencing
+ * tag changes). */
+ uint32_t lineMetricUpdateCounter;
+ /* Count updates of line metric information. */
+ TkRangeList *lineMetricUpdateRanges;
+ /* Stores the range of line numbers which are not yet up-to-date. */
+ TkTextIndex metricIndex; /* If the current metric update line wraps into very many display
+ * lines, then this is used to keep track of what index we've got
* to so far... */
- int metricPixelHeight; /* ...and this is for the height calculation
- * so far...*/
- int metricEpoch; /* ...and this for the epoch of the partial
- * calculation so it can be cancelled if
- * things change once more. This field will be
- * -1 if there is no long-line calculation in
- * progress, and take a non-negative value if
- * there is such a calculation in progress. */
- int lastMetricUpdateLine; /* When the current update line reaches this
- * line, we are done and should stop the
- * asychronous callback mechanism. */
Tcl_TimerToken lineUpdateTimer;
- /* A token pointing to the current line metric
- * update callback. */
+ /* A token pointing to the current line metric update callback. */
Tcl_TimerToken scrollbarTimer;
- /* A token pointing to the current scrollbar
- * update callback. */
+ /* A token pointing to the current scrollbar update callback. */
+ Tcl_TimerToken repickTimer;
+ /* A token pointing to the current repick callback. */
} TextDInfo;
+typedef struct TkTextDispChunkSection {
+ struct TkTextDispChunkSection *nextPtr;
+ /* Next section in chain of display sections. */
+ TkTextDispChunk *chunkPtr; /* First display chunk in this section. */
+ uint32_t numBytes; /* Number of bytes in this section. */
+} TkTextDispChunkSection;
+
/*
- * In TkTextDispChunk structures for character segments, the clientData field
- * points to one of the following structures:
+ * Flag values for TextDInfo structures:
+ *
+ * DINFO_OUT_OF_DATE: Means that the DLine structures for this window are partially or
+ * completely out of date and need to be recomputed.
+ *
+ * REDRAW_PENDING: Means that a when-idle handler has been scheduled to update the display.
+ *
+ * REDRAW_BORDERS: Means window border or pad area has potentially been damaged and must
+ * be redrawn.
+ *
+ * ASYNC_UPDATE: Means that the asynchronous pixel-height calculation is still working.
+ *
+ * ASYNC_PENDING: Means that the asynchronous pixel-height calculation is pending until
+ * the display update (DisplayText) has been finished.
+ *
+ * REPICK_NEEDED: Means that the widget has been modified in a way that could change
+ * the current character (a different character might be under the mouse
+ * cursor now). Need to recompute the current character before the next
+ * redisplay.
*/
-#if !TK_LAYOUT_WITH_BASE_CHUNKS
+#define DINFO_OUT_OF_DATE (1 << 0)
+#define REDRAW_PENDING (1 << 1)
+#define REDRAW_BORDERS (1 << 2)
+#define ASYNC_UPDATE (1 << 3)
+#define ASYNC_PENDING (1 << 4)
+#define REPICK_NEEDED (1 << 5)
-typedef struct CharInfo {
- int numBytes; /* Number of bytes to display. */
- char chars[1]; /* UTF characters to display. Actual size will
- * be numBytes, not 1. THIS MUST BE THE LAST
- * FIELD IN THE STRUCTURE. */
-} CharInfo;
+typedef struct LayoutData {
+ TkText *textPtr;
+ TkTextDispChunk *chunkPtr; /* Start of chunk chain. */
+ TkTextDispChunk *tabChunkPtr;
+ /* Pointer to the chunk containing the previous tab stop. */
+ TkTextDispChunk *firstChunkPtr;
+ /* Pointer to the first chunk. */
+ TkTextDispChunk *lastChunkPtr;
+ /* Pointer to the current chunk. */
+ TkTextDispChunk *firstCharChunkPtr;
+ /* Pointer to the first char/window/image chunk in chain. */
+ TkTextDispChunk *lastCharChunkPtr;
+ /* Pointer to the last char/window/image chunk in chain. */
+ TkTextDispChunk *breakChunkPtr;
+ /* Chunk containing best word break point, if any. */
+ TkTextDispChunk *cursorChunkPtr;
+ /* Pointer to the insert cursor chunk. */
+ TkTextLine *logicalLinePtr; /* Pointer to the logical line. */
+ BreakInfo *breakInfo; /* Line break information of logical line. */
+ const char *brks; /* Buffer for line break information (for TEXT_WRAPMODE_CODEPOINT). */
+ TkTextIndex index; /* Current index. */
+ unsigned countChunks; /* Number of chunks in current display line. */
+ unsigned numBytesSoFar; /* The number of processed bytes (so far). */
+ unsigned byteOffset; /* The byte offset to start of logical line. */
+ unsigned dispLineOffset; /* The byte offset to start of display line. */
+ int increaseNumBytes; /* Increase number of consumed bytes to realize spelling changes. */
+ int decreaseNumBytes; /* Decrease number of displayable bytes to realize spelling changes. */
+ int displayLineNo; /* Current display line number. */
+ int rMargin; /* Right margin width for line. */
+ int hyphenRule; /* Hyphenation rule applied to last char chunk (only in hyphenation
+ * has been applied). */
+ TkTextTabArray *tabArrayPtr;/* Tab stops for line; taken from style for the first character
+ * on line. */
+ int tabStyle; /* One of TABULAR or WORDPROCESSOR. */
+ int tabSize; /* Number of pixels consumed by current tab stop. */
+ int tabIndex; /* Index of the current tab stop. */
+ unsigned tabWidth; /* Default tab width of this widget. */
+ unsigned numSpaces; /* Number of expandable space (needed for full justification). */
+ TkTextJustify justify; /* How to justify line: taken from style for the first character
+ * in this display line. */
+ TkWrapMode wrapMode; /* Wrap mode to use for this chunk. */
+ int maxX; /* Maximal x coord in current line. */
+ int width; /* Maximal x coord in widget. */
+ int x; /* Current x coord. */
+ bool paragraphStart; /* 'true' means that we are on the first line of a paragraph
+ * (used to choose between lmargin1, lmargin2). */
+ bool skipSpaces; /* 'true' means that we have to gobble spaces at start of next
+ * segment. */
+ bool trimSpaces; /* 'true' iff space mode is TEXT_SPACEMODE_TRIM. */
-#else /* TK_LAYOUT_WITH_BASE_CHUNKS */
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+ /*
+ * Support for context drawing.
+ */
-typedef struct CharInfo {
TkTextDispChunk *baseChunkPtr;
- int baseOffset; /* Starting offset in base chunk
- * baseChars. */
- int numBytes; /* Number of bytes that belong to this
- * chunk. */
- const char *chars; /* UTF characters to display. Actually points
- * into the baseChars of the base chunk. Only
- * valid after FinalizeBaseChunk(). */
-} CharInfo;
+ /* The chunk which contains the actual context data. */
+#endif
+} LayoutData;
+
+typedef struct DisplayInfo {
+ int byteOffset; /* Byte offset to start of display line (subtract this offset to
+ * get the index of display line start). */
+ int nextByteOffset; /* Byte offset to start of next display line (add this offset to
+ * get the index of next display line start). */
+ int displayLineNo; /* Number of display line. */
+ unsigned numDispLines; /* Total number of display lines belonging to corresponding logical
+ * line (so far). */
+ int pixels; /* Total height of logical line (so far). */
+ bool isComplete; /* The display line metric is complete for this logical line? */
+ const TkTextDispLineEntry *entry;
+ /* Pointer to entry in display pixel info for displayLineNo. Note
+ * that the predecessing entries can be accessed, but not the successing
+ * entries. */
+ DLine *dLinePtr; /* Cached display lines, produced while ComputeDisplayLineInfo is
+ * computing the line metrics. */
+ DLine *lastDLinePtr; /* Pointer to last cached display line. */
+ unsigned numCachedLines; /* Number of cached lines. */
+ unsigned heightOfCachedLines;
+ /* Sum of cached display line heights. */
+ TkTextIndex index; /* Index where the computation has finished. */
+ TkTextLine *linePtr; /* Logical line, where computation has started. */
+ const TkTextPixelInfo *pixelInfo;
+ /* Pixel information of logical line. */
+ BreakInfo *lineBreakInfo; /* We have to cache the line break information (for
+ * TEXT_WRAPMODE_CODEPOINT), to avoid repeated computations when
+ * scrolling. */
+
+ /*
+ * This attribute is private.
+ */
+
+ TkTextDispLineEntry entryBuffer[2];
+ /* This buffer will be used if the logical line has no entries
+ * (single display line). */
+} DisplayInfo;
/*
- * The BaseCharInfo is a CharInfo with some additional data added.
+ * Action values for FreeDLines:
+ *
+ * DLINE_UNLINK: Free, unlink from current display, and set 'dLinesInvalidated'.
+ *
+ * DLINE_UNLINK_KEEP_BRKS:
+ * Same as DLINE_UNLINK, but do not destroy break info (except if
+ * now outside of peer).
+ *
+ * DLINE_FREE_TEMP: Free, but don't unlink, and also don't set 'dLinesInvalidated'.
+ *
+ * DLINE_CACHE: Don't free, don't unlink, cache this line, and don't set 'dLinesInvalidated'.
+ *
+ * DLINE_METRIC: Don't free, don't unlink, cache this line temporarily, and don't set
+ * 'dLinesInvalidated'.
+ *
+ * DLINE_SAVE: Don't free, unlink, and save this line for displaying later.
*/
-typedef struct BaseCharInfo {
- CharInfo ci;
- Tcl_DString baseChars; /* Actual characters for the stretch of text
- * represented by this base chunk. */
- int width; /* Width in pixels of the whole string, if
- * known, else -1. Valid during
- * LayoutDLine(). */
-} BaseCharInfo;
+typedef enum {
+ DLINE_UNLINK, DLINE_UNLINK_KEEP_BRKS, DLINE_FREE_TEMP, DLINE_CACHE, DLINE_METRIC, DLINE_SAVE
+} FreeDLineAction;
-/* TODO: Thread safety */
-static TkTextDispChunk *baseCharChunkPtr = NULL;
+/*
+ * Maximal number of cached display lines.
+ */
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
+#define MAX_CACHED_DISPLAY_LINES 8
/*
- * Flag values for TextDInfo structures:
- *
- * DINFO_OUT_OF_DATE: Non-zero means that the DLine structures for
- * this window are partially or completely out of
- * date and need to be recomputed.
- * REDRAW_PENDING: Means that a when-idle handler has been
- * scheduled to update the display.
- * REDRAW_BORDERS: Means window border or pad area has
- * potentially been damaged and must be redrawn.
- * REPICK_NEEDED: 1 means that the widget has been modified in a
- * way that could change the current character (a
- * different character might be under the mouse
- * cursor now). Need to recompute the current
- * character before the next redisplay.
+ * We will also mark logical lines with current line metric epoch even if the computation
+ * has been done only partial. In this case we add a special bit to mark it as partially
+ * computed.
*/
-#define DINFO_OUT_OF_DATE 1
-#define REDRAW_PENDING 2
-#define REDRAW_BORDERS 4
-#define REPICK_NEEDED 8
+#define EPOCH_MASK 0x7fffffff
+#define PARTIAL_COMPUTED_BIT 0x80000000
/*
- * Action values for FreeDLines:
- *
- * DLINE_FREE: Free the lines, but no need to unlink them from the
- * current list of actual display lines.
- * DLINE_UNLINK: Free and unlink from current display.
- * DLINE_FREE_TEMP: Free, but don't unlink, and also don't set
- * 'dLinesInvalidated'.
+ * Result values returned by TextGetScrollInfoObj:
*/
-#define DLINE_FREE 0
-#define DLINE_UNLINK 1
-#define DLINE_FREE_TEMP 2
+typedef enum {
+ SCROLL_MOVETO,
+ SCROLL_PAGES,
+ SCROLL_UNITS,
+ SCROLL_ERROR,
+ SCROLL_PIXELS
+} ScrollMethod;
/*
- * The following counters keep statistics about redisplay that can be checked
- * to see how clever this code is at reducing redisplays.
+ * Threshold type for ComputeMissingMetric:
*/
-static int numRedisplays; /* Number of calls to DisplayText. */
-static int linesRedrawn; /* Number of calls to DisplayDLine. */
-static int numCopies; /* Number of calls to XCopyArea to copy part
- * of the screen. */
-static int lineHeightsRecalculated;
- /* Number of line layouts purely for height
- * calculation purposes.*/
+typedef enum {
+ THRESHOLD_BYTE_OFFSET, /* compute until byte offset has been reached */
+ THRESHOLD_LINE_OFFSET, /* compute until display line offset has been reached */
+ THRESHOLD_PIXEL_DISTANCE /* compute until pixel distance has been reached */
+} Threshold;
+
/*
- * Forward declarations for functions defined later in this file:
+ * We don't want less than 10 chunks per display section.
*/
+#define MIN_CHUNKS_PER_SECTION 10
-static void AdjustForTab(TkText *textPtr,
- TkTextTabArray *tabArrayPtr, int index,
- TkTextDispChunk *chunkPtr);
-static void CharBboxProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr, int index, int y,
- int lineHeight, int baseline, int *xPtr,
- int *yPtr, int *widthPtr, int *heightPtr);
-static int CharChunkMeasureChars(TkTextDispChunk *chunkPtr,
- const char *chars, int charsLen,
- int start, int end, int startX, int maxX,
- int flags, int *nextX);
-static void CharDisplayProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr, int x, int y,
- int height, int baseline, Display *display,
- Drawable dst, int screenY);
-static int CharMeasureProc(TkTextDispChunk *chunkPtr, int x);
-static void CharUndisplayProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr);
-#if TK_LAYOUT_WITH_BASE_CHUNKS
-static void FinalizeBaseChunk(TkTextDispChunk *additionalChunkPtr);
-static void FreeBaseChunk(TkTextDispChunk *baseChunkPtr);
-static int IsSameFGStyle(TextStyle *style1, TextStyle *style2);
-static void RemoveFromBaseChunk(TkTextDispChunk *chunkPtr);
-#endif
/*
- * Definitions of elided procs. Compiler can't inline these since we use
- * pointers to these functions. ElideDisplayProc and ElideUndisplayProc are
- * special-cased for speed, as potentially many elided DLine chunks if large,
- * tag toggle-filled elided region.
+ * We don't want more than 20 sections per display line.
*/
-static void ElideBboxProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr, int index, int y,
- int lineHeight, int baseline, int *xPtr,
- int *yPtr, int *widthPtr, int *heightPtr);
+#define MAX_SECTIONS_PER_LINE 20
+
+/*
+ * Forward declarations for functions defined later in this file:
+ */
+
+static void AdjustForTab(LayoutData *data);
+static void ComputeSizeOfTab(LayoutData *data);
+static void ElideBboxProc(TkText *textPtr, TkTextDispChunk *chunkPtr, int index, int y,
+ int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr,
+ int *heightPtr);
static int ElideMeasureProc(TkTextDispChunk *chunkPtr, int x);
-static void DisplayDLine(TkText *textPtr, DLine *dlPtr,
- DLine *prevPtr, Pixmap pixmap);
-static void DisplayLineBackground(TkText *textPtr, DLine *dlPtr,
- DLine *prevPtr, Pixmap pixmap);
+static void DisplayDLine(TkText *textPtr, DLine *dlPtr, DLine *prevPtr, Pixmap pixmap);
+static void DisplayLineBackground(TkText *textPtr, DLine *dlPtr, DLine *prevPtr,
+ Pixmap pixmap);
static void DisplayText(ClientData clientData);
-static DLine * FindDLine(TkText *textPtr, DLine *dlPtr,
- const TkTextIndex *indexPtr);
-static void FreeDLines(TkText *textPtr, DLine *firstPtr,
- DLine *lastPtr, int action);
+static DLine * FindCachedDLine(TkText *textPtr, const TkTextIndex *indexPtr);
+static DLine * FindDLine(TkText *textPtr, DLine *dlPtr, const TkTextIndex *indexPtr);
+static DLine * FreeDLines(TkText *textPtr, DLine *firstPtr, DLine *lastPtr,
+ FreeDLineAction action);
static void FreeStyle(TkText *textPtr, TextStyle *stylePtr);
-static TextStyle * GetStyle(TkText *textPtr, const TkTextIndex *indexPtr);
-static void GetXView(Tcl_Interp *interp, TkText *textPtr,
- int report);
-static void GetYView(Tcl_Interp *interp, TkText *textPtr,
- int report);
-static int GetYPixelCount(TkText *textPtr, DLine *dlPtr);
-static DLine * LayoutDLine(TkText *textPtr,
- const TkTextIndex *indexPtr);
-static int MeasureChars(Tk_Font tkfont, const char *source,
- int maxBytes, int rangeStart, int rangeLength,
- int startX, int maxX, int flags, int *nextXPtr);
-static void MeasureUp(TkText *textPtr,
- const TkTextIndex *srcPtr, int distance,
- TkTextIndex *dstPtr, int *overlap);
-static int NextTabStop(Tk_Font tkfont, int x, int tabOrigin);
+static TextStyle * GetStyle(TkText *textPtr, TkTextSegment *segPtr);
+static void UpdateDefaultStyle(TkText *textPtr);
+static void GetXView(Tcl_Interp *interp, TkText *textPtr, bool report);
+static void GetYView(Tcl_Interp *interp, TkText *textPtr, bool report);
+static unsigned GetYPixelCount(TkText *textPtr, DLine *dlPtr);
+static DLine * LayoutDLine(const TkTextIndex *indexPtr, int displayLineNo);
+static bool MeasureUp(TkText *textPtr, const TkTextIndex *srcPtr, int distance,
+ TkTextIndex *dstPtr, int32_t *overlap);
+static bool MeasureDown(TkText *textPtr, TkTextIndex *srcPtr, int distance,
+ int32_t *overlap, bool saveDisplayLines);
+static int NextTabStop(unsigned tabWidth, int x, int tabOrigin);
static void UpdateDisplayInfo(TkText *textPtr);
static void YScrollByLines(TkText *textPtr, int offset);
static void YScrollByPixels(TkText *textPtr, int offset);
-static int SizeOfTab(TkText *textPtr, int tabStyle,
- TkTextTabArray *tabArrayPtr, int *indexPtr, int x,
- int maxX);
-static void TextChanged(TkText *textPtr,
- const TkTextIndex *index1Ptr,
- const TkTextIndex *index2Ptr);
static void TextInvalidateRegion(TkText *textPtr, TkRegion region);
-static void TextRedrawTag(TkText *textPtr,
- TkTextIndex *index1Ptr, TkTextIndex *index2Ptr,
- TkTextTag *tagPtr, int withTag);
-static void TextInvalidateLineMetrics(TkText *textPtr,
- TkTextLine *linePtr, int lineCount, int action);
-static int CalculateDisplayLineHeight(TkText *textPtr,
- const TkTextIndex *indexPtr, int *byteCountPtr,
- int *mergedLinePtr);
-static void DlineIndexOfX(TkText *textPtr,
- DLine *dlPtr, int x, TkTextIndex *indexPtr);
-static int DlineXOfIndex(TkText *textPtr,
- DLine *dlPtr, int byteIndex);
-static int TextGetScrollInfoObj(Tcl_Interp *interp,
- TkText *textPtr, int objc,
- Tcl_Obj *const objv[], double *dblPtr,
- int *intPtr);
-static void AsyncUpdateLineMetrics(ClientData clientData);
-static void GenerateWidgetViewSyncEvent(TkText *textPtr, Bool InSync);
+static void TextInvalidateLineMetrics(TkText *textPtr, TkTextLine *linePtr,
+ unsigned lineCount, TkTextInvalidateAction action);
+static int CalculateDisplayLineHeight(TkText *textPtr, const TkTextIndex *indexPtr,
+ unsigned *byteCountPtr);
+static TkTextDispChunk * DLineChunkOfX(TkText *textPtr, DLine *dlPtr, int x, TkTextIndex *indexPtr,
+ bool *nearby);
+static void DLineIndexOfX(TkText *textPtr, TkTextDispChunk *chunkPtr, int x,
+ TkTextIndex *indexPtr);
+static int DLineXOfIndex(TkText *textPtr, DLine *dlPtr, int byteIndex);
+static ScrollMethod TextGetScrollInfoObj(Tcl_Interp *interp, TkText *textPtr, int objc,
+ Tcl_Obj *const objv[], double *dblPtr, int *intPtr);
+static void InvokeAsyncUpdateLineMetrics(TkText *textPtr);
+static void InvokeAsyncUpdateYScrollbar(TkText *textPtr);
static void AsyncUpdateYScrollbar(ClientData clientData);
-static int IsStartOfNotMergedLine(TkText *textPtr,
- const TkTextIndex *indexPtr);
+static void AsyncUpdateLineMetrics(ClientData clientData);
+static void UpdateLineMetrics(TkText *textPtr, unsigned doThisMuch);
+static bool TestIfLinesUpToDate(const TkTextIndex *indexPtr);
+static void SaveDisplayLines(TkText *textPtr, DisplayInfo *info, bool append);
+static TkTextLine * ComputeDisplayLineInfo(TkText *textPtr, const TkTextIndex *indexPtr,
+ DisplayInfo *info);
+static void ComputeMissingMetric(TkText *textPtr, DisplayInfo *info,
+ Threshold threshold, int offset);
+static unsigned GetPixelsTo(TkText *textPtr, const TkTextIndex *indexPtr,
+ bool inclusiveLastLine, DisplayInfo *info);
+static unsigned FindDisplayLineOffset(TkText *textPtr, TkTextLine *linePtr, int32_t *distance);
+static void FindDisplayLineStartEnd(TkText *textPtr, TkTextIndex *indexPtr, bool end,
+ int cacheType);
+static void CheckIfLineMetricIsUpToDate(TkText *textPtr);
+static void RunUpdateLineMetricsFinished(ClientData clientData);
+static void CheckLineMetricConsistency(const TkText *textPtr);
+static int ComputeBreakIndex(TkText *textPtr, const TkTextDispChunk *chunkPtr,
+ TkTextSegment *segPtr, int byteOffset, TkWrapMode wrapMode,
+ TkTextSpaceMode spaceMode);
+static int CharChunkMeasureChars(TkTextDispChunk *chunkPtr, const char *chars, int charsLen,
+ int start, int end, int startX, int maxX, int flags, int *nextXPtr);
+static void CharDisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr, int x, int y,
+ int height, int baseline, Display *display, Drawable dst, int screenY);
+static void CharUndisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr);
+static void HyphenUndisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr);
+static void DisplayChars(TkText *textPtr, TkTextDispChunk *chunkPtr, int x, int y,
+ int baseline, Display *display, Drawable dst);
+static int CharMeasureProc(TkTextDispChunk *chunkPtr, int x);
+static void CharBboxProc(TkText *textPtr, TkTextDispChunk *chunkPtr, int index, int y,
+ int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr,
+ int *heightPtr);
+static int MeasureChars(Tk_Font tkfont, const char *source, int maxBytes, int rangeStart,
+ int rangeLength, int startX, int maxX, int flags, int *nextXPtr);
+static CharInfo * AllocCharInfo(TkText *textPtr);
+static void FreeCharInfo(TkText *textPtr, CharInfo *ciPtr);
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+static bool IsSameFGStyle(TextStyle *style1, TextStyle *style2);
+#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
+
+static const TkTextDispChunkProcs layoutCharProcs = {
+ TEXT_DISP_CHAR, /* type */
+ CharDisplayProc, /* displayProc */
+ CharUndisplayProc, /* undisplayProc */
+ CharMeasureProc, /* measureProc */
+ CharBboxProc, /* bboxProc */
+};
+
+#define CHAR_CHUNK_GET_SEGMENT(chunkPtr) (((const CharInfo *) chunkPtr->clientData)->segPtr)
+
+static const TkTextDispChunkProcs layoutHyphenProcs = {
+ TEXT_DISP_HYPHEN, /* type */
+ CharDisplayProc, /* displayProc */
+ HyphenUndisplayProc, /* undisplayProc */
+ CharMeasureProc, /* measureProc */
+ CharBboxProc, /* bboxProc */
+};
+
/*
- * Result values returned by TextGetScrollInfoObj:
+ * Pointer to int, for some portable pointer hacks - it's guaranteed that
+ * 'uintptr_'t and 'void *' are convertible in both directions (C99 7.18.1.4).
+ */
+
+typedef union {
+ void *ptr;
+ uintptr_t flag;
+} __ptr_to_int;
+
+static void * MarkPointer(void *ptr) { __ptr_to_int p; p.ptr = ptr; p.flag |= 1; return p.ptr; }
+
+static const TkTextDispChunkProcs layoutElideProcs = {
+ TEXT_DISP_ELIDED, /* type */
+ NULL, /* displayProc */
+ NULL, /* undisplayProc */
+ ElideMeasureProc, /* measureProc */
+ ElideBboxProc, /* bboxProc */
+};
+
+#if !NDEBUG
+/*
+ * The following counters keep statistics about redisplay that can be checked
+ * to see how clever this code is at reducing redisplays.
+ */
+
+typedef struct Statistic {
+ unsigned numRedisplays; /* Number of calls to DisplayText. */
+ unsigned linesRedrawn; /* Number of calls to DisplayDLine. */
+ unsigned numLayouted; /* Number of calls to LayoutDLine. */
+ unsigned numCopies; /* Number of calls to XCopyArea to copy part of the screen. */
+ unsigned lineHeightsRecalculated;
+ /* Number of line layouts purely for height calculation purposes. */
+ unsigned breakInfo; /* Number of line break computations. */
+ unsigned numCached; /* Number of computed cached lines. */
+ unsigned numHits; /* Number of found cached lines. */
+ unsigned numReused; /* Number of re-used display lines. */
+
+ bool perfFuncIsHooked;
+} Statistic;
+
+static Statistic stats;
+
+static void
+PerfStatistic()
+{
+ if (!tkBTreeDebug) {
+ return;
+ }
+
+ printf("PERFORMANCE -------------------\n");
+ printf("Calls to DisplayText: %6u\n", stats.numRedisplays);
+ printf("Calls to DisplayDLine: %6u\n", stats.linesRedrawn);
+ printf("Calls to LayoutDLine: %6u\n", stats.numLayouted);
+ printf("Calls to XCopyArea: %6u\n", stats.numCopies);
+ printf("Re-used display lines: %6u\n", stats.numReused);
+ printf("Cached display lines: %6u\n", stats.numCached);
+ printf("Found in cache: %6u\n", stats.numHits);
+ printf("Line metric calculation: %6u\n", stats.lineHeightsRecalculated);
+ printf("Break info computation: %6u\n", stats.breakInfo);
+}
+#endif /* NDEBUG */
+
+#if TK_CHECK_ALLOCS
+
+/*
+ * Some stuff for memory checks, and allocation statistic.
+ */
+
+static unsigned tkTextCountNewStyle = 0;
+static unsigned tkTextCountDestroyStyle = 0;
+static unsigned tkTextCountNewChunk = 0;
+static unsigned tkTextCountDestroyChunk = 0;
+static unsigned tkTextCountNewSection = 0;
+static unsigned tkTextCountDestroySection = 0;
+static unsigned tkTextCountNewCharInfo = 0;
+static unsigned tkTextCountDestroyCharInfo = 0;
+static unsigned tkTextCountNewBreakInfo = 0;
+static unsigned tkTextCountDestroyBreakInfo = 0;
+static unsigned tkTextCountNewDLine = 0;
+static unsigned tkTextCountDestroyDLine = 0;
+static unsigned tkTextCountNewDispInfo = 0;
+unsigned tkTextCountDestroyDispInfo = 0; /* referenced in tkTextBTree.c */
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+unsigned tkTextCountNewBaseChars = 0;
+unsigned tkTextCountDestroyBaseChars = 0;
+#endif
+
+extern unsigned tkTextCountDestroySegment;
+extern unsigned tkRangeListCountNew;
+extern unsigned tkRangeListCountDestroy;
+
+static bool hookStatFunc = true;
+
+static void
+AllocStatistic()
+{
+ if (!tkBTreeDebug) {
+ return;
+ }
+
+ printf("--------------------------------\n");
+ printf("ALLOCATION: new destroy\n");
+ printf("--------------------------------\n");
+ printf("DLine: %8u - %8u\n", tkTextCountNewDLine, tkTextCountDestroyDLine);
+ printf("Chunk: %8u - %8u\n", tkTextCountNewChunk, tkTextCountDestroyChunk);
+ printf("Section: %8u - %8u\n", tkTextCountNewSection, tkTextCountDestroySection);
+ printf("CharInfo: %8u - %8u\n", tkTextCountNewCharInfo, tkTextCountDestroyCharInfo);
+ printf("DispInfo: %8u - %8u\n", tkTextCountNewDispInfo, tkTextCountDestroyDispInfo);
+ printf("BreakInfo: %8u - %8u\n", tkTextCountNewBreakInfo, tkTextCountDestroyBreakInfo);
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+ printf("BaseChars: %8u - %8u\n", tkTextCountNewBaseChars, tkTextCountDestroyBaseChars);
+#endif
+ printf("Style: %8u - %8u\n", tkTextCountNewStyle, tkTextCountDestroyStyle);
+ printf("RangeList: %8u - %8u\n", tkRangeListCountNew, tkRangeListCountDestroy);
+
+ if (tkTextCountNewDLine != tkTextCountDestroyDLine
+ || tkTextCountNewChunk != tkTextCountDestroyChunk
+ || tkTextCountNewSection != tkTextCountDestroySection
+ || tkTextCountNewCharInfo != tkTextCountDestroyCharInfo
+ || tkTextCountNewDispInfo != tkTextCountDestroyDispInfo
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+ || tkTextCountNewBaseChars != tkTextCountDestroyBaseChars
+#endif
+ || tkTextCountNewStyle != tkTextCountDestroyStyle
+ || tkRangeListCountNew != tkRangeListCountDestroy) {
+ printf("*** memory leak detected ***\n");
+ }
+}
+#endif /* TK_CHECK_ALLOCS */
+
+/*
+ * Some helpers:
+ */
+
+static const char doNotBreakAtAll[8] = {
+ LINEBREAK_NOBREAK, LINEBREAK_NOBREAK, LINEBREAK_NOBREAK, LINEBREAK_NOBREAK,
+ LINEBREAK_NOBREAK, LINEBREAK_NOBREAK, LINEBREAK_NOBREAK, LINEBREAK_NOBREAK };
+
+static bool IsPowerOf2(unsigned n) { return !(n & (n - 1)); }
+
+static unsigned
+NextPowerOf2(uint32_t n)
+{
+ --n;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+ return ++n;
+}
+
+static bool
+IsExpandableSpace(
+ const char *s)
+{
+ /* Normal space or non-break space? */
+ return UCHAR(s[0]) == 0x20 || (UCHAR(s[0]) == 0xc2 && UCHAR(s[1]) == 0x0a);
+}
+
+static void
+LogTextHeightCalc(
+ TkText *textPtr,
+ const TkTextIndex *indexPtr)
+{
+ char string[TK_POS_CHARS];
+
+ assert(tkTextDebug);
+
+ /*
+ * Debugging is enabled, so keep a log of all the lines whose
+ * height was recalculated. The test suite uses this information.
+ */
+
+ TkTextPrintIndex(textPtr, indexPtr, string);
+ LOG("tk_textHeightCalc", string);
+}
+
+static void
+LogTextRelayout(
+ TkText *textPtr,
+ const TkTextIndex *indexPtr)
+{
+ char string[TK_POS_CHARS];
+
+ assert(tkTextDebug);
+
+ /*
+ * Debugging is enabled, so keep a log of all the lines that
+ * were re-layed out. The test suite uses this information.
+ */
+
+ TkTextPrintIndex(textPtr, indexPtr, string);
+ LOG("tk_textRelayout", string);
+}
+
+static void
+LogTextInvalidateLine(
+ TkText *textPtr,
+ unsigned count)
+{
+ char buffer[4*TCL_INTEGER_SPACE + 3];
+ const TkRangeList *ranges = textPtr->dInfoPtr->lineMetricUpdateRanges;
+ unsigned totalCount = TkRangeListCount(ranges) - count;
+ unsigned totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
+ int lineNum = TkRangeListIsEmpty(ranges) ? -1 : TkRangeListLow(ranges);
+
+ assert(tkTextDebug);
+
+ snprintf(buffer, sizeof(buffer), "%d %u - %u %u", lineNum, totalLines, count, totalCount);
+ LOG("tk_textInvalidateLine", buffer);
+}
+
+static void
+DisplayTextWhenIdle(
+ TkText *textPtr)
+{
+ if (textPtr->sharedTextPtr->allowUpdateLineMetrics && !(textPtr->dInfoPtr->flags & REDRAW_PENDING)) {
+ textPtr->dInfoPtr->flags |= REDRAW_PENDING;
+ Tcl_DoWhenIdle(DisplayText, textPtr);
+ }
+}
+
+static int
+GetLeftLineMargin(
+ const DLine *dlPtr,
+ const StyleValues *sValuePtr)
+{
+ assert(dlPtr);
+ assert(sValuePtr);
+ return (dlPtr->flags & PARAGRAPH_START) ? sValuePtr->lMargin1 : sValuePtr->lMargin2;
+}
+
+#if SPEEDUP_MONOSPACED_LINE_HEIGHTS
+
+static bool
+TestMonospacedLineHeights(
+ const TkText *textPtr)
+{
+ return textPtr->wrapMode == TEXT_WRAPMODE_NONE
+ && textPtr->dInfoPtr->countImages == 0
+ && textPtr->dInfoPtr->countWindows == 0
+ && TkTextTagSetDisjunctiveBits(TkBTreeRootTagInfo(textPtr->sharedTextPtr->tree),
+ textPtr->sharedTextPtr->affectLineHeightTags);
+ return false;
+}
+
+#endif /* SPEEDUP_MONOSPACED_LINE_HEIGHTS */
+
+static bool
+UseMonospacedLineHeights(
+ const TkText *textPtr)
+{
+#if SPEEDUP_MONOSPACED_LINE_HEIGHTS
+ return TestMonospacedLineHeights(textPtr)
+ && TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges);
+#else
+ return false;
+#endif
+}
+
+/*
+ * Some helpers for hyphenation support (Latin-1 only):
+ */
+
+static const unsigned char isVowel[256] = {
+#define _ 0
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 00 - 0f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 10 - 1f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 20 - 2f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 30 - 3f */
+ _, 1, _, _, _, 1, _, _, _, 1, _, _, _, _, _, 1, /* 40 - 4f */
+ _, _, _, _, _, 1, _, _, _, _, _, _, _, _, _, _, /* 50 - 5f */
+ _, 1, _, _, _, 1, _, _, _, 1, _, _, _, _, _, 1, /* 60 - 6f */
+ _, _, _, _, _, 1, _, _, _, _, _, _, _, _, _, _, /* 70 - 7f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 80 - 8f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 90 - 9f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* a0 - af */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* b0 - bf */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* c0 - cf */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* d0 - df */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* e0 - ef */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+#undef _
+};
+
+static const unsigned char isConsonant[256] = {
+#define _ 0
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 00 - 0f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 10 - 1f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 20 - 2f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 30 - 3f */
+ _, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, /* 40 - 4f */
+ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, _, _, _, _, _, /* 50 - 5f */
+ _, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, /* 60 - 6f */
+ 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, _, _, _, _, _, /* 70 - 7f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 80 - 8f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 90 - 9f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* a0 - af */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* b0 - bf */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* c0 - cf */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* d0 - df */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* e0 - ef */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+#undef _
+};
+
+static const unsigned char isUmlaut[256] = {
+#define _ 0
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 00 - 0f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 10 - 1f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 20 - 2f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 30 - 3f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 40 - 4f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 50 - 5f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 60 - 6f */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* 70 - 7f */
+ _, _, _, _, 1, _, _, _, _, _, _, 1, _, _, _, _, /* 80 - 8f */
+ _, _, _, _, _, _, 1, _, _, _, _, _, 1, _, _, _, /* 90 - 9f */
+ _, _, _, _, 1, _, _, _, _, _, _, 1, _, _, _, _, /* a0 - af */
+ _, _, _, _, _, _, 1, _, _, _, _, _, 1, _, _, _, /* b0 - bf */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* c0 - cf */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* d0 - df */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* e0 - ef */
+ _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+#undef _
+};
+
+static const unsigned char umlautToVowel[256] = {
+#define ___ 0
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 00 - 0f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 10 - 1f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 20 - 2f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 30 - 3f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 40 - 4f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 50 - 5f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 60 - 6f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 70 - 7f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 80 - 8f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* 90 - 9f */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* a0 - af */
+ ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, /* b0 - bf */
+ ___, ___, ___, ___, 'A', ___, ___, ___, ___, ___, ___, 'E', ___, ___, ___, ___, /* c0 - cf */
+ ___, ___, ___, ___, ___, ___, 'O', ___, ___, ___, ___, ___, 'U', ___, ___, ___, /* d0 - df */
+ ___, ___, ___, ___, 'a', ___, ___, ___, ___, ___, ___, 'e', ___, ___, ___, ___, /* e0 - ef */
+ ___, ___, ___, ___, ___, ___, 'o', ___, ___, ___, ___, ___, 'u', ___, ___, ___, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+#undef ___
+};
+
+static bool IsVowel(unsigned char c) { return isVowel[c]; }
+static bool IsUmlaut(unsigned char c) { return umlautToVowel[c] != 0; }
+static bool IsConsonant(unsigned char c) { return isConsonant[c]; }
+
+static unsigned char UmlautToVowel(unsigned char c) { return umlautToVowel[c]; }
+static unsigned char ConvertC3Next(unsigned char c) { return 0xc0 | (c - 0x80); }
+
+static bool
+IsUmlautOrVowel(const char *s)
+{
+ return UCHAR(s[0]) == 0xc3 ? isUmlaut[UCHAR(s[1])] : UCHAR(s[0]) < 0x80 && isVowel[UCHAR(s[0])];
+}
+
+static void
+SetupHyphenChars(
+ TkTextSegment *segPtr,
+ unsigned offset)
+{
+ assert(offset <= 2); /* don't exceed 5 characters */
+
+ /*
+ * NOTE: U+2010 (HYPHEN) always has a visible rendition, but U+00AD
+ * (SOFT HYPHEN) is an invisible format character (per definition).
+ * And don't use '-' (U+002D = HYPHEN-MINUS), because the meaning of
+ * this character is contextual. So we have to use U+2010.
+ */
+
+ assert(segPtr->typePtr->group == SEG_GROUP_HYPHEN);
+ assert(sizeof(doNotBreakAtAll) >= 6); /* we need this break array for hyphens */
+
+ memcpy(segPtr->body.chars + offset, "\xe2\x80\x90", 4); /* U+2010 */
+ segPtr->body.hyphen.textSize = 3 + offset;
+}
+
+static bool
+IsDoubleDigraph(
+ char c1,
+ char c2)
+{
+ switch (c1) {
+ case 'c': /* fallthru */ /* c-cs -> cs-cs */
+ case 'z': return c2 == 's'; /* z-zs -> zs-zs */
+ case 'g': /* fallthru */ /* g-gy -> gy-gy */
+ case 'l': /* fallthru */ /* l-ly -> ly-ly */
+ case 'n': /* fallthru */ /* n-ny -> ny-ny */
+ case 't': return c2 == 'y'; /* t-ty -> ty-ty */
+ case 's': return c2 == 'z'; /* s-sz -> sz-sz */
+ }
+ return false;
+}
+
+static bool
+IsHyphenChunk(
+ const TkTextDispChunk *chunkPtr)
+{
+ assert(chunkPtr);
+ return chunkPtr->layoutProcs && chunkPtr->layoutProcs->type == TEXT_DISP_HYPHEN;
+}
+
+static bool
+IsCharChunk(
+ const TkTextDispChunk *chunkPtr)
+{
+ assert(chunkPtr);
+ return chunkPtr->layoutProcs && chunkPtr->layoutProcs->type == TEXT_DISP_CHAR;
+}
+
+static char
+GetLastCharInChunk(
+ const TkTextDispChunk *chunkPtr)
+{
+ const CharInfo *ciPtr;
+
+ if (!chunkPtr) {
+ return '\0';
+ }
+
+ assert(chunkPtr->layoutProcs);
+ assert(chunkPtr->clientData);
+
+ if (!IsCharChunk(chunkPtr)) {
+ return '\0';
+ }
+
+ ciPtr = chunkPtr->clientData;
+ assert(ciPtr->numBytes > 0);
+ return ciPtr->u.chars[ciPtr->baseOffset + ciPtr->numBytes - 1];
+}
+
+static char
+GetSecondLastCharInChunk(
+ const TkTextDispChunk *chunkPtr)
+{
+ const CharInfo *ciPtr;
+
+ if (!chunkPtr || !IsCharChunk(chunkPtr)) {
+ return '\0';
+ }
+
+ ciPtr = chunkPtr->clientData;
+ assert(chunkPtr->clientData);
+ assert(ciPtr->numBytes > 0);
+
+ if (ciPtr->numBytes > 1) {
+ return ciPtr->u.chars[ciPtr->baseOffset + ciPtr->numBytes - 2];
+ }
+ if ((chunkPtr = chunkPtr->prevCharChunkPtr) && IsCharChunk(chunkPtr)) {
+ ciPtr = chunkPtr->clientData;
+ assert(ciPtr->numBytes > 0);
+ return ciPtr->u.chars[ciPtr->baseOffset + ciPtr->numBytes - 1];
+ }
+
+ return '\0';
+}
+
+static int
+FilterHyphenRules(
+ int hyphenRules,
+ const char *lang)
+{
+ if (lang && hyphenRules) {
+ enum {
+ CA_RULES = (1 << TK_TEXT_HYPHEN_GEMINATION),
+ DE_RULES = (1 << TK_TEXT_HYPHEN_CK)|(1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT),
+ HU_RULES = (1 << TK_TEXT_HYPHEN_DOUBLE_DIGRAPH),
+ NL_RULES = (1 << TK_TEXT_HYPHEN_DOUBLE_VOWEL)|(1 << TK_TEXT_HYPHEN_TREMA),
+ NO_RULES = (1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT),
+ PL_RULES = (1 << TK_TEXT_HYPHEN_REPEAT),
+ SV_RULES = (1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT)
+ };
+
+ switch (lang[0]) {
+ case 'c': if (lang[1] == 'a') { hyphenRules &= CA_RULES; }; break;
+ case 'd': if (lang[1] == 'e') { hyphenRules &= DE_RULES; }; break;
+ case 'h': if (lang[1] == 'u') { hyphenRules &= HU_RULES; }; break;
+ case 'p': if (lang[1] == 'l') { hyphenRules &= PL_RULES; }; break;
+ case 's': if (lang[1] == 'v') { hyphenRules &= SV_RULES; }; break;
+ case 'n':
+ switch (lang[1]) {
+ case 'b': /* fallthru */
+ case 'n': /* fallthru */
+ case 'o': hyphenRules &= NO_RULES; break;
+ case 'l': hyphenRules &= NL_RULES; break;
+ }
+ break;
+ }
+ }
+
+ return hyphenRules;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextPendingSync --
+ *
+ * This function checks if any line heights are not up-to-date.
+ *
+ * Results:
+ * Returns boolean 'true' if it is the case, or 'false' if all line
+ * heights are up-to-date.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
*/
-#define TKTEXT_SCROLL_MOVETO 1
-#define TKTEXT_SCROLL_PAGES 2
-#define TKTEXT_SCROLL_UNITS 3
-#define TKTEXT_SCROLL_ERROR 4
-#define TKTEXT_SCROLL_PIXELS 5
+bool
+TkTextPendingSync(
+ const TkText *textPtr) /* Information about text widget. */
+{
+ /*
+ * NOTE: We cannot use
+ *
+ * !TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges)
+ *
+ * because this statement does not guarantee that TkTextRunAfterSyncCmd has
+ * been triggered, and we need the state after triggering.
+ */
+
+ return !!(textPtr->dInfoPtr->flags & (ASYNC_UPDATE|ASYNC_PENDING));
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TestIfLinesUpToDate --
+ *
+ * This function checks whether the lines up to given index
+ * position (inclusive) is up-to-date.
+ *
+ * Results:
+ * Returns boolean 'true' if it is the case, or 'false' if these
+ * line heights aren't up-to-date.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+TestIfLinesUpToDate(
+ const TkTextIndex *indexPtr) /* last line of range (inclusive) */
+{
+ const TkRangeList *ranges;
+
+ assert(indexPtr->textPtr);
+
+ ranges = indexPtr->textPtr->dInfoPtr->lineMetricUpdateRanges;
+
+ if (TkRangeListIsEmpty(ranges)) {
+ return true;
+ }
+
+ return TkTextIndexGetLineNumber(indexPtr, indexPtr->textPtr) < TkRangeListLow(ranges);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InvokeAsyncUpdateYScrollbar --
+ *
+ * This function invokes the update of the vertical scrollbar.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+InvokeAsyncUpdateYScrollbar(
+ TkText *textPtr)
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ assert(!dInfoPtr->scrollbarTimer);
+
+ if (textPtr->syncTime == 0) {
+ AsyncUpdateYScrollbar(textPtr);
+ } else {
+ textPtr->refCount += 1;
+ dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(textPtr->syncTime,
+ AsyncUpdateYScrollbar, textPtr);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InvokeAsyncUpdateLineMetrics --
+ *
+ * This function invokes the update of the line metric calculation.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+InvokeAsyncUpdateLineMetrics(
+ TkText *textPtr)
+{
+ assert(textPtr->sharedTextPtr->allowUpdateLineMetrics);
+
+ if (textPtr->syncTime > 0) {
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ if (!dInfoPtr->lineUpdateTimer) {
+ textPtr->refCount += 1;
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, textPtr);
+ }
+ }
+}
/*
*----------------------------------------------------------------------
@@ -640,46 +1459,144 @@ static int IsStartOfNotMergedLine(TkText *textPtr,
*----------------------------------------------------------------------
*/
+static void
+SetupEolSegment(
+ TkText *textPtr,
+ TextDInfo *dInfoPtr)
+{
+ char eolChar[10];
+ Tcl_UniChar uc;
+ const char *p = textPtr->eolCharPtr ? Tcl_GetString(textPtr->eolCharPtr) : NULL;
+ int len;
+
+ if (!p || !*p) { p = "\xc2\xb6"; /* U+00B6 = PILCROW SIGN */ }
+ len = Tcl_UtfToUniChar(p, &uc);
+ strcpy(eolChar, p);
+ strcpy(eolChar + len, "\n");
+ if (dInfoPtr->endOfLineSegPtr) {
+ TkBTreeFreeSegment(dInfoPtr->endOfLineSegPtr);
+ }
+ dInfoPtr->endOfLineSegPtr = TkBTreeMakeCharSegment(
+ eolChar, len + 1, textPtr->sharedTextPtr->emptyTagInfoPtr);
+}
+
void
TkTextCreateDInfo(
- TkText *textPtr) /* Overall information for text widget. */
+ TkText *textPtr) /* Overall information for text widget. */
{
- register TextDInfo *dInfoPtr;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextBTree tree = sharedTextPtr->tree;
+ TextDInfo *dInfoPtr;
XGCValues gcValues;
+ bool isMonospaced;
- dInfoPtr = ckalloc(sizeof(TextDInfo));
+ dInfoPtr = memset(malloc(sizeof(TextDInfo)), 0, sizeof(TextDInfo));
Tcl_InitHashTable(&dInfoPtr->styleTable, sizeof(StyleValues)/sizeof(int));
- dInfoPtr->dLinePtr = NULL;
dInfoPtr->copyGC = None;
gcValues.graphics_exposures = True;
- dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,
- &gcValues);
- dInfoPtr->topOfEof = 0;
- dInfoPtr->newXPixelOffset = 0;
- dInfoPtr->curXPixelOffset = 0;
- dInfoPtr->maxLength = 0;
+ dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures, &gcValues);
+ dInfoPtr->insertFgGC = None;
dInfoPtr->xScrollFirst = -1;
dInfoPtr->xScrollLast = -1;
dInfoPtr->yScrollFirst = -1;
dInfoPtr->yScrollLast = -1;
- dInfoPtr->scanMarkXPixel = 0;
- dInfoPtr->scanMarkX = 0;
- dInfoPtr->scanTotalYScroll = 0;
- dInfoPtr->scanMarkY = 0;
- dInfoPtr->dLinesInvalidated = 0;
+ dInfoPtr->topLineNo = -1;
+ dInfoPtr->topByteIndex = -1;
dInfoPtr->flags = DINFO_OUT_OF_DATE;
- dInfoPtr->topPixelOffset = 0;
- dInfoPtr->newTopPixelOffset = 0;
- dInfoPtr->currentMetricUpdateLine = -1;
- dInfoPtr->lastMetricUpdateLine = -1;
+ dInfoPtr->lineMetricUpdateRanges = TkRangeListCreate(64);
+ dInfoPtr->firstLineNo = TkBTreeLinesTo(tree, NULL, TkBTreeGetStartLine(textPtr), NULL);
+ dInfoPtr->lastLineNo = TkBTreeLinesTo(tree, NULL, TkBTreeGetLastLine(textPtr), NULL);
dInfoPtr->lineMetricUpdateEpoch = 1;
- dInfoPtr->metricEpoch = -1;
- dInfoPtr->metricIndex.textPtr = NULL;
- dInfoPtr->metricIndex.linePtr = NULL;
- dInfoPtr->lineUpdateTimer = NULL;
- dInfoPtr->scrollbarTimer = NULL;
+ dInfoPtr->strBufferSize = 512;
+ dInfoPtr->strBuffer = malloc(dInfoPtr->strBufferSize);
+ TkTextIndexClear(&dInfoPtr->metricIndex, textPtr);
+ TkTextIndexClear(&dInfoPtr->currChunkIndex, textPtr);
+ SetupEolSegment(textPtr, dInfoPtr);
+
+ if (textPtr->state == TK_TEXT_STATE_NORMAL
+ && textPtr->blockCursorType
+ && textPtr->showInsertFgColor) {
+ XGCValues gcValues;
+ gcValues.foreground = textPtr->insertFgColorPtr->pixel;
+ dInfoPtr->insertFgGC = Tk_GetGC(textPtr->tkwin, GCForeground, &gcValues);
+ }
+
+ /*
+ * Note: Setup of defaultStyle must be postponed.
+ */
textPtr->dInfoPtr = dInfoPtr;
+ isMonospaced = UseMonospacedLineHeights(textPtr);
+
+ if (isMonospaced) {
+ TkBTreeUpdatePixelHeights(textPtr, TkBTreeGetStartLine(textPtr), 1,
+ dInfoPtr->lineMetricUpdateEpoch);
+ } else {
+ dInfoPtr->lineMetricUpdateRanges = TkRangeListAdd(dInfoPtr->lineMetricUpdateRanges, 0, 0);
+ }
+
+ if (!sharedTextPtr->breakInfoTableIsInitialized) {
+ Tcl_InitHashTable(&sharedTextPtr->breakInfoTable, TCL_ONE_WORD_KEYS);
+ sharedTextPtr->breakInfoTableIsInitialized = true;
+ }
+
+ if (sharedTextPtr->allowUpdateLineMetrics) {
+ if (!isMonospaced) {
+ InvokeAsyncUpdateLineMetrics(textPtr);
+ }
+ InvokeAsyncUpdateYScrollbar(textPtr);
+ }
+
+#if TK_CHECK_ALLOCS
+ if (hookStatFunc) {
+ atexit(AllocStatistic);
+ hookStatFunc = false;
+ }
+#endif
+#if !NDEBUG
+ if (!stats.perfFuncIsHooked) {
+ atexit(PerfStatistic);
+ stats.perfFuncIsHooked = true;
+ }
+#endif
+}
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextDeleteBreakInfoTableEntries --
+ *
+ * Delete all cached break information. Normally this table will
+ * be empty when this function is called, but under some specific
+ * conditions the given table will not be empty - this will only
+ * happen if a tag redraw action has been interrupted, and this
+ * will be seldom the case.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some resources might be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextDeleteBreakInfoTableEntries(
+ Tcl_HashTable *breakInfoTable)
+{
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr;
+
+ assert(breakInfoTable);
+
+ for (hPtr = Tcl_FirstHashEntry(breakInfoTable, &search); hPtr; hPtr = Tcl_NextHashEntry(&search)) {
+ BreakInfo *breakInfo = Tcl_GetHashValue(hPtr);
+
+ assert(breakInfo->brks);
+ free(breakInfo->brks);
+ free(breakInfo);
+ DEBUG_ALLOC(tkTextCountDestroyBreakInfo++);
+ }
}
/*
@@ -703,7 +1620,22 @@ void
TkTextFreeDInfo(
TkText *textPtr) /* Overall information for text widget. */
{
- register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ TkTextDispChunk *chunkPtr;
+ TkTextDispChunkSection *sectionPtr;
+ DLine *dlPtr;
+ CharInfo *ciPtr;
+
+ /*
+ * Cancel pending events.
+ */
+
+ if (dInfoPtr->pendingUpdateLineMetricsFinished) {
+ Tcl_CancelIdleCall(RunUpdateLineMetricsFinished, (ClientData) textPtr);
+ }
+ if (dInfoPtr->flags & REDRAW_PENDING) {
+ Tcl_CancelIdleCall(DisplayText, textPtr);
+ }
/*
* Be careful to free up styleTable *after* freeing up all the DLines, so
@@ -713,25 +1645,131 @@ TkTextFreeDInfo(
*/
FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
- Tcl_DeleteHashTable(&dInfoPtr->styleTable);
+ FreeDLines(textPtr, dInfoPtr->savedDLinePtr, NULL, DLINE_FREE_TEMP);
+ FreeDLines(textPtr, NULL, NULL, DLINE_CACHE); /* release cached lines */
+ FreeDLines(textPtr, NULL, NULL, DLINE_METRIC); /* release cached lines */
+
if (dInfoPtr->copyGC != None) {
Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
}
Tk_FreeGC(textPtr->display, dInfoPtr->scrollGC);
- if (dInfoPtr->flags & REDRAW_PENDING) {
- Tcl_CancelIdleCall(DisplayText, textPtr);
+ if (dInfoPtr->insertFgGC != None) {
+ Tk_FreeGC(textPtr->display, dInfoPtr->insertFgGC);
}
- if (dInfoPtr->lineUpdateTimer != NULL) {
+ if (dInfoPtr->lineUpdateTimer) {
Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer);
- textPtr->refCount--;
+ textPtr->refCount -= 1;
dInfoPtr->lineUpdateTimer = NULL;
}
- if (dInfoPtr->scrollbarTimer != NULL) {
+ if (dInfoPtr->scrollbarTimer) {
Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer);
- textPtr->refCount--;
+ textPtr->refCount -= 1;
dInfoPtr->scrollbarTimer = NULL;
}
- ckfree(dInfoPtr);
+ if (dInfoPtr->repickTimer) {
+ Tcl_DeleteTimerHandler(dInfoPtr->repickTimer);
+ textPtr->refCount -= 1;
+ dInfoPtr->repickTimer = NULL;
+ }
+ ciPtr = dInfoPtr->charInfoPoolPtr;
+ while (ciPtr) {
+ CharInfo *nextPtr = ciPtr->u.next;
+ free(ciPtr);
+ DEBUG_ALLOC(tkTextCountDestroyCharInfo++);
+ ciPtr = nextPtr;
+ }
+ sectionPtr = dInfoPtr->sectionPoolPtr;
+ while (sectionPtr) {
+ TkTextDispChunkSection *nextPtr = sectionPtr->nextPtr;
+ free(sectionPtr);
+ DEBUG_ALLOC(tkTextCountDestroySection++);
+ sectionPtr = nextPtr;
+ }
+ chunkPtr = dInfoPtr->chunkPoolPtr;
+ while (chunkPtr) {
+ TkTextDispChunk *nextPtr = chunkPtr->nextPtr;
+ free(chunkPtr);
+ DEBUG_ALLOC(tkTextCountDestroyChunk++);
+ chunkPtr = nextPtr;
+ }
+ dlPtr = dInfoPtr->dLinePoolPtr;
+ while (dlPtr) {
+ DLine *nextPtr = dlPtr->nextPtr;
+ free(dlPtr);
+ DEBUG_ALLOC(tkTextCountDestroyDLine++);
+ dlPtr = nextPtr;
+ }
+ if (dInfoPtr->defaultStyle) {
+#if 0
+ /*
+ * TODO: The following assertion sometimes fails. Luckily it doesn't matter,
+ * because it will be freed anyway, but why can it fail (and only sometimes)?
+ */
+ DEBUG_ALLOC(assert(dInfoPtr->defaultStyle->refCount == 1));
+#endif
+ FreeStyle(textPtr, dInfoPtr->defaultStyle);
+ }
+ Tcl_DeleteHashTable(&dInfoPtr->styleTable);
+ TkRangeListDestroy(&dInfoPtr->lineMetricUpdateRanges);
+ TkBTreeFreeSegment(dInfoPtr->endOfLineSegPtr);
+ free(dInfoPtr->strBuffer);
+ free(dInfoPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextResetDInfo --
+ *
+ * This function will be called when the whole text has been deleted.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Line metrics will be updated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextResetDInfo(
+ TkText *textPtr) /* Overall information for text widget. */
+{
+ TextDInfo *dInfoPtr;
+ TkSharedText *sharedTextPtr;
+ TkTextIndex index1, index2;
+ unsigned lineNo1, lineNo2;
+
+ if (UseMonospacedLineHeights(textPtr)) {
+ return; /* already synchronized */
+ }
+
+ dInfoPtr = textPtr->dInfoPtr;
+ sharedTextPtr = textPtr->sharedTextPtr;
+
+ TkTextIndexSetupToStartOfText(&index1, textPtr, sharedTextPtr->tree);
+ TkTextIndexSetupToEndOfText(&index2, textPtr, sharedTextPtr->tree);
+ TkTextChanged(sharedTextPtr, NULL, &index1, &index2);
+
+ lineNo1 = TkBTreeLinesTo(sharedTextPtr->tree, textPtr, TkTextIndexGetLine(&index1), NULL);
+ lineNo2 = TkBTreeLinesTo(sharedTextPtr->tree, textPtr, TkTextIndexGetLine(&index2), NULL);
+
+ assert(lineNo1 < lineNo2);
+
+ TkRangeListClear(dInfoPtr->lineMetricUpdateRanges);
+ dInfoPtr->lineMetricUpdateRanges =
+ TkRangeListAdd(dInfoPtr->lineMetricUpdateRanges, lineNo1, lineNo2 - 1);
+ dInfoPtr->lineMetricUpdateEpoch = 1;
+ dInfoPtr->topLineNo = -1;
+ dInfoPtr->topByteIndex = -1;
+
+ if (textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ TkTextUpdateLineMetrics(textPtr, lineNo1, lineNo2);
+ }
+
+ FreeDLines(textPtr, NULL, NULL, DLINE_CACHE); /* release cached lines */
+ FreeDLines(textPtr, NULL, NULL, DLINE_METRIC); /* release cached lines */
}
/*
@@ -753,31 +1791,33 @@ TkTextFreeDInfo(
*/
static TextStyle *
-GetStyle(
- TkText *textPtr, /* Overall information about text widget. */
- const TkTextIndex *indexPtr)/* The character in the text for which display
- * information is wanted. */
+MakeStyle(
+ TkText *textPtr,
+ TkTextTag *tagPtr)
{
- TkTextTag **tagPtrs;
- register TkTextTag *tagPtr;
StyleValues styleValues;
TextStyle *stylePtr;
Tcl_HashEntry *hPtr;
- int numTags, isNew, i;
- int isSelected;
+ int isNew;
+ bool isSelected;
XGCValues gcValues;
unsigned long mask;
+
/*
* The variables below keep track of the highest-priority specification
* that has occurred for each of the various fields of the StyleValues.
*/
- int borderPrio, borderWidthPrio, reliefPrio, bgStipplePrio;
- int fgPrio, fontPrio, fgStipplePrio;
- int underlinePrio, elidePrio, justifyPrio, offsetPrio;
- int lMargin1Prio, lMargin2Prio, rMarginPrio;
- int lMarginColorPrio, rMarginColorPrio;
- int spacing1Prio, spacing2Prio, spacing3Prio;
- int overstrikePrio, tabPrio, tabStylePrio, wrapPrio;
+
+ int borderPrio = -1, borderWidthPrio = -1, reliefPrio = -1;
+ int bgStipplePrio = -1, indentBgPrio = -1;
+ int fgPrio = -1, fontPrio = -1, fgStipplePrio = -1;
+ int underlinePrio = -1, elidePrio = -1, justifyPrio = -1, offsetPrio = -1;
+ int lMargin1Prio = -1, lMargin2Prio = -1, rMarginPrio = -1;
+ int lMarginColorPrio = -1, rMarginColorPrio = -1;
+ int spacing1Prio = -1, spacing2Prio = -1, spacing3Prio = -1;
+ int overstrikePrio = -1, tabPrio = -1, tabStylePrio = -1;
+ int wrapPrio = -1, langPrio = -1, hyphenRulesPrio = -1;
+ int eolColorPrio = -1, hyphenColorPrio = -1;
/*
* Find out what tags are present for the character, then compute a
@@ -785,42 +1825,30 @@ GetStyle(
* the tags, saving information for the highest-priority tag).
*/
- tagPtrs = TkBTreeGetTags(indexPtr, textPtr, &numTags);
- borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1;
- fgPrio = fontPrio = fgStipplePrio = -1;
- underlinePrio = elidePrio = justifyPrio = offsetPrio = -1;
- lMargin1Prio = lMargin2Prio = rMarginPrio = -1;
- lMarginColorPrio = rMarginColorPrio = -1;
- spacing1Prio = spacing2Prio = spacing3Prio = -1;
- overstrikePrio = tabPrio = tabStylePrio = wrapPrio = -1;
memset(&styleValues, 0, sizeof(StyleValues));
styleValues.relief = TK_RELIEF_FLAT;
styleValues.fgColor = textPtr->fgColor;
+ styleValues.eolColor = textPtr->eolColor;
+ styleValues.hyphenColor = textPtr->hyphenColor;
styleValues.underlineColor = textPtr->fgColor;
styleValues.overstrikeColor = textPtr->fgColor;
styleValues.tkfont = textPtr->tkfont;
- styleValues.justify = TK_JUSTIFY_LEFT;
+ styleValues.justify = textPtr->justify;
styleValues.spacing1 = textPtr->spacing1;
styleValues.spacing2 = textPtr->spacing2;
styleValues.spacing3 = textPtr->spacing3;
styleValues.tabArrayPtr = textPtr->tabArrayPtr;
styleValues.tabStyle = textPtr->tabStyle;
styleValues.wrapMode = textPtr->wrapMode;
- styleValues.elide = 0;
- isSelected = 0;
+ styleValues.lang = textPtr->lang;
+ styleValues.hyphenRules = textPtr->hyphenRulesPtr ? textPtr->hyphenRules : TK_TEXT_HYPHEN_MASK;
- for (i = 0 ; i < numTags; i++) {
- if (textPtr->selTagPtr == tagPtrs[i]) {
- isSelected = 1;
- break;
- }
- }
+ isSelected = false;
- for (i = 0 ; i < numTags; i++) {
+ for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
Tk_3DBorder border;
XColor *fgColor;
- tagPtr = tagPtrs[i];
border = tagPtr->border;
fgColor = tagPtr->fgColor;
@@ -830,89 +1858,87 @@ GetStyle(
* focus.
*/
- if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) {
- if (textPtr->inactiveSelBorder == NULL
+ if (tagPtr == textPtr->selTagPtr && !(textPtr->flags & HAVE_FOCUS)) {
+ if (!textPtr->inactiveSelBorder) {
+ continue;
+ }
#ifdef MAC_OSX_TK
- /* Don't show inactive selection in disabled widgets. */
- || textPtr->state == TK_TEXT_STATE_DISABLED
-#endif
- ) {
+ /* Don't show inactive selection in disabled widgets. */
+ if (textPtr->state == TK_TEXT_STATE_DISABLED) {
continue;
}
+#endif
border = textPtr->inactiveSelBorder;
}
- if ((tagPtr->selBorder != NULL) && (isSelected)) {
+ if (tagPtr->selBorder && isSelected) {
border = tagPtr->selBorder;
}
-
- if ((tagPtr->selFgColor != None) && (isSelected)) {
+ if (tagPtr->selFgColor != None && isSelected) {
fgColor = tagPtr->selFgColor;
}
-
- if ((border != NULL) && (tagPtr->priority > borderPrio)) {
+ if (border && tagPtr->priority > borderPrio) {
styleValues.border = border;
borderPrio = tagPtr->priority;
}
- if ((tagPtr->borderWidthPtr != NULL)
- && (Tcl_GetString(tagPtr->borderWidthPtr)[0] != '\0')
- && (tagPtr->priority > borderWidthPrio)) {
+ if (tagPtr->borderWidthPtr
+ && Tcl_GetString(tagPtr->borderWidthPtr)[0] != '\0'
+ && tagPtr->priority > borderWidthPrio) {
styleValues.borderWidth = tagPtr->borderWidth;
borderWidthPrio = tagPtr->priority;
}
- if ((tagPtr->reliefString != NULL)
- && (tagPtr->priority > reliefPrio)) {
- if (styleValues.border == NULL) {
+ if (tagPtr->reliefString && tagPtr->priority > reliefPrio) {
+ if (!styleValues.border) {
styleValues.border = textPtr->border;
}
+ assert(tagPtr->relief < 8);
styleValues.relief = tagPtr->relief;
reliefPrio = tagPtr->priority;
}
- if ((tagPtr->bgStipple != None)
- && (tagPtr->priority > bgStipplePrio)) {
+ if (tagPtr->bgStipple != None && tagPtr->priority > bgStipplePrio) {
styleValues.bgStipple = tagPtr->bgStipple;
bgStipplePrio = tagPtr->priority;
}
- if ((fgColor != None) && (tagPtr->priority > fgPrio)) {
+ if (tagPtr->indentBgString != None && tagPtr->priority > indentBgPrio) {
+ assert(tagPtr->indentBg <= 1);
+ styleValues.indentBg = tagPtr->indentBg;
+ indentBgPrio = tagPtr->priority;
+ }
+ if (fgColor != None && tagPtr->priority > fgPrio) {
styleValues.fgColor = fgColor;
fgPrio = tagPtr->priority;
}
- if ((tagPtr->tkfont != None) && (tagPtr->priority > fontPrio)) {
+ if (tagPtr->tkfont != None && tagPtr->priority > fontPrio) {
styleValues.tkfont = tagPtr->tkfont;
fontPrio = tagPtr->priority;
}
- if ((tagPtr->fgStipple != None)
- && (tagPtr->priority > fgStipplePrio)) {
+ if (tagPtr->fgStipple != None && tagPtr->priority > fgStipplePrio) {
styleValues.fgStipple = tagPtr->fgStipple;
fgStipplePrio = tagPtr->priority;
}
- if ((tagPtr->justifyString != NULL)
- && (tagPtr->priority > justifyPrio)) {
+ if (tagPtr->justifyString && tagPtr->priority > justifyPrio) {
+ /* assert(tagPtr->justify < 8); always true due to range */
styleValues.justify = tagPtr->justify;
justifyPrio = tagPtr->priority;
}
- if ((tagPtr->lMargin1String != NULL)
- && (tagPtr->priority > lMargin1Prio)) {
+ if (tagPtr->lMargin1String && tagPtr->priority > lMargin1Prio) {
styleValues.lMargin1 = tagPtr->lMargin1;
lMargin1Prio = tagPtr->priority;
}
- if ((tagPtr->lMargin2String != NULL)
- && (tagPtr->priority > lMargin2Prio)) {
+ if (tagPtr->lMargin2String && tagPtr->priority > lMargin2Prio) {
styleValues.lMargin2 = tagPtr->lMargin2;
lMargin2Prio = tagPtr->priority;
}
- if ((tagPtr->lMarginColor != NULL)
- && (tagPtr->priority > lMarginColorPrio)) {
+ if (tagPtr->lMarginColor && tagPtr->priority > lMarginColorPrio) {
styleValues.lMarginColor = tagPtr->lMarginColor;
lMarginColorPrio = tagPtr->priority;
}
- if ((tagPtr->offsetString != NULL)
- && (tagPtr->priority > offsetPrio)) {
+ if (tagPtr->offsetString && tagPtr->priority > offsetPrio) {
styleValues.offset = tagPtr->offset;
offsetPrio = tagPtr->priority;
}
- if ((tagPtr->overstrikeString != NULL)
- && (tagPtr->priority > overstrikePrio)) {
+ if (tagPtr->overstrikeString && tagPtr->priority > overstrikePrio) {
+ assert(tagPtr->overstrike <= 1);
styleValues.overstrike = tagPtr->overstrike;
overstrikePrio = tagPtr->priority;
if (tagPtr->overstrikeColor != None) {
@@ -921,85 +1947,89 @@ GetStyle(
styleValues.overstrikeColor = fgColor;
}
}
- if ((tagPtr->rMarginString != NULL)
- && (tagPtr->priority > rMarginPrio)) {
+ if (tagPtr->rMarginString && tagPtr->priority > rMarginPrio) {
styleValues.rMargin = tagPtr->rMargin;
rMarginPrio = tagPtr->priority;
}
- if ((tagPtr->rMarginColor != NULL)
- && (tagPtr->priority > rMarginColorPrio)) {
+ if (tagPtr->rMarginColor && tagPtr->priority > rMarginColorPrio) {
styleValues.rMarginColor = tagPtr->rMarginColor;
rMarginColorPrio = tagPtr->priority;
}
- if ((tagPtr->spacing1String != NULL)
- && (tagPtr->priority > spacing1Prio)) {
+ if (tagPtr->spacing1String && tagPtr->priority > spacing1Prio) {
styleValues.spacing1 = tagPtr->spacing1;
spacing1Prio = tagPtr->priority;
}
- if ((tagPtr->spacing2String != NULL)
- && (tagPtr->priority > spacing2Prio)) {
+ if (tagPtr->spacing2String && tagPtr->priority > spacing2Prio) {
styleValues.spacing2 = tagPtr->spacing2;
spacing2Prio = tagPtr->priority;
}
- if ((tagPtr->spacing3String != NULL)
- && (tagPtr->priority > spacing3Prio)) {
+ if (tagPtr->spacing3String && tagPtr->priority > spacing3Prio) {
styleValues.spacing3 = tagPtr->spacing3;
spacing3Prio = tagPtr->priority;
}
- if ((tagPtr->tabStringPtr != NULL)
- && (tagPtr->priority > tabPrio)) {
+ if (tagPtr->tabStringPtr && tagPtr->priority > tabPrio) {
styleValues.tabArrayPtr = tagPtr->tabArrayPtr;
tabPrio = tagPtr->priority;
}
- if ((tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE)
- && (tagPtr->priority > tabStylePrio)) {
+ if (tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE && tagPtr->priority > tabStylePrio) {
+ assert(tagPtr->tabStyle < 8);
styleValues.tabStyle = tagPtr->tabStyle;
tabStylePrio = tagPtr->priority;
}
- if ((tagPtr->underlineString != NULL)
- && (tagPtr->priority > underlinePrio)) {
+ if (tagPtr->eolColor && tagPtr->priority > eolColorPrio) {
+ styleValues.eolColor = tagPtr->eolColor;
+ eolColorPrio = tagPtr->priority;
+ }
+ if (tagPtr->hyphenColor && tagPtr->priority > hyphenColorPrio) {
+ styleValues.hyphenColor = tagPtr->hyphenColor;
+ hyphenColorPrio = tagPtr->priority;
+ }
+ if (tagPtr->underlineString && tagPtr->priority > underlinePrio) {
+ assert(tagPtr->underline <= 1);
styleValues.underline = tagPtr->underline;
underlinePrio = tagPtr->priority;
if (tagPtr->underlineColor != None) {
- styleValues.underlineColor = tagPtr->underlineColor;
+ styleValues.underlineColor = tagPtr->underlineColor;
} else if (fgColor != None) {
- styleValues.underlineColor = fgColor;
+ styleValues.underlineColor = fgColor;
}
}
- if ((tagPtr->elideString != NULL)
- && (tagPtr->priority > elidePrio)) {
+ if (tagPtr->elideString && tagPtr->priority > elidePrio) {
+ assert(tagPtr->elide <= 1);
styleValues.elide = tagPtr->elide;
elidePrio = tagPtr->priority;
}
- if ((tagPtr->wrapMode != TEXT_WRAPMODE_NULL)
- && (tagPtr->priority > wrapPrio)) {
+ if (tagPtr->langPtr && tagPtr->priority > langPrio) {
+ styleValues.lang = tagPtr->lang;
+ langPrio = tagPtr->priority;
+ }
+ if (tagPtr->hyphenRulesPtr && tagPtr->priority > hyphenRulesPrio) {
+ styleValues.hyphenRules = tagPtr->hyphenRules;
+ hyphenRulesPrio = tagPtr->priority;
+ }
+ if (tagPtr->wrapMode != TEXT_WRAPMODE_NULL && tagPtr->priority > wrapPrio) {
+ /* assert(tagPtr->wrapMode < 8); always true due to range */
styleValues.wrapMode = tagPtr->wrapMode;
wrapPrio = tagPtr->priority;
}
}
- if (tagPtrs != NULL) {
- ckfree(tagPtrs);
- }
/*
* Use an existing style if there's one around that matches.
*/
- hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable,
- (char *) &styleValues, &isNew);
+ hPtr = Tcl_CreateHashEntry(&textPtr->dInfoPtr->styleTable, (char *) &styleValues, &isNew);
if (!isNew) {
- stylePtr = Tcl_GetHashValue(hPtr);
- stylePtr->refCount++;
- return stylePtr;
+ return Tcl_GetHashValue(hPtr);
}
/*
* No existing style matched. Make a new one.
*/
- stylePtr = ckalloc(sizeof(TextStyle));
- stylePtr->refCount = 1;
- if (styleValues.border != NULL) {
+ stylePtr = malloc(sizeof(TextStyle));
+ stylePtr->refCount = 0;
+ if (styleValues.border) {
gcValues.foreground = Tk_3DBorderColor(styleValues.border)->pixel;
mask = GCForeground;
if (styleValues.bgStipple != None) {
@@ -1014,6 +2044,18 @@ GetStyle(
mask = GCFont;
gcValues.font = Tk_FontId(styleValues.tkfont);
mask |= GCForeground;
+ if (styleValues.eolColor) {
+ gcValues.foreground = styleValues.eolColor->pixel;
+ stylePtr->eolGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
+ } else {
+ stylePtr->eolGC = None;
+ }
+ if (styleValues.hyphenColor) {
+ gcValues.foreground = styleValues.hyphenColor->pixel;
+ stylePtr->hyphenGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
+ } else {
+ stylePtr->hyphenGC = None;
+ }
gcValues.foreground = styleValues.fgColor->pixel;
if (styleValues.fgStipple != None) {
gcValues.stipple = styleValues.fgStipple;
@@ -1026,16 +2068,74 @@ GetStyle(
stylePtr->ulGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
gcValues.foreground = styleValues.overstrikeColor->pixel;
stylePtr->ovGC = Tk_GetGC(textPtr->tkwin, mask, &gcValues);
- stylePtr->sValuePtr = (StyleValues *)
- Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
+ stylePtr->sValuePtr = (StyleValues *) Tcl_GetHashKey(&textPtr->dInfoPtr->styleTable, hPtr);
stylePtr->hPtr = hPtr;
Tcl_SetHashValue(hPtr, stylePtr);
+ DEBUG_ALLOC(tkTextCountNewStyle++);
+ return stylePtr;
+}
+
+static TextStyle *
+GetStyle(
+ TkText *textPtr, /* Overall information about text widget. */
+ TkTextSegment *segPtr) /* The text for which display information is wanted. */
+{
+ TextStyle *stylePtr;
+ TkTextTag *tagPtr;
+
+ if (segPtr && (tagPtr = TkBTreeGetSegmentTags(textPtr->sharedTextPtr, segPtr, textPtr))) {
+ stylePtr = MakeStyle(textPtr, tagPtr);
+ } else {
+ /*
+ * Take into account that this function can be called before UpdateDefaultStyle
+ * has been called for the first time.
+ */
+ if (!textPtr->dInfoPtr->defaultStyle) {
+ UpdateDefaultStyle(textPtr);
+ }
+ stylePtr = textPtr->dInfoPtr->defaultStyle;
+ }
+
+ stylePtr->refCount += 1;
return stylePtr;
}
/*
*----------------------------------------------------------------------
*
+ * UpdateDefaultStyle --
+ *
+ * This function is called if something has changed, and some DLines
+ * have to be updated.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+UpdateDefaultStyle(
+ TkText *textPtr)
+{
+ TextStyle *stylePtr = MakeStyle(textPtr, NULL);
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ if (stylePtr != dInfoPtr->defaultStyle) {
+ if (dInfoPtr->defaultStyle) {
+ FreeStyle(textPtr, dInfoPtr->defaultStyle);
+ }
+ dInfoPtr->defaultStyle = stylePtr;
+ stylePtr->refCount += 1;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* FreeStyle --
*
* This function is called when a TextStyle structure is no longer
@@ -1055,11 +2155,12 @@ GetStyle(
static void
FreeStyle(
TkText *textPtr, /* Information about overall widget. */
- register TextStyle *stylePtr)
- /* Information about style to free. */
+ TextStyle *stylePtr) /* Information about style to free. */
{
- stylePtr->refCount--;
- if (stylePtr->refCount == 0) {
+ assert(stylePtr);
+ assert(stylePtr->refCount > 0);
+
+ if (--stylePtr->refCount == 0) {
if (stylePtr->bgGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->bgGC);
}
@@ -1072,14 +2173,95 @@ FreeStyle(
if (stylePtr->ovGC != None) {
Tk_FreeGC(textPtr->display, stylePtr->ovGC);
}
+ if (stylePtr->eolGC != None) {
+ Tk_FreeGC(textPtr->display, stylePtr->eolGC);
+ }
+ if (stylePtr->hyphenGC != None) {
+ Tk_FreeGC(textPtr->display, stylePtr->hyphenGC);
+ }
Tcl_DeleteHashEntry(stylePtr->hPtr);
- ckfree(stylePtr);
+ free(stylePtr);
+ DEBUG_ALLOC(tkTextCountDestroyStyle++);
}
}
/*
*----------------------------------------------------------------------
*
+ * IsStartOfNotMergedLine --
+ *
+ * This function checks whether the given index is the start of a
+ * logical line that is not merged with the previous logical line
+ * (due to elision of the eol of the previous line).
+ *
+ * Results:
+ * Returns whether the given index denotes the first index of a
+ * logical line not merged with its previous line.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static bool
+IsStartOfNotMergedLine(
+ const TkTextIndex *indexPtr) /* Index to check. */
+{
+ return TkTextIndexGetLine(indexPtr)->logicalLine
+ ? TkTextIndexIsStartOfLine(indexPtr)
+ : TkTextIndexIsStartOfText(indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * IsSameFGStyle --
+ *
+ * Compare the foreground attributes of two styles. Specifically must
+ * consider: foreground color, font, font style and font decorations,
+ * elide, "offset" and foreground stipple. Do *not* consider: background
+ * color, border, relief or background stipple.
+ *
+ * If we use TkpDrawCharsInContext, we also don't need to check
+ * foreground color, font decorations, elide, offset and foreground
+ * stipple, so all that is left is font (including font size and font
+ * style) and "offset".
+ *
+ * Results:
+ * 'true' if the two styles match, 'false' otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+
+static bool
+IsSameFGStyle(
+ TextStyle *style1,
+ TextStyle *style2)
+{
+ StyleValues *sv1;
+ StyleValues *sv2;
+
+ if (style1 == style2) {
+ return true;
+ }
+
+ sv1 = style1->sValuePtr;
+ sv2 = style2->sValuePtr;
+
+ return sv1->tkfont == sv2->tkfont && sv1->offset == sv2->offset;
+}
+
+#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
+
+/*
+ *----------------------------------------------------------------------
+ *
* LayoutDLine --
*
* This function generates a single DLine structure for a display line
@@ -1101,691 +2283,2556 @@ FreeStyle(
* height is never behind what is known when this function is called
* elsewhere.
*
- * Unfortunately, this function is currently called from many different
- * places, not just to layout a display line for actual display, but also
- * simply to calculate some metric or other of one or more display lines
- * (typically the height). It would be a good idea to do some profiling
- * of typical text widget usage and the way in which this is called and
- * see if some optimization could or should be done.
- *
*----------------------------------------------------------------------
*/
-static DLine *
-LayoutDLine(
- TkText *textPtr, /* Overall information about text widget. */
- const TkTextIndex *indexPtr)/* Beginning of display line. May not
- * necessarily point to a character
- * segment. */
+static TkTextSegment *
+LayoutGetNextSegment(
+ TkTextSegment *segPtr)
{
- register DLine *dlPtr; /* New display line. */
- TkTextSegment *segPtr; /* Current segment in text. */
- TkTextDispChunk *lastChunkPtr;
- /* Last chunk allocated so far for line. */
- TkTextDispChunk *chunkPtr; /* Current chunk. */
- TkTextIndex curIndex;
- TkTextDispChunk *breakChunkPtr;
- /* Chunk containing best word break point, if
- * any. */
- TkTextIndex breakIndex; /* Index of first character in
- * breakChunkPtr. */
- int breakByteOffset; /* Byte offset of character within
- * breakChunkPtr just to right of best break
- * point. */
- int noCharsYet; /* Non-zero means that no characters have been
- * placed on the line yet. */
- int paragraphStart; /* Non-zero means that we are on the first
- * line of a paragraph (used to choose between
- * lmargin1, lmargin2). */
- int justify; /* How to justify line: taken from style for
- * the first character in line. */
- int jIndent; /* Additional indentation (beyond margins) due
- * to justification. */
- int rMargin; /* Right margin width for line. */
- TkWrapMode wrapMode; /* Wrap mode to use for this line. */
- int x = 0, maxX = 0; /* Initializations needed only to stop
- * compiler warnings. */
- int wholeLine; /* Non-zero means this display line runs to
- * the end of the text line. */
- int tabIndex; /* Index of the current tab stop. */
- int gotTab; /* Non-zero means the current chunk contains a
- * tab. */
- TkTextDispChunk *tabChunkPtr;
- /* Pointer to the chunk containing the
- * previous tab stop. */
- int maxBytes; /* Maximum number of bytes to include in this
- * chunk. */
- TkTextTabArray *tabArrayPtr;/* Tab stops for line; taken from style for
- * the first character on line. */
- int tabStyle; /* One of TABULAR or WORDPROCESSOR. */
- int tabSize; /* Number of pixels consumed by current tab
- * stop. */
- TkTextDispChunk *lastCharChunkPtr;
- /* Pointer to last chunk in display lines with
- * numBytes > 0. Used to drop 0-sized chunks
- * from the end of the line. */
- int byteOffset, ascent, descent, code, elide, elidesize;
- StyleValues *sValuePtr;
- TkTextElideInfo info; /* Keep track of elide state. */
+ while ((segPtr = segPtr->nextPtr)) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ return segPtr;
+ }
+ if (segPtr->typePtr == &tkTextBranchType) {
+ segPtr = segPtr->body.branch.nextPtr;
+ }
+ }
+ return NULL;
+}
- /*
- * Create and initialize a new DLine structure.
- */
+static TkTextDispChunk *
+LayoutGetNextCharChunk(
+ TkTextDispChunk *chunkPtr)
+{
+ while ((chunkPtr = chunkPtr->nextPtr)) {
+ switch (chunkPtr->layoutProcs->type) {
+ case TEXT_DISP_CHAR: return chunkPtr;
+ case TEXT_DISP_WINDOW: /* fallthru */
+ case TEXT_DISP_IMAGE: return NULL;
+ case TEXT_DISP_HYPHEN: /* fallthru */
+ case TEXT_DISP_ELIDED: /* fallthru */
+ case TEXT_DISP_CURSOR: break;
+ }
+ }
+ return NULL;
+}
- dlPtr = ckalloc(sizeof(DLine));
- dlPtr->index = *indexPtr;
- dlPtr->byteCount = 0;
- dlPtr->y = 0;
- dlPtr->oldY = 0; /* Only set to avoid compiler warnings. */
- dlPtr->height = 0;
- dlPtr->baseline = 0;
- dlPtr->chunkPtr = NULL;
- dlPtr->nextPtr = NULL;
- dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID;
- dlPtr->logicalLinesMerged = 0;
- dlPtr->lMarginColor = NULL;
- dlPtr->lMarginWidth = 0;
- dlPtr->rMarginColor = NULL;
- dlPtr->rMarginWidth = 0;
+static void
+LayoutSetupDispLineInfo(
+ TkTextPixelInfo *pixelInfo)
+{
+ TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
+ unsigned oldNumDispLines = TkBTreeGetNumberOfDisplayLines(pixelInfo);
- /*
- * This is not necessarily totally correct, where we have merged logical
- * lines. Fixing this would require a quite significant overhaul, though,
- * so currently we make do with this.
- */
+ if (!dispLineInfo) {
+ dispLineInfo = malloc(TEXT_DISPLINEINFO_SIZE(2));
+ DEBUG(memset(dispLineInfo, 0xff, TEXT_DISPLINEINFO_SIZE(2)));
+ DEBUG_ALLOC(tkTextCountNewDispInfo++);
+ pixelInfo->dispLineInfo = dispLineInfo;
+ }
+
+ dispLineInfo->numDispLines = 1;
+ /* remember old display line count, see TkBTreeGetNumberOfDisplayLines */
+ dispLineInfo->entry[1].pixels = oldNumDispLines;
+}
+
+static void
+LayoutUpdateLineHeightInformation(
+ const LayoutData *data,
+ DLine *dlPtr,
+ TkTextLine *linePtr, /* The corresponding logical line. */
+ bool finished, /* Did we finish the layout of a complete logical line? */
+ int hyphenRule) /* Applied hyphen rule; zero if no rule has been applied. */
+{
+ TkText *textPtr = data->textPtr;
+ unsigned epoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
+ TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
+ unsigned oldNumDispLines = TkBTreeGetNumberOfDisplayLines(pixelInfo);
+ TkTextDispLineInfo *dispLineInfo;
+ TkTextLine *nextLogicalLinePtr;
+
+ assert(dlPtr->byteCount > 0);
+ assert(dlPtr->displayLineNo >= 0);
+ assert(linePtr->logicalLine);
+ assert(linePtr == TkBTreeGetLogicalLine(
+ textPtr->sharedTextPtr, textPtr, TkTextIndexGetLine(&dlPtr->index)));
+
+ if (pixelInfo->epoch == epoch) {
+ int lineNo = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL);
+
+ if (TkRangeListContains(textPtr->dInfoPtr->lineMetricUpdateRanges, lineNo)) {
+ int mergedLines = 1;
+
+ nextLogicalLinePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ if (linePtr->nextPtr != nextLogicalLinePtr) {
+ mergedLines = TkBTreeCountLines(textPtr->sharedTextPtr->tree, linePtr,
+ nextLogicalLinePtr) - 1;
+ }
+ TkRangeListRemove(textPtr->dInfoPtr->lineMetricUpdateRanges, lineNo, lineNo + mergedLines);
+ }
- paragraphStart = (indexPtr->byteIndex == 0);
+ return; /* already up-to-date */
+ }
+
+ TK_TEXT_DEBUG(LogTextHeightCalc(textPtr, &dlPtr->index));
+ dispLineInfo = pixelInfo->dispLineInfo;
+ dlPtr->hyphenRule = hyphenRule;
+
+ if (dlPtr->displayLineNo > 0) {
+ TkTextDispLineEntry *dispLineEntry;
+
+ assert(dispLineInfo);
+ assert(data->byteOffset == dispLineInfo->entry[dlPtr->displayLineNo].byteOffset);
+
+ if (dlPtr->displayLineNo >= dispLineInfo->numDispLines
+ && !IsPowerOf2(dlPtr->displayLineNo + 2)) {
+ unsigned size = NextPowerOf2(dlPtr->displayLineNo + 2);
+ dispLineInfo = realloc(dispLineInfo, TEXT_DISPLINEINFO_SIZE(size));
+ DEBUG(memset(dispLineInfo->entry + dlPtr->displayLineNo + 1, 0xff,
+ (size - dlPtr->displayLineNo - 1)*sizeof(dispLineInfo->entry[0])));
+ pixelInfo->dispLineInfo = dispLineInfo;
+ }
+ dispLineInfo->numDispLines = dlPtr->displayLineNo + 1;
+ dispLineEntry = dispLineInfo->entry + dlPtr->displayLineNo;
+ (dispLineEntry + 1)->byteOffset = data->byteOffset + dlPtr->byteCount;
+ (dispLineEntry + 1)->pixels = oldNumDispLines;
+ dispLineEntry->height = dlPtr->height;
+ dispLineEntry->pixels = (dispLineEntry - 1)->pixels + dlPtr->height;
+ dispLineEntry->byteOffset = data->byteOffset;
+ dispLineEntry->hyphenRule = hyphenRule;
+ } else if (!finished) {
+ LayoutSetupDispLineInfo(pixelInfo);
+ dispLineInfo = pixelInfo->dispLineInfo;
+ dispLineInfo->entry[0].height = dlPtr->height;
+ dispLineInfo->entry[0].pixels = dlPtr->height;
+ dispLineInfo->entry[0].byteOffset = data->byteOffset;
+ dispLineInfo->entry[0].hyphenRule = hyphenRule;
+ dispLineInfo->entry[1].byteOffset = data->byteOffset + dlPtr->byteCount;
+ }
+
+ assert(finished || dispLineInfo);
+
+ if (finished) {
+ TkTextLine *nextLogicalLinePtr;
+ unsigned lineHeight, mergedLines, lineNo, numDispLines, i;
+
+ if (dlPtr->displayLineNo > 0) {
+ lineHeight = dispLineInfo->entry[dispLineInfo->numDispLines - 1].pixels;
+ numDispLines = dispLineInfo->numDispLines;
+ } else {
+ lineHeight = dlPtr->height;
+ numDispLines = lineHeight > 0;
+ }
+ nextLogicalLinePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ mergedLines = TkBTreeCountLines(textPtr->sharedTextPtr->tree, linePtr, nextLogicalLinePtr);
+ if (mergedLines > 0) {
+ mergedLines -= 1; /* subtract first line */
+ }
+ if (pixelInfo->height != lineHeight || mergedLines > 0 || numDispLines != oldNumDispLines) {
+ /*
+ * Do this B-Tree update before updating the epoch, because this action
+ * needs the old values.
+ */
+ TkBTreeAdjustPixelHeight(textPtr, linePtr, lineHeight, mergedLines, numDispLines);
+ }
+ if (dispLineInfo && dlPtr->displayLineNo == 0) {
+ /*
+ * This is the right place to destroy the superfluous dispLineInfo. Don't do
+ * this before TkBTreeAdjustPixelHeight has been called, because the latter
+ * function needs the old display line count.
+ */
+ free(dispLineInfo);
+ DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
+ pixelInfo->dispLineInfo = NULL;
+ }
+ textPtr->dInfoPtr->lineMetricUpdateCounter += 1;
+ pixelInfo->epoch = epoch;
+ lineNo = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL);
+ for (i = 0; i < mergedLines; ++i) {
+ pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr = linePtr->nextPtr);
+ pixelInfo->epoch = epoch;
+ if (pixelInfo->dispLineInfo) {
+ free(pixelInfo->dispLineInfo);
+ DEBUG_ALLOC(tkTextCountDestroyDispInfo++);
+ pixelInfo->dispLineInfo = NULL;
+ }
+ }
+ TkRangeListRemove(textPtr->dInfoPtr->lineMetricUpdateRanges, lineNo, lineNo + mergedLines);
+ } else {
+ /*
+ * This line is wrapping into several display lines. We mark it as already
+ * up-to-date, even with a partial computation. This is the right way to
+ * handle very long lines efficiently, because with very long lines the chance
+ * will be high that the lookup into the cache succeeds even with a partial
+ * computation. (If the lookup fails, because the cache for this line is not
+ * yet complete, then the required remaining lines will be computed, and the
+ * result will also be stored in the cache, because all metric computation
+ * will be done with LayoutDLine, and this function is caching any computation.)
+ */
+
+ pixelInfo->epoch = epoch | PARTIAL_COMPUTED_BIT;
+ }
+}
+
+static unsigned
+LayoutComputeBreakLocations(
+ LayoutData *data)
+{
+ unsigned totalSize = 0;
+ TkText *textPtr = data->textPtr;
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ TkTextSegment *segPtr = data->logicalLinePtr->segPtr;
+ bool useUniBreak = data->textPtr->useUniBreak;
+ char const *lang = useUniBreak ? textPtr->lang : NULL;
+ char const *nextLang = NULL;
+ unsigned capacity = dInfoPtr->strBufferSize;
+ char *str = dInfoPtr->strBuffer;
+ char *brks = textPtr->brksBuffer;
/*
- * Special case entirely elide line as there may be 1000s or more.
+ * The codepoint line break computation requires the whole logical line (due to a
+ * poor design of libunibreak), but separated by languages, because this line break
+ * algorithm is in general language dependent.
*/
- elide = TkTextIsElided(textPtr, indexPtr, &info);
- if (elide && indexPtr->byteIndex == 0) {
- maxBytes = 0;
- for (segPtr = info.segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
- if (segPtr->size > 0) {
- if (elide == 0) {
- /*
- * We toggled a tag and the elide state changed to
- * visible, and we have something of non-zero size.
- * Therefore we must bail out.
- */
+ while (segPtr) {
+ unsigned size = 0;
+ unsigned newTotalSize;
- break;
+ for ( ; segPtr; segPtr = segPtr->nextPtr) {
+ switch ((int) segPtr->typePtr->group) {
+ case SEG_GROUP_CHAR: {
+ unsigned newSize;
+
+ if (useUniBreak) {
+ const char *myLang = TkBTreeGetLang(textPtr, segPtr);
+
+ if (myLang[0] != lang[0] || myLang[1] != lang[1]) {
+ nextLang = myLang;
+ break;
+ }
+ }
+ if ((newSize = size + segPtr->size) >= capacity) {
+ capacity = MAX(2*capacity, newSize + 1);
+ str = realloc(str, newSize);
+ }
+ memcpy(str + size, segPtr->body.chars, segPtr->size);
+ size = newSize;
+ break;
+ }
+ case SEG_GROUP_HYPHEN:
+ if (useUniBreak) {
+ const char *myLang = TkBTreeGetLang(textPtr, segPtr);
+
+ if (myLang[0] != lang[0] || myLang[1] != lang[1]) {
+ nextLang = myLang;
+ break;
+ }
+ }
+ if (size + 1 >= capacity) {
+ assert(2*capacity > size + 1);
+ str = realloc(str, capacity *= 2);
}
- maxBytes += segPtr->size;
/*
- * Reset tag elide priority, since we're on a new character.
+ * Use TAB (U+0009) instead of SHY (U+00AD), because SHY needs two bytes,
+ * but TAB needs only one byte, and this corresponds to the byte size of
+ * a hyphen segment. The TAB character has the same character class as
+ * the SHY character, so it's a proper substitution.
+ *
+ * NOTE: Do not use '-' (U+002D) for substitution, because the meaning
+ * of this character is contextual.
*/
- } else if ((segPtr->typePtr == &tkTextToggleOffType)
- || (segPtr->typePtr == &tkTextToggleOnType)) {
- TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
+ str[size++] = '\t';
+ break;
+ case SEG_GROUP_IMAGE:
+ case SEG_GROUP_WINDOW:
+ /* The language variable doesn't matter here. */
+ if (size + 1 >= capacity) {
+ assert(2*capacity > size + 1);
+ str = realloc(str, capacity *= 2);
+ }
+ /* Substitute with a TAB, so we can break at this point. */
+ str[size++] = '\t';
+ break;
+ case SEG_GROUP_BRANCH:
+ segPtr = segPtr->body.branch.nextPtr;
+ break;
+ }
+ }
+ if (size > 0) {
+ newTotalSize = totalSize + size;
+ if (newTotalSize > textPtr->brksBufferSize) {
/*
- * The elide state only changes if this tag is either the
- * current highest priority tag (and is therefore being
- * toggled off), or it's a new tag with higher priority.
+ * Take into account that the buffer must be a bit larger, because we need
+ * one additional byte for trailing NUL (see below).
*/
+ textPtr->brksBufferSize = MAX(newTotalSize, textPtr->brksBufferSize + 512);
+ textPtr->brksBuffer = realloc(textPtr->brksBuffer, textPtr->brksBufferSize + 1);
+ brks = textPtr->brksBuffer;
+ }
- if (tagPtr->elideString != NULL) {
- info.tagCnts[tagPtr->priority]++;
- if (info.tagCnts[tagPtr->priority] & 1) {
- info.tagPtrs[tagPtr->priority] = tagPtr;
- }
- if (tagPtr->priority >= info.elidePriority) {
- if (segPtr->typePtr == &tkTextToggleOffType) {
- /*
- * If it is being toggled off, and it has an elide
- * string, it must actually be the current highest
- * priority tag, so this check is redundant:
- */
-
- if (tagPtr->priority != info.elidePriority) {
- Tcl_Panic("Bad tag priority being toggled off");
- }
+ str[size] = '\0'; /* TkTextComputeBreakLocations expects traling nul */
+ TkTextComputeBreakLocations(data->textPtr->interp, str, size,
+ lang ? (*lang ? lang : "en") : NULL, brks + totalSize);
+ totalSize = newTotalSize;
+ }
+ lang = nextLang;
+ }
- /*
- * Find previous elide tag, if any (if not then
- * elide will be zero, of course).
- */
-
- elide = 0;
- while (--info.elidePriority > 0) {
- if (info.tagCnts[info.elidePriority] & 1) {
- elide = info.tagPtrs[info.elidePriority]
- ->elide;
- break;
- }
- }
- } else {
- elide = tagPtr->elide;
- info.elidePriority = tagPtr->priority;
- }
+ dInfoPtr->strBuffer = str;
+ dInfoPtr->strBufferSize = capacity;
+
+ return totalSize;
+}
+
+static void
+LayoutLookAheadChars(
+ TkTextDispChunk *chunkPtr,
+ const char *str,
+ unsigned numChars,
+ char *buf)
+{
+ TkTextSegment *segPtr = ((CharInfo *) chunkPtr->clientData)->segPtr;
+
+ for ( ; numChars > 0; --numChars) {
+ if (!*str) {
+ segPtr = LayoutGetNextSegment(segPtr);
+ if (!segPtr) {
+ memset(buf, '\0', numChars);
+ return;
+ }
+ str = segPtr->body.chars;
+ }
+ *buf++ = *str++;
+ }
+}
+
+static void
+LayoutApplyHyphenRules(
+ LayoutData *data,
+ TkTextDispChunk *prevCharChunkPtr,
+ TkTextDispChunk *hyphenChunkPtr,
+ TkTextDispChunk *nextCharChunkPtr)
+{
+ TkTextSegment *hyphenPtr = hyphenChunkPtr->clientData;
+ const StyleValues *sValPtr = hyphenChunkPtr->stylePtr->sValuePtr;
+ int hyphenRules = sValPtr->hyphenRules & hyphenChunkPtr->hyphenRules;
+
+ data->increaseNumBytes = 0;
+ data->decreaseNumBytes = 0;
+ SetupHyphenChars(hyphenPtr, 0);
+ hyphenRules = FilterHyphenRules(hyphenRules, sValPtr->lang);
+
+ if (hyphenRules) {
+ const CharInfo *prevCiPtr;
+ const CharInfo *nextCiPtr;
+ const char *prevCharPtr;
+ const char *nextCharPtr;
+ unsigned char prevChar;
+ unsigned char nextChar;
+ char lookAhead[3];
+
+ if (hyphenRules & (1 << TK_TEXT_HYPHEN_REPEAT)) {
+ data->increaseNumBytes = -1;
+ data->hyphenRule = TK_TEXT_HYPHEN_REPEAT;
+ return;
+ }
+
+ if (!IsCharChunk(prevCharChunkPtr)) {
+ return;
+ }
+
+ while ((prevCiPtr = prevCharChunkPtr->clientData)->numBytes == 0) {
+ if (!(prevCharChunkPtr = prevCharChunkPtr->prevCharChunkPtr)
+ || !IsCharChunk(prevCharChunkPtr)) {
+ return;
+ }
+ }
+ prevCharPtr = prevCiPtr->u.chars + prevCiPtr->baseOffset + prevCiPtr->numBytes - 1;
+
+ /*
+ * We know that we have to inspect only Latin-1 characters, either
+ * from ASCII code page (< 0x80), or starting with 0xc3.
+ */
+
+ if (UCHAR(prevCharPtr[0]) < 0x80) {
+ prevChar = UCHAR(prevCharPtr[0]);
+ } else if (prevCiPtr->numBytes > 1 && UCHAR(prevCharPtr[-1]) == 0xc3) {
+ prevChar = ConvertC3Next(prevCharPtr[1]);
+ } else {
+ return;
+ }
+
+ if (hyphenRules & (1 << TK_TEXT_HYPHEN_DOUBLE_VOWEL)) {
+ /* op(aa-)tje -> op(a-)tje */
+ /* caf(ee-)tje -> caf(é-)tje */
+ if (IsVowel(prevChar)) {
+ char secondPrevChar = '\0';
+
+ if (prevCiPtr->numBytes > 1) {
+ secondPrevChar = prevCharPtr[-1];
+ } else {
+ const TkTextDispChunk *chunkPtr = prevCharChunkPtr->prevCharChunkPtr;
+ if (chunkPtr && IsCharChunk(chunkPtr)) {
+ const TkTextSegment *segPtr = CHAR_CHUNK_GET_SEGMENT(chunkPtr);
+ secondPrevChar = segPtr->body.chars[segPtr->size - 1];
}
}
+ if (prevChar == secondPrevChar) {
+ if (prevChar == 'e') {
+ char *s = hyphenPtr->body.chars; /* this avoids warnings */
+ data->decreaseNumBytes = 2;
+ s[0] = 0xc3; s[1] = 0xa9; /* 'é' = U+00E9 */
+ SetupHyphenChars(hyphenPtr, 2);
+ } else {
+ data->decreaseNumBytes = 1;
+ }
+ data->hyphenRule = TK_TEXT_HYPHEN_DOUBLE_VOWEL;
+ return;
+ }
}
}
- if (elide) {
- dlPtr->byteCount = maxBytes;
- dlPtr->spaceAbove = dlPtr->spaceBelow = dlPtr->length = 0;
- if (dlPtr->index.byteIndex == 0) {
- /*
- * Elided state goes from beginning to end of an entire
- * logical line. This means we can update the line's pixel
- * height, and bring its pixel calculation up to date.
- */
-
- TkBTreeLinePixelEpoch(textPtr, dlPtr->index.linePtr)
- = textPtr->dInfoPtr->lineMetricUpdateEpoch;
+ if (!IsCharChunk(nextCharChunkPtr)) {
+ return;
+ }
+ if ((nextCiPtr = nextCharChunkPtr->clientData)->numBytes == 0) {
+ TkTextSegment *segPtr = LayoutGetNextSegment(nextCharChunkPtr->clientData);
+ if (!segPtr) {
+ return;
+ }
+ nextCharPtr = segPtr->body.chars;
+ } else {
+ nextCharPtr = nextCiPtr->u.chars + nextCiPtr->baseOffset;
+ }
+ if (UCHAR(nextCharPtr[0]) < 0x80) {
+ nextChar = UCHAR(nextCharPtr[0]);
+ } else if (UCHAR(nextCharPtr[0]) == 0xc3) {
+ nextChar = ConvertC3Next(nextCharPtr[1]);
+ } else {
+ return;
+ }
- if (TkBTreeLinePixelCount(textPtr,dlPtr->index.linePtr) != 0) {
- TkBTreeAdjustPixelHeight(textPtr,
- dlPtr->index.linePtr, 0, 0);
+ if (hyphenRules & (1 << TK_TEXT_HYPHEN_CK)) {
+ /* Dru(c-k)er -> Dru(k-k)er */
+ if (prevChar == UCHAR('c') && nextChar == UCHAR('k')) {
+ data->decreaseNumBytes = 1;
+ hyphenPtr->body.chars[0] = 'k';
+ SetupHyphenChars(hyphenPtr, 1);
+ data->hyphenRule = TK_TEXT_HYPHEN_CK;
+ return;
+ }
+ }
+ if (hyphenRules & (1 << TK_TEXT_HYPHEN_DOUBLE_DIGRAPH)) {
+ /* vi(s-sz)a -> vi(sz-sz)a */
+ if (prevChar == nextChar) {
+ LayoutLookAheadChars(nextCharChunkPtr, nextCharPtr + 1, 1, lookAhead);
+ if (lookAhead[0] && IsDoubleDigraph(prevChar, lookAhead[0])) {
+ hyphenPtr->body.chars[0] = lookAhead[0];
+ SetupHyphenChars(hyphenPtr, 1);
+ data->hyphenRule = TK_TEXT_HYPHEN_DOUBLE_DIGRAPH;
+ return;
+ }
+ }
+ }
+ if (hyphenRules & (1 << TK_TEXT_HYPHEN_TREMA)) {
+ /* r(e-ëe)l -> r(e-ee)l */
+ if (IsVowel(prevChar) && IsUmlaut(nextChar)) {
+ data->hyphenRule = TK_TEXT_HYPHEN_TREMA;
+ return;
+ }
+ }
+ if (hyphenRules & (1 << TK_TEXT_HYPHEN_GEMINATION)) {
+ /* para(-l·l)el -> para(l-l)el */
+ if (tolower(nextChar) == 'l') {
+ LayoutLookAheadChars(nextCharChunkPtr, nextCharPtr + 1, 3, lookAhead);
+ /* test for U+00B7 = MIDDOT */
+ if (UCHAR(lookAhead[0]) == 0xc2
+ && UCHAR(lookAhead[1]) == 0xb7
+ && lookAhead[2] == nextChar) {
+ data->increaseNumBytes = 3;
+ hyphenPtr->body.chars[0] = nextChar;
+ SetupHyphenChars(hyphenPtr, 1);
+ data->hyphenRule = TK_TEXT_HYPHEN_GEMINATION;
+ return;
}
}
- TkTextFreeElideInfo(&info);
- return dlPtr;
}
}
- TkTextFreeElideInfo(&info);
+}
+
+static unsigned
+LayoutMakeCharInfo(
+ LayoutData *data,
+ TkTextSegment *segPtr,
+ int byteOffset,
+ int maxBytes)
+{
+ char const *p = segPtr->body.chars + byteOffset;
+ CharInfo *ciPtr = AllocCharInfo(data->textPtr);
+
+ assert(data->chunkPtr);
+ assert(!data->chunkPtr->clientData);
/*
- * Each iteration of the loop below creates one TkTextDispChunk for the
- * new display line. The line will always have at least one chunk (for the
- * newline character at the end, if there's nothing else available).
+ * Take into account that maxBytes == 0 is possible.
*/
- curIndex = *indexPtr;
- lastChunkPtr = NULL;
- chunkPtr = NULL;
- noCharsYet = 1;
- elide = 0;
- breakChunkPtr = NULL;
- breakByteOffset = 0;
- justify = TK_JUSTIFY_LEFT;
- tabIndex = -1;
- tabChunkPtr = NULL;
- tabArrayPtr = NULL;
- tabStyle = TK_TEXT_TABSTYLE_TABULAR;
- rMargin = 0;
- wrapMode = TEXT_WRAPMODE_CHAR;
- tabSize = 0;
- lastCharChunkPtr = NULL;
-
- /*
- * Find the first segment to consider for the line. Can't call
- * TkTextIndexToSeg for this because it won't return a segment with zero
- * size (such as the insertion cursor's mark).
- */
-
- connectNextLogicalLine:
- byteOffset = curIndex.byteIndex;
- segPtr = curIndex.linePtr->segPtr;
- while ((byteOffset > 0) && (byteOffset >= segPtr->size)) {
- byteOffset -= segPtr->size;
- segPtr = segPtr->nextPtr;
+ if (data->trimSpaces && maxBytes > 0 && p[maxBytes - 1] == ' ') {
+ while (maxBytes > 1 && p[maxBytes - 2] == ' ') {
+ maxBytes -= 1;
+ }
+ }
- if (segPtr == NULL) {
- /*
- * Two logical lines merged into one display line through eliding
- * of a newline.
- */
+#if TK_LAYOUT_WITH_BASE_CHUNKS
- TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
- if (linePtr == NULL) {
- break;
- }
+ if (data->baseChunkPtr
+ && (!IsSameFGStyle(data->baseChunkPtr->stylePtr, data->chunkPtr->stylePtr)
+ || (data->lastCharChunkPtr && data->lastCharChunkPtr->numSpaces > 0))) {
+ data->baseChunkPtr = NULL;
+ }
+
+ if (!data->baseChunkPtr) {
+ data->baseChunkPtr = data->chunkPtr;
+ Tcl_DStringInit(&data->chunkPtr->baseChars);
+ DEBUG_ALLOC(tkTextCountNewBaseChars++);
+ }
+
+ data->chunkPtr->baseChunkPtr = data->baseChunkPtr;
+ ciPtr->baseOffset = Tcl_DStringLength(&data->baseChunkPtr->baseChars);
+ ciPtr->u.chars = Tcl_DStringAppend(&data->baseChunkPtr->baseChars, p, maxBytes);
+
+#else
+
+ ciPtr->baseOffset = 0;
+ ciPtr->u.chars = p;
+
+#endif
+
+ /*
+ * Keep the char segment, otherwise a split may invalidate our string. This segment
+ * is also used for hyphenation support.
+ */
+
+ segPtr->refCount += 1;
+ ciPtr->segPtr = segPtr;
+ ciPtr->numBytes = maxBytes;
+ data->chunkPtr->clientData = ciPtr;
+
+ return maxBytes;
+}
+
+static void
+LayoutFinalizeCharInfo(
+ LayoutData *data,
+ bool gotTab)
+{
+ CharInfo *ciPtr = data->chunkPtr->clientData;
+
+ assert(data->trimSpaces ?
+ data->chunkPtr->numBytes >= ciPtr->numBytes :
+ data->chunkPtr->numBytes == ciPtr->numBytes);
+
+ /*
+ * Update the character information. Take into account that we don't want
+ * to display the newline character.
+ */
+
+ if (ciPtr->u.chars[ciPtr->baseOffset + ciPtr->numBytes - 1] == '\n') {
+ ciPtr->numBytes -= 1;
+ }
+
+#if TK_LAYOUT_WITH_BASE_CHUNKS
- dlPtr->logicalLinesMerged++;
- curIndex.byteIndex = 0;
- curIndex.linePtr = linePtr;
- segPtr = curIndex.linePtr->segPtr;
+ assert(data->chunkPtr->baseChunkPtr);
+
+ /*
+ * Final update for the current base chunk data.
+ */
+
+ Tcl_DStringSetLength(&data->baseChunkPtr->baseChars, ciPtr->baseOffset + ciPtr->numBytes);
+ data->baseChunkPtr->baseWidth =
+ data->chunkPtr->width + (data->chunkPtr->x - data->baseChunkPtr->x);
+
+ /*
+ * Finalize the base chunk if this chunk ends in a tab, which definitly breaks the context.
+ */
+
+ if (gotTab) {
+ data->baseChunkPtr = NULL;
+ }
+
+#endif
+}
+
+static void
+LayoutUndisplay(
+ LayoutData *data,
+ TkTextDispChunk *chunkPtr)
+{
+ assert(chunkPtr->layoutProcs);
+
+ if (chunkPtr->layoutProcs->undisplayProc) {
+ chunkPtr->layoutProcs->undisplayProc(data->textPtr, chunkPtr);
+ }
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+ if (chunkPtr == data->baseChunkPtr) {
+ data->baseChunkPtr = NULL;
+ }
+#endif
+}
+
+static void
+LayoutReleaseChunk(
+ TkText *textPtr,
+ TkTextDispChunk *chunkPtr)
+{
+ if (chunkPtr->layoutProcs) {
+ if (chunkPtr->layoutProcs->type == TEXT_DISP_IMAGE) {
+ textPtr->dInfoPtr->countImages -= 1;
+ } else if (chunkPtr->layoutProcs->type == TEXT_DISP_WINDOW) {
+ textPtr->dInfoPtr->countWindows -= 1;
}
}
+ FreeStyle(textPtr, chunkPtr->stylePtr);
+}
- while (segPtr != NULL) {
- /*
- * Every logical line still gets at least one chunk due to
- * expectations in the rest of the code, but we are able to skip
- * elided portions of the line quickly.
- *
- * If current chunk is elided and last chunk was too, coalese.
- *
- * This also means that each logical line which is entirely elided
- * still gets laid out into a DLine, but with zero height. This isn't
- * particularly a problem, but it does seem somewhat unnecessary. We
- * may wish to redesign the code to remove these zero height DLines in
- * the future.
- */
+static void
+LayoutFreeChunk(
+ LayoutData *data)
+{
+ TextDInfo *dInfoPtr = data->textPtr->dInfoPtr;
+ TkTextDispChunk *chunkPtr = data->chunkPtr;
+
+ assert(chunkPtr);
+ assert(data->lastChunkPtr != chunkPtr);
+ assert(data->lastCharChunkPtr != chunkPtr);
+ assert(!chunkPtr->sectionPtr);
+
+ if (chunkPtr->layoutProcs) {
+ LayoutUndisplay(data, chunkPtr);
+ }
+
+ LayoutReleaseChunk(data->textPtr, chunkPtr);
+ DEBUG(chunkPtr->stylePtr = NULL);
+ assert(!chunkPtr->clientData);
+ data->numBytesSoFar -= chunkPtr->numBytes;
+ chunkPtr->nextPtr = dInfoPtr->chunkPoolPtr;
+ dInfoPtr->chunkPoolPtr = chunkPtr;
+ dInfoPtr->chunkPoolPtr->prevPtr = NULL;
+ data->chunkPtr = NULL;
+ assert(data->countChunks > 0);
+ data->countChunks -= 1;
+}
+
+static void
+LayoutDoWidthAdjustmentForContextDrawing(
+ LayoutData *data)
+{
+#if TK_LAYOUT_WITH_BASE_CHUNKS && TK_DRAW_IN_CONTEXT
+ TkTextDispChunk *chunkPtr = data->chunkPtr;
+
+ if (chunkPtr->prevPtr) {
+ chunkPtr->x += chunkPtr->prevPtr->xAdjustment;
+ }
+
+ if (IsCharChunk(chunkPtr)) {
+ int newWidth;
+
+ CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, 0, -1, 0, &newWidth);
+ chunkPtr->xAdjustment = newWidth - chunkPtr->width;
+ chunkPtr->width = newWidth;
+ }
+#endif
+}
- if (elide && (lastChunkPtr != NULL)
- && (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) {
- elidesize = segPtr->size - byteOffset;
- if (elidesize > 0) {
- curIndex.byteIndex += elidesize;
- lastChunkPtr->numBytes += elidesize;
- breakByteOffset = lastChunkPtr->breakIndex
- = lastChunkPtr->numBytes;
+static void
+LayoutFinalizeChunk(
+ LayoutData *data)
+{
+ const TkTextDispChunkProcs *layoutProcs;
+
+ if (!data->chunkPtr) {
+ return;
+ }
+
+ layoutProcs = data->chunkPtr->layoutProcs;
+
+ if (!layoutProcs) {
+ assert(data->chunkPtr->numBytes == 0);
+ assert(!data->chunkPtr->clientData);
+ LayoutFreeChunk(data);
+ return;
+ }
+
+ if (layoutProcs->type & TEXT_DISP_CONTENT) {
+ data->lastCharChunkPtr = data->chunkPtr;
+ if (!data->firstCharChunkPtr) {
+ data->firstCharChunkPtr = data->chunkPtr;
+ }
+ if (layoutProcs->type & TEXT_DISP_TEXT) {
+ LayoutDoWidthAdjustmentForContextDrawing(data);
+ }
+ }
+ if (data->chunkPtr->breakIndex > 0) {
+ data->breakChunkPtr = data->chunkPtr;
+ }
+ if (!data->firstChunkPtr) {
+ assert(!data->lastChunkPtr);
+ data->firstChunkPtr = data->chunkPtr;
+ } else {
+ assert(data->lastChunkPtr);
+ data->lastChunkPtr->nextPtr = data->chunkPtr;
+ }
+ data->lastChunkPtr = data->chunkPtr;
+ data->dispLineOffset += data->chunkPtr->numBytes;
+ data->chunkPtr = NULL;
+}
+
+static TkTextDispChunkSection *
+LayoutNewSection(
+ TextDInfo *dInfoPtr)
+{
+ TkTextDispChunkSection *sectionPtr = dInfoPtr->sectionPoolPtr;
+
+ if (sectionPtr) {
+ dInfoPtr->sectionPoolPtr = dInfoPtr->sectionPoolPtr->nextPtr;
+ } else {
+ DEBUG_ALLOC(tkTextCountNewSection++);
+ sectionPtr = malloc(sizeof(TkTextDispChunkSection));
+ }
+
+ memset(sectionPtr, 0, sizeof(TkTextDispChunkSection));
+ return sectionPtr;
+}
+
+static void
+LayoutMakeNewChunk(
+ LayoutData *data)
+{
+ TkTextDispChunk *newChunkPtr;
+ TextDInfo *dInfoPtr = data->textPtr->dInfoPtr;
+
+ LayoutFinalizeChunk(data);
+ if ((newChunkPtr = dInfoPtr->chunkPoolPtr)) {
+ dInfoPtr->chunkPoolPtr = newChunkPtr->nextPtr;
+ } else {
+ newChunkPtr = malloc(sizeof(TkTextDispChunk));
+ DEBUG_ALLOC(tkTextCountNewChunk++);
+ }
+ memset(newChunkPtr, 0, sizeof(TkTextDispChunk));
+ newChunkPtr->prevPtr = data->lastChunkPtr;
+ newChunkPtr->prevCharChunkPtr = data->lastCharChunkPtr;
+ newChunkPtr->stylePtr = GetStyle(data->textPtr, NULL);
+ newChunkPtr->x = data->x;
+ newChunkPtr->byteOffset = data->dispLineOffset;
+ data->chunkPtr = newChunkPtr;
+ data->countChunks += 1;
+}
+
+static void
+LayoutSkipBytes(
+ LayoutData *data,
+ DLine *dlPtr,
+ const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2)
+{
+ LayoutMakeNewChunk(data);
+ data->chunkPtr->layoutProcs = &layoutElideProcs;
+ data->chunkPtr->numBytes = TkTextIndexCountBytes(indexPtr1, indexPtr2);
+}
+
+static void
+LayoutSetupChunk(
+ LayoutData *data,
+ TkTextSegment *segPtr)
+{
+ TkTextDispChunk *chunkPtr = data->chunkPtr;
+ TkText *textPtr = data->textPtr;
+ TextStyle *stylePtr;
+
+ assert(segPtr->tagInfoPtr);
+ assert(chunkPtr->stylePtr == textPtr->dInfoPtr->defaultStyle);
+ assert(chunkPtr->stylePtr->refCount > 1);
+
+ chunkPtr->stylePtr->refCount -= 1;
+ chunkPtr->stylePtr = stylePtr = GetStyle(textPtr, segPtr);
+
+ if (data->wrapMode == TEXT_WRAPMODE_CODEPOINT) {
+ const TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, data->logicalLinePtr);
+
+ if (!data->brks) {
+ Tcl_HashEntry *hPtr;
+ BreakInfo *breakInfo;
+ int new;
+
+ hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->breakInfoTable,
+ (void *) data->logicalLinePtr, &new);
+
+ if (new) {
+ breakInfo = malloc(sizeof(BreakInfo));
+ breakInfo->refCount = 1;
+ breakInfo->brks = NULL;
+ data->logicalLinePtr->changed = false;
+ Tcl_SetHashValue(hPtr, breakInfo);
+ DEBUG_ALLOC(tkTextCountNewBreakInfo++);
+ } else {
+ breakInfo = Tcl_GetHashValue(hPtr);
+ breakInfo->refCount += 1;
/*
- * If have we have a tag toggle, there is a chance that
- * invisibility state changed, so bail out.
+ * We have to avoid repeated computations of line break information,
+ * so we use the 'changed' flag of the logical line for the determination
+ * whether a recomputation has to be performed. This is the only purpose
+ * of flag 'changed', and required because our current line break
+ * information algorithm has to process the whole logical line. If this
+ * behavior will change - for example a switch to the ICU library - then
+ * flag 'changed' has no use anymore and can be removed. But currently
+ * all line modifications have to update this flag.
*/
- } else if ((segPtr->typePtr == &tkTextToggleOffType)
- || (segPtr->typePtr == &tkTextToggleOnType)) {
- if (segPtr->body.toggle.tagPtr->elideString != NULL) {
- elide = (segPtr->typePtr == &tkTextToggleOffType)
- ^ segPtr->body.toggle.tagPtr->elide;
+
+ if (data->logicalLinePtr->changed) {
+ new = true;
+ data->logicalLinePtr->changed = false;
}
}
- byteOffset = 0;
- segPtr = segPtr->nextPtr;
+ if (new) {
+ unsigned brksSize;
- if (segPtr == NULL) {
/*
- * Two logical lines merged into one display line through
- * eliding of a newline.
+ * In this case we have to parse the whole logical line for the computation
+ * of the break locations.
*/
- TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
-
- if (linePtr != NULL) {
- dlPtr->logicalLinesMerged++;
- curIndex.byteIndex = 0;
- curIndex.linePtr = linePtr;
- goto connectNextLogicalLine;
- }
+ brksSize = LayoutComputeBreakLocations(data);
+ breakInfo->brks = realloc(breakInfo->brks, brksSize);
+ memcpy(breakInfo->brks, textPtr->brksBuffer, brksSize);
+ DEBUG(stats.breakInfo += 1);
}
- /*
- * Code no longer needed, now that we allow logical lines to merge
- * into a single display line.
- *
- if (segPtr == NULL && chunkPtr != NULL) {
- ckfree(chunkPtr);
- chunkPtr = NULL;
- }
- */
-
- continue;
+ data->breakInfo = breakInfo;
+ data->brks = breakInfo->brks;
}
- if (segPtr->typePtr->layoutProc == NULL) {
- segPtr = segPtr->nextPtr;
- byteOffset = 0;
- continue;
+ if (segPtr->sectionPtr) {
+ chunkPtr->brks = data->brks;
+ if (data->displayLineNo > 0) {
+ assert(pixelInfo->dispLineInfo);
+ chunkPtr->brks += pixelInfo->dispLineInfo->entry[data->displayLineNo].byteOffset;
+ } else {
+ /* Consider that inside peers the line may start after byte index zero. */
+ chunkPtr->brks += data->byteOffset;
+ }
+ chunkPtr->brks += data->dispLineOffset;
+ } else {
+ /* This is an artificial chunk for the realization of spelling changes. */
+ assert(chunkPtr->numBytes <= sizeof(doNotBreakAtAll));
+ chunkPtr->brks = doNotBreakAtAll;
}
- if (chunkPtr == NULL) {
- chunkPtr = ckalloc(sizeof(TkTextDispChunk));
- chunkPtr->nextPtr = NULL;
- chunkPtr->clientData = NULL;
+ }
+
+ if (data->numBytesSoFar == 0) {
+ const TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ const StyleValues *sValuePtr = stylePtr->sValuePtr;
+
+ data->tabArrayPtr = sValuePtr->tabArrayPtr;
+ data->tabStyle = sValuePtr->tabStyle;
+ data->justify = sValuePtr->justify;
+ data->rMargin = sValuePtr->rMargin;
+ data->wrapMode = sValuePtr->wrapMode;
+ data->x = data->paragraphStart ? sValuePtr->lMargin1 : sValuePtr->lMargin2;
+ data->width = dInfoPtr->maxX - dInfoPtr->x - data->rMargin;
+ data->maxX = data->wrapMode == TEXT_WRAPMODE_NONE ? -1 : MAX(data->width, data->x);
+
+ chunkPtr->x = data->x;
+
+ if (data->cursorChunkPtr) {
+ data->cursorChunkPtr->x = data->x;
}
- chunkPtr->stylePtr = GetStyle(textPtr, &curIndex);
- elide = chunkPtr->stylePtr->sValuePtr->elide;
+ }
+}
+static bool
+LayoutChars(
+ LayoutData *data,
+ TkTextSegment *segPtr,
+ int size,
+ int byteOffset)
+{
+ const char *base = segPtr->body.chars + byteOffset;
+ TkTextDispChunk *chunkPtr;
+ bool gotTab = false;
+ bool finished = true;
+ unsigned maxBytes;
+ unsigned numBytes;
+
+ assert(size - byteOffset > 0); /* this will ensure maxBytes > 0 */
+ assert(byteOffset < size);
+ assert(segPtr->typePtr->layoutProc);
+
+ LayoutMakeNewChunk(data);
+ LayoutSetupChunk(data, segPtr);
+
+ chunkPtr = data->chunkPtr;
+ maxBytes = size - byteOffset;
+
+ if (data->textPtr->showEndOfLine
+ && base[maxBytes - 1] == '\n'
+ && segPtr->sectionPtr->linePtr->nextPtr != TkBTreeGetLastLine(data->textPtr)) {
+ maxBytes -= 1; /* now may beome zero */
+ }
+
+ if (maxBytes == 0) {
/*
- * Save style information such as justification and indentation, up
- * until the first character is encountered, then retain that
- * information for the rest of the line.
+ * Can only happen if we are at end of logical line.
*/
- if (!elide && noCharsYet) {
- tabArrayPtr = chunkPtr->stylePtr->sValuePtr->tabArrayPtr;
- tabStyle = chunkPtr->stylePtr->sValuePtr->tabStyle;
- justify = chunkPtr->stylePtr->sValuePtr->justify;
- rMargin = chunkPtr->stylePtr->sValuePtr->rMargin;
- wrapMode = chunkPtr->stylePtr->sValuePtr->wrapMode;
+ segPtr = data->textPtr->dInfoPtr->endOfLineSegPtr;
+ base = segPtr->body.chars;
+ maxBytes = segPtr->size;
+ byteOffset = 0;
+ } else if (segPtr->typePtr != &tkTextHyphenType
+ && segPtr->sectionPtr) { /* ignore artifical segments (spelling changes) */
+ if (data->wrapMode == TEXT_WRAPMODE_CODEPOINT) {
+ const char *brks = chunkPtr->brks;
+ unsigned i;
+
+ assert(brks);
+
+ for (i = 1; i < maxBytes; ++i) {
+ if (brks[i] == LINEBREAK_MUSTBREAK) {
+ if (i < maxBytes - 2 && base[i] != '\n') {
+ maxBytes = i + 1;
+ }
+ break;
+ }
+ }
+ }
+
+ if (data->textPtr->hyphenate && finished) {
+ const char *p = base;
/*
- * See above - this test may not be entirely correct where we have
- * partially elided lines (and therefore merged logical lines).
- * In such a case a byteIndex of zero doesn't necessarily mean the
- * beginning of a logical line.
+ * Check whether the "tripleconsonant" rule has to be applied. This rule is
+ * very special, because in this case we are virtually doing the opposite.
+ * Instead of doing a spelling change when hyphenating, we are doing a spelling
+ * change when *not* hyphenating.
*/
- if (paragraphStart) {
- /*
- * Beginning of logical line.
- */
+ if (IsConsonant(*p)
+ && data->lastCharChunkPtr
+ && data->lastCharChunkPtr->prevCharChunkPtr
+ && data->lastChunkPtr
+ && data->lastChunkPtr->layoutProcs
+ && data->lastChunkPtr->layoutProcs->type == TEXT_DISP_HYPHEN
+ && *p == GetLastCharInChunk(data->lastCharChunkPtr->prevCharChunkPtr)
+ && *p == GetSecondLastCharInChunk(data->lastCharChunkPtr->prevCharChunkPtr)) {
+ const char *nextCharPtr;
+
+ if (maxBytes > 1) {
+ nextCharPtr = p + 1;
+ } else {
+ const TkTextSegment *nextCharSegPtr = LayoutGetNextSegment(segPtr);
+ nextCharPtr = nextCharSegPtr ? nextCharSegPtr->body.chars : NULL;
+ }
- x = chunkPtr->stylePtr->sValuePtr->lMargin1;
- } else {
- /*
- * Beginning of display line.
- */
+ /* For Norwegian it's required to consider 'j' as a vowel. */
+ if (nextCharPtr && (nextCharPtr[0] == 'j' || IsUmlautOrVowel(nextCharPtr))) {
+ /*
+ * Probably we have to apply hyphen rule "tripleconsonant" to the first
+ * character after possible (but unapplied) hyphenation point.
+ */
+
+ const StyleValues *sValPtr = data->lastChunkPtr->stylePtr->sValuePtr;
+ int hyphenRules = FilterHyphenRules(sValPtr->hyphenRules, sValPtr->lang);
- x = chunkPtr->stylePtr->sValuePtr->lMargin2;
+ if (hyphenRules & (1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT)) {
+ /* Schi(ff-f)ahrt -> Schi(ff)ahrt */
+ byteOffset += 1;
+ base += 1;
+ maxBytes -= 1; /* now may become zero */
+ chunkPtr->skipFirstChar = true;
+ }
+ }
}
- dlPtr->lMarginWidth = x;
- if (wrapMode == TEXT_WRAPMODE_NONE) {
- maxX = -1;
- } else {
- maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x
- - rMargin;
- if (maxX < x) {
- maxX = x;
+ }
+
+ if (data->trimSpaces) {
+ int i;
+
+ for (i = 0; i < maxBytes; ++i) {
+ if (base[i] == ' ' && base[i + 1] == ' ') {
+ while (base[i] == ' ') {
+ ++i;
+ }
+ maxBytes = i;
+ data->skipSpaces = true;
+ break;
}
}
}
- gotTab = 0;
- maxBytes = segPtr->size - byteOffset;
- if (segPtr->typePtr == &tkTextCharType) {
+ /*
+ * See if there is a tab in the current chunk; if so, only layout
+ * characters up to (and including) the tab.
+ */
- /*
- * See if there is a tab in the current chunk; if so, only layout
- * characters up to (and including) the tab.
- */
+ if (data->justify == TK_TEXT_JUSTIFY_LEFT) {
+ const char *p = base;
+ int i;
- if (!elide && justify == TK_JUSTIFY_LEFT) {
- char *p;
+ /* TODO: also TK_TEXT_JUSTIFY_RIGHT should support tabs */
+ /* TODO: direction of tabs should depend on gravity of insert mark?! */
- for (p = segPtr->body.chars + byteOffset; *p != 0; p++) {
- if (*p == '\t') {
- maxBytes = (p + 1 - segPtr->body.chars) - byteOffset;
- gotTab = 1;
- break;
- }
+ for (i = 0; i < maxBytes; ++i, ++p) {
+ if (*p == '\t') {
+ maxBytes = i + 1;
+ gotTab = true;
+ break;
}
}
-
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- if (baseCharChunkPtr != NULL) {
- int expectedX =
- ((BaseCharInfo *) baseCharChunkPtr->clientData)->width
- + baseCharChunkPtr->x;
-
- if ((expectedX != x) || !IsSameFGStyle(
- baseCharChunkPtr->stylePtr, chunkPtr->stylePtr)) {
- FinalizeBaseChunk(NULL);
+ } else if (data->justify == TK_TEXT_JUSTIFY_FULL) {
+ const char *p = base;
+ const char *e = p + maxBytes;
+
+ for ( ; p < e && !IsExpandableSpace(p); ++p) {
+ if (*p == '\t') {
+ chunkPtr->numSpaces = 0;
+ maxBytes = p - base + 1;
+ gotTab = true;
+ break;
}
}
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
+ if (!gotTab && p < e) {
+ assert(IsExpandableSpace(p));
+
+ do {
+ chunkPtr->numSpaces += 1;
+
+ if (*p == '\t'
+ && (!data->tabArrayPtr || data->tabIndex < data->tabArrayPtr->numTabs)) {
+ /*
+ * Don't expand spaces if we have numeric tabs.
+ */
+ chunkPtr->numSpaces = 0;
+ gotTab = true;
+ p += 1;
+ break;
+ }
+
+ p = Tcl_UtfNext(p);
+ } while (IsExpandableSpace(p));
+
+ maxBytes = p - base;
+ }
}
- chunkPtr->x = x;
- if (elide /*&& maxBytes*/) {
- /*
- * Don't free style here, as other code expects to be able to do
- * that.
- */
+ }
+
+ if (maxBytes == 0) {
+ /*
+ * In seldom cases, if hyphenation is activated, we may have an empty
+ * chunk here, caused by the "tripleconsonant" rule. This chunk has to
+ * consume one character.
+ */
+
+ assert(size == 1);
+ assert(chunkPtr->skipFirstChar);
+ data->chunkPtr->layoutProcs = &layoutElideProcs;
+ data->chunkPtr->numBytes = 1;
+ return true;
+ }
+
+ numBytes = LayoutMakeCharInfo(data, segPtr, byteOffset, maxBytes);
+
+ if (segPtr->typePtr->layoutProc(&data->index, segPtr, byteOffset,
+ data->maxX - data->tabSize, numBytes, data->numBytesSoFar == 0,
+ data->wrapMode, data->textPtr->spaceMode, chunkPtr) == 0) {
+ /*
+ * No characters from this segment fit in the window: this means
+ * we're at the end of the display line.
+ */
+
+ chunkPtr->numSpaces = 0;
+ return false;
+ }
+
+ if (numBytes == chunkPtr->numBytes) {
+ chunkPtr->numBytes = maxBytes;
+ assert(maxBytes > 0);
+
+ if (data->trimSpaces && base[maxBytes - 1] == ' ') {
+ data->skipSpaces = true;
+ }
+ }
+
+ assert(chunkPtr->numBytes + chunkPtr->skipFirstChar > 0);
+
+ LayoutFinalizeCharInfo(data, gotTab);
+ data->x += chunkPtr->width;
+
+ if (segPtr == data->textPtr->dInfoPtr->endOfLineSegPtr) {
+ chunkPtr->numBytes = (chunkPtr->numBytes == maxBytes) ? 1 : 0;
+ chunkPtr->breakIndex = chunkPtr->numBytes;
+ maxBytes = 1;
+ } else {
+ chunkPtr->numBytes += chunkPtr->skipFirstChar;
+ }
+
+ data->numBytesSoFar += chunkPtr->numBytes;
+ data->numSpaces += chunkPtr->numSpaces;
+
+ if (chunkPtr->numBytes != maxBytes + chunkPtr->skipFirstChar) {
+ return false;
+ }
+
+ /*
+ * If we're at a new tab, adjust the layout for all the chunks pertaining to the
+ * previous tab. Also adjust the amount of space left in the line to account for
+ * space that will be eaten up by the tab.
+ */
+
+ if (gotTab) {
+ if (data->tabIndex >= 0) {
+ data->lastChunkPtr->nextPtr = data->chunkPtr; /* we need the complete chain. */
+ AdjustForTab(data);
+ data->lastChunkPtr->nextPtr = NULL; /* restore */
+ data->x = chunkPtr->x + chunkPtr->width;
+ }
+ data->tabChunkPtr = chunkPtr;
+ ComputeSizeOfTab(data);
+ if (data->maxX >= 0 && data->tabSize >= data->maxX - data->x) {
+ return false; /* end of line reached */
+ }
+ }
+
+ return finished;
+}
+
+static bool
+LayoutHyphen(
+ LayoutData *data,
+ TkTextSegment *segPtr)
+{
+ bool rc;
+
+ assert(segPtr->sectionPtr); /* don't works with artificial segments */
+
+ if (data->textPtr->hyphenate) {
+ LayoutMakeNewChunk(data);
+ LayoutSetupChunk(data, segPtr);
+ data->numBytesSoFar += segPtr->size;
+ segPtr->body.hyphen.textSize = 0;
+ data->chunkPtr->layoutProcs = &layoutHyphenProcs;
+ data->chunkPtr->clientData = segPtr;
+ data->chunkPtr->breakIndex = -1;
+ data->chunkPtr->numBytes = segPtr->size;
+ data->chunkPtr->hyphenRules = segPtr->body.hyphen.rules;
+ segPtr->refCount += 1;
+ rc = true;
+ } else {
+ SetupHyphenChars(segPtr, 0);
+ rc = LayoutChars(data, segPtr, segPtr->body.hyphen.textSize, 0);
+ data->chunkPtr->numBytes = MIN(1, data->chunkPtr->numBytes);
+ }
+
+ data->chunkPtr->breakIndex = data->chunkPtr->numBytes;
+ return rc;
+}
+
+static bool
+LayoutEmbedded(
+ LayoutData *data,
+ TkTextSegment *segPtr)
+{
+ assert(segPtr->typePtr->layoutProc);
+
+ LayoutMakeNewChunk(data);
+
+ if (segPtr->typePtr->layoutProc(&data->index, segPtr, 0, data->maxX - data->tabSize, 0,
+ data->numBytesSoFar == 0, data->wrapMode, data->textPtr->spaceMode, data->chunkPtr) != 1) {
+ return false;
+ }
+
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+ data->baseChunkPtr = NULL;
+#endif
+ LayoutSetupChunk(data, segPtr);
+ data->numBytesSoFar += data->chunkPtr->numBytes;
+ data->x += data->chunkPtr->width;
- /* breakByteOffset =*/
- chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes;
- chunkPtr->width = 0;
- chunkPtr->minAscent = chunkPtr->minDescent
- = chunkPtr->minHeight = 0;
+ if (segPtr->typePtr->group == SEG_GROUP_IMAGE) {
+ data->textPtr->dInfoPtr->countImages += 1;
+ } else {
+ data->textPtr->dInfoPtr->countWindows += 1;
+ }
+
+ return true;
+}
+
+static bool
+LayoutMark(
+ LayoutData *data,
+ TkTextSegment *segPtr)
+{
+ assert(segPtr->typePtr->layoutProc);
+
+ if (segPtr != data->textPtr->insertMarkPtr) {
+ return false;
+ }
+ LayoutMakeNewChunk(data);
+ segPtr->typePtr->layoutProc(&data->index, segPtr, 0, data->maxX - data->tabSize, 0,
+ data->numBytesSoFar == 0, data->wrapMode, data->textPtr->spaceMode, data->chunkPtr);
+ return true;
+}
+
+static bool
+LayoutLogicalLine(
+ LayoutData *data,
+ DLine *dlPtr)
+{
+ TkTextSegment *segPtr, *endPtr;
+ int byteIndex, byteOffset;
+
+ assert(!TkTextIsElided(&data->index));
+
+ byteIndex = TkTextIndexGetByteIndex(&data->index);
+
+ if (data->textPtr->hyphenate && data->displayLineNo > 0) {
+ const TkTextDispLineInfo *dispLineInfo;
+ int byteOffset;
+ int hyphenRule;
+
+ segPtr = TkTextIndexGetContentSegment(&data->index, &byteOffset);
+ dispLineInfo = TkBTreeLinePixelInfo(data->textPtr, data->logicalLinePtr)->dispLineInfo;
+ assert(dispLineInfo);
+ hyphenRule = dispLineInfo->entry[data->displayLineNo - 1].hyphenRule;
+
+ switch (hyphenRule) {
+ case TK_TEXT_HYPHEN_REPEAT:
+ case TK_TEXT_HYPHEN_TREMA:
+ case TK_TEXT_HYPHEN_DOUBLE_DIGRAPH: {
+ int numBytes = 0; /* prevents compiler warning */
+ TkTextSegment *nextCharSegPtr;
+ char buf[1];
+ bool cont;
/*
- * Would just like to point to canonical empty chunk.
+ * We have to realize spelling changes.
*/
- chunkPtr->displayProc = NULL;
- chunkPtr->undisplayProc = NULL;
- chunkPtr->measureProc = ElideMeasureProc;
- chunkPtr->bboxProc = ElideBboxProc;
+ switch (hyphenRule) {
+ case TK_TEXT_HYPHEN_REPEAT:
+ buf[0] = '-';
+ numBytes = 1;
+ break;
+ case TK_TEXT_HYPHEN_TREMA:
+ assert(UCHAR(segPtr->body.chars[byteOffset]) == 0xc3);
+ buf[0] = UmlautToVowel(ConvertC3Next(segPtr->body.chars[byteOffset + 1]));
+ numBytes = 2;
+ break;
+ case TK_TEXT_HYPHEN_DOUBLE_DIGRAPH:
+ buf[0] = segPtr->body.chars[0];
+ numBytes = 1;
+ break;
+ }
+ nextCharSegPtr = TkBTreeMakeCharSegment(buf, 1, segPtr->tagInfoPtr);
+ cont = LayoutChars(data, nextCharSegPtr, 1, 0);
+ TkBTreeFreeSegment(nextCharSegPtr);
+ data->chunkPtr->numBytes = numBytes;
+ if (!cont) {
+ LayoutFinalizeChunk(data);
+ return false;
+ }
+ TkTextIndexForwBytes(data->textPtr, &data->index, data->chunkPtr->numBytes, &data->index);
+ byteIndex += data->chunkPtr->numBytes;
+ break;
+ }
+ }
+ }
+
+ segPtr = TkTextIndexGetFirstSegment(&data->index, &byteOffset);
+ endPtr = data->textPtr->endMarker;
- code = 1;
+ if (segPtr->typePtr == &tkTextLinkType) {
+ segPtr = segPtr->nextPtr;
+ }
+
+ /*
+ * Each iteration of the loop below creates one TkTextDispChunk for the
+ * new display line. The line will always have at least one chunk (for the
+ * newline character at the end, if there's nothing else available).
+ */
+
+ while (true) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ if (data->skipSpaces) {
+ if (segPtr->body.chars[byteOffset] == ' ') {
+ TkTextIndex index = data->index;
+ int offset = byteOffset;
+
+ while (segPtr->body.chars[byteOffset] == ' ') {
+ byteOffset += 1;
+ }
+ TkTextIndexForwBytes(data->textPtr, &index, byteOffset - offset, &data->index);
+ LayoutSkipBytes(data, dlPtr, &index, &data->index);
+ byteIndex = TkTextIndexGetByteIndex(&data->index);
+ }
+ data->skipSpaces = false;
+ }
+ if (segPtr->size > byteOffset) {
+ if (!LayoutChars(data, segPtr, segPtr->size, byteOffset)) {
+ /* finished with this display line */
+ LayoutFinalizeChunk(data);
+ return false;
+ }
+ assert(data->chunkPtr);
+ byteIndex += data->chunkPtr->numBytes;
+ if ((byteOffset += data->chunkPtr->numBytes) == segPtr->size) {
+ segPtr = segPtr->nextPtr;
+ byteOffset = 0;
+ }
+ } else {
+ assert(segPtr->size == byteOffset);
+ segPtr = segPtr->nextPtr;
+ byteOffset = 0;
+ }
} else {
- code = segPtr->typePtr->layoutProc(textPtr, &curIndex, segPtr,
- byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode,
- chunkPtr);
+ switch (segPtr->typePtr->group) {
+ case SEG_GROUP_HYPHEN:
+ if (!LayoutHyphen(data, segPtr)) {
+ /* finished with this display line */
+ LayoutFinalizeChunk(data);
+ return false;
+ }
+ byteIndex += segPtr->size;
+ data->skipSpaces = false;
+ break;
+ case SEG_GROUP_IMAGE:
+ case SEG_GROUP_WINDOW:
+ if (!LayoutEmbedded(data, segPtr)) {
+ /* finished with this display line */
+ LayoutFinalizeChunk(data);
+ return false;
+ }
+ byteIndex += segPtr->size;
+ data->skipSpaces = false;
+ break;
+ case SEG_GROUP_MARK:
+ if (segPtr == endPtr) {
+ /*
+ * We need a final chunk containing the final newline, otherwise x position
+ * lookup will not work. Here we will not use LayoutSkipBytes() for the bytes
+ * between current position and last char in line, because this would require
+ * an inconsistent index, and it's easier to avoid this. It's only a single
+ * newline which terminates the line, so no bad things will happen if we omit
+ * this skip-chunk.
+ */
+ segPtr = segPtr->sectionPtr->linePtr->lastPtr;
+ LayoutChars(data, segPtr, segPtr->size, segPtr->size - 1);
+ } else {
+ if (LayoutMark(data, segPtr)) {
+ data->cursorChunkPtr = data->chunkPtr;
+ }
+ assert(segPtr->size == 0);
+ }
+ break;
+ case SEG_GROUP_BRANCH: {
+ TkTextIndex index = data->index;
+ assert(segPtr->typePtr == &tkTextBranchType);
+ assert(segPtr->size == 0);
+ TkTextIndexSetSegment(&data->index, segPtr = segPtr->body.branch.nextPtr);
+ LayoutSkipBytes(data, dlPtr, &index, &data->index);
+ byteIndex = TkTextIndexGetByteIndex(&data->index);
+ break;
+ }
+ case SEG_GROUP_PROTECT:
+ case SEG_GROUP_TAG:
+ case SEG_GROUP_CHAR:
+ assert(!"unexpected segment type");
+ break;
+ }
+ segPtr = segPtr->nextPtr;
+ byteOffset = 0;
}
- if (code <= 0) {
- FreeStyle(textPtr, chunkPtr->stylePtr);
- if (code < 0) {
+ if (!segPtr) {
+ LayoutFinalizeChunk(data);
+ return true;
+ }
+ TkTextIndexSetPosition(&data->index, byteIndex, segPtr);
+ }
+
+ return false; /* never reached */
+}
+
+static void
+LayoutDestroyChunks(
+ LayoutData *data)
+{
+ TkTextDispChunk *chunkPtr = data->lastChunkPtr;
+ TextDInfo *dInfoPtr;
+
+ if (chunkPtr == data->breakChunkPtr) {
+ return;
+ }
+
+ dInfoPtr = data->textPtr->dInfoPtr;
+
+ /*
+ * We have to destroy the chunks backward, because the context support
+ * is expecting this.
+ */
+
+ for ( ; chunkPtr != data->breakChunkPtr; chunkPtr = chunkPtr->prevPtr) {
+ assert(chunkPtr != data->firstCharChunkPtr);
+ assert(chunkPtr->layoutProcs);
+ assert(!chunkPtr->sectionPtr);
+
+ data->numSpaces -= chunkPtr->numSpaces;
+ data->dispLineOffset -= chunkPtr->numBytes;
+ data->numBytesSoFar -= chunkPtr->numBytes;
+ data->countChunks -= 1;
+
+ if (chunkPtr == data->cursorChunkPtr) {
+ data->cursorChunkPtr = NULL;
+ } else if (chunkPtr == data->lastCharChunkPtr) {
+ data->lastCharChunkPtr = chunkPtr->prevCharChunkPtr;
+ }
+ if (chunkPtr->layoutProcs->type == TEXT_DISP_IMAGE) {
+ dInfoPtr->countImages -= 1;
+ } else if (chunkPtr->layoutProcs->type == TEXT_DISP_WINDOW) {
+ dInfoPtr->countWindows -= 1;
+ }
+ LayoutUndisplay(data, chunkPtr);
+ LayoutReleaseChunk(data->textPtr, chunkPtr);
+ DEBUG(chunkPtr->stylePtr = NULL);
+ }
+
+ data->lastChunkPtr->nextPtr = dInfoPtr->chunkPoolPtr;
+ dInfoPtr->chunkPoolPtr = data->breakChunkPtr->nextPtr;
+ dInfoPtr->chunkPoolPtr->prevPtr = NULL;
+ data->breakChunkPtr->nextPtr = NULL;
+ data->lastChunkPtr = data->breakChunkPtr;
+ data->chunkPtr = NULL;
+ data->x = data->lastChunkPtr->x + data->lastChunkPtr->width;
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+ data->baseChunkPtr = data->breakChunkPtr->baseChunkPtr;
+#endif
+}
+
+static void
+LayoutBreakLine(
+ LayoutData *data,
+ const TkTextIndex *indexPtr) /* Index of display line start. */
+{
+ if (!data->breakChunkPtr) {
+ /*
+ * This code makes sure that we don't accidentally display chunks with
+ * no characters at the end of the line (such as the insertion
+ * cursor). These chunks belong on the next line. So, throw away
+ * everything after the last chunk that has characters in it.
+ */
+
+ data->breakChunkPtr = data->lastCharChunkPtr;
+ }
+
+ while (IsHyphenChunk(data->breakChunkPtr)) {
+ TkTextDispChunk *hyphenChunkPtr;
+ TkTextDispChunk *prevChunkPtr;
+ TkTextDispChunk *nextChunkPtr;
+
+ /*
+ * This can only happen if the breaking chunk is a hyphen segment.
+ * So try to hyphenate at this point. Normally this will succeed,
+ * but in seldom cases the hyphenation does not fit, then we have
+ * to search back for the next breaking chunk.
+ */
+
+ hyphenChunkPtr = data->breakChunkPtr;
+ prevChunkPtr = hyphenChunkPtr->prevCharChunkPtr;
+ nextChunkPtr = LayoutGetNextCharChunk(hyphenChunkPtr);
+
+ if (prevChunkPtr && nextChunkPtr) {
+ TkTextSegment *hyphenSegPtr = hyphenChunkPtr->clientData;
+
+ LayoutApplyHyphenRules(data, prevChunkPtr, hyphenChunkPtr, nextChunkPtr);
+ data->breakChunkPtr = prevChunkPtr;
+ LayoutDestroyChunks(data);
+
+ if (data->decreaseNumBytes > 0) {
+ TkTextIndex index = *indexPtr;
+ TkTextSegment *segPtr;
+ unsigned newNumBytes = 0;
+ unsigned numBytes;
+
/*
- * This segment doesn't wish to display itself (e.g. most
- * marks).
+ * We need a new layout of the preceding char chunk because of possible
+ * spelling changes.
*/
- segPtr = segPtr->nextPtr;
- byteOffset = 0;
- continue;
+ while (data->decreaseNumBytes >= prevChunkPtr->numBytes
+ && prevChunkPtr != data->firstCharChunkPtr) {
+ data->decreaseNumBytes -= prevChunkPtr->numBytes;
+ newNumBytes += prevChunkPtr->numBytes;
+ prevChunkPtr = prevChunkPtr->prevPtr;
+ }
+
+ data->breakChunkPtr = prevChunkPtr;
+ LayoutDestroyChunks(data);
+ newNumBytes += prevChunkPtr->numBytes;
+
+ if (data->decreaseNumBytes > 0) {
+ segPtr = CHAR_CHUNK_GET_SEGMENT(prevChunkPtr);
+ prevChunkPtr->numBytes -= data->decreaseNumBytes;
+ numBytes = prevChunkPtr->numBytes;
+ assert(prevChunkPtr->layoutProcs);
+ LayoutUndisplay(data, prevChunkPtr);
+ data->chunkPtr = prevChunkPtr;
+ LayoutMakeCharInfo(data, segPtr, prevChunkPtr->segByteOffset, numBytes);
+ TkTextIndexForwBytes(data->textPtr, &index, prevChunkPtr->byteOffset, &index);
+ segPtr->typePtr->layoutProc(&index, segPtr, prevChunkPtr->segByteOffset,
+ data->maxX, numBytes, 0, data->wrapMode, data->textPtr->spaceMode,
+ prevChunkPtr);
+ LayoutFinalizeCharInfo(data, false); /* second parameter doesn't matter here */
+
+ if (prevChunkPtr->numBytes != numBytes && prevChunkPtr != data->firstCharChunkPtr) {
+ /*
+ * The content doesn't fits into the display line (but it must fit if
+ * this is the first char chunk).
+ */
+ hyphenSegPtr = NULL;
+ }
+ }
+
+ prevChunkPtr->numBytes = newNumBytes;
+ data->chunkPtr = NULL;
}
- /*
- * No characters from this segment fit in the window: this means
- * we're at the end of the display line.
- */
+ if (hyphenSegPtr) {
+ int maxX = data->maxX;
+ bool fits;
- if (chunkPtr != NULL) {
- ckfree(chunkPtr);
+ data->x = prevChunkPtr->x + prevChunkPtr->width;
+ if (prevChunkPtr == data->firstCharChunkPtr && prevChunkPtr->breakIndex <= 0) {
+ data->maxX = INT_MAX; /* The hyphen must be shown. */
+ }
+ fits = LayoutChars(data, hyphenSegPtr, hyphenSegPtr->body.hyphen.textSize, 0);
+ assert(!fits || data->chunkPtr->numBytes == hyphenSegPtr->body.hyphen.textSize);
+ hyphenChunkPtr = data->chunkPtr;
+ data->maxX = maxX;
+
+ if (fits) {
+ /* The hyphen fits, so we're done. */
+ LayoutFinalizeChunk(data);
+ hyphenChunkPtr->numBytes = 1 + data->increaseNumBytes;
+ return;
+ }
+
+ LayoutFreeChunk(data);
+ data->hyphenRule = 0;
}
- break;
}
/*
- * We currently say we have some characters (and therefore something
- * from which to examine tag values for the first character of the
- * line) even if those characters are actually elided. This behaviour
- * is not well documented, and it might be more consistent to
- * completely ignore such elided characters and their tags. To do so
- * change this to:
- *
- * if (!elide && chunkPtr->numBytes > 0).
+ * We couldn't hyphenate, so search for next candidate for wrapping.
*/
- if (!elide && chunkPtr->numBytes > 0) {
- noCharsYet = 0;
- lastCharChunkPtr = chunkPtr;
- }
- if (lastChunkPtr == NULL) {
- dlPtr->chunkPtr = chunkPtr;
- } else {
- lastChunkPtr->nextPtr = chunkPtr;
+ if (IsHyphenChunk(data->breakChunkPtr)) {
+ if (!(data->breakChunkPtr = data->breakChunkPtr->prevPtr)) {
+ return;
+ }
}
- lastChunkPtr = chunkPtr;
- x += chunkPtr->width;
- if (chunkPtr->breakIndex > 0) {
- breakByteOffset = chunkPtr->breakIndex;
- breakIndex = curIndex;
- breakChunkPtr = chunkPtr;
+ if (data->breakChunkPtr->breakIndex <= 0) {
+ do {
+ if (!(data->breakChunkPtr = data->breakChunkPtr->prevPtr)) {
+ return;
+ }
+ } while (data->breakChunkPtr->breakIndex <= 0 && !IsHyphenChunk(data->breakChunkPtr));
}
- if (chunkPtr->numBytes != maxBytes) {
- break;
+
+ data->chunkPtr = NULL;
+ }
+
+ /*
+ * Now check if we must break because the line length have been exceeded. At this point
+ * hyphenation is not involved.
+ */
+
+ if (data->breakChunkPtr
+ && (data->lastChunkPtr != data->breakChunkPtr
+ || (data->lastChunkPtr->breakIndex > 0
+ && data->lastChunkPtr->breakIndex != data->lastChunkPtr->numBytes))) {
+ unsigned addNumBytes = 0;
+
+ LayoutDestroyChunks(data);
+
+ if (data->breakChunkPtr->breakIndex > 0 && data->breakChunkPtr->numSpaces > 0) {
+ const TkTextDispChunk *breakChunkPtr = data->breakChunkPtr;
+ const CharInfo *ciPtr = breakChunkPtr->clientData;
+ const char *p = ciPtr->u.chars + ciPtr->baseOffset + breakChunkPtr->breakIndex;
+ const char *q = Tcl_UtfPrev(p, ciPtr->u.chars + ciPtr->baseOffset);
+
+ if (IsExpandableSpace(q)
+ && !(breakChunkPtr->wrappedAtSpace
+ && breakChunkPtr->breakIndex == breakChunkPtr->numBytes)) {
+ addNumBytes = p - q;
+ data->breakChunkPtr->breakIndex -= addNumBytes;
+ data->breakChunkPtr->numSpaces -= 1;
+ data->numSpaces -= 1;
+ }
}
- /*
- * If we're at a new tab, adjust the layout for all the chunks
- * pertaining to the previous tab. Also adjust the amount of space
- * left in the line to account for space that will be eaten up by the
- * tab.
- */
+ if (data->breakChunkPtr->breakIndex != data->breakChunkPtr->numBytes) {
+ TkTextSegment *segPtr;
+ TkTextDispChunk *chunkPtr = data->breakChunkPtr;
+ TkTextIndex index = *indexPtr;
- if (gotTab) {
- if (tabIndex >= 0) {
- AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
- x = chunkPtr->x + chunkPtr->width;
+ LayoutUndisplay(data, chunkPtr);
+ data->chunkPtr = chunkPtr;
+ TkTextIndexForwBytes(data->textPtr, &index, chunkPtr->byteOffset, &index);
+ segPtr = TkTextIndexGetContentSegment(&index, NULL);
+ LayoutMakeCharInfo(data, segPtr, chunkPtr->segByteOffset, data->breakChunkPtr->breakIndex);
+ segPtr->typePtr->layoutProc(&index, segPtr, chunkPtr->segByteOffset, data->maxX,
+ data->breakChunkPtr->breakIndex, 0, data->wrapMode, data->textPtr->spaceMode,
+ chunkPtr);
+ LayoutFinalizeCharInfo(data, false); /* second parameter doesn't matter here */
+ LayoutDoWidthAdjustmentForContextDrawing(data);
+ chunkPtr->numBytes += addNumBytes;
+
+ if (chunkPtr->skipFirstChar) {
+ chunkPtr->numBytes += 1;
}
- tabChunkPtr = chunkPtr;
- tabSize = SizeOfTab(textPtr, tabStyle, tabArrayPtr, &tabIndex, x,
- maxX);
- if ((maxX >= 0) && (tabSize >= maxX - x)) {
- break;
+ }
+ }
+
+ /*
+ * Remove all the empty chunks at end of line. In this way we avoid to have
+ * an insert cursur chunk at end of line, which should belong to the next line.
+ */
+
+ if (data->lastChunkPtr->numBytes == 0) {
+ data->breakChunkPtr = data->breakChunkPtr->prevPtr;
+ assert(data->breakChunkPtr);
+ while (data->breakChunkPtr->numBytes == 0) {
+ data->breakChunkPtr = data->breakChunkPtr->prevPtr;
+ assert(data->breakChunkPtr);
+ }
+ LayoutDestroyChunks(data);
+ }
+}
+
+static void
+LayoutFullJustification(
+ LayoutData *data,
+ DLine *dlPtr)
+{
+ TkTextDispChunk *chunkPtr;
+ TkTextDispChunk *nextChunkPtr;
+ unsigned numSpaces;
+ int remainingPixels;
+ int shiftX;
+
+ numSpaces = data->numSpaces;
+ remainingPixels = data->maxX - dlPtr->length;
+
+ if (numSpaces == 0 || remainingPixels <= 0) {
+ return;
+ }
+
+ shiftX = 0;
+ chunkPtr = dlPtr->chunkPtr;
+
+ while ((nextChunkPtr = chunkPtr->nextPtr)) {
+ if (chunkPtr->numSpaces > 0) {
+ unsigned expand = 0;
+ unsigned i;
+
+ assert(IsCharChunk(chunkPtr));
+
+ for (i = 0; i < chunkPtr->numSpaces; ++i) {
+ unsigned space;
+
+ assert(numSpaces > 0);
+ space = (remainingPixels + numSpaces - 1)/numSpaces;
+ expand += space;
+ remainingPixels -= space;
+ numSpaces -= 1;
}
+
+ shiftX += expand;
+ chunkPtr->width += expand;
+ chunkPtr->additionalWidth = expand;
}
- curIndex.byteIndex += chunkPtr->numBytes;
- byteOffset += chunkPtr->numBytes;
- if (byteOffset >= segPtr->size) {
- byteOffset = 0;
- segPtr = segPtr->nextPtr;
- if (elide && segPtr == NULL) {
- /*
- * An elided section started on this line, and carries on
- * until the newline. Hence the newline is actually elided,
- * and we want to merge the display of the next logical line
- * with this one.
- */
- TkTextLine *linePtr = TkBTreeNextLine(NULL, curIndex.linePtr);
+ nextChunkPtr->x += shiftX;
+ chunkPtr = nextChunkPtr;
+ }
+}
+
+static bool
+LayoutPrevDispLineEndsWithSpace(
+ const TkText *textPtr,
+ const TkTextSegment *segPtr,
+ int offset)
+{
+ assert(segPtr);
+ assert(offset < segPtr->size);
+
+ if (TkTextSegmentIsElided(textPtr, segPtr)) {
+ if (!(segPtr = TkBTreeFindStartOfElidedRange(textPtr->sharedTextPtr, textPtr, segPtr))) {
+ return false;
+ }
+ offset = -1;
+ }
- if (linePtr != NULL) {
- dlPtr->logicalLinesMerged++;
- curIndex.byteIndex = 0;
- curIndex.linePtr = linePtr;
- chunkPtr = NULL;
- goto connectNextLogicalLine;
+ if (offset == -1) {
+ while (true) {
+ if (!(segPtr = segPtr->prevPtr)) {
+ return false;
+ }
+ switch ((int) segPtr->typePtr->group) {
+ case SEG_GROUP_CHAR:
+ return segPtr->body.chars[segPtr->size - 1] == ' ';
+ case SEG_GROUP_BRANCH:
+ if (segPtr->typePtr == &tkTextLinkType) {
+ segPtr = segPtr->body.link.prevPtr;
}
+ break;
+ case SEG_GROUP_MARK:
+ /* skip */
+ break;
+ case SEG_GROUP_HYPHEN:
+ case SEG_GROUP_IMAGE:
+ case SEG_GROUP_WINDOW:
+ return false;
}
}
+ }
+
+ return segPtr->typePtr == &tkTextCharType && segPtr->body.chars[offset] == ' ';
+}
- chunkPtr = NULL;
+static DLine *
+LayoutDLine(
+ const TkTextIndex *indexPtr,/* Beginning of display line. May not necessarily point to
+ * a character segment. */
+ int displayLineNo) /* Display line number of logical line, needed for caching. */
+{
+ TextDInfo *dInfoPtr;
+ DLine *dlPtr;
+ TkText *textPtr;
+ StyleValues *sValPtr;
+ TkTextDispChunk *chunkPtr;
+ TkTextDispChunkSection *sectionPtr;
+ TkTextDispChunkSection *prevSectionPtr;
+ TkTextSegment *segPtr;
+ LayoutData data;
+ bool endOfLogicalLine;
+ bool isStartOfLine;
+ int ascent, descent, leading, jIndent;
+ unsigned countChunks;
+ unsigned chunksPerSection;
+ int length, offset;
+
+ assert(displayLineNo >= 0);
+ assert((displayLineNo == 0) ==
+ (IsStartOfNotMergedLine(indexPtr) || TkTextIndexIsStartOfText(indexPtr)));
+
+ DEBUG(stats.numLayouted += 1);
+
+ textPtr = indexPtr->textPtr;
+ assert(textPtr);
+ dInfoPtr = textPtr->dInfoPtr;
+
+ /*
+ * Create and initialize a new DLine structure.
+ */
+
+ if (dInfoPtr->dLinePoolPtr) {
+ dlPtr = dInfoPtr->dLinePoolPtr;
+ dInfoPtr->dLinePoolPtr = dlPtr->nextPtr;
+ } else {
+ dlPtr = malloc(sizeof(DLine));
+ DEBUG_ALLOC(tkTextCountNewDLine++);
}
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- FinalizeBaseChunk(NULL);
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
- if (noCharsYet) {
- dlPtr->spaceAbove = 0;
- dlPtr->spaceBelow = 0;
- dlPtr->length = 0;
+ dlPtr = memset(dlPtr, 0, sizeof(DLine));
+ dlPtr->flags = NEW_LAYOUT|OLD_Y_INVALID;
+ dlPtr->index = *indexPtr;
+ dlPtr->displayLineNo = displayLineNo;
+ TkTextIndexToByteIndex(&dlPtr->index);
+ isStartOfLine = TkTextIndexIsStartOfLine(&dlPtr->index);
+
+ /*
+ * Initialize layout data.
+ */
+
+ memset(&data, 0, sizeof(data));
+ data.index = dlPtr->index;
+ data.justify = textPtr->justify;
+ data.tabIndex = -1;
+ data.tabStyle = TK_TEXT_TABSTYLE_TABULAR;
+ data.wrapMode = textPtr->wrapMode;
+ data.paragraphStart = displayLineNo == 0;
+ data.trimSpaces = textPtr->spaceMode == TEXT_SPACEMODE_TRIM;
+ data.displayLineNo = displayLineNo;
+ data.textPtr = textPtr;
+
+ if (data.paragraphStart) {
+ dlPtr->flags |= PARAGRAPH_START;
+ data.logicalLinePtr = TkTextIndexGetLine(indexPtr);
+ data.byteOffset = TkTextIndexGetByteIndex(indexPtr);
+ } else {
+ TkTextLine *linePtr = TkTextIndexGetLine(indexPtr);
+ TkTextIndex index2 = *indexPtr;
+
+ data.logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ DEBUG(TkTextIndexSetPeer(&index2, NULL)); /* allow index outside of peer */
+ TkTextIndexSetByteIndex2(&index2, data.logicalLinePtr, 0);
+ data.byteOffset = TkTextIndexCountBytes(&index2, indexPtr);
+ }
+
+ segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
+ data.skipSpaces = data.trimSpaces && LayoutPrevDispLineEndsWithSpace(textPtr, segPtr, offset - 1);
+
+ /*
+ * Skip elided region.
+ */
+
+ if (TkTextSegmentIsElided(textPtr, segPtr)) {
+ segPtr = TkBTreeFindEndOfElidedRange(textPtr->sharedTextPtr, textPtr, segPtr);
+ TkTextIndexSetSegment(&data.index, segPtr);
+ LayoutSkipBytes(&data, dlPtr, indexPtr, &data.index);
/*
- * We used to Tcl_Panic here, saying that LayoutDLine couldn't place
- * any characters on a line, but I believe a more appropriate response
- * is to return a DLine with zero height. With elided lines, tag
- * transitions and asynchronous line height calculations, it is hard
- * to avoid this situation ever arising with the current code design.
+ * NOTE: it is possible that we have reached now the end of text. This is
+ * the only case that an empty display line can be produced, which will be
+ * linked into the list of display lines. It's only a single superfluous
+ * line, so we can live with that.
*/
- return dlPtr;
+ if (TkTextIndexIsEndOfText(&data.index)) {
+ assert(data.chunkPtr);
+ assert(!data.chunkPtr->nextPtr);
+ dlPtr->byteCount = data.chunkPtr->numBytes;
+ LayoutFreeChunk(&data);
+ LayoutUpdateLineHeightInformation(&data, dlPtr, data.logicalLinePtr, true, 0);
+ return dlPtr;
+ }
}
- wholeLine = (segPtr == NULL);
+
+ endOfLogicalLine = LayoutLogicalLine(&data, dlPtr);
+ assert(data.numBytesSoFar > 0);
/*
* We're at the end of the display line. Throw away everything after the
* most recent word break, if there is one; this may potentially require
- * the last chunk to be layed out again.
+ * the last chunk to be layed out again. Also perform hyphenation, if
+ * enabled, this probably requires the re-layout of a few chunks at the
+ * end of the line.
*/
- if (breakChunkPtr == NULL) {
+ if (!endOfLogicalLine) {
+ LayoutBreakLine(&data, &dlPtr->index);
+ }
+
+ if (data.textPtr->hyphenate) {
+ TkTextDispChunk *chunkPtr = data.firstChunkPtr->nextPtr;
+
/*
- * This code makes sure that we don't accidentally display chunks with
- * no characters at the end of the line (such as the insertion
- * cursor). These chunks belong on the next line. So, throw away
- * everything after the last chunk that has characters in it.
+ * Remove all unused hyphen segments, this will speed up the display process,
+ * because this removal will be done only one time, but the display process
+ * may iterate over the chunks several times.
*/
- breakChunkPtr = lastCharChunkPtr;
- breakByteOffset = breakChunkPtr->numBytes;
- }
- if ((breakChunkPtr != NULL) && ((lastChunkPtr != breakChunkPtr)
- || (breakByteOffset != lastChunkPtr->numBytes))) {
- while (1) {
- chunkPtr = breakChunkPtr->nextPtr;
- if (chunkPtr == NULL) {
- break;
- }
- FreeStyle(textPtr, chunkPtr->stylePtr);
- breakChunkPtr->nextPtr = chunkPtr->nextPtr;
- if (chunkPtr->undisplayProc != NULL) {
- chunkPtr->undisplayProc(textPtr, chunkPtr);
- }
- ckfree(chunkPtr);
- }
- if (breakByteOffset != breakChunkPtr->numBytes) {
- if (breakChunkPtr->undisplayProc != NULL) {
- breakChunkPtr->undisplayProc(textPtr, breakChunkPtr);
+ while (chunkPtr) {
+ TkTextDispChunk *nextChunkPtr = chunkPtr->nextPtr;
+
+ if (nextChunkPtr && chunkPtr->width == 0 && chunkPtr != data.cursorChunkPtr) {
+ chunkPtr->prevPtr->numBytes += chunkPtr->numBytes;
+
+ if ((chunkPtr->prevPtr->nextPtr = nextChunkPtr)) {
+ nextChunkPtr->prevPtr = chunkPtr->prevPtr;
+ data.chunkPtr = chunkPtr;
+ LayoutFreeChunk(&data);
+ }
}
- segPtr = TkTextIndexToSeg(&breakIndex, &byteOffset);
- segPtr->typePtr->layoutProc(textPtr, &breakIndex, segPtr,
- byteOffset, maxX, breakByteOffset, 0, wrapMode,
- breakChunkPtr);
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- FinalizeBaseChunk(NULL);
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
+
+ chunkPtr = nextChunkPtr;
}
- lastChunkPtr = breakChunkPtr;
- wholeLine = 0;
}
/*
+ * This has to be done after LayoutBreakLine.
+ */
+
+ dlPtr->chunkPtr = data.firstChunkPtr;
+ dlPtr->lastChunkPtr = data.lastChunkPtr;
+ dlPtr->cursorChunkPtr = data.cursorChunkPtr;
+ dlPtr->firstCharChunkPtr = data.firstCharChunkPtr;
+ dlPtr->breakInfo = data.breakInfo;
+
+ /*
* Make tab adjustments for the last tab stop, if there is one.
*/
- if ((tabIndex >= 0) && (tabChunkPtr != NULL)) {
- AdjustForTab(textPtr, tabArrayPtr, tabIndex, tabChunkPtr);
+ if (data.tabIndex >= 0) {
+ assert(data.tabChunkPtr);
+ AdjustForTab(&data);
}
/*
* Make one more pass over the line to recompute various things like its
* height, length, and total number of bytes. Also modify the x-locations
- * of chunks to reflect justification. If we're not wrapping, I'm not sure
- * what is the best way to handle right and center justification: should
- * the total length, for purposes of justification, be (a) the window
- * width, (b) the length of the longest line in the window, or (c) the
- * length of the longest line in the text? (c) isn't available, (b) seems
- * weird, since it can change with vertical scrolling, so (a) is what is
- * implemented below.
- */
-
- if (wrapMode == TEXT_WRAPMODE_NONE) {
- maxX = textPtr->dInfoPtr->maxX - textPtr->dInfoPtr->x - rMargin;
- }
- dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
- if (justify == TK_JUSTIFY_LEFT) {
- jIndent = 0;
- } else if (justify == TK_JUSTIFY_RIGHT) {
- jIndent = maxX - dlPtr->length;
- } else {
- jIndent = (maxX - dlPtr->length)/2;
+ * of chunks to reflect justification.
+ */
+
+ if (data.wrapMode == TEXT_WRAPMODE_NONE) {
+ data.maxX = dInfoPtr->maxX - dInfoPtr->x - data.rMargin;
+ }
+ length = dlPtr->length = data.lastChunkPtr->x + data.lastChunkPtr->width;
+ if (data.wrapMode != TEXT_WRAPMODE_NONE) {
+ length = MIN(length, data.maxX);
+ }
+
+ jIndent = 0;
+
+ switch (data.justify) {
+ case TK_TEXT_JUSTIFY_LEFT:
+ /* no action */
+ break;
+ case TK_TEXT_JUSTIFY_RIGHT:
+ jIndent = data.maxX - length;
+ break;
+ case TK_TEXT_JUSTIFY_FULL:
+ if (!endOfLogicalLine) {
+ LayoutFullJustification(&data, dlPtr);
+ }
+ break;
+ case TK_TEXT_JUSTIFY_CENTER:
+ jIndent = (data.maxX - length)/2;
+ break;
}
+
ascent = descent = 0;
- for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL;
- chunkPtr = chunkPtr->nextPtr) {
- chunkPtr->x += jIndent;
+ sectionPtr = prevSectionPtr = NULL;
+ chunksPerSection = (data.countChunks + MAX_SECTIONS_PER_LINE - 1)/MAX_SECTIONS_PER_LINE;
+ chunksPerSection = MAX(chunksPerSection, MIN_CHUNKS_PER_SECTION);
+ countChunks = chunksPerSection - 1;
+
+ for (chunkPtr = dlPtr->chunkPtr; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
+ if (++countChunks == chunksPerSection) {
+ /*
+ * Create next section.
+ */
+ sectionPtr = LayoutNewSection(dInfoPtr);
+ if (prevSectionPtr) {
+ prevSectionPtr->nextPtr = sectionPtr;
+ }
+ sectionPtr->chunkPtr = chunkPtr;
+ prevSectionPtr = sectionPtr;
+ countChunks = 0;
+ }
+ chunkPtr->sectionPtr = sectionPtr;
+ sectionPtr->numBytes += chunkPtr->numBytes;
dlPtr->byteCount += chunkPtr->numBytes;
- if (chunkPtr->minAscent > ascent) {
- ascent = chunkPtr->minAscent;
+ chunkPtr->x += jIndent;
+ ascent = MAX(ascent, chunkPtr->minAscent);
+ descent = MAX(descent, chunkPtr->minDescent);
+ dlPtr->height = MAX(dlPtr->height, chunkPtr->minHeight);
+ sValPtr = chunkPtr->stylePtr->sValuePtr;
+ if (sValPtr->borderWidth > 0 && sValPtr->relief != TK_RELIEF_FLAT) {
+ dlPtr->flags |= HAS_3D_BORDER;
+ }
+ }
+
+ leading = ascent + descent;
+
+ if (dlPtr->height < leading) {
+ dlPtr->height = leading;
+ dlPtr->baseline = ascent;
+ } else {
+ dlPtr->baseline = ascent + (dlPtr->height - leading)/2;
+ }
+
+ sValPtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
+
+ dlPtr->spaceAbove = isStartOfLine ? sValPtr->spacing1 : (sValPtr->spacing2 + 1)/2;
+ dlPtr->spaceBelow = endOfLogicalLine ? sValPtr->spacing3 : sValPtr->spacing2/2;
+ dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow;
+ dlPtr->baseline += dlPtr->spaceAbove;
+ /* line length may have changed because of justification */
+ dlPtr->length = data.lastChunkPtr->x + jIndent + data.lastChunkPtr->width;
+
+ LayoutUpdateLineHeightInformation(&data, dlPtr, data.logicalLinePtr,
+ endOfLogicalLine, data.hyphenRule);
+
+ return dlPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * CheckIfLineMetricIsUpToDate --
+ *
+ * This function wil be invoked after update of line metric calculations.
+ * It checks whether the all line metrics are up-to-date, and will
+ * invoke the appropriate actions.
+ *
+ * Results:
+ * Returns true if the widget has not been deleted by receiver of the
+ * triggered callback.
+ *
+ * Side effects:
+ * Firing the <<WidgetViewSync>> event, scrollbar update, and resetting
+ * some states.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static bool
+TriggerWatchCursor(
+ TkText *textPtr)
+{
+ if (textPtr->watchCmd) {
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ char buf[2][2*TK_POS_CHARS + 2];
+
+ if (memcmp(&dInfoPtr->curPixelPos, &dInfoPtr->prevPixelPos, sizeof(PixelPos)) != 0) {
+ textPtr->sharedTextPtr->triggerWatchCmd = false;
+ snprintf(buf[0], sizeof(buf[0]), "@%d,%d",
+ dInfoPtr->curPixelPos.xFirst, dInfoPtr->curPixelPos.yFirst);
+ snprintf(buf[1], sizeof(buf[1]), "@%d,%d",
+ dInfoPtr->curPixelPos.xLast, dInfoPtr->curPixelPos.yLast);
+ TkTextTriggerWatchCmd(textPtr, "view", buf[0], buf[1], NULL, false);
+ memcpy(&textPtr->dInfoPtr->prevPixelPos, &textPtr->dInfoPtr->curPixelPos, sizeof(PixelPos));
+ textPtr->sharedTextPtr->triggerWatchCmd = true;
}
- if (chunkPtr->minDescent > descent) {
- descent = chunkPtr->minDescent;
+ }
+
+ return !(textPtr->flags & DESTROYED);
+}
+
+static void
+UpdateLineMetricsFinished(
+ TkText *textPtr,
+ bool sendImmediately)
+{
+ assert(TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges));
+
+ textPtr->dInfoPtr->flags &= ~(ASYNC_UPDATE|ASYNC_PENDING);
+ textPtr->dInfoPtr->pendingUpdateLineMetricsFinished = false;
+
+ TkTextRunAfterSyncCmd(textPtr);
+
+ /*
+ * Fire the <<WidgetViewSync>> event since the widget view is in sync
+ * with its internal data (actually it will be after the next trip
+ * through the event loop, because the widget redraws at idle-time).
+ */
+
+ TkTextGenerateWidgetViewSyncEvent(textPtr, sendImmediately);
+}
+
+static void
+RunUpdateLineMetricsFinished(
+ ClientData clientData)
+{
+ TkText *textPtr = (TkText *) clientData;
+
+ if (!(textPtr->flags & DESTROYED)) {
+ textPtr->dInfoPtr->pendingUpdateLineMetricsFinished = false;
+ if (TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges)) {
+ UpdateLineMetricsFinished(textPtr, true);
+ }
+ }
+}
+
+static void
+CheckIfLineMetricIsUpToDate(
+ TkText *textPtr)
+{
+ if (textPtr->sharedTextPtr->allowUpdateLineMetrics
+ && TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges)) {
+ /*
+ * Remove the async handler.
+ */
+
+ if (textPtr->dInfoPtr->lineUpdateTimer) {
+ Tcl_DeleteTimerHandler(textPtr->dInfoPtr->lineUpdateTimer);
+ textPtr->refCount -= 1;
+ textPtr->dInfoPtr->lineUpdateTimer = NULL;
}
- if (chunkPtr->minHeight > dlPtr->height) {
- dlPtr->height = chunkPtr->minHeight;
+
+ /*
+ * If we have a full update, then also update the scrollbar.
+ */
+
+ GetYView(textPtr->interp, textPtr, true);
+
+ if (!(TriggerWatchCursor(textPtr))) {
+ return; /* the widget has been deleted */
}
- sValuePtr = chunkPtr->stylePtr->sValuePtr;
- if ((sValuePtr->borderWidth > 0)
- && (sValuePtr->relief != TK_RELIEF_FLAT)) {
- dlPtr->flags |= HAS_3D_BORDER;
+
+ /*
+ * Report finish of full update.
+ */
+
+ if (!textPtr->dInfoPtr->pendingUpdateLineMetricsFinished) {
+ textPtr->dInfoPtr->pendingUpdateLineMetricsFinished = true;
+ Tcl_DoWhenIdle(RunUpdateLineMetricsFinished, (ClientData) textPtr);
+ }
+
+ if (tkBTreeDebug) {
+ CheckLineMetricConsistency(textPtr);
}
}
- if (dlPtr->height < (ascent + descent)) {
- dlPtr->height = ascent + descent;
- dlPtr->baseline = ascent;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SaveDisplayLines --
+ *
+ * Save the display lines, produced during line metric computation,
+ * for displaying. So UpdateDisplayInfo eventually must not re-compute
+ * these lines. This function will only be called if it is sure that
+ * DisplayText will be triggered afterwards, because UpdateDisplayInfo
+ * (called by DisplayText) is responsible for releasing the unused lines.
+ * The caller is responsible that the display will be saved in order from
+ * top to bottom, without gaps.
+ *
+ * Saving the produced display lines is an important speed improvement
+ * (especially on Mac). Consider the following use case:
+ *
+ * 1. The text widget will be created.
+ * 2. Content will be inserted.
+ * 3. The view will be changed (for example to the end of the file).
+ *
+ * In this case MeasureDown will produce display lines for line metric
+ * calculation, needed for the change of the view, and afterwards
+ * UpdateDisplayInfo needs (some of) these lines for displaying.
+ *
+ * Note that no more lines will be saved than fitting into the widget,
+ * all surplus lines will be released immediately.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The display lines will be saved in TextDInfo. Function UpdateDisplayInfo
+ * is responsible to release the unused lines. The cached lines in
+ * argument 'info' will be taken over, this means that 'info->dLinePtr'
+ * is NULL after this function has done his work.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+SaveDisplayLines(
+ TkText *textPtr,
+ DisplayInfo *info,
+ bool append) /* Append to previously saved lines if 'true', otherwise prepend. */
+{
+ TextDInfo *dInfoPtr;
+ DLine *firstPtr, *lastPtr;
+ int height, viewHeight;
+
+ if (!(firstPtr = info->dLinePtr)) {
+ return;
+ }
+
+ assert(info->lastDLinePtr);
+ lastPtr = info->lastDLinePtr;
+ dInfoPtr = textPtr->dInfoPtr;
+ height = dInfoPtr->savedDisplayLinesHeight + info->heightOfCachedLines;
+ viewHeight = Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth;
+ /* we need some overhead, because the widget may show lines only partially */
+ viewHeight += info->dLinePtr->height;
+
+ if (append) {
+ if (dInfoPtr->lastSavedDLinePtr) {
+ dInfoPtr->lastSavedDLinePtr->nextPtr = firstPtr;
+ firstPtr->prevPtr = dInfoPtr->lastSavedDLinePtr;
+ } else {
+ dInfoPtr->savedDLinePtr = firstPtr;
+ }
+ dInfoPtr->lastSavedDLinePtr = lastPtr;
+ firstPtr = lastPtr = dInfoPtr->savedDLinePtr;
+ while (lastPtr->nextPtr && height >= viewHeight - lastPtr->height) {
+ height -= lastPtr->height;
+ lastPtr = lastPtr->nextPtr;
+ }
+ if (firstPtr != lastPtr) {
+ FreeDLines(textPtr, firstPtr, lastPtr, DLINE_FREE_TEMP);
+ assert(dInfoPtr->savedDLinePtr == lastPtr);
+ }
} else {
- dlPtr->baseline = ascent + (dlPtr->height - ascent - descent)/2;
+ if (dInfoPtr->savedDLinePtr) {
+ lastPtr->nextPtr = dInfoPtr->savedDLinePtr;
+ dInfoPtr->savedDLinePtr->prevPtr = lastPtr;
+ } else {
+ dInfoPtr->lastSavedDLinePtr = lastPtr;
+ }
+ dInfoPtr->savedDLinePtr = firstPtr;
+ firstPtr = lastPtr = dInfoPtr->lastSavedDLinePtr;
+ while (firstPtr->prevPtr && height >= viewHeight - firstPtr->height) {
+ height -= firstPtr->height;
+ firstPtr = firstPtr->prevPtr;
+ }
+ if (firstPtr != lastPtr) {
+ FreeDLines(textPtr, firstPtr->nextPtr, NULL, DLINE_FREE_TEMP);
+ assert(!firstPtr->nextPtr);
+ dInfoPtr->lastSavedDLinePtr = firstPtr;
+ }
}
- sValuePtr = dlPtr->chunkPtr->stylePtr->sValuePtr;
- if (dlPtr->index.byteIndex == 0) {
- dlPtr->spaceAbove = sValuePtr->spacing1;
+
+ dInfoPtr->savedDisplayLinesHeight = height;
+ info->dLinePtr = info->lastDLinePtr = NULL;
+ info->numCachedLines = 0;
+ info->heightOfCachedLines = 0;
+ assert(!dInfoPtr->savedDLinePtr == !dInfoPtr->lastSavedDLinePtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ComputeDisplayLineInfo --
+ *
+ * This functions computes the display line information for struct
+ * DisplayInfo. If the cached information is still incomplete for
+ * this computation then LayoutDLine will be used for the computation
+ * of the missing display lines.
+ *
+ * If additional display line computation is required for the line
+ * metric computation, then these lines will ba cached, but only
+ * the last produced lines which can fit into the widget (this means:
+ * no more lines than fitting into the widget will be cached).
+ *
+ * Results:
+ * The attributes of 'info' will be set. The return value is the
+ * corresponding logical line.
+ *
+ * Side effects:
+ * The cache may be filled with more line metric information.
+ * Furthermore some of the produced display lines will be cached,
+ * the caller is responsible to release these lines.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextDispLineEntry *
+SearchDispLineEntry(
+ TkTextDispLineEntry *first,
+ const TkTextDispLineEntry *last,
+ unsigned byteOffset)
+{
+ /*
+ * NOTE: here 'last' is the last entry (not the pointer after the last
+ * element as usual).
+ */
+
+ if (byteOffset >= last->byteOffset) {
+ return (TkTextDispLineEntry *) last; /* frequent case */
+ }
+
+ while (first != last) {
+ TkTextDispLineEntry *mid = first + (last - first)/2;
+
+ if (byteOffset >= (mid + 1)->byteOffset) {
+ first = mid + 1;
+ } else {
+ last = mid;
+ }
+ }
+
+ return first;
+}
+
+static void
+InsertDLine(
+ TkText *textPtr,
+ DisplayInfo *info,
+ DLine *dlPtr,
+ unsigned viewHeight)
+{
+ DLine *firstPtr = info->dLinePtr;
+
+ assert(!dlPtr->nextPtr);
+ assert(!dlPtr->prevPtr);
+
+ info->heightOfCachedLines += dlPtr->height;
+
+ if (firstPtr && info->heightOfCachedLines >= viewHeight + firstPtr->height) {
+ info->heightOfCachedLines -= firstPtr->height;
+ if ((info->dLinePtr = firstPtr->nextPtr)) {
+ info->dLinePtr->prevPtr = NULL;
+ } else {
+ info->lastDLinePtr = NULL;
+ }
+ firstPtr->nextPtr = NULL;
+ FreeDLines(textPtr, firstPtr, NULL, DLINE_FREE_TEMP);
} else {
- dlPtr->spaceAbove = sValuePtr->spacing2 - sValuePtr->spacing2/2;
+ info->numCachedLines += 1;
}
- if (wholeLine) {
- dlPtr->spaceBelow = sValuePtr->spacing3;
+ if (info->lastDLinePtr) {
+ assert(info->dLinePtr);
+ info->lastDLinePtr->nextPtr = dlPtr;
+ dlPtr->prevPtr = info->lastDLinePtr;
} else {
- dlPtr->spaceBelow = sValuePtr->spacing2/2;
+ assert(!info->dLinePtr);
+ info->dLinePtr = dlPtr;
}
- dlPtr->height += dlPtr->spaceAbove + dlPtr->spaceBelow;
- dlPtr->baseline += dlPtr->spaceAbove;
- dlPtr->lMarginColor = sValuePtr->lMarginColor;
- dlPtr->rMarginColor = sValuePtr->rMarginColor;
- if (wrapMode != TEXT_WRAPMODE_NONE) {
- dlPtr->rMarginWidth = rMargin;
+ info->lastDLinePtr = dlPtr;
+}
+
+static TkTextLine *
+ComputeDisplayLineInfo(
+ TkText *textPtr,
+ const TkTextIndex *indexPtr,
+ DisplayInfo *info)
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ TkTextPixelInfo *pixelInfo;
+ TkTextDispLineInfo *dispLineInfo;
+ TkTextDispLineEntry *entry;
+ TkTextLine *logicalLinePtr;
+ TkTextLine *linePtr;
+ unsigned byteOffset;
+ unsigned startByteOffset;
+ unsigned viewHeight;
+
+ assert(info);
+
+ linePtr = TkTextIndexGetLine(indexPtr);
+ logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ pixelInfo = TkBTreeLinePixelInfo(textPtr, logicalLinePtr);
+ dispLineInfo = pixelInfo->dispLineInfo;
+ info->index = *indexPtr;
+ TkTextIndexSetToStartOfLine2(&info->index, logicalLinePtr);
+ startByteOffset = TkTextIndexGetByteIndex(&info->index);
+ byteOffset = TkTextIndexCountBytes(&info->index, indexPtr);
+ byteOffset += TkTextIndexGetByteIndex(&info->index);
+
+ info->pixelInfo = pixelInfo;
+ info->displayLineNo = 0;
+ info->numDispLines = 1;
+ info->entry = info->entryBuffer;
+ info->dLinePtr = info->lastDLinePtr = NULL;
+ info->nextByteOffset = -1;
+ info->numCachedLines = 0;
+ info->heightOfCachedLines = 0;
+ info->linePtr = linePtr;
+
+ if (dInfoPtr->lineMetricUpdateEpoch == (pixelInfo->epoch & EPOCH_MASK)) {
+ if (!dispLineInfo) {
+ TkTextLine *nextLogicalLinePtr =
+ TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, logicalLinePtr);
+
+ entry = info->entryBuffer;
+ if (logicalLinePtr->nextPtr == nextLogicalLinePtr
+ && TkTextIndexIsStartOfLine(&info->index)) {
+ info->nextByteOffset = logicalLinePtr->size - byteOffset;
+ entry->byteOffset = 0;
+ (entry + 1)->byteOffset = logicalLinePtr->size;
+ } else {
+ TkTextIndex index2 = info->index;
+ TkTextIndexSetToStartOfLine2(&index2, nextLogicalLinePtr);
+ info->nextByteOffset = TkTextIndexCountBytes(&info->index, &index2);
+ entry->byteOffset = TkTextIndexGetByteIndex(&info->index);
+ (entry + 1)->byteOffset = entry->byteOffset + info->nextByteOffset;
+ }
+ info->byteOffset = byteOffset;
+ info->isComplete = true;
+ info->pixels = pixelInfo->height;
+ entry->height = pixelInfo->height;
+ entry->pixels = pixelInfo->height;
+ byteOffset = (entry + 1)->byteOffset - startByteOffset;
+ TkTextIndexForwBytes(textPtr, &info->index, byteOffset, &info->index);
+ return logicalLinePtr;
+ }
+
+ if (dispLineInfo->numDispLines > 0) {
+ const TkTextDispLineEntry *last;
+ unsigned nextByteOffset;
+
+ last = dispLineInfo->entry + dispLineInfo->numDispLines;
+ entry = SearchDispLineEntry(dispLineInfo->entry, last, byteOffset);
+
+ if (entry != last) {
+ info->entry = entry;
+ info->byteOffset = byteOffset - entry->byteOffset;
+ info->nextByteOffset = (entry + 1)->byteOffset - byteOffset;
+ info->displayLineNo = entry - dispLineInfo->entry;
+ info->numDispLines = dispLineInfo->numDispLines;
+ info->pixels = (last - 1)->pixels;
+ info->isComplete = (dInfoPtr->lineMetricUpdateEpoch == pixelInfo->epoch);
+ byteOffset = last->byteOffset - startByteOffset;
+ TkTextIndexForwBytes(textPtr, &info->index, byteOffset, &info->index);
+ return logicalLinePtr;
+ }
+
+ /*
+ * If we reach this point, then we need more information than already
+ * computed for this line.
+ */
+
+ info->displayLineNo = dispLineInfo->numDispLines;
+ nextByteOffset = last->byteOffset - dispLineInfo->entry[0].byteOffset;
+ TkBTreeMoveForward(&info->index, nextByteOffset);
+ byteOffset -= nextByteOffset;
+ }
}
/*
- * Recompute line length: may have changed because of justification.
+ * Compute missing line metric information. Don't throw away the produced display
+ * lines, probably the caller might use it. But do not cache more than fitting into
+ * the widget.
*/
- dlPtr->length = lastChunkPtr->x + lastChunkPtr->width;
+ viewHeight = Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth;
+ /* we need some overhead, because the widget may show lines only partially */
+ viewHeight += dInfoPtr->dLinePtr ? dInfoPtr->dLinePtr->height : 20;
- return dlPtr;
+ while (true) {
+ DLine *dlPtr;
+
+ if (dInfoPtr->lastMetricDLinePtr
+ && TkTextIndexIsEqual(&info->index, &dInfoPtr->lastMetricDLinePtr->index)) {
+ dlPtr = dInfoPtr->lastMetricDLinePtr;
+ dInfoPtr->lastMetricDLinePtr = NULL;
+ assert(dlPtr->displayLineNo == info->displayLineNo);
+ } else {
+ dlPtr = LayoutDLine(&info->index, info->displayLineNo);
+ }
+ InsertDLine(textPtr, info, dlPtr, viewHeight);
+ TkTextIndexForwBytes(textPtr, &info->index, dlPtr->byteCount, &info->index);
+ if (dInfoPtr->lineMetricUpdateEpoch == pixelInfo->epoch || byteOffset < dlPtr->byteCount) {
+ info->byteOffset = byteOffset;
+ info->nextByteOffset = dlPtr->byteCount - byteOffset;
+ info->isComplete = (dInfoPtr->lineMetricUpdateEpoch == pixelInfo->epoch);
+ break;
+ }
+ byteOffset -= dlPtr->byteCount;
+ info->displayLineNo += 1;
+ }
+
+ /*
+ * Note that LayoutDLine may re-allocate 'pixelInfo->dispLineInfo',
+ * so variable 'dispLineInfo' is in general not valid anymore.
+ */
+
+ dispLineInfo = pixelInfo->dispLineInfo;
+
+ if (dispLineInfo) {
+ info->numDispLines = dispLineInfo->numDispLines;
+ info->entry = dispLineInfo->entry + info->displayLineNo;
+ info->pixels = dispLineInfo->entry[dispLineInfo->numDispLines - 1].pixels;
+ } else {
+ info->pixels = pixelInfo->height;
+ info->entryBuffer[0].height = pixelInfo->height;
+ info->entryBuffer[0].pixels = pixelInfo->height;
+ info->entryBuffer[0].byteOffset = byteOffset;
+ info->entryBuffer[1].byteOffset = info->nextByteOffset + info->byteOffset;
+ }
+
+ return logicalLinePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ComputeMissingMetric --
+ *
+ * This functions is continuing the computation of an unfinished
+ * display line metric, which can only happen if a logical line
+ * is wrapping into several display lines. It may not be required
+ * to compute all missing display lines, the computation stops
+ * until the threshold has been reached. But the compuation will
+ * always stop at the end of the logical line.
+ *
+ * Possible threshold types are THRESHOLD_BYTE_OFFSET,
+ * THRESHOLD_PIXEL_DISTANCE, and THRESHOLD_LINE_OFFSET. The two
+ * former thresholds will be specified absolute (but relative to
+ * start of logical line), and the latter thresholds will be specified
+ * relative to info->displayLineNo.
+ *
+ * If additional display line computation is required for the line
+ * metric computation, then these lines will ba cached, but only
+ * the last produced lines which can fit into the widget (this means:
+ * no more lines than fitting into the widget will be cached).
+ *
+ * It is important that this function is only computing the relevant
+ * line metric. It may happen that a logical line may wrap into
+ * thousands of display lines, but if only the first 100 (following
+ * lines) are needed, then only the first 100 should be computed here,
+ * not more.
+ *
+ * Results:
+ * The attributes of 'info' will be updated.
+ *
+ * Side effects:
+ * The cache may be filled with more line metric information.
+ * Furthermore some of the produced display lines will be cached,
+ * the caller is responsible to release these lines.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ComputeMissingMetric(
+ TkText *textPtr,
+ DisplayInfo *info,
+ Threshold thresholdType,
+ int threshold)
+{
+ int byteOffset, additionalLines;
+ int displayLineNo;
+ int *metricPtr = NULL; /* avoids compiler warning */
+ unsigned viewHeight;
+ TkTextIndex index;
+
+ assert(threshold >= 0);
+
+ if (info->isComplete) {
+ return;
+ }
+
+ additionalLines = info->numDispLines - info->displayLineNo;
+ assert(additionalLines > 0);
+ byteOffset = info->entry[additionalLines].byteOffset;
+ displayLineNo = info->numDispLines;
+ viewHeight = Tk_Height(textPtr->tkwin) - 2*textPtr->highlightWidth;
+ /* we need some overhead, because the widget may show lines only partially */
+ viewHeight += textPtr->dInfoPtr->dLinePtr ? textPtr->dInfoPtr->dLinePtr->height : 20;
+ TkTextIndexForwBytes(textPtr, &info->index,
+ byteOffset - info->entry[additionalLines - 1].byteOffset, &index);
+
+ switch (thresholdType) {
+ case THRESHOLD_BYTE_OFFSET: metricPtr = &byteOffset; break;
+ case THRESHOLD_LINE_OFFSET: metricPtr = &additionalLines; break;
+ case THRESHOLD_PIXEL_DISTANCE: metricPtr = &info->pixels; break;
+ }
+
+ while (threshold >= *metricPtr) {
+ DLine *dlPtr = LayoutDLine(&info->index, displayLineNo++);
+ info->pixels += dlPtr->height;
+ byteOffset += dlPtr->byteCount;
+ info->numDispLines += 1;
+ additionalLines -= 1;
+ TkTextIndexForwBytes(textPtr, &info->index, dlPtr->byteCount, &info->index);
+ InsertDLine(textPtr, info, dlPtr, viewHeight);
+
+ if (IsStartOfNotMergedLine(&info->index)) {
+ info->isComplete = true;
+ break;
+ }
+ }
+
+ info->entry = info->pixelInfo->dispLineInfo->entry + info->displayLineNo;
}
/*
@@ -1815,15 +4862,40 @@ LayoutDLine(
*----------------------------------------------------------------------
*/
+static bool
+LineIsUpToDate(
+ TkText *textPtr,
+ DLine *dlPtr,
+ unsigned lineMetricUpdateEpoch)
+{
+ const TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, TkTextIndexGetLine(&dlPtr->index));
+ const TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
+ unsigned epoch = pixelInfo->epoch;
+
+ assert(!(epoch & PARTIAL_COMPUTED_BIT) || dispLineInfo);
+
+ return (epoch & EPOCH_MASK) == lineMetricUpdateEpoch
+ && (!dispLineInfo || dlPtr->displayLineNo < dispLineInfo->numDispLines);
+}
+
static void
UpdateDisplayInfo(
- TkText *textPtr) /* Text widget to update. */
+ TkText *textPtr) /* Text widget to update. */
{
- register TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- register DLine *dlPtr, *prevPtr;
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ DLine *dlPtr;
+ DLine *topLine;
+ DLine *bottomLine;
+ DLine *newTopLine;
+ DLine *savedDLine; /* usable saved display lines */
+ DLine *prevSavedDLine; /* last old unfreed display line */
TkTextIndex index;
TkTextLine *lastLinePtr;
- int y, maxY, xPixelOffset, maxOffset, lineHeight;
+ TkTextLine *linePtr;
+ DisplayInfo info;
+ int y, maxY, xPixelOffset, maxOffset;
+ int displayLineNo;
+ unsigned epoch;
if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
return;
@@ -1831,18 +4903,33 @@ UpdateDisplayInfo(
dInfoPtr->flags &= ~DINFO_OUT_OF_DATE;
/*
+ * At first, update the default style, and reset cached chunk.
+ */
+
+ UpdateDefaultStyle(textPtr);
+ dInfoPtr->currChunkPtr = NULL;
+
+ /*
+ * Release the cached display lines.
+ */
+
+ FreeDLines(textPtr, NULL, NULL, DLINE_CACHE);
+
+ /*
* Delete any DLines that are now above the top of the window.
*/
index = textPtr->topIndex;
- dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index);
- if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
- FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK);
- }
- if (index.byteIndex == 0) {
- lineHeight = 0;
- } else {
- lineHeight = -1;
+ prevSavedDLine = NULL;
+ savedDLine = dInfoPtr->savedDLinePtr;
+
+ if ((dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index))) {
+ /*
+ * Don't throw away unused display lines immediately, because it may happen
+ * that we will reuse some of them later.
+ */
+
+ prevSavedDLine = FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_SAVE);
}
/*
@@ -1850,27 +4937,104 @@ UpdateDisplayInfo(
* information for lines that are missing.
*/
- lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
+ linePtr = TkTextIndexGetLine(&index);
+ lastLinePtr = TkBTreeGetLastLine(textPtr);
dlPtr = dInfoPtr->dLinePtr;
- prevPtr = NULL;
+ topLine = bottomLine = NULL;
y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
maxY = dInfoPtr->maxY;
- while (1) {
- register DLine *newPtr;
+ newTopLine = NULL;
+ epoch = dInfoPtr->lineMetricUpdateEpoch;
+ dInfoPtr->maxLength = 0;
- if (index.linePtr == lastLinePtr) {
- break;
+ if (IsStartOfNotMergedLine(&index)) {
+ displayLineNo = 0;
+ } else {
+ ComputeDisplayLineInfo(textPtr, &index, &info);
+ TkTextIndexBackBytes(textPtr, &index, info.byteOffset, &index);
+ displayLineNo = info.displayLineNo;
+
+ if (info.lastDLinePtr) {
+ /*
+ * Keep last produced display line, probably we can re-use it for new top line.
+ */
+
+ newTopLine = info.lastDLinePtr;
+ if (newTopLine->prevPtr) {
+ newTopLine->prevPtr->nextPtr = NULL;
+ newTopLine->prevPtr = NULL;
+ } else {
+ assert(info.dLinePtr == info.lastDLinePtr);
+ info.dLinePtr = info.lastDLinePtr = NULL;
+ }
+ assert(!newTopLine->nextPtr);
}
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
+ }
+
+ /*
+ * If we have saved display lines from a previous metric computation,
+ * then we will search for the first re-usable line.
+ */
+
+ while (savedDLine && TkTextIndexCompare(&savedDLine->index, &index) < 0) {
+ savedDLine = savedDLine->nextPtr;
+ }
+
+ /*
+ * If we have a cached top-line, then insert this line into the list of saved lines.
+ */
+
+ if (newTopLine) {
+ /*
+ * If we have a cached top-line, then it's not possible that this line
+ * is also in the list of saved lines (because ComputeDisplayLineInfo
+ * cannot produce a display line if the metric of this line is already
+ * known, and the line metric is known as soon as the line has been
+ * computed). This means that we can prepend this top-line to the list
+ * of saved lines.
+ */
+
+ assert(!savedDLine || TkTextIndexCompare(&savedDLine->index, &newTopLine->index) > 0);
+
+ if ((newTopLine->nextPtr = savedDLine)) {
+ newTopLine->prevPtr = savedDLine->prevPtr;
+ savedDLine->prevPtr = newTopLine;
+ } else if (dInfoPtr->savedDLinePtr) {
+ dInfoPtr->lastSavedDLinePtr->nextPtr = newTopLine;
+ newTopLine->prevPtr = dInfoPtr->lastSavedDLinePtr;
+ dInfoPtr->lastSavedDLinePtr = newTopLine;
+ }
+ if (dInfoPtr->savedDLinePtr == savedDLine) {
+ dInfoPtr->savedDLinePtr = newTopLine;
+ }
+ if (!dInfoPtr->lastSavedDLinePtr) {
+ dInfoPtr->lastSavedDLinePtr = newTopLine;
+ }
+
+ savedDLine = newTopLine;
+ } else {
+ newTopLine = savedDLine;
+ }
+
+ if (newTopLine && !prevSavedDLine) {
+ prevSavedDLine = newTopLine->prevPtr;
+ }
+
+ while (linePtr != lastLinePtr) {
+ int cmp;
/*
* There are three possibilities right now:
+ *
* (a) the next DLine (dlPtr) corresponds exactly to the next
* information we want to display: just use it as-is.
+ *
* (b) the next DLine corresponds to a different line, or to a segment
* that will be coming later in the same line: leave this DLine
* alone in the hopes that we'll be able to use it later, then
* create a new DLine in front of it.
+ *
* (c) the next DLine corresponds to a segment in the line we want,
* but it's a segment that has already been processed or will
* never be processed. Delete the DLine and try again.
@@ -1883,134 +5047,91 @@ UpdateDisplayInfo(
* just redrawn).
*/
- if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
+ if (!dlPtr
+ || TkTextIndexGetLine(&dlPtr->index) != linePtr
+ || !LineIsUpToDate(textPtr, dlPtr, epoch)
+ || (cmp = TkTextIndexCompare(&index, &dlPtr->index)) < 0) {
/*
* Case (b) -- must make new DLine.
*/
- makeNewDLine:
- if (tkTextDebug) {
- char string[TK_POS_CHARS];
-
- /*
- * Debugging is enabled, so keep a log of all the lines that
- * were re-layed out. The test suite uses this information.
- */
-
- TkTextPrintIndex(textPtr, &index, string);
- LOG("tk_textRelayout", string);
+ TK_TEXT_DEBUG(LogTextRelayout(textPtr, &index));
+ if (savedDLine && TkTextIndexIsEqual(&index, &savedDLine->index)) {
+ dlPtr = savedDLine;
+ savedDLine = savedDLine->nextPtr;
+ if (dInfoPtr->savedDLinePtr == dlPtr) {
+ dInfoPtr->savedDLinePtr = dlPtr->nextPtr;
+ }
+ if (dInfoPtr->lastSavedDLinePtr == dlPtr) {
+ dInfoPtr->lastSavedDLinePtr = dlPtr->prevPtr;
+ }
+ if (dlPtr->prevPtr) {
+ dlPtr->prevPtr->nextPtr = dlPtr->nextPtr;
+ }
+ if (dlPtr->nextPtr) {
+ dlPtr->nextPtr->prevPtr = dlPtr->prevPtr;
+ }
+ dlPtr->prevPtr = dlPtr->nextPtr = NULL;
+ DEBUG(stats.numReused++);
+ } else {
+ dlPtr = LayoutDLine(&index, displayLineNo);
}
- newPtr = LayoutDLine(textPtr, &index);
- if (prevPtr == NULL) {
- dInfoPtr->dLinePtr = newPtr;
+ assert(!(dlPtr->flags & (LINKED|CACHED|DELETED)));
+ if (!bottomLine) {
+ if ((dlPtr->nextPtr = dInfoPtr->dLinePtr)) {
+ dInfoPtr->dLinePtr->prevPtr = dlPtr;
+ }
+ dInfoPtr->dLinePtr = dlPtr;
} else {
- prevPtr->nextPtr = newPtr;
- if (prevPtr->flags & HAS_3D_BORDER) {
- prevPtr->flags |= OLD_Y_INVALID;
+ if ((dlPtr->nextPtr = bottomLine->nextPtr)) {
+ bottomLine->nextPtr->prevPtr = dlPtr;
+ }
+ bottomLine->nextPtr = dlPtr;
+ dlPtr->prevPtr = bottomLine;
+
+ if (bottomLine->flags & HAS_3D_BORDER) {
+ bottomLine->flags |= OLD_Y_INVALID;
}
}
- newPtr->nextPtr = dlPtr;
- dlPtr = newPtr;
- } else {
+ DEBUG(dlPtr->flags |= LINKED);
+ } else if (cmp == 0) {
/*
- * DlPtr refers to the line we want. Next check the index within
- * the line.
+ * Case (a) - can use existing display line as-is.
*/
- if (index.byteIndex == dlPtr->index.byteIndex) {
- /*
- * Case (a) - can use existing display line as-is.
- */
-
- if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)
- && (prevPtr->flags & (NEW_LAYOUT))) {
- dlPtr->flags |= OLD_Y_INVALID;
- }
- goto lineOK;
- }
- if (index.byteIndex < dlPtr->index.byteIndex) {
- goto makeNewDLine;
+ if (bottomLine && (dlPtr->flags & HAS_3D_BORDER) && (bottomLine->flags & NEW_LAYOUT)) {
+ dlPtr->flags |= OLD_Y_INVALID;
}
-
+ assert(dlPtr->displayLineNo == displayLineNo);
+ } else /* if (cmp > 0) */ {
/*
- * Case (c) - dlPtr is useless. Discard it and start again with
- * the next display line.
+ * Case (c) - dlPtr is useless. Discard it and start again with the next display line.
*/
- newPtr = dlPtr->nextPtr;
- FreeDLines(textPtr, dlPtr, newPtr, DLINE_FREE);
- dlPtr = newPtr;
- if (prevPtr != NULL) {
- prevPtr->nextPtr = newPtr;
- } else {
- dInfoPtr->dLinePtr = newPtr;
- }
+ DLine *nextPtr = dlPtr->nextPtr;
+ FreeDLines(textPtr, dlPtr, nextPtr, DLINE_UNLINK);
+ dlPtr = nextPtr;
continue;
}
/*
- * Advance to the start of the next line.
+ * Advance to the start of the next display line.
*/
- lineOK:
dlPtr->y = y;
y += dlPtr->height;
- if (lineHeight != -1) {
- lineHeight += dlPtr->height;
- }
TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
- prevPtr = dlPtr;
- dlPtr = dlPtr->nextPtr;
+ linePtr = TkTextIndexGetLine(&index);
- /*
- * If we switched text lines, delete any DLines left for the old text
- * line.
- */
-
- if (index.linePtr != prevPtr->index.linePtr) {
- register DLine *nextPtr;
-
- nextPtr = dlPtr;
- while ((nextPtr != NULL)
- && (nextPtr->index.linePtr == prevPtr->index.linePtr)) {
- nextPtr = nextPtr->nextPtr;
- }
- if (nextPtr != dlPtr) {
- FreeDLines(textPtr, dlPtr, nextPtr, DLINE_FREE);
- prevPtr->nextPtr = nextPtr;
- dlPtr = nextPtr;
- }
-
- if ((lineHeight != -1) && (TkBTreeLinePixelCount(textPtr,
- prevPtr->index.linePtr) != lineHeight)) {
- /*
- * The logical line height we just calculated is actually
- * different to the currently cached height of the text line.
- * That is fine (the text line heights are only calculated
- * asynchronously), but we must update the cached height so
- * that any counts made with DLine pointers are the same as
- * counts made through the BTree. This helps to ensure that
- * the scrollbar size corresponds accurately to that displayed
- * contents, even as the window is re-sized.
- */
-
- TkBTreeAdjustPixelHeight(textPtr, prevPtr->index.linePtr,
- lineHeight, 0);
-
- /*
- * I believe we can be 100% sure that we started at the
- * beginning of the logical line, so we can also adjust the
- * 'pixelCalculationEpoch' to mark it as being up to date.
- * There is a slight concern that we might not have got this
- * right for the first line in the re-display.
- */
-
- TkBTreeLinePixelEpoch(textPtr, prevPtr->index.linePtr) =
- dInfoPtr->lineMetricUpdateEpoch;
- }
- lineHeight = 0;
+ if (linePtr->logicalLine && TkTextIndexIsStartOfLine(&index)) {
+ displayLineNo = 0;
+ } else {
+ displayLineNo += 1;
}
+ bottomLine = dlPtr;
+ dlPtr = dlPtr->nextPtr;
+
/*
* It's important to have the following check here rather than in the
* while statement for the loop, so that there's always at least one
@@ -2023,11 +5144,9 @@ UpdateDisplayInfo(
}
}
- /*
- * Delete any DLine structures that don't fit on the screen.
- */
-
+ /* Delete any DLine structures that don't fit on the screen. */
FreeDLines(textPtr, dlPtr, NULL, DLINE_UNLINK);
+ topLine = dInfoPtr->dLinePtr;
/*
* If there is extra space at the bottom of the window (because we've hit
@@ -2058,17 +5177,17 @@ UpdateDisplayInfo(
y += spaceLeft;
spaceLeft = 0;
} else {
- int lineNum, bytesToCount;
- DLine *lowestPtr;
+ TkTextLine *linePtr;
+ TkTextLine *firstLinePtr;
/*
- * Add in all of the current top line, which won't be enough to
- * bring y up to maxY (if it was we would be in the 'if' block
- * above).
+ * Add in all of the current top line, which won't be enough to bring y
+ * up to maxY (if it was we would be in the 'if' block above).
*/
y += dInfoPtr->newTopPixelOffset;
dInfoPtr->newTopPixelOffset = 0;
+ spaceLeft = maxY - y;
/*
* Layout an entire text line (potentially > 1 display line), then
@@ -2077,93 +5196,72 @@ UpdateDisplayInfo(
* has been used up or we've reached the beginning of the text.
*/
- spaceLeft = maxY - y;
- if (dInfoPtr->dLinePtr == NULL) {
- /*
- * No lines have been laid out. This must be an empty peer
- * widget.
- */
-
- lineNum = TkBTreeNumLines(textPtr->sharedTextPtr->tree,
- textPtr) - 1;
- bytesToCount = INT_MAX;
- } else {
- lineNum = TkBTreeLinesTo(textPtr,
- dInfoPtr->dLinePtr->index.linePtr);
- bytesToCount = dInfoPtr->dLinePtr->index.byteIndex;
- if (bytesToCount == 0) {
- bytesToCount = INT_MAX;
- lineNum--;
- }
- }
- for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {
- int pixelHeight = 0;
-
- index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineNum);
- index.byteIndex = 0;
- lowestPtr = NULL;
-
- do {
- dlPtr = LayoutDLine(textPtr, &index);
- pixelHeight += dlPtr->height;
- dlPtr->nextPtr = lowestPtr;
- lowestPtr = dlPtr;
- if (dlPtr->length == 0 && dlPtr->height == 0) {
- bytesToCount--;
- break;
- } /* elide */
- TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount,
- &index);
- bytesToCount -= dlPtr->byteCount;
- } while ((bytesToCount > 0)
- && (index.linePtr == lowestPtr->index.linePtr));
-
- /*
- * We may not have examined the entire line (depending on the
- * value of 'bytesToCount', so we only want to set this if it
- * is genuinely bigger).
- */
-
- if (pixelHeight > TkBTreeLinePixelCount(textPtr,
- lowestPtr->index.linePtr)) {
- TkBTreeAdjustPixelHeight(textPtr,
- lowestPtr->index.linePtr, pixelHeight, 0);
- if (index.linePtr != lowestPtr->index.linePtr) {
- /*
- * We examined the entire line, so can update the
- * epoch.
- */
-
- TkBTreeLinePixelEpoch(textPtr,
- lowestPtr->index.linePtr) =
- dInfoPtr->lineMetricUpdateEpoch;
- }
+ if (spaceLeft > 0) {
+ firstLinePtr = TkBTreeGetStartLine(textPtr)->prevPtr;
+ index = topLine ? topLine->index : textPtr->topIndex;
+ savedDLine = prevSavedDLine;
+ if (TkTextIndexBackBytes(textPtr, &index, 1, &index) == 1) {
+ firstLinePtr = linePtr = NULL; /* we are already at start of text */
+ } else {
+ linePtr = TkTextIndexGetLine(&index);
}
- /*
- * Scan through the display lines from the bottom one up to
- * the top one.
- */
-
- while (lowestPtr != NULL) {
- dlPtr = lowestPtr;
- spaceLeft -= dlPtr->height;
- lowestPtr = dlPtr->nextPtr;
- dlPtr->nextPtr = dInfoPtr->dLinePtr;
- dInfoPtr->dLinePtr = dlPtr;
- if (tkTextDebug) {
- char string[TK_POS_CHARS];
-
- TkTextPrintIndex(textPtr, &dlPtr->index, string);
- LOG("tk_textRelayout", string);
- }
- if (spaceLeft <= 0) {
- break;
+ for ( ; linePtr != firstLinePtr && spaceLeft > 0; linePtr = linePtr->prevPtr) {
+ if (linePtr != TkTextIndexGetLine(&index)) {
+ TkTextIndexSetToLastChar2(&index, linePtr);
}
+ linePtr = ComputeDisplayLineInfo(textPtr, &index, &info);
+
+ do {
+ if (info.lastDLinePtr) {
+ dlPtr = info.lastDLinePtr;
+ if (dlPtr->prevPtr) {
+ dlPtr->prevPtr->nextPtr = NULL;
+ info.lastDLinePtr = dlPtr->prevPtr;
+ dlPtr->prevPtr = NULL;
+ assert(dlPtr != info.dLinePtr);
+ } else {
+ assert(info.dLinePtr == info.lastDLinePtr);
+ info.dLinePtr = info.lastDLinePtr = NULL;
+ }
+ } else {
+ TkTextIndexSetToStartOfLine2(&index, linePtr);
+ TkTextIndexForwBytes(textPtr, &index, info.entry->byteOffset, &index);
+ if (savedDLine && TkTextIndexIsEqual(&index, &savedDLine->index)) {
+ dlPtr = savedDLine;
+ savedDLine = savedDLine->prevPtr;
+ if (dlPtr->prevPtr) {
+ dlPtr->prevPtr->nextPtr = dlPtr->nextPtr;
+ } else {
+ dInfoPtr->savedDLinePtr = dlPtr->nextPtr;
+ }
+ if (dlPtr->nextPtr) {
+ dlPtr->nextPtr->prevPtr = dlPtr->prevPtr;
+ } else {
+ dInfoPtr->lastSavedDLinePtr = dlPtr->prevPtr;
+ }
+ dlPtr->prevPtr = dlPtr->nextPtr = NULL;
+ } else {
+ dlPtr = LayoutDLine(&index, info.displayLineNo);
+ }
+ }
+ if ((dlPtr->nextPtr = topLine)) {
+ topLine->prevPtr = dlPtr;
+ } else {
+ bottomLine = dlPtr;
+ }
+ topLine = dlPtr;
+ DEBUG(dlPtr->flags |= LINKED);
+ TK_TEXT_DEBUG(LogTextRelayout(textPtr, &dlPtr->index));
+ spaceLeft -= dlPtr->height;
+ info.displayLineNo -= 1;
+ info.entry -= 1;
+ } while (spaceLeft > 0 && info.displayLineNo >= 0);
+
+ dInfoPtr->dLinePtr = topLine;
+ /* Delete remaining cached lines. */
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
}
- FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
- bytesToCount = INT_MAX;
}
/*
@@ -2181,15 +5279,13 @@ UpdateDisplayInfo(
*/
dInfoPtr->newTopPixelOffset = -spaceLeft;
- if (dInfoPtr->newTopPixelOffset>=dInfoPtr->dLinePtr->height) {
- /*
- * Somehow the entire first line we laid out is shorter
- * than the new offset. This should not occur and would
- * indicate a bad problem in the logic above.
- */
- Tcl_Panic("Error in pixel height consistency while filling in spacesLeft");
- }
+ /*
+ * If the entire first line we laid out is shorter than the new offset:
+ * this should not occur and would indicate a bad problem in the logic above.
+ */
+
+ assert(dInfoPtr->newTopPixelOffset < topLine->height);
}
}
@@ -2198,14 +5294,14 @@ UpdateDisplayInfo(
* are wrong and the top index for the text is wrong. Update them.
*/
- if (dInfoPtr->dLinePtr != NULL) {
- textPtr->topIndex = dInfoPtr->dLinePtr->index;
+ if (topLine) {
+ dInfoPtr->dLinePtr = topLine;
+ textPtr->topIndex = topLine->index;
+ assert(textPtr->topIndex.textPtr);
+ TkTextIndexToByteIndex(&textPtr->topIndex);
y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
- for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
- dlPtr = dlPtr->nextPtr) {
- if (y > dInfoPtr->maxY) {
- Tcl_Panic("Added too many new lines in UpdateDisplayInfo");
- }
+ for (dlPtr = topLine; dlPtr; dlPtr = dlPtr->nextPtr) {
+ assert(y <= dInfoPtr->maxY);
dlPtr->y = y;
y += dlPtr->height;
}
@@ -2221,47 +5317,59 @@ UpdateDisplayInfo(
* somewhere else on the screen, we may not be able to copy the old bits.
*/
- dlPtr = dInfoPtr->dLinePtr;
- if (dlPtr != NULL) {
- if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {
- dlPtr->flags |= OLD_Y_INVALID;
+ if (topLine) {
+ dInfoPtr->maxLength = MAX(dInfoPtr->maxLength, topLine->length);
+
+ if ((topLine->flags & (TOP_LINE|HAS_3D_BORDER)) == HAS_3D_BORDER) {
+ topLine->flags |= OLD_Y_INVALID;
+ }
+ if ((bottomLine->flags & (BOTTOM_LINE|HAS_3D_BORDER)) == HAS_3D_BORDER) {
+ bottomLine->flags |= OLD_Y_INVALID;
}
- while (1) {
- if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
- && (dlPtr->flags & HAS_3D_BORDER)) {
- dlPtr->flags |= OLD_Y_INVALID;
- }
- /*
- * If the old top-line was not completely showing (i.e. the
- * pixelOffset is non-zero) and is no longer the top-line, then we
- * must re-draw it.
- */
+ if (topLine != bottomLine) {
+ topLine->flags &= ~BOTTOM_LINE;
+ bottomLine->flags &= ~TOP_LINE;
- if ((dlPtr->flags & TOP_LINE) &&
- dInfoPtr->topPixelOffset!=0 && dlPtr!=dInfoPtr->dLinePtr) {
- dlPtr->flags |= OLD_Y_INVALID;
- }
- if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL)
- && (dlPtr->flags & HAS_3D_BORDER)) {
- dlPtr->flags |= OLD_Y_INVALID;
- }
- if (dlPtr->nextPtr == NULL) {
- if ((dlPtr->flags & HAS_3D_BORDER)
- && !(dlPtr->flags & BOTTOM_LINE)) {
+ for (dlPtr = topLine->nextPtr; dlPtr != bottomLine; dlPtr = dlPtr->nextPtr) {
+ dInfoPtr->maxLength = MAX(dInfoPtr->maxLength, dlPtr->length);
+
+ if ((topLine->flags & HAS_3D_BORDER) && (dlPtr->flags & (TOP_LINE|BOTTOM_LINE))) {
dlPtr->flags |= OLD_Y_INVALID;
}
- dlPtr->flags &= ~TOP_LINE;
- dlPtr->flags |= BOTTOM_LINE;
- break;
+
+ /*
+ * If the old top-line was not completely showing (i.e. the
+ * pixelOffset is non-zero) and is no longer the top-line, then we
+ * must re-draw it.
+ */
+
+ if ((dlPtr->flags & TOP_LINE) && dInfoPtr->topPixelOffset != 0) {
+ dlPtr->flags |= OLD_Y_INVALID;
+ }
+
+ dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
}
- dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE);
- dlPtr = dlPtr->nextPtr;
+
+ dInfoPtr->maxLength = MAX(dInfoPtr->maxLength, bottomLine->length);
}
- dInfoPtr->dLinePtr->flags |= TOP_LINE;
+
+ topLine->flags |= TOP_LINE;
+ bottomLine->flags |= BOTTOM_LINE;
+
dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset;
+ dInfoPtr->curYPixelOffset = GetYPixelCount(textPtr, topLine);
+ dInfoPtr->curYPixelOffset += dInfoPtr->topPixelOffset;
}
+ dInfoPtr->lastDLinePtr = bottomLine;
+
+ /*
+ * Delete remaining saved lines.
+ */
+
+ FreeDLines(textPtr, dInfoPtr->savedDLinePtr, NULL, DLINE_FREE_TEMP);
+
/*
* Arrange for scrollbars to be updated.
*/
@@ -2277,22 +5385,8 @@ UpdateDisplayInfo(
* 3. If the wrap mode isn't "none" then re-scroll to the base position.
*/
- dInfoPtr->maxLength = 0;
- for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
- dlPtr = dlPtr->nextPtr) {
- if (dlPtr->length > dInfoPtr->maxLength) {
- dInfoPtr->maxLength = dlPtr->length;
- }
- }
maxOffset = dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x);
-
- xPixelOffset = dInfoPtr->newXPixelOffset;
- if (xPixelOffset > maxOffset) {
- xPixelOffset = maxOffset;
- }
- if (xPixelOffset < 0) {
- xPixelOffset = 0;
- }
+ xPixelOffset = MAX(0, MIN(dInfoPtr->newXPixelOffset, maxOffset));
/*
* Here's a problem: see the tests textDisp-29.2.1-4
@@ -2310,10 +5404,10 @@ UpdateDisplayInfo(
if (xPixelOffset != dInfoPtr->curXPixelOffset) {
dInfoPtr->curXPixelOffset = xPixelOffset;
- for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
- dlPtr = dlPtr->nextPtr) {
+ for (dlPtr = topLine; dlPtr; dlPtr = dlPtr->nextPtr) {
dlPtr->flags |= OLD_Y_INVALID;
}
+ textPtr->configureBboxTree = true;
}
}
@@ -2326,7 +5420,8 @@ UpdateDisplayInfo(
* with one or more DLine structures.
*
* Results:
- * None.
+ * Returns the last unfreed line if action is DLINE_SAVE, otherwise
+ * NULL will be returned.
*
* Side effects:
* Memory gets freed and various other resources are released.
@@ -2334,68 +5429,320 @@ UpdateDisplayInfo(
*----------------------------------------------------------------------
*/
+static bool
+LineIsOutsideOfPeer(
+ const TkText *textPtr,
+ const TkTextIndex *indexPtr)
+{
+ const TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+
+ if (textPtr->startMarker != sharedTextPtr->startMarker) {
+ const TkTextLine *linePtr = textPtr->startMarker->sectionPtr->linePtr;
+ int no1 = TkTextIndexGetLineNumber(indexPtr, NULL);
+ int no2 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr, NULL);
+
+ if (no1 < no2) {
+ return true;
+ }
+ }
+ if (textPtr->endMarker != sharedTextPtr->endMarker) {
+ const TkTextLine *linePtr = textPtr->endMarker->sectionPtr->linePtr;
+ int no1 = TkTextIndexGetLineNumber(indexPtr, NULL);
+ int no2 = TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr, NULL);
+
+ if (no1 > no2) {
+ return true;
+ }
+ }
+ return false;
+}
+
static void
+ReleaseLines(
+ TkText *textPtr,
+ DLine *firstPtr,
+ DLine *lastPtr,
+ FreeDLineAction action)
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ DLine *dlPtr, *lastDeletedPtr = NULL; /* avoids compiler warning */
+
+ assert(firstPtr);
+ assert(firstPtr != lastPtr);
+
+ for (dlPtr = firstPtr; dlPtr != lastPtr; dlPtr = dlPtr->nextPtr) {
+ TkTextDispChunk *chunkPtr;
+
+ assert(!(dlPtr->flags & DELETED));
+ assert((action == DLINE_UNLINK || action == DLINE_UNLINK_KEEP_BRKS)
+ == !!(dlPtr->flags & LINKED));
+ assert((action == DLINE_CACHE) == !!(dlPtr->flags & CACHED));
+ assert(dlPtr != dInfoPtr->savedDLinePtr || dlPtr == firstPtr);
+ assert(dlPtr->chunkPtr || (!dlPtr->lastChunkPtr && !dlPtr->breakInfo));
+
+ if (dlPtr->lastChunkPtr) {
+ TkTextDispChunkSection *sectionPtr = NULL;
+
+ /*
+ * We have to destroy the chunks backward, because the context support
+ * is expecting this.
+ */
+
+ for (chunkPtr = dlPtr->lastChunkPtr; chunkPtr; chunkPtr = chunkPtr->prevPtr) {
+ if (chunkPtr->layoutProcs->undisplayProc) {
+ chunkPtr->layoutProcs->undisplayProc(textPtr, chunkPtr);
+ }
+ LayoutReleaseChunk(textPtr, chunkPtr);
+ DEBUG(chunkPtr->stylePtr = NULL);
+
+ if (chunkPtr->sectionPtr != sectionPtr) {
+ sectionPtr = chunkPtr->sectionPtr;
+ sectionPtr->nextPtr = dInfoPtr->sectionPoolPtr;
+ dInfoPtr->sectionPoolPtr = sectionPtr;
+ }
+ }
+
+ if (dlPtr->breakInfo
+ && (action != DLINE_UNLINK_KEEP_BRKS || LineIsOutsideOfPeer(textPtr, &dlPtr->index))
+ && --dlPtr->breakInfo->refCount == 0) {
+ assert(dlPtr->breakInfo->brks);
+ free(dlPtr->breakInfo->brks);
+ free(dlPtr->breakInfo);
+ Tcl_DeleteHashEntry(Tcl_FindHashEntry(
+ &textPtr->sharedTextPtr->breakInfoTable,
+ (void *) TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr,
+ TkTextIndexGetLine(&dlPtr->index))));
+ DEBUG_ALLOC(tkTextCountDestroyBreakInfo++);
+ }
+
+ dlPtr->lastChunkPtr->nextPtr = dInfoPtr->chunkPoolPtr;
+ dInfoPtr->chunkPoolPtr = dlPtr->chunkPtr;
+ assert(!dInfoPtr->chunkPoolPtr->prevPtr);
+ }
+
+ lastDeletedPtr = dlPtr;
+ DEBUG(dlPtr->flags = DELETED);
+ }
+
+ assert(lastDeletedPtr);
+ lastDeletedPtr->nextPtr = dInfoPtr->dLinePoolPtr;
+ dInfoPtr->dLinePoolPtr = firstPtr;
+
+ if (lastPtr) {
+ lastPtr->prevPtr = firstPtr->prevPtr;
+ }
+ if (firstPtr->prevPtr) {
+ firstPtr->prevPtr->nextPtr = lastPtr;
+ }
+}
+
+static DLine *
FreeDLines(
TkText *textPtr, /* Information about overall text widget. */
- register DLine *firstPtr, /* Pointer to first DLine to free up. */
- DLine *lastPtr, /* Pointer to DLine just after last one to
- * free (NULL means everything starting with
- * firstPtr). */
- int action) /* DLINE_UNLINK means DLines are currently
- * linked into the list rooted at
- * textPtr->dInfoPtr->dLinePtr and they have
- * to be unlinked. DLINE_FREE means just free
- * without unlinking. DLINE_FREE_TEMP means
- * the DLine given is just a temporary one and
- * we shouldn't invalidate anything for the
- * overall widget. */
-{
- register TkTextDispChunk *chunkPtr, *nextChunkPtr;
- register DLine *nextDLinePtr;
-
- if (action == DLINE_FREE_TEMP) {
- lineHeightsRecalculated++;
- if (tkTextDebug) {
- char string[TK_POS_CHARS];
+ DLine *firstPtr, /* Pointer to first DLine to free up. */
+ DLine *lastPtr, /* Pointer to DLine just after last one to free (NULL means
+ * everything starting with firstPtr). */
+ FreeDLineAction action) /* DLINE_UNLINK means DLines are currently linked into the list
+ * rooted at textPtr->dInfoPtr->dLinePtr and they have to be
+ * unlinked. DLINE_FREE_TEMP, and DLINE_CACHE means the DLine given
+ * is just a temporary one and we shouldn't invalidate anything for
+ * the overall widget. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ switch (action) {
+ case DLINE_CACHE:
+ assert(!lastPtr);
+ if (firstPtr) {
+ DLine *prevPtr = firstPtr->prevPtr;
+
+ assert(!(firstPtr->flags & LINKED));
+ assert(!(firstPtr->flags & CACHED));
+ assert(!(firstPtr->flags & DELETED));
+ assert(firstPtr != dInfoPtr->savedDLinePtr);
/*
- * Debugging is enabled, so keep a log of all the lines whose
- * height was recalculated. The test suite uses this information.
+ * Firstly unlink this line from chain.
*/
- TkTextPrintIndex(textPtr, &firstPtr->index, string);
- LOG("tk_textHeightCalc", string);
+ if (firstPtr == dInfoPtr->dLinePtr) {
+ dInfoPtr->dLinePtr = firstPtr->nextPtr;
+ }
+ if (firstPtr == dInfoPtr->lastDLinePtr) {
+ dInfoPtr->lastDLinePtr = prevPtr;
+ }
+ if (prevPtr) {
+ prevPtr->nextPtr = firstPtr->nextPtr;
+ }
+ if (firstPtr->nextPtr) {
+ firstPtr->nextPtr->prevPtr = prevPtr;
+ }
+ firstPtr->prevPtr = NULL;
+
+ /*
+ * Then link into the chain of cached lines.
+ */
+
+ if ((firstPtr->nextPtr = dInfoPtr->cachedDLinePtr)) {
+ dInfoPtr->cachedDLinePtr->prevPtr = firstPtr;
+ } else {
+ dInfoPtr->lastCachedDLinePtr = firstPtr;
+ }
+ dInfoPtr->cachedDLinePtr = firstPtr;
+
+ DEBUG(firstPtr->flags &= ~LINKED);
+ DEBUG(firstPtr->flags |= CACHED);
+ DEBUG(stats.numCached += 1);
+
+ if (dInfoPtr->numCachedLines < MAX_CACHED_DISPLAY_LINES) {
+ dInfoPtr->numCachedLines += 1;
+ return NULL;
+ }
+
+ /*
+ * Release oldest cached display line.
+ */
+
+ if ((firstPtr = dInfoPtr->lastCachedDLinePtr)) {
+ firstPtr->prevPtr->nextPtr = NULL;
+ }
+ dInfoPtr->lastCachedDLinePtr = dInfoPtr->lastCachedDLinePtr->prevPtr;
+ } else {
+ if (!(firstPtr = dInfoPtr->cachedDLinePtr)) {
+ return NULL;
+ }
+ dInfoPtr->cachedDLinePtr = dInfoPtr->lastCachedDLinePtr = NULL;
+ dInfoPtr->numCachedLines = 0;
+ }
+ ReleaseLines(textPtr, firstPtr, lastPtr, action);
+ break;
+ case DLINE_METRIC:
+ assert(!lastPtr);
+ if (dInfoPtr->lastMetricDLinePtr) {
+ ReleaseLines(textPtr, dInfoPtr->lastMetricDLinePtr, NULL, DLINE_FREE_TEMP);
+ dInfoPtr->lastMetricDLinePtr = NULL;
+ }
+ if (firstPtr) {
+ assert(!firstPtr->nextPtr);
+ assert(!(firstPtr->flags & (LINKED|CACHED|DELETED)));
+ dInfoPtr->lastMetricDLinePtr = firstPtr;
+ if (firstPtr->prevPtr) {
+ firstPtr->prevPtr->nextPtr = NULL;
+ firstPtr->prevPtr = NULL;
+ }
}
- } else if (action == DLINE_UNLINK) {
- if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
- textPtr->dInfoPtr->dLinePtr = lastPtr;
+ break;
+ case DLINE_FREE_TEMP:
+ if (!firstPtr || firstPtr == lastPtr) {
+ return NULL;
+ }
+ DEBUG(stats.lineHeightsRecalculated += 1);
+ assert(!(firstPtr->flags & LINKED));
+ assert(!(firstPtr->flags & CACHED));
+ if (firstPtr == dInfoPtr->savedDLinePtr) {
+ dInfoPtr->savedDLinePtr = NULL;
+ if (!lastPtr) {
+ dInfoPtr->lastSavedDLinePtr = NULL;
+ } else {
+ dInfoPtr->savedDLinePtr = lastPtr;
+ }
} else {
- register DLine *prevPtr;
+ assert(!lastPtr || lastPtr != dInfoPtr->lastSavedDLinePtr);
+ }
+ assert(!dInfoPtr->savedDLinePtr == !dInfoPtr->lastSavedDLinePtr);
+ ReleaseLines(textPtr, firstPtr, lastPtr, action);
+ break;
+ case DLINE_UNLINK:
+ case DLINE_UNLINK_KEEP_BRKS:
+ if (!firstPtr || firstPtr == lastPtr) {
+ return NULL;
+ }
+ assert(firstPtr->flags & LINKED);
+ assert(firstPtr != dInfoPtr->savedDLinePtr);
+ if (dInfoPtr->dLinePtr == firstPtr) {
+ if ((dInfoPtr->dLinePtr = lastPtr)) {
+ lastPtr->prevPtr = NULL;
+ }
+ } else {
+ DLine *prevPtr = firstPtr->prevPtr;
- for (prevPtr = textPtr->dInfoPtr->dLinePtr;
- prevPtr->nextPtr != firstPtr; prevPtr = prevPtr->nextPtr) {
- /* Empty loop body. */
+ if (prevPtr && (prevPtr->nextPtr = lastPtr)) {
+ lastPtr->prevPtr = prevPtr;
}
- prevPtr->nextPtr = lastPtr;
}
- }
- while (firstPtr != lastPtr) {
- nextDLinePtr = firstPtr->nextPtr;
- for (chunkPtr = firstPtr->chunkPtr; chunkPtr != NULL;
- chunkPtr = nextChunkPtr) {
- if (chunkPtr->undisplayProc != NULL) {
- chunkPtr->undisplayProc(textPtr, chunkPtr);
+ if (!lastPtr) {
+ dInfoPtr->lastDLinePtr = firstPtr->prevPtr;
+ }
+ dInfoPtr->dLinesInvalidated = true;
+ assert(!dInfoPtr->dLinePtr || !dInfoPtr->dLinePtr->prevPtr);
+ ReleaseLines(textPtr, firstPtr, lastPtr, action);
+ break;
+ case DLINE_SAVE: {
+ if (!firstPtr || firstPtr == lastPtr) {
+ return NULL;
+ }
+ assert(firstPtr == dInfoPtr->dLinePtr);
+ assert(lastPtr);
+
+ unsigned epoch = dInfoPtr->lineMetricUpdateEpoch;
+ DLine *dlPtr;
+
+ assert(lastPtr->prevPtr);
+ dInfoPtr->dLinePtr = lastPtr;
+
+ /*
+ * Free all expired lines, we will only save valid lines.
+ */
+
+ dlPtr = firstPtr;
+ while (dlPtr != lastPtr) {
+ DLine *nextPtr = dlPtr->nextPtr;
+
+ assert(dlPtr->flags & LINKED);
+
+ if (LineIsUpToDate(textPtr, dlPtr, epoch)) {
+ DEBUG(dlPtr->flags &= ~LINKED);
+ } else {
+ if (dlPtr == firstPtr) {
+ firstPtr = nextPtr;
+ }
+ ReleaseLines(textPtr, dlPtr, nextPtr, DLINE_UNLINK);
}
- FreeStyle(textPtr, chunkPtr->stylePtr);
- nextChunkPtr = chunkPtr->nextPtr;
- ckfree(chunkPtr);
+
+ dlPtr = nextPtr;
}
- ckfree(firstPtr);
- firstPtr = nextDLinePtr;
+
+ assert(!firstPtr->prevPtr);
+
+ if (firstPtr == lastPtr) {
+ dInfoPtr->savedDLinePtr = NULL;
+ dInfoPtr->lastSavedDLinePtr = NULL;
+ return NULL;
+ }
+
+ lastPtr = lastPtr->prevPtr;
+ lastPtr->nextPtr->prevPtr = NULL;
+ lastPtr->nextPtr = NULL;
+
+ if (!dInfoPtr->savedDLinePtr) {
+ dInfoPtr->savedDLinePtr = firstPtr;
+ dInfoPtr->lastSavedDLinePtr = lastPtr;
+ } else if (TkTextIndexCompare(&lastPtr->index, &dInfoPtr->savedDLinePtr->index) < 0) {
+ lastPtr->nextPtr = dInfoPtr->savedDLinePtr;
+ dInfoPtr->savedDLinePtr->prevPtr = lastPtr;
+ dInfoPtr->savedDLinePtr = firstPtr;
+ } else {
+ assert(TkTextIndexCompare(&firstPtr->index, &dInfoPtr->lastSavedDLinePtr->index) > 0);
+ firstPtr->prevPtr = dInfoPtr->lastSavedDLinePtr;
+ dInfoPtr->lastSavedDLinePtr->nextPtr = firstPtr;
+ dInfoPtr->lastSavedDLinePtr = lastPtr;
+ }
+
+ return lastPtr;
}
- if (action != DLINE_FREE_TEMP) {
- textPtr->dInfoPtr->dLinesInvalidated = 1;
}
+
+ return NULL;
}
/*
@@ -2417,50 +5764,44 @@ FreeDLines(
static void
DisplayDLine(
- TkText *textPtr, /* Text widget in which to draw line. */
- register DLine *dlPtr, /* Information about line to draw. */
- DLine *prevPtr, /* Line just before one to draw, or NULL if
- * dlPtr is the top line. */
- Pixmap pixmap) /* Pixmap to use for double-buffering. Caller
- * must make sure it's large enough to hold
- * line. */
-{
- register TkTextDispChunk *chunkPtr;
+ TkText *textPtr, /* Text widget in which to draw line. */
+ DLine *dlPtr, /* Information about line to draw. */
+ DLine *prevPtr, /* Line just before one to draw, or NULL if dlPtr is the top line. */
+ Pixmap pixmap) /* Pixmap to use for double-buffering. Caller must make sure
+ * it's large enough to hold line. */
+{
+ TkTextDispChunk *chunkPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
Display *display;
- int height, y_off;
-#ifndef TK_NO_DOUBLE_BUFFERING
- const int y = 0;
-#else
- const int y = dlPtr->y;
-#endif /* TK_NO_DOUBLE_BUFFERING */
+ StyleValues *sValuePtr;
+ int lineHeight, yOffs;
+ int yBase, height, baseline, screenY, xOffs;
+ int xIndent, rMargin;
+ bool delayBlockCursorDrawing;
- if (dlPtr->chunkPtr == NULL) return;
+ if (!dlPtr->chunkPtr) {
+ return;
+ }
display = Tk_Display(textPtr->tkwin);
+ delayBlockCursorDrawing = false;
- height = dlPtr->height;
- if ((height + dlPtr->y) > dInfoPtr->maxY) {
- height = dInfoPtr->maxY - dlPtr->y;
+ lineHeight = dlPtr->height;
+ if (lineHeight + dlPtr->y > dInfoPtr->maxY) {
+ lineHeight = dInfoPtr->maxY - dlPtr->y;
}
if (dlPtr->y < dInfoPtr->y) {
- y_off = dInfoPtr->y - dlPtr->y;
- height -= y_off;
+ yOffs = dInfoPtr->y - dlPtr->y;
+ lineHeight -= yOffs;
} else {
- y_off = 0;
+ yOffs = 0;
}
-#ifdef TK_NO_DOUBLE_BUFFERING
- TkpClipDrawableToRect(display, pixmap, dInfoPtr->x, y + y_off,
- dInfoPtr->maxX - dInfoPtr->x, height);
-#endif /* TK_NO_DOUBLE_BUFFERING */
-
/*
- * First, clear the area of the line to the background color for the text
- * widget.
+ * First, clear the area of the line to the background color for the text widget.
*/
- Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, y,
+ Tk_Fill3DRectangle(textPtr->tkwin, pixmap, textPtr->border, 0, 0,
Tk_Width(textPtr->tkwin), dlPtr->height, 0, TK_RELIEF_FLAT);
/*
@@ -2472,106 +5813,200 @@ DisplayDLine(
/*
* Third, draw the background color of the left and right margins.
*/
- if (dlPtr->lMarginColor != NULL) {
- Tk_Fill3DRectangle(textPtr->tkwin, pixmap, dlPtr->lMarginColor, 0, y,
- dlPtr->lMarginWidth + dInfoPtr->x - dInfoPtr->curXPixelOffset,
+
+ sValuePtr = dlPtr->firstCharChunkPtr->stylePtr->sValuePtr;
+ rMargin = (sValuePtr->wrapMode == TEXT_WRAPMODE_NONE) ? 0 : sValuePtr->rMargin;
+ xIndent = GetLeftLineMargin(dlPtr, sValuePtr);
+
+ if (sValuePtr->lMarginColor != NULL) {
+ Tk_Fill3DRectangle(textPtr->tkwin, pixmap, sValuePtr->lMarginColor, 0, 0,
+ xIndent + dInfoPtr->x - dInfoPtr->curXPixelOffset,
dlPtr->height, 0, TK_RELIEF_FLAT);
}
- if (dlPtr->rMarginColor != NULL) {
- Tk_Fill3DRectangle(textPtr->tkwin, pixmap, dlPtr->rMarginColor,
- dInfoPtr->maxX - dlPtr->rMarginWidth + dInfoPtr->curXPixelOffset,
- y, dlPtr->rMarginWidth, dlPtr->height, 0, TK_RELIEF_FLAT);
+ if (sValuePtr->rMarginColor != NULL) {
+ Tk_Fill3DRectangle(textPtr->tkwin, pixmap, sValuePtr->rMarginColor,
+ dInfoPtr->maxX - rMargin + dInfoPtr->curXPixelOffset,
+ 0, rMargin, dlPtr->height, 0, TK_RELIEF_FLAT);
}
+ yBase = dlPtr->spaceAbove;
+ height = dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow;
+ baseline = dlPtr->baseline - dlPtr->spaceAbove;
+ screenY = dlPtr->y + dlPtr->spaceAbove;
+ xOffs = dInfoPtr->x - dInfoPtr->curXPixelOffset;
+
/*
- * Make another pass through all of the chunks to redraw the insertion
- * cursor, if it is visible on this line. Must do it here rather than in
- * the foreground pass below because otherwise a wide insertion cursor
- * will obscure the character to its left.
+ * Redraw the insertion cursor, if it is visible on this line. Must do it here rather
+ * than in the foreground pass below because otherwise a wide insertion cursor will
+ * obscure the character to its left.
+ *
+ * If the user has specified a foreground color for characters "behind" the block cursor,
+ * we have to draw the cursor after the text has been drawn, see below.
*/
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
- chunkPtr = chunkPtr->nextPtr) {
- if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
- int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
+ if (dlPtr->cursorChunkPtr && textPtr->state == TK_TEXT_STATE_NORMAL) {
+ delayBlockCursorDrawing = dInfoPtr->insertFgGC && TkTextDrawBlockCursor(textPtr);
- chunkPtr->displayProc(textPtr, chunkPtr, x,
- y + dlPtr->spaceAbove,
- dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
- dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
- dlPtr->y + dlPtr->spaceAbove);
- }
+ if (!delayBlockCursorDrawing) {
+ dlPtr->cursorChunkPtr->layoutProcs->displayProc(textPtr, dlPtr->cursorChunkPtr,
+ dlPtr->cursorChunkPtr->x + xOffs, yBase, height, baseline, display, pixmap, screenY);
}
}
/*
- * Make yet another pass through all of the chunks to redraw all of
- * foreground information. Note: we have to call the displayProc even for
- * chunks that are off-screen. This is needed, for example, so that
- * embedded windows can be unmapped in this case.
+ * Iterate through all of the chunks to redraw all of foreground information.
*/
- for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
- chunkPtr = chunkPtr->nextPtr) {
- if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
- /*
- * Already displayed the insertion cursor above. Don't do it again
- * here.
- */
-
+ for (chunkPtr = dlPtr->chunkPtr; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
+ if (chunkPtr == dlPtr->cursorChunkPtr) {
+ /* Don't display the insertion cursor, this will be done separately. */
continue;
}
- /*
- * Don't call if elide. This tax OK since not very many visible DLines
- * in an area, but potentially many elide ones.
- */
+ if (chunkPtr->layoutProcs->displayProc) {
+ int x = chunkPtr->x + xOffs;
- if (chunkPtr->displayProc != NULL) {
- int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
-
- if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
+ if (x + chunkPtr->width <= 0 || dInfoPtr->maxX <= x) {
/*
- * Note: we have to call the displayProc even for chunks that
- * are off-screen. This is needed, for example, so that
- * embedded windows can be unmapped in this case. Display the
- * chunk at a coordinate that can be clearly identified by the
- * displayProc as being off-screen to the left (the
- * displayProc may not be able to tell if something is off to
- * the right).
+ * Note: we have to call the displayProc even for chunks that are off-screen.
+ * This is needed, for example, so that embedded windows can be unmapped in
+ * this case. Display the chunk at a coordinate that can be clearly identified
+ * by the displayProc as being off-screen to the left (the displayProc may not
+ * be able to tell if something is off to the right).
*/
x = -chunkPtr->width;
}
- chunkPtr->displayProc(textPtr, chunkPtr, x,
- y + dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove -
- dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove,
- display, pixmap, dlPtr->y + dlPtr->spaceAbove);
+
+ chunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, x, yBase, height,
+ baseline, display, pixmap, screenY);
+
+ if (dInfoPtr->dLinesInvalidated) {
+ /*
+ * The display process has invalidated any display line, so terminate,
+ * because the display process will be repeated with valid display lines.
+ */
+ return;
+ }
}
+ }
- if (dInfoPtr->dLinesInvalidated) {
- return;
+ if (delayBlockCursorDrawing) {
+ /*
+ * Draw the block cursor and redraw the characters "behind" the block cursor.
+ */
+
+ int cxMin, cxMax, cWidth, cOffs;
+ GC bgGC;
+
+ assert(dInfoPtr->insertFgGC != None);
+
+ cxMin = dlPtr->cursorChunkPtr->x + xOffs;
+ cWidth = TkTextGetCursorWidth(textPtr, &cxMin, &cOffs);
+
+ if ((bgGC = dlPtr->cursorChunkPtr->stylePtr->bgGC) == None) {
+ Tk_3DBorder border;
+
+ if (!(border = dlPtr->cursorChunkPtr->stylePtr->sValuePtr->border)) {
+ border = textPtr->border;
+ }
+ bgGC = Tk_GCForColor(Tk_3DBorderColor(border), Tk_WindowId(textPtr->tkwin));
}
+
+ cxMin += cOffs;
+ cxMax = cxMin + cWidth;
+
+#if CLIPPING_IS_WORKING
+ /*
+ * This is the right implementation if XSetClipRectangles would work; still untested.
+ */
+ {
+ XRectangle crect;
+
+ crect.x = cxMin;
+ crect.y = yBase;
+ crect.width = cWidth;
+ crect.height = height;
+
+ XFillRectangle(display, pixmap, bgGC, crect.x, crect.y, crect.width, crect.height);
+ dlPtr->cursorChunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, cxMin, yBase, height,
+ baseline, display, pixmap, screenY);
+
+ /* for any reason this doesn't work with the Tk lib even under X11 */
+ XSetClipRectangles(display, dInfoPtr->insertFgGC, 0, 0, &crect, 1, Unsorted);
+
+ for (chunkPtr = dlPtr->chunkPtr; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
+ int x = chunkPtr->x + xOffs;
+
+ if (x >= cxMax) {
+ break;
+ }
+ if (IsCharChunk(chunkPtr) && cxMin <= x + chunkPtr->width) {
+ GC fgGC = chunkPtr->stylePtr->fgGC;
+
+ chunkPtr->stylePtr->fgGC = dInfoPtr->insertFgGC;
+ chunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, x, yBase, height,
+ baseline, display, pixmap, screenY);
+ chunkPtr->stylePtr->fgGC = fgGC;
+ }
+ }
+ }
+#else /* if !CLIPPING_IS_WORKING */
+ /*
+ * We don't have clipping, so we need a different approach.
+ */
+ {
+ Pixmap pm = Tk_GetPixmap(display, Tk_WindowId(textPtr->tkwin),
+ cWidth, height, Tk_Depth(textPtr->tkwin));
+
+ XFillRectangle(display, pm, bgGC, 0, 0, cWidth, height);
+ chunkPtr = dlPtr->cursorChunkPtr;
+
+ /* we are using a (pointer) hack in TkTextInsertDisplayProc */
+ chunkPtr->layoutProcs->displayProc(textPtr, MarkPointer(chunkPtr),
+ cxMin, yBase, height, baseline, display, pm, screenY);
+
+ while (chunkPtr->prevPtr && chunkPtr->x + xOffs + chunkPtr->width > cxMin) {
+ chunkPtr = chunkPtr->prevPtr;
+ }
+ for ( ; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
+ int x = chunkPtr->x + xOffs;
+
+ if (x >= cxMax) {
+ break;
+ }
+ if (IsCharChunk(chunkPtr)) {
+ GC fgGC = chunkPtr->stylePtr->fgGC;
+
+ chunkPtr->stylePtr->fgGC = dInfoPtr->insertFgGC;
+ chunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, x - cxMin, 0,
+ height, baseline, display, pm, screenY);
+ chunkPtr->stylePtr->fgGC = fgGC;
+ }
+ }
+
+ XCopyArea(display, pm, pixmap, dInfoPtr->copyGC, 0, 0, cWidth, height, cxMin, yBase);
+ Tk_FreePixmap(display, pm);
+ }
+#endif /* CLIPPING_IS_WORKING */
}
-#ifndef TK_NO_DOUBLE_BUFFERING
/*
* Copy the pixmap onto the screen. If this is the first or last line on
* the screen then copy a piece of the line, so that it doesn't overflow
- * into the border area. Another special trick: copy the padding area to
- * the left of the line; this is because the insertion cursor sometimes
- * overflows onto that area and we want to get as much of the cursor as
- * possible.
+ * into the border area.
+ *
+ * Another special trick: consider the padding area left/right of the line;
+ * this is because the insertion cursor sometimes overflows onto that area
+ * and we want to get as much of the cursor as possible.
*/
+ xOffs = MIN(textPtr->padX, textPtr->insertWidth/2);
XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
- dInfoPtr->x, y + y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
- (unsigned) height, dInfoPtr->x, dlPtr->y + y_off);
-#else
- TkpClipDrawableToRect(display, pixmap, 0, 0, -1, -1);
-#endif /* TK_NO_DOUBLE_BUFFERING */
- linesRedrawn++;
+ dInfoPtr->x - xOffs, yOffs, dInfoPtr->maxX - dInfoPtr->x + 2*xOffs, lineHeight,
+ dInfoPtr->x - xOffs, dlPtr->y + yOffs);
+
+ DEBUG(stats.linesRedrawn += 1);
}
/*
@@ -2594,47 +6029,52 @@ DisplayDLine(
*--------------------------------------------------------------
*/
+/*
+ * The following function determines whether two styles have the same background
+ * so that, for example, no beveled border should be drawn between them.
+ */
+
+static bool
+SameBackground(
+ const TextStyle *s1,
+ const TextStyle *s2)
+{
+ return s1->sValuePtr->border == s2->sValuePtr->border
+ && s1->sValuePtr->borderWidth == s2->sValuePtr->borderWidth
+ && s1->sValuePtr->relief == s2->sValuePtr->relief
+ && s1->sValuePtr->bgStipple == s2->sValuePtr->bgStipple
+ && s1->sValuePtr->indentBg == s2->sValuePtr->indentBg;
+}
+
static void
DisplayLineBackground(
TkText *textPtr, /* Text widget containing line. */
- register DLine *dlPtr, /* Information about line to draw. */
- DLine *prevPtr, /* Line just above dlPtr, or NULL if dlPtr is
- * the top-most line in the window. */
- Pixmap pixmap) /* Pixmap to use for double-buffering. Caller
- * must make sure it's large enough to hold
- * line. Caller must also have filled it with
+ DLine *dlPtr, /* Information about line to draw. */
+ DLine *prevPtr, /* Line just above dlPtr, or NULL if dlPtr is the top-most line in
+ * the window. */
+ Pixmap pixmap) /* Pixmap to use for double-buffering. Caller must make sure it's
+ * large enough to hold line. Caller must also have filled it with
* the background color for the widget. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextDispChunk *chunkPtr; /* Pointer to chunk in the current line. */
- TkTextDispChunk *chunkPtr2; /* Pointer to chunk in the line above or below
- * the current one. NULL if we're to the left
- * of or to the right of the chunks in the
- * line. */
- TkTextDispChunk *nextPtr2; /* Next chunk after chunkPtr2 (it's not the
- * same as chunkPtr2->nextPtr in the case
- * where chunkPtr2 is NULL because the line is
- * indented). */
- int leftX; /* The left edge of the region we're currently
- * working on. */
- int leftXIn; /* 1 means beveled edge at leftX slopes right
- * as it goes down, 0 means it slopes left as
- * it goes down. */
+ TkTextDispChunk *chunkPtr2; /* Pointer to chunk in the line above or below the current one. NULL
+ * if we're to the left of or to the right of the chunks in the line. */
+ TkTextDispChunk *nextPtr2; /* Next chunk after chunkPtr2 (it's not the same as chunkPtr2->nextPtr
+ * in the case where chunkPtr2 is NULL because the line is indented). */
+ int leftX; /* The left edge of the region we're currently working on. */
+ int leftXIn; /* 0 means beveled edge at leftX slopes right as it goes down,
+ * 1 means it slopes left as it goes down. */
int rightX; /* Right edge of chunkPtr. */
int rightX2; /* Right edge of chunkPtr2. */
- int matchLeft; /* Does the style of this line match that of
- * its neighbor just to the left of the
- * current x coordinate? */
- int matchRight; /* Does line's style match its neighbor just
- * to the right of the current x-coord? */
- int minX, maxX, xOffset, bw;
+ int matchLeft; /* Does the style of this line match that of its neighbor just to the
+ * left of the current x coordinate? */
+ int matchRight; /* Does line's style match its neighbor just to the right of the
+ * current x-coord? */
+ int minX, maxX, xOffset, xIndent, borderWidth;
StyleValues *sValuePtr;
Display *display;
-#ifndef TK_NO_DOUBLE_BUFFERING
const int y = 0;
-#else
- const int y = dlPtr->y;
-#endif /* TK_NO_DOUBLE_BUFFERING */
/*
* Pass 1: scan through dlPtr from left to right. For each range of chunks
@@ -2647,6 +6087,7 @@ DisplayLineBackground(
xOffset = dInfoPtr->x - minX;
maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
chunkPtr = dlPtr->chunkPtr;
+ xIndent = 0;
/*
* Note A: in the following statement, and a few others later in this file
@@ -2662,19 +6103,18 @@ DisplayLineBackground(
* highlighted, leaving a ragged left edge for multi-line highlights.
*/
- leftX = 0;
- for (; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {
- if ((chunkPtr->nextPtr != NULL)
- && SAME_BACKGROUND(chunkPtr->nextPtr->stylePtr,
- chunkPtr->stylePtr)) {
+ for (leftX = 0; leftX < maxX; chunkPtr = chunkPtr->nextPtr) {
+ if (chunkPtr->nextPtr && SameBackground(chunkPtr->nextPtr->stylePtr, chunkPtr->stylePtr)) {
continue;
}
sValuePtr = chunkPtr->stylePtr->sValuePtr;
rightX = chunkPtr->x + chunkPtr->width;
- if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
+ if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
if (chunkPtr->stylePtr->bgGC != None) {
+ int indent = 0;
+
/*
* Not visible - bail out now.
*/
@@ -2685,17 +6125,28 @@ DisplayLineBackground(
}
/*
- * Trim the start position for drawing to be no further away than
- * -borderWidth. The reason is that on many X servers drawing from
- * -32768 (or less) to +something simply does not display
- * correctly. [Patch #541999]
+ * Compute additional offset if -indentbackground is set.
+ */
+
+ if (leftX == 0 && sValuePtr->indentBg) {
+ xIndent = GetLeftLineMargin(dlPtr, sValuePtr);
+ if (leftX + xIndent > rightX) {
+ xIndent = rightX - leftX;
+ }
+ indent = xIndent;
+ }
+
+ /*
+ * Trim the start position for drawing to be no further away than -borderWidth.
+ * The reason is that on many X servers drawing from -32768 (or less) to
+ * +something simply does not display correctly. [Patch #541999]
*/
- if ((leftX + xOffset) < -(sValuePtr->borderWidth)) {
- leftX = -sValuePtr->borderWidth - xOffset;
+ if (leftX + xOffset + indent < -sValuePtr->borderWidth) {
+ leftX = -sValuePtr->borderWidth - xOffset - indent;
}
- if ((rightX - leftX) > 32767) {
- rightX = leftX + 32767;
+ if (rightX - leftX - indent > 32767) {
+ rightX = leftX + indent + 32767;
}
/*
@@ -2703,21 +6154,20 @@ DisplayLineBackground(
* which would happen for too large border width.
*/
- bw = sValuePtr->borderWidth;
+ borderWidth = sValuePtr->borderWidth;
if (leftX + sValuePtr->borderWidth > rightX) {
- bw = rightX - leftX;
+ borderWidth = rightX - leftX;
}
XFillRectangle(display, pixmap, chunkPtr->stylePtr->bgGC,
- leftX + xOffset, y, (unsigned int) (rightX - leftX),
- (unsigned int) dlPtr->height);
+ leftX + xOffset + indent, y, rightX - leftX - indent, dlPtr->height);
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
- leftX + xOffset, y, bw, dlPtr->height, 1,
- sValuePtr->relief);
+ leftX + xOffset + indent, y, borderWidth,
+ dlPtr->height, 1, sValuePtr->relief);
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
- rightX - bw + xOffset, y, bw, dlPtr->height, 0,
- sValuePtr->relief);
+ rightX - borderWidth + xOffset, y, borderWidth,
+ dlPtr->height, 0, sValuePtr->relief);
}
}
leftX = rightX;
@@ -2734,11 +6184,11 @@ DisplayLineBackground(
leftX = 0; /* See Note A above. */
leftXIn = 1;
rightX = chunkPtr->x + chunkPtr->width;
- if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
+ if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
chunkPtr2 = NULL;
- if (prevPtr != NULL && prevPtr->chunkPtr != NULL) {
+ if (prevPtr && prevPtr->chunkPtr) {
/*
* Find the chunk in the previous line that covers leftX.
*/
@@ -2746,13 +6196,12 @@ DisplayLineBackground(
nextPtr2 = prevPtr->chunkPtr;
rightX2 = 0; /* See Note A above. */
while (rightX2 <= leftX) {
- chunkPtr2 = nextPtr2;
- if (chunkPtr2 == NULL) {
+ if (!(chunkPtr2 = nextPtr2)) {
break;
}
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
- if (nextPtr2 == NULL) {
+ if (!nextPtr2) {
rightX2 = INT_MAX;
}
}
@@ -2762,8 +6211,7 @@ DisplayLineBackground(
}
while (leftX < maxX) {
- matchLeft = (chunkPtr2 != NULL)
- && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
+ matchLeft = chunkPtr2 && SameBackground(chunkPtr2->stylePtr, chunkPtr->stylePtr);
sValuePtr = chunkPtr->stylePtr->sValuePtr;
if (rightX <= rightX2) {
/*
@@ -2771,14 +6219,14 @@ DisplayLineBackground(
* then draw the bevel for the current style.
*/
- if ((chunkPtr->nextPtr == NULL)
- || !SAME_BACKGROUND(chunkPtr->stylePtr,
- chunkPtr->nextPtr->stylePtr)) {
- if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
+ if (!chunkPtr->nextPtr
+ || !SameBackground(chunkPtr->stylePtr, chunkPtr->nextPtr->stylePtr)) {
+ if (!matchLeft && sValuePtr->relief != TK_RELIEF_FLAT) {
+ int indent = (leftX == 0) ? xIndent : 0;
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
- sValuePtr->border, leftX + xOffset, y,
- rightX - leftX, sValuePtr->borderWidth, leftXIn,
- 1, 1, sValuePtr->relief);
+ sValuePtr->border, leftX + xOffset + indent, y,
+ rightX - leftX - indent, sValuePtr->borderWidth,
+ leftXIn, 1, 1, sValuePtr->relief);
}
leftX = rightX;
leftXIn = 1;
@@ -2788,16 +6236,16 @@ DisplayLineBackground(
* point then advance to the next chunk in that line.
*/
- if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
+ if (rightX == rightX2 && chunkPtr2) {
goto nextChunk2;
}
}
chunkPtr = chunkPtr->nextPtr;
- if (chunkPtr == NULL) {
+ if (!chunkPtr) {
break;
}
rightX = chunkPtr->x + chunkPtr->width;
- if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
+ if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
continue;
@@ -2810,43 +6258,42 @@ DisplayLineBackground(
* other, we have to draw an L-shaped piece of bevel.
*/
- matchRight = (nextPtr2 != NULL)
- && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
+ matchRight = nextPtr2 && SameBackground(nextPtr2->stylePtr, chunkPtr->stylePtr);
if (matchLeft && !matchRight) {
- bw = sValuePtr->borderWidth;
- if (rightX2 - sValuePtr->borderWidth < leftX) {
- bw = rightX2 - leftX;
+ borderWidth = sValuePtr->borderWidth;
+ if (rightX2 - borderWidth < leftX) {
+ borderWidth = rightX2 - leftX;
}
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
- rightX2 - bw + xOffset, y, bw,
+ rightX2 - borderWidth + xOffset, y, borderWidth,
sValuePtr->borderWidth, 0, sValuePtr->relief);
}
- leftX = rightX2 - bw;
+ leftX = rightX2 - borderWidth;
leftXIn = 0;
- } else if (!matchLeft && matchRight
- && (sValuePtr->relief != TK_RELIEF_FLAT)) {
- bw = sValuePtr->borderWidth;
- if (rightX2 + sValuePtr->borderWidth > rightX) {
- bw = rightX - rightX2;
+ } else if (!matchLeft && matchRight && sValuePtr->relief != TK_RELIEF_FLAT) {
+ int indent = (leftX == 0) ? xIndent : 0;
+ borderWidth = sValuePtr->borderWidth;
+ if (rightX2 + borderWidth > rightX) {
+ borderWidth = rightX - rightX2;
}
- Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
- rightX2 + xOffset, y, bw, sValuePtr->borderWidth,
- 1, sValuePtr->relief);
+ Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border, rightX2 + xOffset,
+ y, borderWidth, sValuePtr->borderWidth, 1, sValuePtr->relief);
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
- leftX + xOffset, y, rightX2 + bw - leftX,
+ leftX + xOffset + indent, y,
+ rightX2 + borderWidth - leftX - indent,
sValuePtr->borderWidth, leftXIn, 0, 1,
sValuePtr->relief);
}
nextChunk2:
chunkPtr2 = nextPtr2;
- if (chunkPtr2 == NULL) {
+ if (!chunkPtr2) {
rightX2 = INT_MAX;
} else {
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
- if (nextPtr2 == NULL) {
+ if (!nextPtr2) {
rightX2 = INT_MAX;
}
}
@@ -2861,11 +6308,11 @@ DisplayLineBackground(
leftX = 0; /* See Note A above. */
leftXIn = 0;
rightX = chunkPtr->x + chunkPtr->width;
- if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
+ if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
chunkPtr2 = NULL;
- if (dlPtr->nextPtr != NULL && dlPtr->nextPtr->chunkPtr != NULL) {
+ if (dlPtr->nextPtr && dlPtr->nextPtr->chunkPtr) {
/*
* Find the chunk in the next line that covers leftX.
*/
@@ -2874,12 +6321,12 @@ DisplayLineBackground(
rightX2 = 0; /* See Note A above. */
while (rightX2 <= leftX) {
chunkPtr2 = nextPtr2;
- if (chunkPtr2 == NULL) {
+ if (!chunkPtr2) {
break;
}
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
- if (nextPtr2 == NULL) {
+ if (!nextPtr2) {
rightX2 = INT_MAX;
}
}
@@ -2889,77 +6336,74 @@ DisplayLineBackground(
}
while (leftX < maxX) {
- matchLeft = (chunkPtr2 != NULL)
- && SAME_BACKGROUND(chunkPtr2->stylePtr, chunkPtr->stylePtr);
+ matchLeft = chunkPtr2 && SameBackground(chunkPtr2->stylePtr, chunkPtr->stylePtr);
sValuePtr = chunkPtr->stylePtr->sValuePtr;
if (rightX <= rightX2) {
- if ((chunkPtr->nextPtr == NULL)
- || !SAME_BACKGROUND(chunkPtr->stylePtr,
- chunkPtr->nextPtr->stylePtr)) {
- if (!matchLeft && (sValuePtr->relief != TK_RELIEF_FLAT)) {
+ if (!chunkPtr->nextPtr
+ || !SameBackground(chunkPtr->stylePtr, chunkPtr->nextPtr->stylePtr)) {
+ if (!matchLeft && sValuePtr->relief != TK_RELIEF_FLAT) {
+ int indent = (leftX == 0) ? xIndent : 0;
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap,
- sValuePtr->border, leftX + xOffset,
+ sValuePtr->border, leftX + xOffset + indent,
y + dlPtr->height - sValuePtr->borderWidth,
- rightX - leftX, sValuePtr->borderWidth, leftXIn,
- 0, 0, sValuePtr->relief);
+ rightX - leftX - indent, sValuePtr->borderWidth,
+ leftXIn, 0, 0, sValuePtr->relief);
}
leftX = rightX;
leftXIn = 0;
- if ((rightX == rightX2) && (chunkPtr2 != NULL)) {
+ if (rightX == rightX2 && chunkPtr2) {
goto nextChunk2b;
}
}
chunkPtr = chunkPtr->nextPtr;
- if (chunkPtr == NULL) {
+ if (!chunkPtr) {
break;
}
rightX = chunkPtr->x + chunkPtr->width;
- if ((chunkPtr->nextPtr == NULL) && (rightX < maxX)) {
+ if (!chunkPtr->nextPtr && rightX < maxX) {
rightX = maxX;
}
continue;
}
- matchRight = (nextPtr2 != NULL)
- && SAME_BACKGROUND(nextPtr2->stylePtr, chunkPtr->stylePtr);
+ matchRight = nextPtr2 && SameBackground(nextPtr2->stylePtr, chunkPtr->stylePtr);
if (matchLeft && !matchRight) {
- bw = sValuePtr->borderWidth;
- if (rightX2 - sValuePtr->borderWidth < leftX) {
- bw = rightX2 - leftX;
+ borderWidth = sValuePtr->borderWidth;
+ if (rightX2 - borderWidth < leftX) {
+ borderWidth = rightX2 - leftX;
}
if (sValuePtr->relief != TK_RELIEF_FLAT) {
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
- rightX2 - bw + xOffset,
+ rightX2 - borderWidth + xOffset,
y + dlPtr->height - sValuePtr->borderWidth,
- bw, sValuePtr->borderWidth, 0, sValuePtr->relief);
+ borderWidth, sValuePtr->borderWidth, 0,
+ sValuePtr->relief);
}
- leftX = rightX2 - bw;
+ leftX = rightX2 - borderWidth;
leftXIn = 1;
- } else if (!matchLeft && matchRight
- && (sValuePtr->relief != TK_RELIEF_FLAT)) {
- bw = sValuePtr->borderWidth;
- if (rightX2 + sValuePtr->borderWidth > rightX) {
- bw = rightX - rightX2;
+ } else if (!matchLeft && matchRight && sValuePtr->relief != TK_RELIEF_FLAT) {
+ int indent = (leftX == 0) ? xIndent : 0;
+ borderWidth = sValuePtr->borderWidth;
+ if (rightX2 + borderWidth > rightX) {
+ borderWidth = rightX - rightX2;
}
Tk_3DVerticalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
- rightX2 + xOffset,
- y + dlPtr->height - sValuePtr->borderWidth, bw,
- sValuePtr->borderWidth, 1, sValuePtr->relief);
+ rightX2 + xOffset, y + dlPtr->height - sValuePtr->borderWidth,
+ borderWidth, sValuePtr->borderWidth, 1, sValuePtr->relief);
Tk_3DHorizontalBevel(textPtr->tkwin, pixmap, sValuePtr->border,
- leftX + xOffset,
- y + dlPtr->height - sValuePtr->borderWidth,
- rightX2 + bw - leftX, sValuePtr->borderWidth, leftXIn,
- 1, 0, sValuePtr->relief);
+ leftX + xOffset + indent, y + dlPtr->height - sValuePtr->borderWidth,
+ rightX2 + borderWidth - leftX - indent, sValuePtr->borderWidth,
+ leftXIn, 1, 0, sValuePtr->relief);
}
nextChunk2b:
chunkPtr2 = nextPtr2;
- if (chunkPtr2 == NULL) {
+ if (!chunkPtr2) {
rightX2 = INT_MAX;
} else {
nextPtr2 = chunkPtr2->nextPtr;
rightX2 = chunkPtr2->x + chunkPtr2->width;
- if (nextPtr2 == NULL) {
+ if (!nextPtr2) {
rightX2 = INT_MAX;
}
}
@@ -2993,136 +6437,187 @@ static void
AsyncUpdateLineMetrics(
ClientData clientData) /* Information about widget. */
{
- register TkText *textPtr = clientData;
- TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- int lineNum;
+ TkText *textPtr = clientData;
+ TextDInfo *dInfoPtr;
- dInfoPtr->lineUpdateTimer = NULL;
+ if (TkTextReleaseIfDestroyed(textPtr)) {
+ return;
+ }
- if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)
- || !Tk_IsMapped(textPtr->tkwin)) {
- /*
- * The widget has been deleted, or is not mapped. Don't do anything.
- */
+ dInfoPtr = textPtr->dInfoPtr;
+ dInfoPtr->lineUpdateTimer = NULL;
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
- }
- return;
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ return; /* not yet configured */
}
if (dInfoPtr->flags & REDRAW_PENDING) {
- dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
- AsyncUpdateLineMetrics, clientData);
+ dInfoPtr->flags |= ASYNC_PENDING|ASYNC_UPDATE;
return;
}
/*
- * Reify where we end or all hell breaks loose with the calculations when
- * we try to update. [Bug 2677890]
- */
-
- lineNum = dInfoPtr->currentMetricUpdateLine;
- if (dInfoPtr->lastMetricUpdateLine == -1) {
- dInfoPtr->lastMetricUpdateLine =
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
- }
-
- /*
* Update the lines in blocks of about 24 recalculations, or 250+ lines
* examined, so we pass in 256 for 'doThisMuch'.
*/
- lineNum = TkTextUpdateLineMetrics(textPtr, lineNum,
- dInfoPtr->lastMetricUpdateLine, 256);
-
- dInfoPtr->currentMetricUpdateLine = lineNum;
-
- if (tkTextDebug) {
- char buffer[2 * TCL_INTEGER_SPACE + 1];
+ UpdateLineMetrics(textPtr, 256);
+ TK_TEXT_DEBUG(LogTextInvalidateLine(textPtr, 0));
- sprintf(buffer, "%d %d", lineNum, dInfoPtr->lastMetricUpdateLine);
- LOG("tk_textInvalidateLine", buffer);
- }
-
- /*
- * If we're not in the middle of a long-line calculation (metricEpoch==-1)
- * and we've reached the last line, then we're done.
- */
-
- if (dInfoPtr->metricEpoch == -1
- && lineNum == dInfoPtr->lastMetricUpdateLine) {
+ if (TkRangeListIsEmpty(dInfoPtr->lineMetricUpdateRanges)) {
/*
* We have looped over all lines, so we're done. We must release our
* refCount on the widget (the timer token was already set to NULL
* above). If there is a registered aftersync command, run that first.
*/
- if (textPtr->afterSyncCmd) {
- int code;
- Tcl_Preserve((ClientData) textPtr->interp);
- code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd,
- TCL_EVAL_GLOBAL);
- if (code == TCL_ERROR) {
- Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)");
- Tcl_BackgroundError(textPtr->interp);
- }
- Tcl_Release((ClientData) textPtr->interp);
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- textPtr->afterSyncCmd = NULL;
+ if (!dInfoPtr->pendingUpdateLineMetricsFinished) {
+ UpdateLineMetricsFinished(textPtr, false);
+ GetYView(textPtr->interp, textPtr, true);
}
+ TkTextDecrRefCountAndTestIfDestroyed(textPtr);
+ } else {
+ /*
+ * Re-arm the timer. We already have a refCount on the text widget so no
+ * need to adjust that.
+ */
- /*
- * Fire the <<WidgetViewSync>> event since the widget view is in sync
- * with its internal data (actually it will be after the next trip
- * through the event loop, because the widget redraws at idle-time).
- */
-
- GenerateWidgetViewSyncEvent(textPtr, 1);
-
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
- }
- return;
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, AsyncUpdateLineMetrics, textPtr);
}
-
- /*
- * Re-arm the timer. We already have a refCount on the text widget so no
- * need to adjust that.
- */
-
- dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
- AsyncUpdateLineMetrics, textPtr);
}
/*
*----------------------------------------------------------------------
*
- * GenerateWidgetViewSyncEvent --
+ * UpdateLineMetrics --
*
- * Send the <<WidgetViewSync>> event related to the text widget
- * line metrics asynchronous update.
- * This is equivalent to:
- * event generate $textWidget <<WidgetViewSync>> -data $s
- * where $s is the sync status: true (when the widget view is in
- * sync with its internal data) or false (when it is not).
+ * This function updates the pixel height calculations of a range of
+ * lines in the widget. The range is from lineNum to endLine, but
+ * this function may return earlier, once a certain number of lines
+ * has been examined. The line counts are from 0.
*
* Results:
- * None
+ * The index of the last line examined (or zero if we are about to wrap
+ * around from end to beginning of the widget).
*
* Side effects:
- * If corresponding bindings are present, they will trigger.
+ * Line heights may be recalculated.
*
*----------------------------------------------------------------------
*/
+static unsigned
+NextLineNum(
+ TkTextLine *linePtr,
+ unsigned lineNum,
+ const TkTextIndex *indexPtr)
+{
+ TkText *textPtr;
+
+ assert(indexPtr->textPtr);
+
+ if (linePtr->nextPtr == TkTextIndexGetLine(indexPtr)) {
+ return lineNum + 1;
+ }
+
+ textPtr = indexPtr->textPtr;
+ return TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, TkTextIndexGetLine(indexPtr), NULL);
+}
+
static void
-GenerateWidgetViewSyncEvent(
- TkText *textPtr, /* Information about text widget. */
- Bool InSync) /* true if in sync, false otherwise */
+UpdateLineMetrics(
+ TkText *textPtr, /* Information about widget. */
+ unsigned doThisMuch) /* How many lines to check, or how many 10s of lines to recalculate. */
{
- TkSendVirtualEvent(textPtr->tkwin, "WidgetViewSync",
- Tcl_NewBooleanObj(InSync));
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ const TkRange *range = TkRangeListFirst(dInfoPtr->lineMetricUpdateRanges);
+ unsigned maxDispLines = UINT_MAX;
+ unsigned count = 0;
+
+ assert(textPtr->sharedTextPtr->allowUpdateLineMetrics);
+
+ while (range) {
+ TkTextLine *linePtr;
+ TkTextLine *logicalLinePtr;
+ int lineNum = range->low;
+ int high = range->high;
+
+ linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
+ logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+
+ if (linePtr != logicalLinePtr) {
+ lineNum = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, logicalLinePtr, NULL);
+ linePtr = logicalLinePtr;
+ }
+
+ while (lineNum <= high) {
+ TkTextPixelInfo *pixelInfo;
+
+ TK_TEXT_DEBUG(LogTextInvalidateLine(textPtr, count));
+
+ /*
+ * Now update the line's metrics if necessary.
+ */
+
+ pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
+
+ if (pixelInfo->epoch == dInfoPtr->lineMetricUpdateEpoch) {
+ int firstLineNum = lineNum;
+
+ /*
+ * This line is already up to date. That means there's nothing to do here.
+ */
+
+ if (linePtr->nextPtr->logicalLine) {
+ linePtr = linePtr->nextPtr;
+ lineNum += 1;
+ } else {
+ linePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ lineNum = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL);
+ }
+
+ TkRangeListRemove(dInfoPtr->lineMetricUpdateRanges, firstLineNum, lineNum - 1);
+ } else {
+ TkTextIndex index;
+
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetToStartOfLine2(&index, linePtr);
+
+ /*
+ * Update the line and update the counter, counting 8 for each display line
+ * we actually re-layout. But in case of synchronous update we do a full
+ * computation.
+ */
+
+ if (textPtr->syncTime > 0) {
+ maxDispLines = (doThisMuch - count + 7)/8;
+ }
+ count += 8*TkTextUpdateOneLine(textPtr, linePtr, &index, maxDispLines);
+
+ if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
+ /*
+ * We didn't complete the logical line, because it produced very many
+ * display lines - it must be a long line wrapped many times.
+ */
+ return;
+ }
+
+ /*
+ * We're done with this line.
+ */
+
+ lineNum = NextLineNum(linePtr, lineNum, &index);
+ linePtr = TkTextIndexGetLine(&index);
+ }
+
+ if ((++count >= doThisMuch)) {
+ return;
+ }
+ }
+
+ /* The update process has removed the finished lines. */
+ range = TkRangeListFirst(dInfoPtr->lineMetricUpdateRanges);
+ }
}
/*
@@ -3131,20 +6626,14 @@ GenerateWidgetViewSyncEvent(
* TkTextUpdateLineMetrics --
*
* This function updates the pixel height calculations of a range of
- * lines in the widget. The range is from lineNum to endLine, but, if
- * doThisMuch is positive, then the function may return earlier, once a
- * certain number of lines has been examined. The line counts are from 0.
- *
- * If doThisMuch is -1, then all lines in the range will be updated. This
- * will potentially take quite some time for a large text widget.
+ * lines in the widget. The range is from lineNum to endLine. The line
+ * counts are from 0.
*
- * Note: with bad input for lineNum and endLine, this function can loop
- * indefinitely.
+ * All lines in the range will be updated. This will potentially take
+ * quite some time for a large range of lines.
*
* Results:
- * The index of the last line examined (or -1 if we are about to wrap
- * around from end to beginning of the widget, and the next line will be
- * the first line).
+ * None.
*
* Side effects:
* Line heights may be recalculated.
@@ -3152,369 +6641,749 @@ GenerateWidgetViewSyncEvent(
*----------------------------------------------------------------------
*/
-int
+void
TkTextUpdateLineMetrics(
TkText *textPtr, /* Information about widget. */
- int lineNum, /* Start at this line. */
- int endLine, /* Go no further than this line. */
- int doThisMuch) /* How many lines to check, or how many 10s of
- * lines to recalculate. If '-1' then do
- * everything in the range (which may take a
- * while). */
-{
- TkTextLine *linePtr = NULL;
- int count = 0;
- int totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
-
- if (totalLines == 0) {
- /*
- * Empty peer widget.
- */
+ unsigned lineNum, /* Start at this line. */
+ unsigned endLine) /* Go no further than this line. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ const TkRange *range;
- return endLine;
- }
+ assert(lineNum <= endLine);
+ assert(endLine <= TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
+ assert(textPtr->sharedTextPtr->allowUpdateLineMetrics);
- while (1) {
- /*
- * Get a suitable line.
- */
+ dInfoPtr->insideLineMetricUpdate = true;
- if (lineNum == -1 && linePtr == NULL) {
- lineNum = 0;
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
- lineNum);
- } else {
- if (lineNum == -1 || linePtr == NULL) {
- if (lineNum == -1) {
- lineNum = 0;
- }
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineNum);
- } else {
- lineNum++;
- linePtr = TkBTreeNextLine(textPtr, linePtr);
- }
+ if ((range = TkRangeListFindNearest(dInfoPtr->lineMetricUpdateRanges, lineNum))) {
+ TkTextLine *linePtr = NULL;
+ unsigned count = 0;
+ int high = range->high;
- /*
- * If we're in the middle of a partial-line height calculation,
- * then we can't be done.
- */
+ lineNum = range->low;
+ endLine = MIN(endLine, TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr) - 1);
- if (textPtr->dInfoPtr->metricEpoch == -1 && lineNum == endLine) {
+ while (true) {
+ const TkTextPixelInfo *pixelInfo;
+
+ if (lineNum > high) {
/*
- * We have looped over all lines, so we're done.
+ * Note that the update process has removed the finished lines.
*/
- break;
+ if (!(range = TkRangeListFindNearest(dInfoPtr->lineMetricUpdateRanges, lineNum))) {
+ break;
+ }
+ linePtr = NULL;
+ lineNum = range->low;
+ high = range->high;
}
- }
- if (lineNum < totalLines) {
- if (tkTextDebug) {
- char buffer[4 * TCL_INTEGER_SPACE + 3];
+ if (lineNum > endLine) {
+ break;
+ }
- sprintf(buffer, "%d %d %d %d",
- lineNum, endLine, totalLines, count);
- LOG("tk_textInvalidateLine", buffer);
+ if (!linePtr) {
+ linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
+ linePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
}
- /*
- * Now update the line's metrics if necessary.
- */
+ TK_TEXT_DEBUG(LogTextInvalidateLine(textPtr, count));
+ assert(linePtr->nextPtr);
- if (TkBTreeLinePixelEpoch(textPtr, linePtr)
- == textPtr->dInfoPtr->lineMetricUpdateEpoch) {
- /*
- * This line is already up to date. That means there's nothing
- * to do here.
- */
- } else if (doThisMuch == -1) {
- count += 8 * TkTextUpdateOneLine(textPtr, linePtr, 0,NULL,0);
- } else {
+ pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
+
+ if (pixelInfo->epoch != dInfoPtr->lineMetricUpdateEpoch) {
TkTextIndex index;
- TkTextIndex *indexPtr;
- int pixelHeight;
/*
- * If the metric epoch is the same as the widget's epoch, then
- * we know that indexPtrs are still valid, and if the cached
- * metricIndex (if any) is for the same line as we wish to
- * examine, then we are looking at a long line wrapped many
- * times, which we will examine in pieces.
+ * This line is not (fully) up-to-date.
*/
- if (textPtr->dInfoPtr->metricEpoch ==
- textPtr->sharedTextPtr->stateEpoch &&
- textPtr->dInfoPtr->metricIndex.linePtr==linePtr) {
- indexPtr = &textPtr->dInfoPtr->metricIndex;
- pixelHeight = textPtr->dInfoPtr->metricPixelHeight;
- } else {
- /*
- * We must reset the partial line height calculation data
- * here, so we don't use it when it is out of date.
- */
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetToStartOfLine2(&index, linePtr);
+ TkTextUpdateOneLine(textPtr, linePtr, &index, UINT_MAX);
+ assert(IsStartOfNotMergedLine(&index) || TkTextIndexIsEndOfText(&index));
+ lineNum = NextLineNum(linePtr, lineNum, &index);
+ linePtr = TkTextIndexGetLine(&index);
+ } else {
+ int firstLineNum = lineNum;
- textPtr->dInfoPtr->metricEpoch = -1;
- index.tree = textPtr->sharedTextPtr->tree;
- index.linePtr = linePtr;
- index.byteIndex = 0;
- index.textPtr = NULL;
- indexPtr = &index;
- pixelHeight = 0;
+ if (linePtr->nextPtr->logicalLine) {
+ linePtr = linePtr->nextPtr;
+ lineNum += 1;
+ } else {
+ linePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ lineNum = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL);
}
- /*
- * Update the line and update the counter, counting 8 for each
- * display line we actually re-layout.
- */
+ TkRangeListRemove(dInfoPtr->lineMetricUpdateRanges, firstLineNum, lineNum - 1);
+ }
+ }
+ }
- count += 8 * TkTextUpdateOneLine(textPtr, linePtr,
- pixelHeight, indexPtr, 1);
+ dInfoPtr->insideLineMetricUpdate = false;
+ CheckIfLineMetricIsUpToDate(textPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextInvalidateLineMetrics, TextInvalidateLineMetrics --
+ *
+ * Mark a number of text lines as having invalid line metric
+ * calculations. Depending on 'action' which indicates whether
+ * the given lines are simply invalid or have been inserted or
+ * deleted, the pre-existing asynchronous line update range may
+ * need to be adjusted.
+ *
+ * If linePtr is NULL then 'lineCount' and 'action' are ignored
+ * and all lines are invalidated.
+ *
+ * If linePtr is the last (artificial) line, then do nothing.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May schedule an asychronous callback.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ResetPixelInfo(
+ TkTextPixelInfo *pixelInfo)
+{
+ TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
+
+ if (dispLineInfo) {
+ if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
+ dispLineInfo->numDispLines = dispLineInfo->entry[dispLineInfo->numDispLines].pixels;
+ }
+ }
+ pixelInfo->epoch = 0;
+}
+
+static void
+StartAsyncLineCalculation(
+ TkText *textPtr)
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- if (indexPtr->linePtr == linePtr) {
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ return;
+ }
+
+ /*
+ * Reset cached chunk.
+ */
+
+ dInfoPtr->currChunkPtr = NULL;
+ InvokeAsyncUpdateLineMetrics(textPtr);
+
+ if (!(dInfoPtr->flags & ASYNC_UPDATE)) {
+ dInfoPtr->flags |= ASYNC_UPDATE;
+ TkTextGenerateWidgetViewSyncEvent(textPtr, false);
+ }
+}
+
+static void
+TextInvalidateLineMetrics(
+ TkText *textPtr, /* Widget record for text widget. */
+ TkTextLine *linePtr, /* Invalidation starts from this line; can be NULL, but only in
+ * case of simple invalidation. */
+ unsigned lineCount, /* And includes this amount of following lines. */
+ TkTextInvalidateAction action)
+ /* Indicates what type of invalidation occurred (insert, delete,
+ * or simple). */
+{
+ TkRangeList *ranges = textPtr->dInfoPtr->lineMetricUpdateRanges;
+ unsigned totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
+ unsigned epoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
+ bool isMonospaced = UseMonospacedLineHeights(textPtr);
+ unsigned lineNum = 0; /* suppress compiler warning */
+
+ assert(linePtr || action == TK_TEXT_INVALIDATE_ONLY);
+ assert(TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, NULL) + lineCount
+ < totalLines + (action == TK_TEXT_INVALIDATE_INSERT));
+
+ if (linePtr) {
+ int deviation;
+
+ lineNum = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, linePtr, &deviation);
+
+ assert(lineNum < totalLines);
+ assert(deviation >= 0);
+
+ if (deviation) {
+ lineCount -= MIN(lineCount, deviation);
+ }
+
+ if (action != TK_TEXT_INVALIDATE_ONLY
+ && !isMonospaced
+ && linePtr == TkBTreeGetStartLine(textPtr)
+ && lineCount + 1 >= totalLines) {
+ linePtr = NULL;
+ }
+ } else if (isMonospaced) {
+ linePtr = TkBTreeGetStartLine(textPtr);
+ lineCount = totalLines;
+ }
+
+ if (linePtr) {
+ if (TkRangeListSize(ranges) >= 200) {
+ /*
+ * The range list is a great data structure for fast management of update
+ * information, but this list is not designed for a large amount of entries.
+ * If this arbitrarily chosen number of entries has been reached we will
+ * compact the list, because in this case the line traversal may be faster
+ * than the management of this list. Note that reaching this point is in
+ * general not expected, especially since the range list is amalgamating
+ * adjacent ranges automatically.
+ */
+
+ int low = TkRangeListLow(ranges);
+ int high = TkRangeListHigh(ranges);
+
+ TkRangeListClear(ranges);
+ ranges = TkRangeListAdd(ranges, low, high);
+ }
+
+ switch (action) {
+ case TK_TEXT_INVALIDATE_ONLY: {
+ int counter = MIN(lineCount, totalLines - lineNum);
+
+ if (isMonospaced) {
+ TkBTreeUpdatePixelHeights(textPtr, linePtr, lineCount, epoch);
+ } else {
+ ranges = TkRangeListAdd(ranges, lineNum, lineNum + lineCount);
+ ResetPixelInfo(TkBTreeLinePixelInfo(textPtr, linePtr));
+
+ if (!TkRangeListContainsRange(ranges, lineNum + 1, lineNum + counter)) {
/*
- * We didn't complete the logical line, because it
- * produced very many display lines, which must be because
- * it must be a long line wrapped many times. So we must
- * cache as far as we got for next time around.
+ * Invalidate the height calculations of each line in the given range.
+ * Note that normally only a few lines will be invalidated (in current
+ * case with simple invalidation). Also note that the other cases
+ * (insert, delete) do not need invalidation of single lines, because
+ * inserted lines should be invalid per default, and deleted lines don't
+ * need invalidation at all.
*/
- if (pixelHeight == 0) {
- /*
- * These have already been stored, unless we just
- * started the new line.
- */
-
- textPtr->dInfoPtr->metricIndex = index;
- textPtr->dInfoPtr->metricEpoch =
- textPtr->sharedTextPtr->stateEpoch;
+ for ( ; counter > 0; --counter) {
+ ResetPixelInfo(TkBTreeLinePixelInfo(textPtr, linePtr = linePtr->nextPtr));
}
- textPtr->dInfoPtr->metricPixelHeight =
- TkBTreeLinePixelCount(textPtr, linePtr);
- break;
}
+ }
+ break;
+ }
+ case TK_TEXT_INVALIDATE_ELIDE: {
+ int counter = MIN(lineCount, totalLines - lineNum);
+
+ if (isMonospaced) {
+ TkBTreeUpdatePixelHeights(textPtr, linePtr, lineCount, epoch);
+ } else {
+ TkTextLine *mergedLinePtr = NULL;
+ unsigned count;
+
+ if (!linePtr->logicalLine) {
+#if 1 /* TODO: is this sufficient? */
+ assert(linePtr->prevPtr);
+ linePtr = linePtr->prevPtr;
+ lineNum -= 1;
+ lineCount += 1;
+#else /* TODO: this is sufficient anyway! */
+ TkTextLine *logicalLinePtr =
+ TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+
+ count = TkBTreeCountLines(textPtr->sharedTextPtr->tree, logicalLinePtr, linePtr);
+ lineNum -= count;
+ lineCount += count;
+#endif
+ }
+
+ ranges = TkRangeListAdd(ranges, lineNum, lineNum + lineCount);
+ count = 1;
/*
- * We're done with this long line.
+ * Invalidate the height calculations of each line in the given range.
+ * For merged lines (any line which is not a logical line) we have to
+ * reset the display line count.
*/
- textPtr->dInfoPtr->metricEpoch = -1;
+ for ( ; counter > 0; --counter, linePtr = linePtr->nextPtr) {
+ if (linePtr->logicalLine) {
+ if (mergedLinePtr) {
+ TkBTreeResetDisplayLineCounts(textPtr, mergedLinePtr, count);
+ mergedLinePtr = NULL;
+ }
+ ResetPixelInfo(TkBTreeLinePixelInfo(textPtr, linePtr));
+ } else {
+ if (!mergedLinePtr) {
+ mergedLinePtr = linePtr;
+ count = 1;
+ } else {
+ count += 1;
+ }
+ }
+ }
+ if (mergedLinePtr) {
+ TkBTreeResetDisplayLineCounts(textPtr, mergedLinePtr, count);
+ }
}
- } else {
- /*
- * We must never recalculate the height of the last artificial
- * line. It must stay at zero, and if we recalculate it, it will
- * change.
- */
-
- if (endLine >= totalLines) {
- lineNum = endLine;
- break;
+ break;
+ }
+ case TK_TEXT_INVALIDATE_DELETE:
+ textPtr->dInfoPtr->lastLineNo -= lineCount;
+ if (isMonospaced) {
+ return;
}
+ if (lineCount > 0) {
+ TkTextIndex index;
+ DLine *dlPtr;
- /*
- * Set things up for the next loop through.
- */
+ TkRangeListDelete(ranges, lineNum + 1, lineNum + lineCount);
- lineNum = -1;
- }
- count++;
+ /*
+ * Free all display lines in specified range. This is required, otherwise
+ * it may happen that we are accessing deleted (invalid) data (bug in
+ * old implementation).
+ */
- if (doThisMuch != -1 && count >= doThisMuch) {
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetToStartOfLine2(&index, linePtr->nextPtr);
+ if ((dlPtr = FindDLine(textPtr, textPtr->dInfoPtr->dLinePtr, &index))) {
+ TkTextIndexSetToEndOfLine2(&index,
+ TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum + lineCount));
+ FreeDLines(textPtr, dlPtr, FindDLine(textPtr, dlPtr, &index), DLINE_UNLINK);
+ }
+ }
+ ranges = TkRangeListAdd(ranges, lineNum, lineNum);
+ ResetPixelInfo(TkBTreeLinePixelInfo(textPtr, linePtr));
+ break;
+ case TK_TEXT_INVALIDATE_INSERT:
+ if (lineCount > 0 && lineNum + 1 < totalLines) {
+ int lastLine = MIN(lineNum + lineCount, totalLines - 1);
+ ranges = TkRangeListInsert(ranges, lineNum + 1, lastLine);
+ }
+ textPtr->dInfoPtr->lastLineNo += lineCount;
+ if (isMonospaced) {
+ TkBTreeUpdatePixelHeights(textPtr, linePtr, lineCount, epoch);
+ } else {
+ ranges = TkRangeListAdd(ranges, lineNum, lineNum);
+ ResetPixelInfo(TkBTreeLinePixelInfo(textPtr,
+ TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr)));
+ }
break;
}
- }
- if (doThisMuch == -1) {
+
+ assert(TkRangeListIsEmpty(ranges) || TkRangeListHigh(ranges) < totalLines);
+ } else {
/*
- * If we were requested to provide a full update, then also update the
- * scrollbar.
+ * This invalidates the height of all lines in the widget.
*/
- GetYView(textPtr->interp, textPtr, 1);
+ textPtr->dInfoPtr->lineMetricUpdateEpoch += 1;
+ if (action == TK_TEXT_INVALIDATE_DELETE) {
+ TkRangeListClear(ranges);
+ FreeDLines(textPtr, textPtr->dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
+ totalLines -= lineCount;
+ textPtr->dInfoPtr->lastLineNo -= lineCount;
+ } else if (action == TK_TEXT_INVALIDATE_INSERT) {
+ textPtr->dInfoPtr->lastLineNo += lineCount;
+ }
+ ranges = TkRangeListAdd(ranges, 0, totalLines - 1);
+ }
+
+ FreeDLines(textPtr, NULL, NULL, DLINE_CACHE); /* clear cache */
+ FreeDLines(textPtr, NULL, NULL, DLINE_METRIC); /* clear cache */
+ FreeDLines(textPtr, textPtr->dInfoPtr->savedDLinePtr, NULL, DLINE_FREE_TEMP);
+ textPtr->dInfoPtr->lineMetricUpdateRanges = ranges;
+ textPtr->dInfoPtr->currChunkPtr = NULL;
+
+ if (textPtr->syncTime == 0) {
+#if 0 /* TODO: is it required to update 'lastLineNo' at this place? */
+ textPtr->dInfoPtr->lastLineNo = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL);
+#endif
+ } else {
+ StartAsyncLineCalculation(textPtr);
+ }
+}
+
+void
+TkTextInvalidateLineMetrics(
+ TkSharedText *sharedTextPtr,/* Shared widget section for all peers, or NULL. */
+ TkText *textPtr, /* Widget record for text widget. */
+ TkTextLine *linePtr, /* Invalidation starts from this line. */
+ unsigned lineCount, /* And includes this many following lines. */
+ TkTextInvalidateAction action)
+ /* Indicates what type of invalidation occurred (insert,
+ * delete, or simple). */
+{
+ if (!sharedTextPtr) {
+ if (textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action);
+ }
+ } else if (sharedTextPtr->allowUpdateLineMetrics) {
+ textPtr = sharedTextPtr->peers;
+
+ while (textPtr) {
+ int numLines = lineCount;
+ TkTextLine *firstLinePtr = linePtr;
+
+ if (textPtr->startMarker != sharedTextPtr->startMarker) {
+ TkTextLine *startLinePtr = TkBTreeGetStartLine(textPtr);
+ unsigned lineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, firstLinePtr, NULL);
+ unsigned firstLineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, startLinePtr, NULL);
+
+ if (firstLineNo > lineNo) {
+ firstLinePtr = startLinePtr;
+ numLines -= firstLineNo - lineNo;
+ }
+ }
+ if (textPtr->endMarker != sharedTextPtr->endMarker) {
+ TkTextLine *lastLinePtr = TkBTreeGetLastLine(textPtr);
+ unsigned lineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, firstLinePtr, NULL);
+ unsigned endLineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, lastLinePtr, NULL);
+
+ if (endLineNo <= lineNo + numLines) {
+ numLines = endLineNo - lineNo - 1;
+ }
+ }
+
+ if (numLines >= 0) {
+ TextInvalidateLineMetrics(textPtr, firstLinePtr, numLines, action);
+ }
+
+ textPtr = textPtr->next;
+ }
}
- return lineNum;
}
/*
*----------------------------------------------------------------------
*
- * TkTextInvalidateLineMetrics, TextInvalidateLineMetrics --
+ * TkTextFindDisplayIndex -
*
- * Mark a number of text lines as having invalid line metric
- * calculations. Never call this with linePtr as the last (artificial)
- * line in the text. Depending on 'action' which indicates whether the
- * given lines are simply invalid or have been inserted or deleted, the
- * pre-existing asynchronous line update range may need to be adjusted.
- *
- * If linePtr is NULL then 'lineCount' and 'action' are ignored and all
- * lines are invalidated.
+ * This function is computing the index of display line start; the
+ * computation starts at given index, and is searching some display
+ * lines forward or backward, as specified with 'displayLineOffset'.
*
* Results:
- * None.
+ * Modifies indexPtr to point to the wanted display line start.
+ *
+ * If xOffset is non-NULL, it is set to the x-pixel offset of the given
+ * original index within the given display line.
*
* Side effects:
- * May schedule an asychronous callback.
+ * See 'LayoutDLine' and 'FreeDLines'.
*
*----------------------------------------------------------------------
*/
void
-TkTextInvalidateLineMetrics(
- TkSharedText *sharedTextPtr,/* Shared widget section for all peers, or
- * NULL. */
- TkText *textPtr, /* Widget record for text widget. */
- TkTextLine *linePtr, /* Invalidation starts from this line. */
- int lineCount, /* And includes this many following lines. */
- int action) /* Indicates what type of invalidation
- * occurred (insert, delete, or simple). */
+TkTextFindDisplayIndex(
+ TkText *textPtr,
+ TkTextIndex *indexPtr,
+ int displayLineOffset,
+ int *xOffset)
{
- if (sharedTextPtr == NULL) {
- TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action);
+ DisplayInfo info;
+ TkTextLine *linePtr;
+ TkTextLine *lastLinePtr;
+ unsigned byteOffset;
+ bool upToDate;
+ int myXOffset;
+
+ assert(textPtr);
+
+ if (!xOffset) {
+ xOffset = &myXOffset;
+ }
+
+ lastLinePtr = TkBTreeGetLastLine(textPtr);
+ linePtr = TkTextIndexGetLine(indexPtr);
+
+ if (displayLineOffset >= 0 && linePtr == lastLinePtr) {
+ *xOffset = 0;
+ return;
+ }
+ if (displayLineOffset <= 0 && TkTextIndexIsStartOfText(indexPtr)) {
+ *xOffset = 0;
+ return;
+ }
+
+ if (linePtr == lastLinePtr) {
+ displayLineOffset += 1;
+ *xOffset = 0;
+ xOffset = NULL;
+ TkTextIndexSetToLastChar2(indexPtr, linePtr->prevPtr);
+ }
+
+ if (displayLineOffset > 0) {
+ upToDate = TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges);
} else {
- textPtr = sharedTextPtr->peers;
- while (textPtr != NULL) {
- TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action);
- textPtr = textPtr->next;
+ upToDate = TestIfLinesUpToDate(indexPtr);
+ }
+ linePtr = ComputeDisplayLineInfo(textPtr, indexPtr, &info);
+
+ if (xOffset) {
+ if (IsStartOfNotMergedLine(indexPtr)) {
+ *xOffset = 0;
+ } else {
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ DLine *dlPtr = info.lastDLinePtr;
+ TkTextIndex index = *indexPtr;
+
+ TkTextIndexBackBytes(textPtr, &index, info.byteOffset, &index);
+
+ if (!dlPtr) {
+ dlPtr = FindCachedDLine(textPtr, indexPtr);
+
+ if (!dlPtr
+ && !(dInfoPtr->flags & DINFO_OUT_OF_DATE)
+ && TkTextIndexCompare(indexPtr, &textPtr->topIndex) >= 0) {
+ dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
+ }
+ if (!dlPtr) {
+ dlPtr = LayoutDLine(&index, info.displayLineNo);
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_CACHE);
+ }
+ }
+
+ *xOffset = DLineXOfIndex(textPtr, dlPtr, TkTextIndexCountBytes(&dlPtr->index, indexPtr));
}
}
-}
-
-static void
-TextInvalidateLineMetrics(
- TkText *textPtr, /* Widget record for text widget. */
- TkTextLine *linePtr, /* Invalidation starts from this line. */
- int lineCount, /* And includes this many following lines. */
- int action) /* Indicates what type of invalidation
- * occurred (insert, delete, or simple). */
-{
- int fromLine;
- TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- if (linePtr != NULL) {
- int counter = lineCount;
+ if (upToDate) {
+ const TkTextDispLineInfo *dispLineInfo;
- fromLine = TkBTreeLinesTo(textPtr, linePtr);
+ assert(!info.dLinePtr);
/*
- * Invalidate the height calculations of each line in the given range.
+ * The display line information is complete for the required range, so
+ * use it for finding the requested display line.
*/
- TkBTreeLinePixelEpoch(textPtr, linePtr) = 0;
- while (counter > 0 && linePtr != NULL) {
- linePtr = TkBTreeNextLine(textPtr, linePtr);
- if (linePtr != NULL) {
- TkBTreeLinePixelEpoch(textPtr, linePtr) = 0;
+ if (displayLineOffset == 0) {
+ byteOffset = info.entry->byteOffset;
+ } else {
+ if (displayLineOffset > 0) {
+ linePtr = TkBTreeNextDisplayLine(textPtr, linePtr, &info.displayLineNo,
+ displayLineOffset);
+ } else {
+ linePtr = TkBTreePrevDisplayLine(textPtr, linePtr, &info.displayLineNo,
+ -displayLineOffset);
}
- counter--;
+ dispLineInfo = TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo;
+ byteOffset = dispLineInfo ? dispLineInfo->entry[info.displayLineNo].byteOffset : 0;
}
+ } else {
+ unsigned removedLines;
/*
- * Now schedule an examination of each line in the union of the old
- * and new update ranges, including the (possibly empty) range in
- * between. If that between range is not-empty, then we are examining
- * more lines than is strictly necessary (but the examination of the
- * extra lines should be quick, since their pixelCalculationEpoch will
- * be up to date). However, to keep track of that would require more
- * complex record-keeping than what we have.
+ * We want to cache last produced display line, because it's likely that this
+ * line will be used afterwards.
*/
- if (dInfoPtr->lineUpdateTimer == NULL) {
- dInfoPtr->currentMetricUpdateLine = fromLine;
- if (action == TK_TEXT_INVALIDATE_DELETE) {
- lineCount = 0;
- }
- dInfoPtr->lastMetricUpdateLine = fromLine + lineCount + 1;
- } else {
- int toLine = fromLine + lineCount + 1;
+ removedLines = 0;
+ if (info.lastDLinePtr) {
+ DLine *prevPtr = info.lastDLinePtr->prevPtr;
+ FreeDLines(textPtr, info.lastDLinePtr, NULL, DLINE_CACHE);
+ if (info.dLinePtr == info.lastDLinePtr) { info.dLinePtr = NULL; }
+ info.lastDLinePtr = prevPtr;
+ info.numCachedLines -= 1;
+ removedLines = 1;
+ }
- if (action == TK_TEXT_INVALIDATE_DELETE) {
- if (toLine <= dInfoPtr->currentMetricUpdateLine) {
- dInfoPtr->currentMetricUpdateLine = fromLine;
- if (dInfoPtr->lastMetricUpdateLine != -1) {
- dInfoPtr->lastMetricUpdateLine -= lineCount;
- }
- } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
- dInfoPtr->currentMetricUpdateLine = fromLine;
- if (toLine <= dInfoPtr->lastMetricUpdateLine) {
- dInfoPtr->lastMetricUpdateLine -= lineCount;
- }
- } else {
- if (dInfoPtr->lastMetricUpdateLine != -1) {
- dInfoPtr->lastMetricUpdateLine = toLine;
- }
+ TkTextIndexBackBytes(textPtr, indexPtr, info.byteOffset, indexPtr);
+
+ if (displayLineOffset > 0) {
+ ComputeMissingMetric(textPtr, &info, THRESHOLD_LINE_OFFSET, displayLineOffset);
+ info.numDispLines -= info.displayLineNo;
+
+ while (true) {
+ const TkTextDispLineEntry *last;
+
+ if (info.numDispLines >= displayLineOffset) {
+ last = info.entry + displayLineOffset;
+ byteOffset = last->byteOffset;
+ break;
}
- } else if (action == TK_TEXT_INVALIDATE_INSERT) {
- if (toLine <= dInfoPtr->currentMetricUpdateLine) {
- dInfoPtr->currentMetricUpdateLine = fromLine;
- if (dInfoPtr->lastMetricUpdateLine != -1) {
- dInfoPtr->lastMetricUpdateLine += lineCount;
- }
- } else if (fromLine <= dInfoPtr->currentMetricUpdateLine) {
- dInfoPtr->currentMetricUpdateLine = fromLine;
- if (toLine <= dInfoPtr->lastMetricUpdateLine) {
- dInfoPtr->lastMetricUpdateLine += lineCount;
- }
- if (toLine > dInfoPtr->lastMetricUpdateLine) {
- dInfoPtr->lastMetricUpdateLine = toLine;
- }
- } else {
- if (dInfoPtr->lastMetricUpdateLine != -1) {
- dInfoPtr->lastMetricUpdateLine = toLine;
- }
+ last = info.entry + info.numDispLines;
+ byteOffset = last->byteOffset;
+ displayLineOffset -= info.numDispLines;
+ TkTextIndexForwBytes(textPtr, indexPtr, byteOffset, indexPtr);
+ linePtr = TkTextIndexGetLine(indexPtr);
+ if (linePtr == lastLinePtr) {
+ break;
}
- } else {
- if (fromLine < dInfoPtr->currentMetricUpdateLine) {
- dInfoPtr->currentMetricUpdateLine = fromLine;
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
+ ComputeDisplayLineInfo(textPtr, indexPtr, &info);
+ ComputeMissingMetric(textPtr, &info, THRESHOLD_LINE_OFFSET, displayLineOffset);
+ assert(info.displayLineNo == 0);
+ }
+ } else if (displayLineOffset < 0) {
+ info.numDispLines = info.displayLineNo + 1;
+
+ while (true) {
+ TkTextLine *prevLine;
+
+ if (-displayLineOffset < info.numDispLines) {
+ int skipBack;
+
+ byteOffset = (info.entry + displayLineOffset)->byteOffset;
+ skipBack = displayLineOffset;
+
+ /*
+ * We want to cache this display line, because it's likely that this
+ * line will be used afterwards. Take into account that probably the
+ * last cached line has been removed.
+ */
+
+ if ((skipBack -= removedLines) >= 0 && info.numCachedLines > skipBack) {
+ DLine *dlPtr = info.lastDLinePtr;
+ while (dlPtr && skipBack--) {
+ dlPtr = dlPtr->prevPtr;
+ }
+ if (dlPtr == info.dLinePtr) {
+ info.dLinePtr = dlPtr->nextPtr;
+ }
+ if (dlPtr == info.lastDLinePtr) {
+ info.lastDLinePtr = dlPtr->prevPtr;
+ }
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_CACHE);
+ }
+ break;
}
- if (dInfoPtr->lastMetricUpdateLine != -1
- && toLine > dInfoPtr->lastMetricUpdateLine) {
- dInfoPtr->lastMetricUpdateLine = toLine;
+ displayLineOffset += info.numDispLines;
+ if (!(prevLine = TkBTreePrevLine(textPtr, linePtr))) {
+ byteOffset = info.entry[0].byteOffset;
+ break;
}
+ TkTextIndexSetToLastChar2(indexPtr, linePtr = prevLine);
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
+ linePtr = ComputeDisplayLineInfo(textPtr, indexPtr, &info);
+ removedLines = 0;
}
- }
- } else {
- /*
- * This invalidates the height of all lines in the widget.
- */
-
- if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
- dInfoPtr->lineMetricUpdateEpoch++;
+ } else {
+ byteOffset = info.entry[0].byteOffset;
}
/*
- * This has the effect of forcing an entire new loop of update checks
- * on all lines in the widget.
+ * We want to cache last produced display line, because it's likely that this
+ * line will be used afterwards.
*/
- if (dInfoPtr->lineUpdateTimer == NULL) {
- dInfoPtr->currentMetricUpdateLine = -1;
+ if (info.lastDLinePtr) {
+ FreeDLines(textPtr, info.lastDLinePtr, NULL, DLINE_CACHE);
+ if (info.dLinePtr == info.lastDLinePtr) { info.dLinePtr = NULL; }
}
- dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine;
+
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
}
- /*
- * Now re-set the current update calculations.
- */
+ /* set to first byte, not to start of line */
+ DEBUG(indexPtr->discardConsistencyCheck = true);
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, 0);
+ DEBUG(indexPtr->discardConsistencyCheck = false);
+ TkTextIndexForwBytes(textPtr, indexPtr, byteOffset, indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextCountDisplayLines -
+ *
+ * This function is counting the number of visible display lines
+ * between given indices. This function will be used for computing
+ * "count -displaylines".
+ *
+ * Results:
+ * The number of visible display lines inside given range.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+unsigned
+TkTextCountDisplayLines(
+ TkText *textPtr, /* Widget record for text widget. */
+ const TkTextIndex *indexFrom, /* Start counting at this index. */
+ const TkTextIndex *indexTo) /* Stop counting before this index. */
+{
+ const TkTextPixelInfo *pixelInfo1;
+ const TkTextPixelInfo *pixelInfo2;
+ TkTextDispLineInfo *dispLineInfo;
+ TkTextDispLineEntry *entry;
+ TkTextDispLineEntry *lastEntry;
+ TkTextLine *linePtr1;
+ TkTextLine *linePtr2;
+ TkTextIndex index;
+ unsigned byteOffset;
+ int numLines;
+
+ assert(TkTextIndexCompare(indexFrom, indexTo) <= 0);
+ assert(textPtr->sharedTextPtr->allowUpdateLineMetrics);
+
+ TkTextUpdateLineMetrics(textPtr, TkTextIndexGetLineNumber(indexFrom, textPtr),
+ TkTextIndexGetLineNumber(indexTo, textPtr));
- if (dInfoPtr->lineUpdateTimer == NULL) {
- textPtr->refCount++;
- dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
- AsyncUpdateLineMetrics, textPtr);
- GenerateWidgetViewSyncEvent(textPtr, 0);
+ linePtr1 = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, TkTextIndexGetLine(indexFrom));
+ linePtr2 = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, TkTextIndexGetLine(indexTo));
+ pixelInfo1 = linePtr1->pixelInfo;
+ pixelInfo2 = linePtr2->pixelInfo;
+
+ if (!pixelInfo1->dispLineInfo) {
+ numLines = 0;
+ } else {
+ index = *indexFrom;
+ TkTextIndexSetToStartOfLine2(&index, linePtr1);
+ byteOffset = TkTextIndexCountBytes(&index, indexFrom);
+ dispLineInfo = pixelInfo1->dispLineInfo;
+ lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
+ entry = SearchDispLineEntry(dispLineInfo->entry, lastEntry, byteOffset);
+ numLines = -(entry - dispLineInfo->entry);
+ }
+
+ while (true) {
+ if (pixelInfo1->dispLineInfo) {
+ if (pixelInfo1 == pixelInfo2) {
+ index = *indexTo;
+ TkTextIndexSetToStartOfLine2(&index, linePtr2);
+ byteOffset = TkTextIndexCountBytes(&index, indexTo);
+ dispLineInfo = pixelInfo2->dispLineInfo;
+ lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
+ entry = SearchDispLineEntry(dispLineInfo->entry, lastEntry, byteOffset);
+ return numLines + (entry - dispLineInfo->entry);
+ }
+ numLines += pixelInfo1->dispLineInfo->numDispLines;
+ } else if (pixelInfo1 == pixelInfo2) {
+ return numLines;
+ } else {
+ numLines += 1;
+ }
+ linePtr1 = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr1);
+ pixelInfo1 = linePtr1->pixelInfo;
}
+
+ return 0; /* never reached */
}
/*
*----------------------------------------------------------------------
*
- * TkTextFindDisplayLineEnd --
+ * TkTextFindDisplayLineStartEnd --
*
* This function is invoked to find the index of the beginning or end of
* the particular display line on which the given index sits, whether
* that line is displayed or not.
*
- * If 'end' is zero, we look for the start, and if 'end' is one we look
- * for the end.
+ * If 'end' is 'false', we look for the start, and if 'end' is 'true'
+ * we look for the end.
*
* If the beginning of the current display line is elided, and we are
* looking for the start of the line, then the returned index will be the
@@ -3527,129 +7396,75 @@ TextInvalidateLineMetrics(
* Results:
* Modifies indexPtr to point to the given end.
*
- * If xOffset is non-NULL, it is set to the x-pixel offset of the given
- * original index within the given display line.
- *
* Side effects:
- * The combination of 'LayoutDLine' and 'FreeDLines' seems like a rather
- * time-consuming way of gathering the information we need, so this would
- * be a good place to look to speed up the calculations. In particular
- * these calls will map and unmap embedded windows respectively, which I
- * would hope isn't exactly necessary!
+ * See 'LayoutDLine' and 'FreeDLines'.
*
*----------------------------------------------------------------------
*/
-void
-TkTextFindDisplayLineEnd(
+static void
+FindDisplayLineStartEnd(
TkText *textPtr, /* Widget record for text widget. */
- TkTextIndex *indexPtr, /* Index we will adjust to the display line
- * start or end. */
- int end, /* 0 = start, 1 = end. */
- int *xOffset) /* NULL, or used to store the x-pixel offset
- * of the original index within its display
- * line. */
+ TkTextIndex *indexPtr, /* Index we will adjust to the display line start or end. */
+ bool end, /* 'false' = start, 'true' = end. */
+ int cacheType) /* Argument for FreeDLines, either DLINE_CACHE or DLINE_METRIC. */
{
- TkTextIndex index;
+ DisplayInfo info;
+ int byteCount;
- if (!end && IsStartOfNotMergedLine(textPtr, indexPtr)) {
+ if (TkTextIndexGetLine(indexPtr) == TkBTreeGetLastLine(textPtr)
+ || (!end && IsStartOfNotMergedLine(indexPtr))) {
/*
- * Nothing to do.
+ * Nothing to do, because we are at start/end of a display line.
*/
- if (xOffset != NULL) {
- *xOffset = 0;
- }
return;
}
- index = *indexPtr;
- index.byteIndex = 0;
- index.textPtr = NULL;
-
- while (1) {
- TkTextIndex endOfLastLine;
+ ComputeDisplayLineInfo(textPtr, indexPtr, &info);
+ byteCount = end ? -(info.nextByteOffset - 1) : info.byteOffset;
+ TkTextIndexBackBytes(textPtr, indexPtr, byteCount, indexPtr);
- if (TkTextIndexBackBytes(textPtr, &index, 1, &endOfLastLine)) {
- /*
- * Reached beginning of text.
- */
-
- break;
- }
-
- if (!TkTextIsElided(textPtr, &endOfLastLine, NULL)) {
- /*
- * The eol is not elided, so 'index' points to the start of a
- * display line (as well as logical line).
- */
-
- break;
- }
+ if (end) {
+ int offset;
+ int skipBack = 0;
+ TkTextSegment *segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
+ char const *p = segPtr->body.chars + offset;
/*
- * indexPtr's logical line is actually merged with the previous
- * logical line whose eol is elided. Continue searching back to get a
- * real line start.
+ * We don't want an offset inside a multi-byte sequence, so find the start
+ * of the current character.
*/
- index = endOfLastLine;
- index.byteIndex = 0;
+ while (p > segPtr->body.chars && (*p & 0xc0) == 0x80) {
+ p -= 1;
+ skipBack += 1;
+ }
+ TkTextIndexBackBytes(textPtr, indexPtr, skipBack, indexPtr);
}
- while (1) {
- DLine *dlPtr;
- int byteCount;
- TkTextIndex nextLineStart;
-
- dlPtr = LayoutDLine(textPtr, &index);
- byteCount = dlPtr->byteCount;
-
- TkTextIndexForwBytes(textPtr, &index, byteCount, &nextLineStart);
-
- /*
- * 'byteCount' goes up to the beginning of the next display line, so
- * equality here says we need one more line. We try to perform a quick
- * comparison which is valid for the case where the logical line is
- * the same, but otherwise fall back on a full TkTextIndexCmp.
- */
-
- if (((index.linePtr == indexPtr->linePtr)
- && (index.byteIndex + byteCount > indexPtr->byteIndex))
- || (dlPtr->logicalLinesMerged > 0
- && TkTextIndexCmp(&nextLineStart, indexPtr) > 0)) {
- /*
- * It's on this display line.
- */
-
- if (xOffset != NULL) {
- /*
- * This call takes a byte index relative to the start of the
- * current _display_ line, not logical line. We are about to
- * overwrite indexPtr->byteIndex, so we must do this now.
- */
-
- *xOffset = DlineXOfIndex(textPtr, dlPtr,
- TkTextIndexCountBytes(textPtr, &dlPtr->index,
- indexPtr));
- }
- if (end) {
- /*
- * The index we want is one less than the number of bytes in
- * the display line.
- */
+ /*
+ * We want to cache last produced display line, because it's likely that this
+ * line will be used afterwards.
+ */
- TkTextIndexBackBytes(textPtr, &nextLineStart, 1, indexPtr);
- } else {
- *indexPtr = index;
- }
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
- return;
+ if (info.lastDLinePtr) {
+ FreeDLines(textPtr, info.lastDLinePtr, NULL, cacheType);
+ if (info.dLinePtr == info.lastDLinePtr) {
+ info.dLinePtr = NULL; /* don't release it twice */
}
-
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
- index = nextLineStart;
}
+
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
+}
+
+void
+TkTextFindDisplayLineStartEnd(
+ TkText *textPtr, /* Widget record for text widget. */
+ TkTextIndex *indexPtr, /* Index we will adjust to the display line start or end. */
+ bool end) /* 'false' = start, 'true' = end. */
+{
+ return FindDisplayLineStartEnd(textPtr, indexPtr, end, DLINE_CACHE);
}
/*
@@ -3674,7 +7489,7 @@ TkTextFindDisplayLineEnd(
* of byte indices on the given display line (which can be used to update
* indexPtr in a loop).
*
- * If 'mergedLinePtr' is non-NULL, then returns in that pointer the
+ * If 'numLogicalLinesMergedPtr' is non-NULL, then returns in that pointer the
* number of extra logical lines merged into the given display line.
*
* Side effects:
@@ -3687,171 +7502,206 @@ TkTextFindDisplayLineEnd(
*----------------------------------------------------------------------
*/
+#if !NDEBUG
+static bool
+IsAtStartOfDisplayLine(
+ const TkTextIndex *indexPtr)
+{
+ TkTextIndex index2 = *indexPtr;
+
+ assert(indexPtr->textPtr);
+
+ FindDisplayLineStartEnd(indexPtr->textPtr, &index2, DISP_LINE_START, DLINE_METRIC);
+ return TkTextIndexCompare(&index2, indexPtr) == 0;
+}
+#endif
+
static int
CalculateDisplayLineHeight(
TkText *textPtr, /* Widget record for text widget. */
- const TkTextIndex *indexPtr,/* The index at the beginning of the display
- * line of interest. */
- int *byteCountPtr, /* NULL or used to return the number of byte
- * indices on the given display line. */
- int *mergedLinePtr) /* NULL or used to return if the given display
- * line merges with a following logical line
- * (because the eol is elided). */
+ const TkTextIndex *indexPtr,/* The index at the beginning of the display line of interest. */
+ unsigned *byteCountRef) /* NULL or used to return the number of byte indices on the given
+ * display line. */
{
- DLine *dlPtr;
- int pixelHeight;
-
- if (tkTextDebug) {
- int oldtkTextDebug = tkTextDebug;
- /*
- * Check that the indexPtr we are given really is at the start of a
- * display line. The gymnastics with tkTextDebug is to prevent
- * failure of a test suite test, that checks that lines are rendered
- * exactly once. TkTextFindDisplayLineEnd is used here for checking
- * indexPtr but it calls LayoutDLine/FreeDLine which makes the
- * counting wrong. The debug mode shall therefore be switched off
- * when calling TkTextFindDisplayLineEnd.
- */
-
- TkTextIndex indexPtr2 = *indexPtr;
- tkTextDebug = 0;
- TkTextFindDisplayLineEnd(textPtr, &indexPtr2, 0, NULL);
- tkTextDebug = oldtkTextDebug;
- if (TkTextIndexCmp(&indexPtr2,indexPtr) != 0) {
- Tcl_Panic("CalculateDisplayLineHeight called with bad indexPtr");
- }
- }
+ DisplayInfo info;
+
+ assert(IsAtStartOfDisplayLine(indexPtr));
/*
- * Special case for artificial last line. May be better to move this
- * inside LayoutDLine.
+ * Special case for artificial last line.
*/
- if (indexPtr->byteIndex == 0
- && TkBTreeNextLine(textPtr, indexPtr->linePtr) == NULL) {
- if (byteCountPtr != NULL) {
- *byteCountPtr = 0;
- }
- if (mergedLinePtr != NULL) {
- *mergedLinePtr = 0;
- }
+ if (TkTextIndexGetLine(indexPtr) == TkBTreeGetLastLine(textPtr)) {
+ if (byteCountRef) { *byteCountRef = 0; }
return 0;
}
+ ComputeDisplayLineInfo(textPtr, indexPtr, &info);
+
/*
- * Layout, find the information we need and then free the display-line we
- * laid-out. We must use 'FreeDLines' because it will actually call the
- * relevant code to unmap any embedded windows which were mapped in the
- * LayoutDLine call!
+ * Last computed line has to be cached temporarily.
*/
- dlPtr = LayoutDLine(textPtr, indexPtr);
- pixelHeight = dlPtr->height;
- if (byteCountPtr != NULL) {
- *byteCountPtr = dlPtr->byteCount;
- }
- if (mergedLinePtr != NULL) {
- *mergedLinePtr = dlPtr->logicalLinesMerged;
+ if (info.lastDLinePtr) {
+ FreeDLines(textPtr, info.lastDLinePtr, NULL, DLINE_METRIC);
+ if (info.dLinePtr == info.lastDLinePtr) {
+ info.dLinePtr = NULL; /* don't release it twice */
+ }
}
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
- return pixelHeight;
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
+ if (byteCountRef) { *byteCountRef = info.nextByteOffset + info.byteOffset; }
+ assert(info.entry->height != 0xffffffff);
+ return info.entry->height;
}
/*
*----------------------------------------------------------------------
*
- * TkTextIndexYPixels --
+ * TkTextGetViewOffset --
*
- * This function is invoked to calculate the number of vertical pixels
- * between the first index of the text widget and the given index. The
- * range from first logical line to given logical line is determined
- * using the cached values, and the range inside the given logical line
- * is calculated on the fly.
+ * This function returns the x and y offset of the current view.
*
* Results:
- * The pixel distance between first pixel in the widget and the
- * top of the index's current display line (could be zero).
+ * The pixel offset of the current view.
*
* Side effects:
- * Just those of 'CalculateDisplayLineHeight'.
+ * None.
*
*----------------------------------------------------------------------
*/
-int
-TkTextIndexYPixels(
+void
+TkTextGetViewOffset(
TkText *textPtr, /* Widget record for text widget. */
- const TkTextIndex *indexPtr)/* The index of which we want the pixel
- * distance from top of logical line to top of
- * index. */
+ int *x, /* X offset */
+ int *y) /* Y offset */
{
- int pixelHeight;
- TkTextIndex index;
- int alreadyStartOfLine = 1;
-
- /*
- * Find the index denoting the closest position being at the same time
- * the start of a logical line above indexPtr and the start of a display
- * line.
- */
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- index = *indexPtr;
- while (1) {
- TkTextFindDisplayLineEnd(textPtr, &index, 0, NULL);
- if (index.byteIndex == 0) {
- break;
- }
- TkTextIndexBackBytes(textPtr, &index, 1, &index);
- alreadyStartOfLine = 0;
+ if (dInfoPtr && dInfoPtr->dLinePtr) {
+ *x = dInfoPtr->curXPixelOffset;
+ *y = dInfoPtr->curYPixelOffset;
+ } else {
+ *x = 0;
+ *y = 0;
}
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetPixelsTo --
+ *
+ * This function computes the pixels between the first display line
+ * of the logical line (belonging to given position), and the display
+ * line at the specified position.
+ *
+ * If the line metric computation of the specified logical line is
+ * not yet finished, and 'info' is not NULL, then ComputeMissingMetric
+ * will be used to compute the missing metric compuation.
+ *
+ * Results:
+ * The pixels from first display line (belonging to given position) to
+ * specified display line.
+ *
+ * Side effects:
+ * Just the ones of ComputeMissingMetric.
+ *
+ *----------------------------------------------------------------------
+ */
- pixelHeight = TkBTreePixelsTo(textPtr, index.linePtr);
+static unsigned
+GetPixelsTo(
+ TkText *textPtr,
+ const TkTextIndex *indexPtr,
+ bool inclusiveLastLine,
+ DisplayInfo *info) /* can be NULL */
+{
+ TkTextLine *logicalLinePtr;
+ const TkTextPixelInfo *pixelInfo;
+ TkTextDispLineInfo *dispLineInfo;
+ const TkTextDispLineEntry *lastEntry;
+ const TkTextDispLineEntry *entry;
+ TkTextIndex index;
+ unsigned byteOffset;
- /*
- * Shortcut to avoid layout of a superfluous display line. We know there
- * is nothing more to add up to the height if the index we were given was
- * already on the first display line of a logical line.
- */
+ logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr,
+ TkTextIndexGetLine(indexPtr));
+ if (logicalLinePtr == TkBTreeGetLastLine(textPtr)) {
+ return 0;
+ }
+ pixelInfo = TkBTreeLinePixelInfo(textPtr, logicalLinePtr);
- if (alreadyStartOfLine) {
- return pixelHeight;
+ if (!info && (pixelInfo->epoch & EPOCH_MASK) != textPtr->dInfoPtr->lineMetricUpdateEpoch) {
+ return 0;
}
- /*
- * Iterate through display lines, starting at the logical line belonging
- * to index, adding up the pixel height of each such display line as we
- * go along, until we go past 'indexPtr'.
- */
+ if (!(dispLineInfo = pixelInfo->dispLineInfo)) {
+ return inclusiveLastLine ? pixelInfo->height : 0;
+ }
- while (1) {
- int bytes, height, compare;
+ index = *indexPtr;
+ TkTextIndexSetToStartOfLine2(&index, logicalLinePtr);
+ byteOffset = TkTextIndexCountBytes(&index, indexPtr);
+ lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
+ entry = SearchDispLineEntry(dispLineInfo->entry, lastEntry, byteOffset);
+ if (entry == lastEntry) {
/*
- * Currently this call doesn't have many side-effects. However, if in
- * the future we change the code so there are side-effects (such as
- * adjusting linePtr->pixelHeight), then the code might not quite work
- * as intended, specifically the 'linePtr->pixelHeight == pixelHeight'
- * test below this while loop.
+ * This happens if the line metric calculation for this logical line is not yet complete.
*/
- height = CalculateDisplayLineHeight(textPtr, &index, &bytes, NULL);
-
- TkTextIndexForwBytes(textPtr, &index, bytes, &index);
+ if (info) {
+ unsigned numDispLinesSoFar = dispLineInfo->numDispLines;
- compare = TkTextIndexCmp(&index,indexPtr);
- if (compare > 0) {
- return pixelHeight;
+ ComputeMissingMetric(textPtr, info, THRESHOLD_BYTE_OFFSET, byteOffset);
+ lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
+ entry = SearchDispLineEntry(dispLineInfo->entry + numDispLinesSoFar, lastEntry, byteOffset);
+ if (entry == lastEntry) {
+ entry -= 1;
+ }
+ } else {
+ assert(dispLineInfo->numDispLines > 0);
+ entry -= 1;
}
+ } else if (!inclusiveLastLine && entry-- == dispLineInfo->entry) {
+ return 0;
+ }
- if (height > 0) {
- pixelHeight += height;
- }
+ return entry->pixels;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexYPixels --
+ *
+ * This function is invoked to calculate the number of vertical pixels
+ * between the first index of the text widget and the given index. The
+ * range from first logical line to given logical line is determined
+ * using the cached values, and the range inside the given logical line
+ * is calculated on the fly.
+ *
+ * Results:
+ * The pixel distance between first pixel in the widget and the
+ * top of the index's current display line (could be zero).
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- if (compare == 0) {
- return pixelHeight;
- }
- }
+int
+TkTextIndexYPixels(
+ TkText *textPtr, /* Widget record for text widget. */
+ const TkTextIndex *indexPtr)/* The index of which we want the pixel distance from top of
+ * text widget to top of index. */
+{
+ /* Note that TkBTreePixelsTo is computing up to start of the logical line. */
+ return TkBTreePixelsTo(textPtr, TkTextIndexGetLine(indexPtr)) +
+ GetPixelsTo(textPtr, indexPtr, false, NULL);
}
/*
@@ -3873,8 +7723,8 @@ TkTextIndexYPixels(
*
* Side effects:
* Line heights may be recalculated, and a timer to update the scrollbar
- * may be installed. Also see the called function
- * CalculateDisplayLineHeight for its side effects.
+ * may be installed. Also see the called function CalculateDisplayLineHeight
+ * for its side effects.
*
*----------------------------------------------------------------------
*/
@@ -3882,48 +7732,68 @@ TkTextIndexYPixels(
int
TkTextUpdateOneLine(
TkText *textPtr, /* Widget record for text widget. */
- TkTextLine *linePtr, /* The line of which to calculate the
- * height. */
- int pixelHeight, /* If indexPtr is non-NULL, then this is the
- * number of pixels in the logical line
- * linePtr, up to the index which has been
- * given. */
- TkTextIndex *indexPtr, /* Either NULL or an index at the start of a
- * display line belonging to linePtr, at which
- * we wish to start (e.g. up to which we have
- * already calculated). On return this will be
- * set to the first index on the next line. */
- int partialCalc) /* Set to 1 if we are allowed to do partial
- * height calculations of long-lines. In this
- * case we'll only return what we know so
- * far. */
+ TkTextLine *linePtr, /* The line of which to calculate the height. */
+ TkTextIndex *indexPtr, /* Either NULL or an index at the start of a display line belonging
+ * to linePtr, at which we wish to start (e.g. up to which we have
+ * already calculated). On return this will be set to the first index
+ * on the next line. */
+ unsigned maxDispLines) /* Don't compute more than this number of display lines. */
{
TkTextIndex index;
- int displayLines;
- int mergedLines;
-
- if (indexPtr == NULL) {
- index.tree = textPtr->sharedTextPtr->tree;
- index.linePtr = linePtr;
- index.byteIndex = 0;
- index.textPtr = NULL;
+ TkTextLine *logicalLinePtr;
+ TkTextPixelInfo *pixelInfo;
+ unsigned displayLines;
+ unsigned updateCounter;
+ unsigned pixelHeight;
+
+ assert(linePtr != TkBTreeGetLastLine(textPtr));
+
+ if (!indexPtr) {
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetToStartOfLine2(&index, linePtr);
indexPtr = &index;
- pixelHeight = 0;
}
- /*
- * CalculateDisplayLineHeight _must_ be called (below) with an index at
- * the beginning of a display line. Force this to happen. This is needed
- * when TkTextUpdateOneLine is called with a line that is merged with its
- * previous line: the number of merged logical lines in a display line is
- * calculated correctly only when CalculateDisplayLineHeight receives
- * an index at the beginning of a display line. In turn this causes the
- * merged lines to receive their correct zero pixel height in
- * TkBTreeAdjustPixelHeight.
- */
+ linePtr = TkTextIndexGetLine(indexPtr);
+ logicalLinePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ pixelInfo = TkBTreeLinePixelInfo(textPtr, logicalLinePtr);
+
+ if (pixelInfo->epoch == (textPtr->dInfoPtr->lineMetricUpdateEpoch | PARTIAL_COMPUTED_BIT)) {
+ const TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
+ unsigned bytes;
+
+ /*
+ * We are inside a partial computation. Continue with next display line.
+ */
+
+ assert(dispLineInfo);
+ assert(dispLineInfo->numDispLines > 0);
+ bytes = dispLineInfo->entry[dispLineInfo->numDispLines].byteOffset;
+ bytes -= dispLineInfo->entry[0].byteOffset;
+ TkTextIndexSetToStartOfLine2(indexPtr, logicalLinePtr);
+ TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr);
+ linePtr = TkTextIndexGetLine(indexPtr);
+ assert(!linePtr->logicalLine || !TkTextIndexIsStartOfLine(indexPtr));
+ } else if (!linePtr->logicalLine || !TkTextIndexIsStartOfLine(indexPtr)) {
+ /*
+ * CalculateDisplayLineHeight must be called with an index at the beginning
+ * of a display line. Force this to happen. This is needed when
+ * TkTextUpdateOneLine is called with a line that is merged with its
+ * previous line: the number of merged logical lines in a display line is
+ * calculated correctly only when CalculateDisplayLineHeight receives
+ * an index at the beginning of a display line. In turn this causes the
+ * merged lines to receive their correct zero pixel height in
+ * TkBTreeAdjustPixelHeight.
+ */
- TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
- linePtr = indexPtr->linePtr;
+ FindDisplayLineStartEnd(textPtr, indexPtr, DISP_LINE_START, DLINE_METRIC);
+ linePtr = TkTextIndexGetLine(indexPtr);
+ }
+
+ assert(linePtr->nextPtr);
+ updateCounter = textPtr->dInfoPtr->lineMetricUpdateCounter;
+ pixelHeight = 0;
+ displayLines = 0;
/*
* Iterate through all display-lines corresponding to the single logical
@@ -3934,149 +7804,78 @@ TkTextUpdateOneLine(
* merged into this line.
*/
- displayLines = 0;
- mergedLines = 0;
-
- while (1) {
- int bytes, height, logicalLines;
+ while (true) {
+ unsigned bytes, height;
+ bool atEnd;
/*
* Currently this call doesn't have many side-effects. However, if in
* the future we change the code so there are side-effects (such as
* adjusting linePtr->pixelHeight), then the code might not quite work
- * as intended, specifically the 'linePtr->pixelHeight == pixelHeight'
- * test below this while loop.
+ * as intended.
*/
- height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes,
- &logicalLines);
+ height = CalculateDisplayLineHeight(textPtr, indexPtr, &bytes);
+ atEnd = TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr) == 1
+ || TkTextIndexIsEndOfText(indexPtr);
+
+ assert(bytes > 0);
if (height > 0) {
pixelHeight += height;
- displayLines++;
+ displayLines += 1;
}
- mergedLines += logicalLines;
-
- if (TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr)) {
- break;
+ if (atEnd) {
+ break; /* we are at the end */
}
- if (mergedLines == 0) {
- if (indexPtr->linePtr != linePtr) {
- /*
- * If we reached the end of the logical line, then either way
- * we don't have a partial calculation.
- */
-
- partialCalc = 0;
- break;
- }
- } else {
- if (IsStartOfNotMergedLine(textPtr, indexPtr)) {
- /*
- * We've ended a logical line.
- */
-
- partialCalc = 0;
- break;
- }
+ if (linePtr != TkTextIndexGetLine(indexPtr)) {
+ if (TkTextIndexGetLine(indexPtr)->logicalLine) {
+ break; /* we've reached the end of the logical line */
+ }
+ linePtr = TkTextIndexGetLine(indexPtr);
+ } else {
+ /*
+ * We must still be on the same wrapped line, on a new logical
+ * line merged with the logical line 'linePtr'.
+ */
+ }
- /*
- * We must still be on the same wrapped line, on a new logical
- * line merged with the logical line 'linePtr'.
- */
- }
- if (partialCalc && displayLines > 50 && mergedLines == 0) {
+ if (displayLines == maxDispLines) {
/*
- * Only calculate 50 display lines at a time, to avoid huge
- * delays. In any case it is very rare that a single line wraps 50
- * times!
- *
- * If we have any merged lines, we must complete the full logical
- * line layout here and now, because the partial-calculation code
- * isn't designed to handle merged logical lines. Hence the
- * 'mergedLines == 0' check.
+ * We are calculating a limited number of display lines at a time, to avoid huge delays.
*/
+ /* check that LayoutUpdateLineHeightInformation has set this bit */
+ assert(pixelInfo->epoch & PARTIAL_COMPUTED_BIT);
break;
}
}
- if (!partialCalc) {
- int changed = 0;
-
+ if (updateCounter != textPtr->dInfoPtr->lineMetricUpdateCounter) {
/*
- * Cancel any partial line height calculation state.
+ * Otherwise nothing relevant has changed.
*/
- textPtr->dInfoPtr->metricEpoch = -1;
-
- /*
- * Mark the logical line as being up to date (caution: it isn't yet up
- * to date, that will happen in TkBTreeAdjustPixelHeight just below).
- */
-
- TkBTreeLinePixelEpoch(textPtr, linePtr)
- = textPtr->dInfoPtr->lineMetricUpdateEpoch;
- if (TkBTreeLinePixelCount(textPtr, linePtr) != pixelHeight) {
- changed = 1;
- }
-
- if (mergedLines > 0) {
- int i = mergedLines;
- TkTextLine *mergedLinePtr;
-
- /*
- * Loop over all merged logical lines, marking them up to date
- * (again, the pixel count setting will actually happen in
- * TkBTreeAdjustPixelHeight).
- */
+ if (tkTextDebug) {
+ char buffer[2*TCL_INTEGER_SPACE + 1];
- mergedLinePtr = linePtr;
- while (i-- > 0) {
- mergedLinePtr = TkBTreeNextLine(textPtr, mergedLinePtr);
- TkBTreeLinePixelEpoch(textPtr, mergedLinePtr)
- = textPtr->dInfoPtr->lineMetricUpdateEpoch;
- if (TkBTreeLinePixelCount(textPtr, mergedLinePtr) != 0) {
- changed = 1;
- }
+ if (!TkBTreeNextLine(textPtr, linePtr)) {
+ Tcl_Panic("Must never ever update line height of last artificial line");
}
- }
- if (!changed) {
- /*
- * If there's nothing to change, then we can already return.
- */
-
- return displayLines;
+ pixelHeight = TkBTreeNumPixels(textPtr);
+ snprintf(buffer, sizeof(buffer), "%u %u",
+ TkBTreeLinesTo(indexPtr->tree, textPtr, linePtr, NULL), pixelHeight);
+ LOG("tk_textNumPixels", buffer);
}
- }
-
- /*
- * We set the line's height, but the return value is now the height of the
- * entire widget, which may be used just below for reporting/debugging
- * purposes.
- */
-
- pixelHeight = TkBTreeAdjustPixelHeight(textPtr, linePtr, pixelHeight,
- mergedLines);
-
- if (tkTextDebug) {
- char buffer[2 * TCL_INTEGER_SPACE + 1];
- if (TkBTreeNextLine(textPtr, linePtr) == NULL) {
- Tcl_Panic("Mustn't ever update line height of last artificial line");
+ if (!textPtr->dInfoPtr->scrollbarTimer) {
+ InvokeAsyncUpdateYScrollbar(textPtr);
}
-
- sprintf(buffer, "%d %d", TkBTreeLinesTo(textPtr,linePtr), pixelHeight);
- LOG("tk_textNumPixels", buffer);
- }
- if (textPtr->dInfoPtr->scrollbarTimer == NULL) {
- textPtr->refCount++;
- textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200,
- AsyncUpdateYScrollbar, textPtr);
}
+
return displayLines;
}
@@ -4101,72 +7900,63 @@ static void
DisplayText(
ClientData clientData) /* Information about widget. */
{
- register TkText *textPtr = clientData;
+ TkText *textPtr = clientData;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- register DLine *dlPtr;
- DLine *prevPtr;
+ DLine *dlPtr;
Pixmap pixmap;
int maxHeight, borders;
- int bottomY = 0; /* Initialization needed only to stop compiler
- * warnings. */
+ int bottomY = 0; /* Initialization needed only to stop compiler warnings. */
Tcl_Interp *interp;
#ifdef MAC_OSX_TK
/*
- * If drawing is disabled, all we need to do is
- * clear the REDRAW_PENDING flag.
+ * If drawing is disabled, all we need to do is clear the REDRAW_PENDING flag.
*/
TkWindow *winPtr = (TkWindow *)(textPtr->tkwin);
MacDrawable *macWin = winPtr->privatePtr;
- if (macWin && (macWin->flags & TK_DO_NOT_DRAW)){
+ if (macWin && (macWin->flags & TK_DO_NOT_DRAW)) {
dInfoPtr->flags &= ~REDRAW_PENDING;
+ if (dInfoPtr->flags & ASYNC_PENDING) {
+ assert(dInfoPtr->flags & ASYNC_UPDATE);
+ dInfoPtr->flags &= ~ASYNC_PENDING;
+ /* continue with asynchronous pixel-height calculation */
+ InvokeAsyncUpdateLineMetrics(textPtr);
+ }
return;
}
-#endif
-
- if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
- /*
- * The widget has been deleted. Don't do anything.
- */
+#endif /* MAC_OSX_TK */
- return;
+ if (textPtr->flags & DESTROYED) {
+ return; /* the widget has been deleted */
}
interp = textPtr->interp;
Tcl_Preserve(interp);
- if (tkTextDebug) {
- Tcl_SetVar2(interp, "tk_textRelayout", NULL, "", TCL_GLOBAL_ONLY);
- }
+ TK_TEXT_DEBUG(Tcl_SetVar2(interp, "tk_textRelayout", NULL, "", TCL_GLOBAL_ONLY));
- if (!Tk_IsMapped(textPtr->tkwin) || (dInfoPtr->maxX <= dInfoPtr->x)
- || (dInfoPtr->maxY <= dInfoPtr->y)) {
+ if (!Tk_IsMapped(textPtr->tkwin) || dInfoPtr->maxX <= dInfoPtr->x || dInfoPtr->maxY <= dInfoPtr->y) {
UpdateDisplayInfo(textPtr);
dInfoPtr->flags &= ~REDRAW_PENDING;
goto doScrollbars;
}
- numRedisplays++;
- if (tkTextDebug) {
- Tcl_SetVar2(interp, "tk_textRedraw", NULL, "", TCL_GLOBAL_ONLY);
- }
+ DEBUG(stats.numRedisplays += 1);
+ TK_TEXT_DEBUG(Tcl_SetVar2(interp, "tk_textRedraw", NULL, "", TCL_GLOBAL_ONLY));
/*
* Choose a new current item if that is needed (this could cause event
* handlers to be invoked, hence the preserve/release calls and the loop,
* since the handlers could conceivably necessitate yet another current
- * item calculation). The tkwin check is because the whole window could go
- * away in the Tcl_Release call.
+ * item calculation). The textPtr check is because the whole window could go
+ * away in the meanwhile.
*/
- while (dInfoPtr->flags & REPICK_NEEDED) {
- textPtr->refCount++;
+ if (dInfoPtr->flags & REPICK_NEEDED) {
+ textPtr->refCount += 1;
dInfoPtr->flags &= ~REPICK_NEEDED;
+ dInfoPtr->currChunkPtr = NULL;
TkTextPickCurrent(textPtr, &textPtr->pickEvent);
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
- goto end;
- }
- if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
+ if (TkTextDecrRefCountAndTestIfDestroyed(textPtr)) {
goto end;
}
}
@@ -4176,7 +7966,7 @@ DisplayText(
*/
UpdateDisplayInfo(textPtr);
- dInfoPtr->dLinesInvalidated = 0;
+ dInfoPtr->dLinesInvalidated = false;
/*
* See if it's possible to bring some parts of the screen up-to-date by
@@ -4186,8 +7976,8 @@ DisplayText(
* some scrolling purposes.
*/
- for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
- register DLine *dlPtr2;
+ for (dlPtr = dInfoPtr->dLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
+ DLine *dlPtr2;
int offset, height, y, oldY;
TkRegion damageRgn;
@@ -4196,10 +7986,8 @@ DisplayText(
*
* 1. If the line is already marked as invalid
* 2. If the line hasn't moved
- * 3. If the line overlaps the bottom of the window and we are
- * scrolling up.
- * 4. If the line overlaps the top of the window and we are scrolling
- * down.
+ * 3. If the line overlaps the bottom of the window and we are scrolling up.
+ * 4. If the line overlaps the top of the window and we are scrolling down.
*
* If any of these tests are true, then we can't scroll this line's
* part of the display.
@@ -4211,10 +7999,9 @@ DisplayText(
*/
if ((dlPtr->flags & OLD_Y_INVALID)
- || (dlPtr->y == dlPtr->oldY)
- || (((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)
- && (dlPtr->y < dlPtr->oldY))
- || ((dlPtr->oldY < dInfoPtr->y) && (dlPtr->y > dlPtr->oldY))) {
+ || dlPtr->y == dlPtr->oldY
+ || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY && dlPtr->y < dlPtr->oldY)
+ || (dlPtr->oldY < dInfoPtr->y && dlPtr->y > dlPtr->oldY)) {
continue;
}
@@ -4227,11 +8014,10 @@ DisplayText(
offset = dlPtr->y - dlPtr->oldY;
height = dlPtr->height;
y = dlPtr->y;
- for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;
- dlPtr2 = dlPtr2->nextPtr) {
+ for (dlPtr2 = dlPtr->nextPtr; dlPtr2; dlPtr2 = dlPtr2->nextPtr) {
if ((dlPtr2->flags & OLD_Y_INVALID)
- || ((dlPtr2->oldY + offset) != dlPtr2->y)
- || ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {
+ || dlPtr2->oldY + offset != dlPtr2->y
+ || dlPtr2->oldY + dlPtr2->height > dInfoPtr->maxY) {
break;
}
height += dlPtr2->height;
@@ -4242,7 +8028,7 @@ DisplayText(
* overwriting the border area.
*/
- if ((y + height) > dInfoPtr->maxY) {
+ if (y + height > dInfoPtr->maxY) {
height = dInfoPtr->maxY - y;
}
oldY = dlPtr->oldY;
@@ -4258,12 +8044,19 @@ DisplayText(
y = dInfoPtr->y;
}
+#if 0 /* TODO: this can happen in certain situations, but shouldn't happen */
+ assert(height > 0); /* otherwise dInfoPtr->topPixelOffset is wrong */
+#else
+ if (height <= 0) {
+ fprintf(stderr, "DisplayText: height <= 0 is unexpected\n");
+ }
+#endif
+
/*
- * Update the lines we are going to scroll to show that they have been
- * copied.
+ * Update the lines we are going to scroll to show that they have been copied.
*/
- while (1) {
+ while (true) {
/*
* The DLine already has OLD_Y_INVALID cleared.
*/
@@ -4281,10 +8074,10 @@ DisplayText(
* for redisplay.
*/
- for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
- if ((!(dlPtr2->flags & OLD_Y_INVALID))
- && ((dlPtr2->oldY + dlPtr2->height) > y)
- && (dlPtr2->oldY < (y + height))) {
+ for ( ; dlPtr2; dlPtr2 = dlPtr2->nextPtr) {
+ if (!(dlPtr2->flags & OLD_Y_INVALID)
+ && dlPtr2->oldY + dlPtr2->height > y
+ && dlPtr2->oldY < y + height) {
dlPtr2->flags |= OLD_Y_INVALID;
}
}
@@ -4296,24 +8089,28 @@ DisplayText(
damageRgn = TkCreateRegion();
if (TkScrollWindow(textPtr->tkwin, dInfoPtr->scrollGC, dInfoPtr->x,
- oldY, dInfoPtr->maxX-dInfoPtr->x, height, 0, y-oldY,
- damageRgn)) {
-#ifndef MAC_OSX_TK
+ oldY, dInfoPtr->maxX - dInfoPtr->x, height, 0, y - oldY, damageRgn)) {
+#ifdef MAC_OSX_TK
+ /* the processing of the Expose event is damaging the region on Mac */
+#else
TextInvalidateRegion(textPtr, damageRgn);
#endif
}
- numCopies++;
+ DEBUG(stats.numCopies += 1);
TkDestroyRegion(damageRgn);
+
+ if (y != oldY) {
+ textPtr->configureBboxTree = true;
+ }
}
/*
- * Clear the REDRAW_PENDING flag here. This is actually pretty tricky. We
- * want to wait until *after* doing the scrolling, since that could
- * generate more areas to redraw and don't want to reschedule a redisplay
- * for them. On the other hand, we can't wait until after all the
- * redisplaying, because the act of redisplaying could actually generate
- * more redisplays (e.g. in the case of a nested window with event
- * bindings triggered by redisplay).
+ * Clear the REDRAW_PENDING flag here. This is actually pretty tricky. We want to
+ * wait until *after* doing the scrolling, since that could generate more areas to
+ * redraw and don't want to reschedule a redisplay for them. On the other hand, we
+ * can't wait until after all the redisplaying, because the act of redisplaying
+ * could actually generate more redisplays (e.g. in the case of a nested window
+ * with event bindings triggered by redisplay).
*/
dInfoPtr->flags &= ~REDRAW_PENDING;
@@ -4323,11 +8120,9 @@ DisplayText(
*/
if (dInfoPtr->flags & REDRAW_BORDERS) {
- if (tkTextDebug) {
- LOG("tk_textRedraw", "borders");
- }
+ TK_TEXT_DEBUG(LOG("tk_textRedraw", "borders"));
- if (textPtr->tkwin == NULL) {
+ if (!textPtr->tkwin) {
/*
* The widget has been deleted. Don't do anything.
*/
@@ -4344,11 +8139,9 @@ DisplayText(
if (textPtr->highlightWidth != 0) {
GC fgGC, bgGC;
- bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr,
- Tk_WindowId(textPtr->tkwin));
- if (textPtr->flags & GOT_FOCUS) {
- fgGC = Tk_GCForColor(textPtr->highlightColorPtr,
- Tk_WindowId(textPtr->tkwin));
+ bgGC = Tk_GCForColor(textPtr->highlightBgColorPtr, Tk_WindowId(textPtr->tkwin));
+ if (textPtr->flags & HAVE_FOCUS) {
+ fgGC = Tk_GCForColor(textPtr->highlightColorPtr, Tk_WindowId(textPtr->tkwin));
TkpDrawHighlightBorder(textPtr->tkwin, fgGC, bgGC,
textPtr->highlightWidth, Tk_WindowId(textPtr->tkwin));
} else {
@@ -4391,10 +8184,8 @@ DisplayText(
*/
maxHeight = -1;
- for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
- dlPtr = dlPtr->nextPtr) {
- if ((dlPtr->height > maxHeight) &&
- ((dlPtr->flags&OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) {
+ for (dlPtr = dInfoPtr->dLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
+ if (dlPtr->height > maxHeight && ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y)) {
maxHeight = dlPtr->height;
}
bottomY = dlPtr->y + dlPtr->height;
@@ -4408,22 +8199,17 @@ DisplayText(
* 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'.
*/
- if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) {
+ if (maxHeight > dInfoPtr->maxY + dInfoPtr->topPixelOffset) {
maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset);
}
if (maxHeight > 0) {
-#ifndef TK_NO_DOUBLE_BUFFERING
pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
maxHeight, Tk_Depth(textPtr->tkwin));
-#else
- pixmap = Tk_WindowId(textPtr->tkwin);
-#endif /* TK_NO_DOUBLE_BUFFERING */
- for (prevPtr = NULL, dlPtr = textPtr->dInfoPtr->dLinePtr;
- (dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
- prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
- if (dlPtr->chunkPtr == NULL) {
+
+ for (dlPtr = dInfoPtr->dLinePtr; dlPtr && dlPtr->y < dInfoPtr->maxY; dlPtr = dlPtr->nextPtr) {
+ if (!dlPtr->chunkPtr) {
continue;
}
if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) {
@@ -4433,18 +8219,17 @@ DisplayText(
TkTextPrintIndex(textPtr, &dlPtr->index, string);
LOG("tk_textRedraw", string);
}
- DisplayDLine(textPtr, dlPtr, prevPtr, pixmap);
+ DisplayDLine(textPtr, dlPtr, dlPtr->prevPtr, pixmap);
if (dInfoPtr->dLinesInvalidated) {
-#ifndef TK_NO_DOUBLE_BUFFERING
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
-#endif /* TK_NO_DOUBLE_BUFFERING */
- return;
+ goto doScrollbars;
}
dlPtr->oldY = dlPtr->y;
dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID);
- } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0)
- || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) {
- register TkTextDispChunk *chunkPtr;
+ } else if (dInfoPtr->countWindows > 0
+ && dlPtr->chunkPtr
+ && (dlPtr->y < 0 || dlPtr->y + dlPtr->height > dInfoPtr->maxY)) {
+ TkTextDispChunk *chunkPtr;
/*
* It's the first or last DLine which are also overlapping the
@@ -4461,14 +8246,14 @@ DisplayText(
* proc of embedded windows only.
*/
- for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
- chunkPtr = chunkPtr->nextPtr) {
+ for (chunkPtr = dlPtr->chunkPtr; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
int x;
- if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) {
+
+ if (chunkPtr->layoutProcs->type != TEXT_DISP_WINDOW) {
continue;
}
x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
- if ((x + chunkPtr->width <= 0) || (x >= dInfoPtr->maxX)) {
+ if (x + chunkPtr->width <= 0 || x >= dInfoPtr->maxX) {
/*
* Note: we have to call the displayProc even for
* chunks that are off-screen. This is needed, for
@@ -4482,18 +8267,16 @@ DisplayText(
x = -chunkPtr->width;
}
- TkTextEmbWinDisplayProc(textPtr, chunkPtr, x,
+ chunkPtr->layoutProcs->displayProc(textPtr, chunkPtr, x,
dlPtr->spaceAbove,
- dlPtr->height-dlPtr->spaceAbove-dlPtr->spaceBelow,
+ dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, NULL,
(Drawable) None, dlPtr->y + dlPtr->spaceAbove);
}
}
}
-#ifndef TK_NO_DOUBLE_BUFFERING
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
-#endif /* TK_NO_DOUBLE_BUFFERING */
}
/*
@@ -4507,22 +8290,16 @@ DisplayText(
dInfoPtr->topOfEof = dInfoPtr->maxY;
}
if (bottomY < dInfoPtr->topOfEof) {
- if (tkTextDebug) {
- LOG("tk_textRedraw", "eof");
- }
-
- if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
- /*
- * The widget has been deleted. Don't do anything.
- */
+ TK_TEXT_DEBUG(LOG("tk_textRedraw", "eof"));
- goto end;
+ if (textPtr->flags & DESTROYED) {
+ goto end; /* the widget has been deleted */
}
Tk_Fill3DRectangle(textPtr->tkwin, Tk_WindowId(textPtr->tkwin),
textPtr->border, dInfoPtr->x - textPtr->padX, bottomY,
dInfoPtr->maxX - (dInfoPtr->x - textPtr->padX),
- dInfoPtr->topOfEof-bottomY, 0, TK_RELIEF_FLAT);
+ dInfoPtr->topOfEof - bottomY, 0, TK_RELIEF_FLAT);
}
dInfoPtr->topOfEof = bottomY;
@@ -4534,28 +8311,36 @@ DisplayText(
doScrollbars:
if (textPtr->flags & UPDATE_SCROLLBARS) {
- textPtr->flags &= ~UPDATE_SCROLLBARS;
- if (textPtr->yScrollCmd != NULL) {
- GetYView(textPtr->interp, textPtr, 1);
- }
- if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
- /*
- * The widget has been deleted. Don't do anything.
- */
+ /*
+ * Update the vertical scrollbar, if any.
+ */
- goto end;
+ textPtr->flags &= ~UPDATE_SCROLLBARS;
+ if (textPtr->yScrollCmd || textPtr->watchCmd) {
+ GetYView(textPtr->interp, textPtr, true);
}
/*
* Update the horizontal scrollbar, if any.
*/
- if (textPtr->xScrollCmd != NULL) {
- GetXView(textPtr->interp, textPtr, 1);
+ if (textPtr->xScrollCmd || textPtr->watchCmd) {
+ GetXView(textPtr->interp, textPtr, true);
+ }
+
+ if (!(TriggerWatchCursor(textPtr))) {
+ goto end; /* the widget has been deleted */
}
}
+ if (dInfoPtr->flags & ASYNC_PENDING) {
+ assert(dInfoPtr->flags & ASYNC_UPDATE);
+ dInfoPtr->flags &= ~ASYNC_PENDING;
+ /* continue with asynchronous pixel-height calculation */
+ InvokeAsyncUpdateLineMetrics(textPtr);
+ }
+
end:
Tcl_Release(interp);
}
@@ -4581,13 +8366,8 @@ void
TkTextEventuallyRepick(
TkText *textPtr) /* Widget record for text widget. */
{
- TextDInfo *dInfoPtr = textPtr->dInfoPtr;
-
- dInfoPtr->flags |= REPICK_NEEDED;
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- dInfoPtr->flags |= REDRAW_PENDING;
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
+ textPtr->dInfoPtr->flags |= REPICK_NEEDED;
+ DisplayTextWhenIdle(textPtr);
}
/*
@@ -4611,12 +8391,10 @@ TkTextEventuallyRepick(
void
TkTextRedrawRegion(
TkText *textPtr, /* Widget record for text widget. */
- int x, int y, /* Coordinates of upper-left corner of area to
- * be redrawn, in pixels relative to textPtr's
- * window. */
+ int x, int y, /* Coordinates of upper-left corner of area to be redrawn, in
+ * pixels relative to textPtr's window. */
int width, int height) /* Width and height of area to be redrawn. */
{
- TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkRegion damageRgn = TkCreateRegion();
XRectangle rect;
@@ -4625,14 +8403,10 @@ TkTextRedrawRegion(
rect.width = width;
rect.height = height;
TkUnionRectWithRegion(&rect, damageRgn, damageRgn);
-
TextInvalidateRegion(textPtr, damageRgn);
-
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- dInfoPtr->flags |= REDRAW_PENDING;
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
TkDestroyRegion(damageRgn);
+
+ DisplayTextWhenIdle(textPtr);
}
/*
@@ -4656,40 +8430,37 @@ TextInvalidateRegion(
TkText *textPtr, /* Widget record for text widget. */
TkRegion region) /* Region of area to redraw. */
{
- register DLine *dlPtr;
+ DLine *dlPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
int maxY, inset;
XRectangle rect;
/*
- * Find all lines that overlap the given region and mark them for
- * redisplay.
+ * Find all lines that overlap the given region and mark them for redisplay.
*/
TkClipBox(region, &rect);
maxY = rect.y + rect.height;
- for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
- dlPtr = dlPtr->nextPtr) {
- if ((!(dlPtr->flags & OLD_Y_INVALID))
- && (TkRectInRegion(region, rect.x, dlPtr->y,
- rect.width, (unsigned int) dlPtr->height) != RectangleOut)) {
+ for (dlPtr = dInfoPtr->dLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
+ if (!(dlPtr->flags & OLD_Y_INVALID)
+ && TkRectInRegion(region, rect.x, dlPtr->y, rect.width, dlPtr->height) != RectangleOut) {
dlPtr->flags |= OLD_Y_INVALID;
}
}
if (dInfoPtr->topOfEof < maxY) {
dInfoPtr->topOfEof = maxY;
}
+ dInfoPtr->currChunkPtr = NULL;
/*
* Schedule the redisplay operation if there isn't one already scheduled.
*/
inset = textPtr->borderWidth + textPtr->highlightWidth;
- if ((rect.x < (inset + textPtr->padX))
- || (rect.y < (inset + textPtr->padY))
- || ((int) (rect.x + rect.width) > (Tk_Width(textPtr->tkwin)
- - inset - textPtr->padX))
- || (maxY > (Tk_Height(textPtr->tkwin) - inset - textPtr->padY))) {
+ if (rect.x < inset + textPtr->padX
+ || rect.y < inset + textPtr->padY
+ || (int) (rect.x + rect.width) > Tk_Width(textPtr->tkwin) - inset - textPtr->padX
+ || maxY > Tk_Height(textPtr->tkwin) - inset - textPtr->padY) {
dInfoPtr->flags |= REDRAW_BORDERS;
}
}
@@ -4697,7 +8468,7 @@ TextInvalidateRegion(
/*
*----------------------------------------------------------------------
*
- * TkTextChanged, TextChanged --
+ * TkTextChanged --
*
* This function is invoked when info in a text widget is about to be
* modified in a way that changes how it is displayed (e.g. characters
@@ -4720,58 +8491,18 @@ TextInvalidateRegion(
*----------------------------------------------------------------------
*/
-void
-TkTextChanged(
- TkSharedText *sharedTextPtr,/* Shared widget section, or NULL. */
- TkText *textPtr, /* Widget record for text widget, or NULL. */
- const TkTextIndex*index1Ptr,/* Index of first character to redisplay. */
- const TkTextIndex*index2Ptr)/* Index of character just after last one to
- * redisplay. */
-{
- if (sharedTextPtr == NULL) {
- TextChanged(textPtr, index1Ptr, index2Ptr);
- } else {
- textPtr = sharedTextPtr->peers;
- while (textPtr != NULL) {
- TextChanged(textPtr, index1Ptr, index2Ptr);
- textPtr = textPtr->next;
- }
- }
-}
-
static void
TextChanged(
- TkText *textPtr, /* Widget record for text widget, or NULL. */
- const TkTextIndex*index1Ptr,/* Index of first character to redisplay. */
- const TkTextIndex*index2Ptr)/* Index of character just after last one to
- * redisplay. */
+ TkText *textPtr, /* Widget record for text widget, or NULL. */
+ const TkTextIndex *index1Ptr, /* Index of first character to redisplay. */
+ const TkTextIndex *index2Ptr) /* Index of character just after last one to redisplay. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- DLine *firstPtr, *lastPtr;
+ TkTextLine *lastLinePtr = TkBTreeGetLastLine(textPtr);
+ DLine *firstPtr = NULL;
+ DLine *lastPtr= NULL;
TkTextIndex rounded;
TkTextLine *linePtr;
- int notBegin;
-
- /*
- * Schedule both a redisplay and a recomputation of display information.
- * It's done here rather than the end of the function for two reasons:
- *
- * 1. If there are no display lines to update we'll want to return
- * immediately, well before the end of the function.
- * 2. It's important to arrange for the redisplay BEFORE calling
- * FreeDLines. The reason for this is subtle and has to do with
- * embedded windows. The chunk delete function for an embedded window
- * will schedule an idle handler to unmap the window. However, we want
- * the idle handler for redisplay to be called first, so that it can
- * put the embedded window back on the screen again (if appropriate).
- * This will prevent the window from ever being unmapped, and thereby
- * avoid flashing.
- */
-
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
- dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
/*
* Find the DLines corresponding to index1Ptr and index2Ptr. There is one
@@ -4787,87 +8518,118 @@ TextChanged(
* the end of the first non elided text line after this line end).
*/
- rounded = *index1Ptr;
- rounded.byteIndex = 0;
- notBegin = 0;
- while (!IsStartOfNotMergedLine(textPtr, &rounded) && notBegin) {
- notBegin = !TkTextIndexBackBytes(textPtr, &rounded, 1, &rounded);
- rounded.byteIndex = 0;
- }
+ if ((linePtr = TkTextIndexGetLine(index1Ptr)) != lastLinePtr) {
+ rounded = *index1Ptr;
+ TkTextIndexSetLine(&rounded, TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr));
- /*
- * 'rounded' now points to the start of a display line as well as the
- * real (non elided) start of a logical line, and this index is the
- * closest before index1Ptr.
- */
+ if (!(firstPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded))) {
+ /*
+ * index1Ptr pertains to no display line, i.e this index is after
+ * the last display line. Since index2Ptr is after index1Ptr, there
+ * is no display line to free/redisplay and we can return early.
+ */
+ } else {
+ rounded = *index2Ptr;
+ linePtr = TkTextIndexGetLine(index2Ptr);
+ if (linePtr == lastLinePtr) {
+ linePtr = NULL;
+ } else {
+ linePtr = TkBTreeNextLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr);
+ TkTextIndexSetLine(&rounded, linePtr);
+ }
- firstPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded);
+ if (!linePtr) {
+ lastPtr = NULL;
+ } else {
+ /*
+ * 'rounded' now points to the start of a display line as well as the
+ * start of a logical line not merged with its previous line, and
+ * this index is the closest after index2Ptr.
+ */
- if (firstPtr == NULL) {
- /*
- * index1Ptr pertains to no display line, i.e this index is after
- * the last display line. Since index2Ptr is after index1Ptr, there
- * is no display line to free/redisplay and we can return early.
- */
+ lastPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded);
- return;
+ /*
+ * At least one display line is supposed to change. This makes the
+ * redisplay OK in case the display line we expect to get here was
+ * unlinked by a previous call to TkTextChanged and the text widget
+ * did not update before reaching this point. This happens for
+ * instance when moving the cursor up one line.
+ * Note that lastPtr != NULL here, otherwise we would have returned
+ * earlier when we tested for firstPtr being NULL.
+ */
+
+ if (lastPtr && lastPtr == firstPtr) {
+ lastPtr = lastPtr->nextPtr;
+ }
+ }
+ }
}
- rounded = *index2Ptr;
- linePtr = index2Ptr->linePtr;
- do {
- linePtr = TkBTreeNextLine(textPtr, linePtr);
- if (linePtr == NULL) {
- break;
- }
- rounded.linePtr = linePtr;
- rounded.byteIndex = 0;
- } while (!IsStartOfNotMergedLine(textPtr, &rounded));
+ /*
+ * Schedule both a redisplay and a recomputation of display information.
+ * It's done here rather than the end of the function for two reasons:
+ *
+ * 1. If there are no display lines to update we'll want to return
+ * immediately, well before the end of the function.
+ *
+ * 2. It's important to arrange for the redisplay BEFORE calling
+ * FreeDLines. The reason for this is subtle and has to do with
+ * embedded windows. The chunk delete function for an embedded window
+ * will schedule an idle handler to unmap the window. However, we want
+ * the idle handler for redisplay to be called first, so that it can
+ * put the embedded window back on the screen again (if appropriate).
+ * This will prevent the window from ever being unmapped, and thereby
+ * avoid flashing.
+ */
- if (linePtr == NULL) {
- lastPtr = NULL;
- } else {
- /*
- * 'rounded' now points to the start of a display line as well as the
- * start of a logical line not merged with its previous line, and
- * this index is the closest after index2Ptr.
- */
-
- lastPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &rounded);
-
- /*
- * At least one display line is supposed to change. This makes the
- * redisplay OK in case the display line we expect to get here was
- * unlinked by a previous call to TkTextChanged and the text widget
- * did not update before reaching this point. This happens for
- * instance when moving the cursor up one line.
- * Note that lastPtr != NULL here, otherwise we would have returned
- * earlier when we tested for firstPtr being NULL.
- */
-
- if (lastPtr == firstPtr) {
- lastPtr = lastPtr->nextPtr;
- }
- }
+ DisplayTextWhenIdle(textPtr);
+ dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
+ dInfoPtr->currChunkPtr = NULL;
/*
* Delete all the DLines from firstPtr up to but not including lastPtr.
*/
- FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK);
+ FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK_KEEP_BRKS);
+}
+
+void
+TkTextChanged(
+ TkSharedText *sharedTextPtr, /* Shared widget section, or NULL. */
+ TkText *textPtr, /* Widget record for text widget, or NULL. */
+ const TkTextIndex *index1Ptr, /* Index of first character to redisplay. */
+ const TkTextIndex *index2Ptr) /* Index of character just after last one to redisplay. */
+{
+ assert(!sharedTextPtr != !textPtr);
+
+ if (!sharedTextPtr) {
+ TextChanged(textPtr, index1Ptr, index2Ptr);
+ } else {
+ TkTextIndex index1 = *index1Ptr;
+ TkTextIndex index2 = *index2Ptr;
+
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
+ DEBUG(index1.discardConsistencyCheck = true);
+ DEBUG(index2.discardConsistencyCheck = true);
+ TkTextIndexSetPeer(&index1, textPtr);
+ TkTextIndexSetPeer(&index2, textPtr);
+ TextChanged(textPtr, &index1, &index2);
+ }
+ }
}
/*
*----------------------------------------------------------------------
*
- * TkTextRedrawTag, TextRedrawTag --
+ * TkTextRedrawTag --
*
* This function is invoked to request a redraw of all characters in a
* given range that have a particular tag on or off. It's called, for
* example, when tag options change.
*
* Results:
- * None.
+ * Return whether any redraw will happen.
*
* Side effects:
* Information on the screen may be redrawn, and the layout of the screen
@@ -4876,127 +8638,63 @@ TextChanged(
*----------------------------------------------------------------------
*/
-void
-TkTextRedrawTag(
- TkSharedText *sharedTextPtr,/* Shared widget section, or NULL. */
- TkText *textPtr, /* Widget record for text widget. */
- TkTextIndex *index1Ptr, /* First character in range to consider for
- * redisplay. NULL means start at beginning of
- * text. */
- TkTextIndex *index2Ptr, /* Character just after last one to consider
- * for redisplay. NULL means process all the
- * characters in the text. */
- TkTextTag *tagPtr, /* Information about tag. */
- int withTag) /* 1 means redraw characters that have the
- * tag, 0 means redraw those without. */
-{
- if (sharedTextPtr == NULL) {
- TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag);
- } else {
- textPtr = sharedTextPtr->peers;
- while (textPtr != NULL) {
- TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag);
- textPtr = textPtr->next;
- }
- }
-}
-
static void
TextRedrawTag(
TkText *textPtr, /* Widget record for text widget. */
- TkTextIndex *index1Ptr, /* First character in range to consider for
- * redisplay. NULL means start at beginning of
- * text. */
- TkTextIndex *index2Ptr, /* Character just after last one to consider
- * for redisplay. NULL means process all the
- * characters in the text. */
- TkTextTag *tagPtr, /* Information about tag. */
- int withTag) /* 1 means redraw characters that have the
- * tag, 0 means redraw those without. */
-{
- register DLine *dlPtr;
+ const TkTextIndex *index1Ptr,
+ /* First character in range to consider for redisplay. NULL
+ * means start at beginning of text. */
+ const TkTextIndex *index2Ptr,
+ /* Character just after last one to consider for redisplay.
+ * NULL means process all the characters in the text. */
+ bool affectsDisplayGeometry)/* Whether the display geometry is affected. */
+{
+ TextDInfo *dInfoPtr;
+ DLine *dlPtr;
DLine *endPtr;
- int tagOn;
- TkTextSearch search;
- TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- TkTextIndex *curIndexPtr;
- TkTextIndex endOfText, *endIndexPtr;
- /*
- * Invalidate the pixel calculation of all lines in the given range. This
- * may be a bit over-aggressive, so we could consider more subtle
- * techniques here in the future. In particular, when we create a tag for
- * the first time with '.t tag configure foo -font "Arial 20"', say, even
- * though that obviously can't apply to anything at all (the tag didn't
- * exist a moment ago), we invalidate every single line in the widget.
- */
-
- if (tagPtr->affectsDisplayGeometry) {
- TkTextLine *startLine, *endLine;
- int lineCount;
-
- if (index2Ptr == NULL) {
- endLine = NULL;
- lineCount = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
- } else {
- endLine = index2Ptr->linePtr;
- lineCount = TkBTreeLinesTo(textPtr, endLine);
- }
- if (index1Ptr == NULL) {
- startLine = NULL;
- } else {
- startLine = index1Ptr->linePtr;
- lineCount -= TkBTreeLinesTo(textPtr, startLine);
- }
- TkTextInvalidateLineMetrics(NULL, textPtr, startLine, lineCount,
- TK_TEXT_INVALIDATE_ONLY);
+ if (textPtr->flags & DESTROYED) {
+ return;
}
- /*
- * Round up the starting position if it's before the first line visible on
- * the screen (we only care about what's on the screen).
- */
+ assert(index1Ptr);
+ assert(index2Ptr);
+ assert(textPtr);
+ dInfoPtr = textPtr->dInfoPtr;
dlPtr = dInfoPtr->dLinePtr;
- if (dlPtr == NULL) {
+
+ if (!dlPtr) {
return;
}
- if ((index1Ptr == NULL) || (TkTextIndexCmp(&dlPtr->index, index1Ptr)>0)) {
- index1Ptr = &dlPtr->index;
- }
/*
- * Set the stopping position if it wasn't specified.
+ * Invalidate the pixel calculation of all lines in the given range.
*/
- if (index2Ptr == NULL) {
- int lastLine = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
+ if (affectsDisplayGeometry) {
+ TkTextLine *startLine, *endLine;
+ unsigned lineCount;
- index2Ptr = TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lastLine, 0, &endOfText);
+ dInfoPtr->currChunkPtr = NULL; /* reset cached chunk */
+ endLine = TkTextIndexGetLine(index2Ptr);
+ if (endLine == textPtr->endMarker->sectionPtr->linePtr) {
+ assert(endLine->prevPtr);
+ endLine = endLine->prevPtr;
+ }
+ lineCount = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, endLine, NULL);
+ startLine = TkTextIndexGetLine(index1Ptr);
+ lineCount -= TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, startLine, NULL);
+ TkTextInvalidateLineMetrics(NULL, textPtr, startLine, lineCount, TK_TEXT_INVALIDATE_ONLY);
}
/*
- * Initialize a search through all transitions on the tag, starting with
- * the first transition where the tag's current state is different from
- * what it will eventually be.
- */
-
- TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
-
- /*
- * Make our own curIndex because at this point search.curIndex may not
- * equal index1Ptr->curIndex in the case the first tag toggle comes after
- * index1Ptr (See the use of FindTagStart in TkBTreeStartSearch).
+ * Round up the starting position if it's before the first line visible on
+ * the screen (we only care about what's on the screen).
*/
- curIndexPtr = index1Ptr;
- tagOn = TkBTreeCharTagged(index1Ptr, tagPtr);
- if (tagOn != withTag) {
- if (!TkBTreeNextTag(&search)) {
- return;
- }
- curIndexPtr = &search.curIndex;
+ if (TkTextIndexCompare(&dlPtr->index, index1Ptr) > 0) {
+ index1Ptr = &dlPtr->index;
}
/*
@@ -5005,10 +8703,8 @@ TextRedrawTag(
* given in TkTextChanged.
*/
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
- dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
+ DisplayTextWhenIdle(textPtr);
+ dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
/*
* Each loop through the loop below is for one range of characters where
@@ -5017,41 +8713,15 @@ TextRedrawTag(
* in the range.
*/
- while (1) {
- /*
- * Find the first DLine structure in the range. Note: if the desired
- * character isn't the first in its text line, then look for the
- * character just before it instead. This is needed to handle the case
- * where the first character of a wrapped display line just got
- * smaller, so that it now fits on the line before: need to relayout
- * the line containing the previous character.
- */
-
- if (IsStartOfNotMergedLine(textPtr, curIndexPtr)) {
- dlPtr = FindDLine(textPtr, dlPtr, curIndexPtr);
- } else {
- TkTextIndex tmp = *curIndexPtr;
-
- TkTextIndexBackBytes(textPtr, &tmp, 1, &tmp);
- dlPtr = FindDLine(textPtr, dlPtr, &tmp);
- }
- if (dlPtr == NULL) {
- break;
- }
+ dlPtr = FindDLine(textPtr, dlPtr, index1Ptr);
+ if (dlPtr) {
/*
* Find the first DLine structure that's past the end of the range.
*/
- if (!TkBTreeNextTag(&search)) {
- endIndexPtr = index2Ptr;
- } else {
- curIndexPtr = &search.curIndex;
- endIndexPtr = curIndexPtr;
- }
- endPtr = FindDLine(textPtr, dlPtr, endIndexPtr);
- if ((endPtr != NULL)
- && (TkTextIndexCmp(&endPtr->index,endIndexPtr) < 0)) {
+ endPtr = FindDLine(textPtr, dlPtr, index2Ptr);
+ if (endPtr && TkTextIndexCompare(&endPtr->index, index2Ptr) < 0) {
endPtr = endPtr->nextPtr;
}
@@ -5061,16 +8731,176 @@ TextRedrawTag(
*/
FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK);
- dlPtr = endPtr;
+ }
+}
+
+static void
+RedrawTagsInPeer(
+ const TkSharedText *sharedTextPtr,
+ TkText *textPtr,
+ TkTextIndex *indexPtr1,
+ TkTextIndex *indexPtr2,
+ bool affectsDisplayGeometry)
+{
+ TkTextIndex start, end;
+
+ if (!textPtr->dInfoPtr || !textPtr->dInfoPtr->dLinePtr) {
+ return;
+ }
+
+ if (textPtr->startMarker != sharedTextPtr->startMarker) {
+ TkTextIndexSetupToStartOfText(&start, textPtr, sharedTextPtr->tree);
+ if (TkTextIndexCompare(indexPtr1, &start) <= 0) {
+ indexPtr1 = &start;
+ }
+ }
+
+ if (textPtr->endMarker != sharedTextPtr->endMarker) {
+ TkTextIndexSetupToEndOfText(&end, textPtr, sharedTextPtr->tree);
+ if (TkTextIndexCompare(indexPtr2, &end) <= 0) {
+ indexPtr2 = &end;
+ }
+ }
+
+ TkTextIndexSetPeer(indexPtr1, textPtr);
+ TkTextIndexSetPeer(indexPtr2, textPtr);
+ TextRedrawTag(textPtr, indexPtr1, indexPtr2, affectsDisplayGeometry);
+}
+
+bool
+TkTextRedrawTag(
+ const TkSharedText *sharedTextPtr,
+ /* Shared widget section, or NULL if textPtr is not NULL. */
+ TkText *textPtr, /* Widget record for text widget, or NULL if sharedTextPtr is not
+ * NULL. */
+ const TkTextIndex *index1Ptr,
+ /* First character in range to consider for redisplay. NULL means
+ * start at beginning of text. */
+ const TkTextIndex *index2Ptr,
+ /* Character just after last one to consider for redisplay. NULL
+ * means process all the characters in the text. Note that either
+ * both indices are NULL, or both are non-Null. */
+ const TkTextTag *tagPtr, /* Information about tag, can be NULL, but only if the indices are
+ * non-NULL*/
+ bool affectsDisplayGeometry)/* Whether the display geometry is affected. If argument tagPtr is
+ * given, then also this tag will be tested if the display geometry
+ * is affected. */
+{
+ assert(!index1Ptr == !index2Ptr);
+ assert(index1Ptr || tagPtr);
+ assert(sharedTextPtr || textPtr);
+
+ if (!sharedTextPtr && !textPtr->dInfoPtr->dLinePtr) {
+ return false;
+ }
+
+ if (tagPtr && tagPtr->affectsDisplayGeometry) {
+ affectsDisplayGeometry = true;
+ }
+
+ if (!index1Ptr) {
+ TkTextSegment *endMarker;
+ TkTextSearch search;
+ TkTextIndex startIndex, endIndex;
+
+ if (!sharedTextPtr) {
+ TkTextIndexClear2(&startIndex, NULL, textPtr->sharedTextPtr->tree);
+ TkTextIndexClear2(&endIndex, NULL, textPtr->sharedTextPtr->tree);
+ TkTextIndexSetSegment(&startIndex, textPtr->startMarker);
+ TkTextIndexSetSegment(&endIndex, textPtr->endMarker);
+ endMarker = textPtr->endMarker;
+ } else {
+ TkTextIndexClear2(&startIndex, NULL, sharedTextPtr->tree);
+ TkTextIndexClear2(&endIndex, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&startIndex, sharedTextPtr->startMarker);
+ TkTextIndexSetSegment(&endIndex, sharedTextPtr->endMarker);
+ endMarker = sharedTextPtr->endMarker;
+ }
/*
- * Find the first text line in the next range.
+ * Now we try to restrict the range, because redrawing is in general an expensive
+ * operation.
*/
- if (!TkBTreeNextTag(&search)) {
- break;
+ if (tagPtr) {
+ bool found = false;
+
+ TkBTreeStartSearch(&startIndex, &endIndex, tagPtr, &search, SEARCH_EITHER_TAGON_TAGOFF);
+
+ while (true) {
+ if (!TkBTreeNextTag(&search)) {
+ return found;
+ }
+ if (search.tagon) {
+ /* we need end of range */
+ startIndex = search.curIndex;
+ TkBTreeNextTag(&search);
+ assert(search.segPtr); /* search must not fail */
+ } else {
+ assert(!found);
+ }
+ found = true;
+ assert(!search.tagon);
+ if (!sharedTextPtr) {
+ TextRedrawTag(textPtr, &startIndex, &search.curIndex, affectsDisplayGeometry);
+ } else {
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
+ RedrawTagsInPeer(sharedTextPtr, textPtr, &startIndex, &search.curIndex,
+ affectsDisplayGeometry);
+ }
+ }
+ }
+ } else {
+ const TkBitField *discardTags = NULL;
+ TkTextSegment *segPtr;
+ TkTextIndex index2;
+
+ if (affectsDisplayGeometry) {
+ if (sharedTextPtr) {
+ discardTags = sharedTextPtr->notAffectDisplayTags;
+ } else {
+ discardTags = textPtr->sharedTextPtr->notAffectDisplayTags;
+ }
+ }
+ if (!(segPtr = TkBTreeFindNextTagged(&startIndex, &endIndex, discardTags))) {
+ return false;
+ }
+ index2 = endIndex;
+
+ while (segPtr) {
+ TkTextSegment *endPtr;
+
+ TkTextIndexSetSegment(&startIndex, segPtr);
+ endPtr = TkBTreeFindNextUntagged(&startIndex, &endIndex, discardTags);
+
+ if (!endPtr) {
+ endPtr = endMarker;
+ }
+
+ TkTextIndexSetSegment(&index2, endPtr);
+
+ if (!sharedTextPtr) {
+ TextRedrawTag(textPtr, &startIndex, &index2, affectsDisplayGeometry);
+ } else {
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
+ RedrawTagsInPeer(sharedTextPtr, textPtr, &startIndex, &index2,
+ affectsDisplayGeometry);
+ }
+ }
+ }
+ }
+ } else if (!sharedTextPtr) {
+ TextRedrawTag(textPtr, index1Ptr, index2Ptr, affectsDisplayGeometry);
+ } else {
+ TkTextIndex index1 = *index1Ptr;
+ TkTextIndex index2 = *index2Ptr;
+
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
+ RedrawTagsInPeer(sharedTextPtr, textPtr, &index1, &index2, affectsDisplayGeometry);
}
}
+
+ return true;
}
/*
@@ -5097,23 +8927,29 @@ TextRedrawTag(
void
TkTextRelayoutWindow(
TkText *textPtr, /* Widget record for text widget. */
- int mask) /* OR'd collection of bits showing what has
- * changed. */
+ int mask) /* OR'd collection of bits showing what has changed. */
{
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- GC newGC;
XGCValues gcValues;
+ GC newGC;
+ bool recomputeGeometry;
+ bool asyncLineCalculation;
+ int firstLineNo;
+ int lastLineNo;
+ int maxX;
+
+ if ((mask & TK_TEXT_LINE_REDRAW_BOTTOM_LINE) && dInfoPtr->lastDLinePtr) {
+ dInfoPtr->lastDLinePtr->flags |= OLD_Y_INVALID;
+ }
/*
* Schedule the window redisplay. See TkTextChanged for the reason why
* this has to be done before any calls to FreeDLines.
*/
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
- dInfoPtr->flags |= REDRAW_PENDING|REDRAW_BORDERS|DINFO_OUT_OF_DATE
- |REPICK_NEEDED;
+ DisplayTextWhenIdle(textPtr);
+ dInfoPtr->flags |= REDRAW_BORDERS|DINFO_OUT_OF_DATE|REPICK_NEEDED;
/*
* (Re-)create the graphics context for drawing the traversal highlight.
@@ -5127,40 +8963,56 @@ TkTextRelayoutWindow(
dInfoPtr->copyGC = newGC;
/*
- * Throw away all the current layout information.
+ * (Re-)create the graphics context for drawing the characters "behind" the block cursor.
*/
- FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
- dInfoPtr->dLinePtr = NULL;
+ if (dInfoPtr->insertFgGC != None) {
+ Tk_FreeGC(textPtr->display, dInfoPtr->insertFgGC);
+ dInfoPtr->insertFgGC = None;
+ }
+ if (textPtr->state == TK_TEXT_STATE_NORMAL
+ && textPtr->blockCursorType
+ && textPtr->showInsertFgColor) {
+ gcValues.foreground = textPtr->insertFgColorPtr->pixel;
+ dInfoPtr->insertFgGC = Tk_GetGC(textPtr->tkwin, GCForeground, &gcValues);
+ }
+
+ maxX = MAX(Tk_Width(textPtr->tkwin) - dInfoPtr->x, dInfoPtr->x + 1);
+ firstLineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, TkBTreeGetStartLine(textPtr), NULL);
+ lastLineNo = TkBTreeLinesTo(sharedTextPtr->tree, NULL, TkBTreeGetLastLine(textPtr), NULL);
+ recomputeGeometry = (maxX != dInfoPtr->maxX) || (mask & TK_TEXT_LINE_GEOMETRY);
/*
- * Recompute some overall things for the layout. Even if the window gets
- * very small, pretend that there's at least one pixel of drawing space in
- * it.
+ * Throw away all the current display lines, except the visible ones if
+ * they will not change.
*/
- if (textPtr->highlightWidth < 0) {
- textPtr->highlightWidth = 0;
- }
- dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth
- + textPtr->padX;
- dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth
- + textPtr->padY;
- dInfoPtr->maxX = Tk_Width(textPtr->tkwin) - textPtr->highlightWidth
- - textPtr->borderWidth - textPtr->padX;
- if (dInfoPtr->maxX <= dInfoPtr->x) {
- dInfoPtr->maxX = dInfoPtr->x + 1;
+ if (recomputeGeometry || (mask & TK_TEXT_LINE_REDRAW)) {
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK_KEEP_BRKS);
}
+ FreeDLines(textPtr, NULL, NULL, DLINE_CACHE); /* release cached display lines */
+ FreeDLines(textPtr, NULL, NULL, DLINE_METRIC); /* release cached lines */
+ FreeDLines(textPtr, dInfoPtr->savedDLinePtr, NULL, DLINE_FREE_TEMP);
+
+ /*
+ * Recompute some overall things for the layout. Even if the window gets very small,
+ * pretend that there's at least one pixel of drawing space in it.
+ */
+
+ assert(textPtr->highlightWidth >= 0);
+ assert(textPtr->borderWidth >= 0);
+
+ dInfoPtr->x = textPtr->highlightWidth + textPtr->borderWidth + textPtr->padX;
+ dInfoPtr->y = textPtr->highlightWidth + textPtr->borderWidth + textPtr->padY;
+
+ dInfoPtr->maxX = MAX(Tk_Width(textPtr->tkwin) - dInfoPtr->x, dInfoPtr->x + 1);
+
/*
* This is the only place where dInfoPtr->maxY is set.
*/
- dInfoPtr->maxY = Tk_Height(textPtr->tkwin) - textPtr->highlightWidth
- - textPtr->borderWidth - textPtr->padY;
- if (dInfoPtr->maxY <= dInfoPtr->y) {
- dInfoPtr->maxY = dInfoPtr->y + 1;
- }
+ dInfoPtr->maxY = MAX(Tk_Height(textPtr->tkwin) - dInfoPtr->y, dInfoPtr->y + 1);
dInfoPtr->topOfEof = dInfoPtr->maxY;
/*
@@ -5169,46 +9021,113 @@ TkTextRelayoutWindow(
* could change the way lines wrap.
*/
- if (!IsStartOfNotMergedLine(textPtr, &textPtr->topIndex)) {
- TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
+ if (!IsStartOfNotMergedLine(&textPtr->topIndex)) {
+ TkTextFindDisplayLineStartEnd(textPtr, &textPtr->topIndex, DISP_LINE_START);
}
/*
- * Invalidate cached scrollbar positions, so that scrollbars sliders will
- * be udpated.
+ * Invalidate cached scrollbar positions, so that scrollbars sliders will be udpated.
*/
dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
+ /*
+ * Invalidate cached cursor chunk.
+ */
+
+ dInfoPtr->currChunkPtr = NULL;
+
if (mask & TK_TEXT_LINE_GEOMETRY) {
+ /* Setup end of line segment. */
+ SetupEolSegment(textPtr, dInfoPtr);
+ }
+
+ asyncLineCalculation = false;
+
+#if SPEEDUP_MONOSPACED_LINE_HEIGHTS
+ if (TestMonospacedLineHeights(textPtr)) {
+ TkRangeList *ranges = textPtr->dInfoPtr->lineMetricUpdateRanges;
+
+ if (!TkRangeListIsEmpty(ranges)) {
+ TkBTreeUpdatePixelHeights(textPtr,
+ TkBTreeFindLine(sharedTextPtr->tree, textPtr, TkRangeListLow(ranges)),
+ TkRangeListSpan(ranges), dInfoPtr->lineMetricUpdateEpoch);
+ TkRangeListClear(ranges);
+ }
+ if (dInfoPtr->lineHeight != textPtr->lineHeight) {
+ TkBTreeUpdatePixelHeights(textPtr, TkBTreeGetStartLine(textPtr), lastLineNo - firstLineNo,
+ dInfoPtr->lineMetricUpdateEpoch);
+ dInfoPtr->lineHeight = textPtr->lineHeight;
+ }
+ } else
+#endif
+ if (recomputeGeometry) {
/*
* Set up line metric recalculation.
- *
- * Avoid the special zero value, since that is used to mark individual
- * lines as being out of date.
*/
- if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
- dInfoPtr->lineMetricUpdateEpoch++;
+ dInfoPtr->lineHeight = 0;
+ TkRangeListClear(dInfoPtr->lineMetricUpdateRanges);
+ if (lastLineNo > firstLineNo) {
+ dInfoPtr->lineMetricUpdateRanges =
+ TkRangeListAdd(dInfoPtr->lineMetricUpdateRanges, 0, lastLineNo - firstLineNo - 1);
+ dInfoPtr->lineMetricUpdateEpoch += 1;
+ asyncLineCalculation = true;
}
+ } else {
+ TkTextIndex index;
+ DLine *dlPtr;
+ int numLines;
- dInfoPtr->currentMetricUpdateLine = -1;
+ dInfoPtr->lineHeight = 0;
/*
- * Also cancel any partial line-height calculations (for long-wrapped
- * lines) in progress.
+ * We have to handle -startindex, -endIndex.
*/
- dInfoPtr->metricEpoch = -1;
-
- if (dInfoPtr->lineUpdateTimer == NULL) {
- textPtr->refCount++;
- dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
- AsyncUpdateLineMetrics, textPtr);
- GenerateWidgetViewSyncEvent(textPtr, 0);
+ if (lastLineNo == firstLineNo) {
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
+ TkRangeListClear(dInfoPtr->lineMetricUpdateRanges);
+ } else if (dInfoPtr->lastLineNo <= firstLineNo || lastLineNo <= dInfoPtr->firstLineNo) {
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, NULL, DLINE_UNLINK);
+ TkRangeListClear(dInfoPtr->lineMetricUpdateRanges);
+ dInfoPtr->lineMetricUpdateRanges = TkRangeListAdd(
+ dInfoPtr->lineMetricUpdateRanges, 0, lastLineNo - firstLineNo - 1);
+ asyncLineCalculation = true;
+ } else {
+ if (firstLineNo < dInfoPtr->firstLineNo) {
+ dInfoPtr->lineMetricUpdateRanges = TkRangeListInsert(
+ dInfoPtr->lineMetricUpdateRanges, 0, dInfoPtr->firstLineNo - firstLineNo - 1);
+ asyncLineCalculation = true;
+ } else if (dInfoPtr->firstLineNo < firstLineNo) {
+ TkTextIndexSetupToStartOfText(&index, textPtr, sharedTextPtr->tree);
+ dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index);
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK);
+ numLines = firstLineNo - dInfoPtr->firstLineNo;
+ TkRangeListDelete(dInfoPtr->lineMetricUpdateRanges, 0, numLines - 1);
+ }
+ if (dInfoPtr->lastLineNo < lastLineNo) {
+ dInfoPtr->lineMetricUpdateRanges = TkRangeListAdd(
+ dInfoPtr->lineMetricUpdateRanges,
+ dInfoPtr->lastLineNo - dInfoPtr->firstLineNo,
+ lastLineNo - firstLineNo - 1);
+ asyncLineCalculation = true;
+ } else if (lastLineNo < dInfoPtr->lastLineNo) {
+ TkTextIndexSetupToEndOfText(&index, textPtr, sharedTextPtr->tree);
+ dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index);
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_UNLINK);
+ TkRangeListTruncateAtEnd(dInfoPtr->lineMetricUpdateRanges, lastLineNo - firstLineNo - 1);
+ }
}
}
+
+ dInfoPtr->firstLineNo = firstLineNo;
+ dInfoPtr->lastLineNo = lastLineNo;
+
+ if (asyncLineCalculation) {
+ StartAsyncLineCalculation(textPtr);
+ }
}
/*
@@ -5233,58 +9152,62 @@ TkTextRelayoutWindow(
void
TkTextSetYView(
TkText *textPtr, /* Widget record for text widget. */
- TkTextIndex *indexPtr, /* Position that is to appear somewhere in the
- * view. */
- int pickPlace) /* 0 means the given index must appear exactly
- * at the top of the screen. TK_TEXT_PICKPLACE
- * (-1) means we get to pick where it appears:
- * minimize screen motion or else display line
- * at center of screen. TK_TEXT_NOPIXELADJUST
- * (-2) indicates to make the given index the
- * top line, but if it is already the top
- * line, don't nudge it up or down by a few
- * pixels just to make sure it is entirely
- * displayed. Positive numbers indicate the
- * number of pixels of the index's line which
- * are to be off the top of the screen. */
+ TkTextIndex *indexPtr, /* Position that is to appear somewhere in the view. */
+ int pickPlace) /* 0 means the given index must appear exactly at the top of the
+ * screen. TK_TEXT_PICKPLACE (-1) means we get to pick where it
+ * appears: minimize screen motion or else display line at center
+ * of screen. TK_TEXT_NOPIXELADJUST (-2) indicates to make the
+ * given index the top line, but if it is already the top line,
+ * don't nudge it up or down by a few pixels just to make sure
+ * it is entirely displayed. Positive numbers indicate the number
+ * of pixels of the index's line which are to be off the top of
+ * the screen. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- register DLine *dlPtr;
- int bottomY, close, lineIndex;
- TkTextIndex tmpIndex, rounded;
+ DLine *dlPtr;
+ int bottomY, close;
+ TkTextIndex tmpIndex;
+ TkTextLine *linePtr;
int lineHeight;
+ int topLineNo;
+ int topByteIndex;
+ int32_t overlap;
+
+ if (TkTextIsDeadPeer(textPtr)) {
+ textPtr->topIndex = *indexPtr;
+ TkTextIndexSetPeer(&textPtr->topIndex, textPtr);
+ return;
+ }
/*
* If the specified position is the extra line at the end of the text,
* round it back to the last real line.
*/
- lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
- if (lineIndex == TkBTreeNumLines(indexPtr->tree, textPtr)) {
- TkTextIndexBackChars(textPtr, indexPtr, 1, &rounded, COUNT_INDICES);
- indexPtr = &rounded;
+ linePtr = TkTextIndexGetLine(indexPtr);
+
+ if (linePtr == TkBTreeGetLastLine(textPtr) && TkTextIndexGetByteIndex(indexPtr) == 0) {
+ assert(linePtr->prevPtr);
+ assert(TkBTreeGetStartLine(textPtr) != linePtr);
+ TkTextIndexSetToEndOfLine2(indexPtr, linePtr->prevPtr);
}
if (pickPlace == TK_TEXT_NOPIXELADJUST) {
- if (textPtr->topIndex.linePtr == indexPtr->linePtr
- && textPtr->topIndex.byteIndex == indexPtr->byteIndex) {
- pickPlace = dInfoPtr->topPixelOffset;
- } else {
- pickPlace = 0;
- }
+ pickPlace = TkTextIndexIsEqual(&textPtr->topIndex, indexPtr) ? dInfoPtr->topPixelOffset : 0;
}
if (pickPlace != TK_TEXT_PICKPLACE) {
/*
* The specified position must go at the top of the screen. Just leave
- * all the DLine's alone: we may be able to reuse some of the
- * information that's currently on the screen without redisplaying it
- * all.
+ * all the DLine's alone: we may be able to reuse some of the information
+ * that's currently on the screen without redisplaying it all.
*/
textPtr->topIndex = *indexPtr;
- if (!IsStartOfNotMergedLine(textPtr, indexPtr)) {
- TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
+ TkTextIndexSetPeer(&textPtr->topIndex, textPtr);
+ TkTextIndexToByteIndex(&textPtr->topIndex);
+ if (!IsStartOfNotMergedLine(indexPtr)) {
+ TkTextFindDisplayLineStartEnd(textPtr, &textPtr->topIndex, DISP_LINE_START);
}
dInfoPtr->newTopPixelOffset = pickPlace;
goto scheduleUpdate;
@@ -5300,8 +9223,8 @@ TkTextSetYView(
UpdateDisplayInfo(textPtr);
}
dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
- if (dlPtr != NULL) {
- if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
+ if (dlPtr) {
+ if (dlPtr->y + dlPtr->height > dInfoPtr->maxY) {
/*
* Part of the line hangs off the bottom of the screen; pretend
* the whole line is off-screen.
@@ -5309,7 +9232,7 @@ TkTextSetYView(
dlPtr = NULL;
} else {
- if (TkTextIndexCmp(&dlPtr->index, indexPtr) <= 0) {
+ if (TkTextIndexCompare(&dlPtr->index, indexPtr) <= 0) {
if (dInfoPtr->dLinePtr == dlPtr && dInfoPtr->topPixelOffset != 0) {
/*
* It is on the top line, but that line is hanging off the top
@@ -5318,7 +9241,7 @@ TkTextSetYView(
dInfoPtr->newTopPixelOffset = 0;
goto scheduleUpdate;
- }
+ }
/*
* The line is already on screen, with no need to scroll.
*/
@@ -5336,21 +9259,20 @@ TkTextSetYView(
*/
tmpIndex = *indexPtr;
- TkTextFindDisplayLineEnd(textPtr, &tmpIndex, 0, NULL);
- lineHeight = CalculateDisplayLineHeight(textPtr, &tmpIndex, NULL, NULL);
+ TkTextFindDisplayLineStartEnd(textPtr, &tmpIndex, DISP_LINE_START);
+ lineHeight = CalculateDisplayLineHeight(textPtr, &tmpIndex, NULL);
/*
* It would be better if 'bottomY' were calculated using the actual height
- * of the given line, not 'textPtr->charHeight'.
+ * of the given line, not 'textPtr->lineHeight'.
*/
bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2;
close = (dInfoPtr->maxY - dInfoPtr->y)/3;
- if (close < 3*textPtr->charHeight) {
- close = 3*textPtr->charHeight;
+ if (close < 3*textPtr->lineHeight) {
+ close = 3*textPtr->lineHeight;
}
- if (dlPtr != NULL) {
- int overlap;
+ if (dlPtr) {
/*
* The desired line is above the top of screen. If it is "close" to
@@ -5359,26 +9281,24 @@ TkTextSetYView(
* add an extra half line to be sure we count far enough.
*/
- MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2,
- &tmpIndex, &overlap);
- if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
+ MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->lineHeight/2, &tmpIndex, &overlap);
+ if (TkTextIndexCompare(&tmpIndex, indexPtr) <= 0) {
textPtr->topIndex = *indexPtr;
- TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
+ TkTextIndexSetPeer(&textPtr->topIndex, textPtr);
+ TkTextIndexToByteIndex(&textPtr->topIndex);
+ TkTextFindDisplayLineStartEnd(textPtr, &textPtr->topIndex, DISP_LINE_START);
dInfoPtr->newTopPixelOffset = 0;
goto scheduleUpdate;
}
} else {
- int overlap;
-
/*
* The desired line is below the bottom of the screen. If it is
* "close" to the bottom of the screen then position it at the bottom
* of the screen.
*/
- MeasureUp(textPtr, indexPtr, close + lineHeight
- - textPtr->charHeight/2, &tmpIndex, &overlap);
- if (FindDLine(textPtr, dInfoPtr->dLinePtr, &tmpIndex) != NULL) {
+ MeasureUp(textPtr, indexPtr, close + lineHeight - textPtr->lineHeight/2, &tmpIndex, &overlap);
+ if (FindDLine(textPtr, dInfoPtr->dLinePtr, &tmpIndex)) {
bottomY = dInfoPtr->maxY - dInfoPtr->y;
}
}
@@ -5400,20 +9320,107 @@ TkTextSetYView(
* of the window.
*/
- MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex,
- &dInfoPtr->newTopPixelOffset);
+ MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex, &dInfoPtr->newTopPixelOffset);
scheduleUpdate:
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- Tcl_DoWhenIdle(DisplayText, textPtr);
+ topLineNo = TkTextIndexGetLineNumber(&textPtr->topIndex, NULL);
+ topByteIndex = TkTextIndexGetByteIndex(&textPtr->topIndex);
+
+ if (dInfoPtr->newTopPixelOffset != dInfoPtr->topPixelOffset
+ || dInfoPtr->topLineNo != topLineNo
+ || dInfoPtr->topByteIndex != topByteIndex) {
+ DisplayTextWhenIdle(textPtr);
+ dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
+ dInfoPtr->topLineNo = topLineNo;
+ dInfoPtr->topByteIndex = topByteIndex;
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * FindDisplayLineOffset --
+ *
+ * Given a line pointer to a logical line, and a distance in
+ * pixels, find the byte offset of the corresponding display
+ * line. If only one display line belongs to the given logical
+ * line, then the offset is always zero.
+ *
+ * Results:
+ * Returns the offset to the display line at specified pixel
+ * position, relative to the given logical line. 'distance'
+ * will be set to the vertical distance in pixels measured
+ * from the top pixel in specified display line (this means,
+ * that all the display lines pixels between the top of the
+ * logical line and the corresponding display line will be
+ * subtracted).
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static const TkTextDispLineEntry *
+SearchPixelEntry(
+ const TkTextDispLineEntry *first,
+ const TkTextDispLineEntry *last,
+ unsigned pixels)
+{
+ assert(first != last);
+
+ if ((last - 1)->pixels < pixels) {
+ return last - 1; /* catch a frequent case */
}
- dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
+
+ do {
+ const TkTextDispLineEntry *mid = first + (last - first)/2;
+
+ if (mid->pixels <= pixels) {
+ first = mid + 1;
+ } else {
+ last = mid;
+ }
+ } while (first != last);
+
+ return first;
+}
+
+static unsigned
+FindDisplayLineOffset(
+ TkText *textPtr,
+ TkTextLine *linePtr,
+ int32_t *distance) /* IN: distance in pixels to logical line
+ * OUT: distance in pixels of specified display line. */
+{
+ const TkTextPixelInfo *pixelInfo = TkBTreeLinePixelInfo(textPtr, linePtr);
+ const TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
+ const TkTextDispLineEntry *lastEntry;
+ const TkTextDispLineEntry *entry;
+
+ assert(distance);
+ assert(*distance >= 0);
+ assert(linePtr->logicalLine);
+
+ if (!dispLineInfo) {
+ return 0;
+ }
+
+ lastEntry = dispLineInfo->entry + dispLineInfo->numDispLines;
+ entry = SearchPixelEntry(dispLineInfo->entry, lastEntry, *distance);
+ assert(entry != lastEntry);
+ if (entry == dispLineInfo->entry) {
+ return 0;
+ }
+
+ *distance -= (entry - 1)->pixels;
+ return entry->byteOffset;
}
/*
*--------------------------------------------------------------
*
- * TkTextMeasureDown --
+ * MeasureDown --
*
* Given one index, find the index of the first character on the highest
* display line that would be displayed no more than "distance" pixels
@@ -5421,7 +9428,9 @@ TkTextSetYView(
*
* Results:
* The srcPtr is manipulated in place to reflect the new position. We
- * return the number of pixels by which 'distance' overlaps the srcPtr.
+ * return the number of pixels by which 'distance' overlaps the srcPtr
+ * in 'overlap'. The return valus is 'false' if we are already at top
+ * of view, otherwise the return valus is 'true'.
*
* Side effects:
* None.
@@ -5429,39 +9438,139 @@ TkTextSetYView(
*--------------------------------------------------------------
*/
-int
-TkTextMeasureDown(
+static bool
+AlreadyAtBottom(
+ const TkText *textPtr)
+{
+ const TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ DLine *dlPtr = dInfoPtr->lastDLinePtr;
+ TkTextIndex index;
+
+ if (!dlPtr) {
+ return true;
+ }
+ if (dlPtr->y + dlPtr->height != dInfoPtr->maxY) {
+ return false;
+ }
+
+ index = dlPtr->index;
+ TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
+ return TkTextIndexIsEndOfText(&index);
+}
+
+static bool
+MeasureDown(
TkText *textPtr, /* Text widget in which to measure. */
- TkTextIndex *srcPtr, /* Index of character from which to start
- * measuring. */
- int distance) /* Vertical distance in pixels measured from
- * the top pixel in srcPtr's logical line. */
+ TkTextIndex *srcPtr, /* Index of character from which to start measuring. */
+ int distance, /* Vertical distance in pixels measured from the top pixel in
+ * srcPtr's logical line. */
+ int32_t *overlap, /* The number of pixels by which 'distance' overlaps the srcPtr. */
+ bool saveDisplayLines) /* Save produced display line for re-use in UpdateDisplayInfo? */
{
- TkTextLine *lastLinePtr;
- DLine *dlPtr;
- TkTextIndex loop;
+ const TkTextLine *lastLinePtr;
+ TkTextLine *linePtr;
+ TkTextIndex index;
+ int byteOffset;
+ int32_t myOverlap;
- lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
+ if (AlreadyAtBottom(textPtr)) {
+ return false;
+ }
- do {
- dlPtr = LayoutDLine(textPtr, srcPtr);
- dlPtr->nextPtr = NULL;
+ if (!overlap) {
+ overlap = &myOverlap;
+ }
- if (distance < dlPtr->height) {
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
- break;
+ linePtr = TkTextIndexGetLine(srcPtr);
+ lastLinePtr = TkBTreeGetLastLine(textPtr);
+
+ if (TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges)) {
+ int pixelHeight;
+
+ /*
+ * No display line metric calculation is pending, this is fine,
+ * now we can use the B-Tree for the measurement.
+ *
+ * Note that TkBTreePixelsTo is measuring up to the logical line.
+ */
+
+ pixelHeight = TkBTreePixelsTo(textPtr, linePtr);
+ pixelHeight += GetPixelsTo(textPtr, srcPtr, false, NULL);
+ pixelHeight += distance;
+ linePtr = TkBTreeFindPixelLine(srcPtr->tree, textPtr, pixelHeight, overlap);
+
+ if (linePtr == lastLinePtr) {
+ TkTextLine *prevLinePtr = TkBTreePrevLine(textPtr, linePtr);
+ if (prevLinePtr) {
+ linePtr = prevLinePtr;
+ }
}
- distance -= dlPtr->height;
- TkTextIndexForwBytes(textPtr, srcPtr, dlPtr->byteCount, &loop);
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
- if (loop.linePtr == lastLinePtr) {
- break;
+
+ /*
+ * We have the logical line, and the overlap, now search for the display line.
+ */
+
+ byteOffset = FindDisplayLineOffset(textPtr, linePtr, overlap);
+ } else {
+ DisplayInfo info;
+
+ /*
+ * Search down line by line until we'e found the bottom line for given distance.
+ */
+
+ linePtr = ComputeDisplayLineInfo(textPtr, srcPtr, &info);
+ distance += GetPixelsTo(textPtr, srcPtr, false, &info);
+ index = *srcPtr;
+
+ while (true) {
+ ComputeMissingMetric(textPtr, &info, THRESHOLD_PIXEL_DISTANCE, distance);
+ if (saveDisplayLines) {
+ SaveDisplayLines(textPtr, &info, true);
+ } else {
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
+ }
+
+ if (distance < info.pixels) {
+ const TkTextDispLineInfo *dispLineInfo = info.pixelInfo->dispLineInfo;
+
+ if (dispLineInfo) {
+ const TkTextDispLineEntry *entry, *last;
+
+ last = dispLineInfo->entry + dispLineInfo->numDispLines;
+ entry = SearchPixelEntry(dispLineInfo->entry, last, distance);
+ assert(entry < last);
+ byteOffset = entry->byteOffset;
+ if (entry != dispLineInfo->entry) {
+ distance -= (entry - 1)->pixels;
+ }
+ } else {
+ byteOffset = 0;
+ }
+ break;
+ }
+
+ if (TkTextIndexGetLine(&info.index) == lastLinePtr) {
+ byteOffset = 0;
+ break;
+ }
+ linePtr = TkTextIndexGetLine(&info.index);
+ if ((distance -= info.pixels) == 0) {
+ byteOffset = 0;
+ break;
+ }
+ TkTextIndexSetToStartOfLine2(&index, linePtr);
+ linePtr = ComputeDisplayLineInfo(textPtr, &index, &info);
}
- *srcPtr = loop;
- } while (distance > 0);
- return distance;
+ *overlap = distance;
+ }
+
+ assert(linePtr != lastLinePtr);
+
+ TkTextIndexSetToStartOfLine2(srcPtr, linePtr);
+ TkTextIndexForwBytes(textPtr, srcPtr, byteOffset, srcPtr);
+
+ return true;
}
/*
@@ -5475,8 +9584,8 @@ TkTextMeasureDown(
*
* If this function is called with distance=0, it simply finds the first
* index on the same display line as srcPtr. However, there is a another
- * function TkTextFindDisplayLineEnd designed just for that task which is
- * probably better to use.
+ * function TkTextFindDisplayLineStartEnd designed just for that task
+ * which is probably better to use.
*
* Results:
* *dstPtr is filled in with the index of the first character on a
@@ -5484,7 +9593,8 @@ TkTextMeasureDown(
* pixels above the pixel just below an imaginary display line that
* contains srcPtr. If the display line that covers this coordinate
* actually extends above the coordinate, then return any excess pixels
- * in *overlap, if that is non-NULL.
+ * in *overlap. The return valus is 'false' if we are already at top
+ * of view, otherwise the return valus is 'false'.
*
* Side effects:
* None.
@@ -5492,103 +9602,93 @@ TkTextMeasureDown(
*--------------------------------------------------------------
*/
-static void
+static bool
+AlreadyAtTop(
+ const TkText *textPtr)
+{
+ const TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ if (!dInfoPtr->dLinePtr) {
+ return true;
+ }
+ return dInfoPtr->topPixelOffset == 0 && TkTextIndexIsStartOfText(&dInfoPtr->dLinePtr->index);
+}
+
+static bool
MeasureUp(
TkText *textPtr, /* Text widget in which to measure. */
- const TkTextIndex *srcPtr, /* Index of character from which to start
- * measuring. */
- int distance, /* Vertical distance in pixels measured from
- * the pixel just below the lowest one in
- * srcPtr's line. */
+ const TkTextIndex *srcPtr, /* Index of character from which to start measuring. */
+ int distance, /* Vertical distance in pixels measured from the pixel just below
+ * the lowest one in srcPtr's line. */
TkTextIndex *dstPtr, /* Index to fill in with result. */
- int *overlap) /* Used to store how much of the final index
- * returned was not covered by 'distance'. */
+ int32_t *overlap) /* Used to store how much of the final index returned was not covered
+ * by 'distance'. */
{
- int lineNum; /* Number of current line. */
- int bytesToCount; /* Maximum number of bytes to measure in
- * current line. */
- TkTextIndex index;
- DLine *dlPtr, *lowestPtr;
+ TkTextLine *linePtr;
+ TkTextLine *startLinePtr;
+ unsigned byteOffset;
- bytesToCount = srcPtr->byteIndex + 1;
- index.tree = srcPtr->tree;
- for (lineNum = TkBTreeLinesTo(textPtr, srcPtr->linePtr); lineNum >= 0;
- lineNum--) {
- /*
- * Layout an entire text line (potentially > 1 display line).
- *
- * For the first line, which contains srcPtr, only layout the part up
- * through srcPtr (bytesToCount is non-infinite to accomplish this).
- * Make a list of all the display lines in backwards order (the lowest
- * DLine on the screen is first in the list).
- */
+ assert(overlap);
+ assert(dstPtr);
- index.linePtr = TkBTreeFindLine(srcPtr->tree, textPtr, lineNum);
- index.byteIndex = 0;
- TkTextFindDisplayLineEnd(textPtr, &index, 0, NULL);
- lineNum = TkBTreeLinesTo(textPtr, index.linePtr);
- lowestPtr = NULL;
- do {
- dlPtr = LayoutDLine(textPtr, &index);
- dlPtr->nextPtr = lowestPtr;
- lowestPtr = dlPtr;
- TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
- bytesToCount -= dlPtr->byteCount;
- } while (bytesToCount>0 && index.linePtr==dlPtr->index.linePtr);
+ if (TkTextIndexIsStartOfText(srcPtr) && AlreadyAtTop(textPtr)) {
+ return false;
+ }
+
+ *dstPtr = *srcPtr;
+ startLinePtr = TkBTreeGetStartLine(textPtr);
+ linePtr = TkTextIndexGetLine(srcPtr);
+
+ if (TestIfLinesUpToDate(srcPtr)) {
+ int pixelHeight;
/*
- * Scan through the display lines to see if we've covered enough
- * vertical distance. If so, save the starting index for the line at
- * the desired location. If distance was zero to start with then we
- * simply get the first index on the same display line as the original
- * index.
+ * No display line height calculation is pending (not in required range),
+ * this is fine, now we can use the B-Tree for the measurement.
+ *
+ * Note that TkBTreePixelsTo is measuring up to the logical line.
*/
- for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
- distance -= dlPtr->height;
- if (distance <= 0) {
- *dstPtr = dlPtr->index;
-
- /*
- * dstPtr is the start of a display line that is or is not
- * the start of a logical line. If it is the start of a
- * logical line, we must check whether this line is merged
- * with the previous logical line, and if so we must adjust
- * dstPtr to the start of the display line since a display
- * line start needs to be returned.
- */
- if (!IsStartOfNotMergedLine(textPtr, dstPtr)) {
- TkTextFindDisplayLineEnd(textPtr, dstPtr, 0, NULL);
- }
+ pixelHeight = TkBTreePixelsTo(textPtr, linePtr);
+ pixelHeight += GetPixelsTo(textPtr, srcPtr, true, NULL);
+ pixelHeight -= distance;
- if (overlap != NULL) {
- *overlap = -distance;
- }
- break;
- }
+ if (pixelHeight <= 0) {
+ linePtr = startLinePtr;
+ byteOffset = *overlap = 0;
+ } else {
+ linePtr = TkBTreeFindPixelLine(srcPtr->tree, textPtr, pixelHeight, overlap);
+ byteOffset = FindDisplayLineOffset(textPtr, linePtr, overlap);
}
+ } else {
+ DisplayInfo info;
/*
- * Discard the display lines, then either return or prepare for the
- * next display line to lay out.
+ * Search up line by line until we have found the start line for given distance.
*/
- FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
- if (distance <= 0) {
- return;
- }
- bytesToCount = INT_MAX; /* Consider all chars. in next line. */
- }
+ linePtr = ComputeDisplayLineInfo(textPtr, srcPtr, &info);
+ SaveDisplayLines(textPtr, &info, false);
+ distance -= GetPixelsTo(textPtr, srcPtr, true, &info);
- /*
- * Ran off the beginning of the text. Return the first character in the
- * text.
- */
+ while (linePtr != startLinePtr && distance > 0) {
+ TkTextIndexSetToLastChar2(dstPtr, linePtr->prevPtr);
+ linePtr = ComputeDisplayLineInfo(textPtr, dstPtr, &info);
+ SaveDisplayLines(textPtr, &info, false);
+ distance -= info.pixels;
+ }
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, dstPtr);
- if (overlap != NULL) {
- *overlap = 0;
+ if (distance < 0) {
+ *overlap = -distance;
+ byteOffset = FindDisplayLineOffset(textPtr, linePtr, overlap);
+ } else {
+ byteOffset = *overlap = 0;
+ }
}
+
+ TkTextIndexSetToStartOfLine2(dstPtr, linePtr);
+ TkTextIndexForwBytes(textPtr, dstPtr, byteOffset, dstPtr);
+ return true;
}
/*
@@ -5614,9 +9714,8 @@ TkTextSeeCmd(
TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "see". */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "see". */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextIndex index;
@@ -5628,18 +9727,20 @@ TkTextSeeCmd(
Tcl_WrongNumArgs(interp, 2, objv, "index");
return TCL_ERROR;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[2], &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
return TCL_ERROR;
}
+ if (TkTextIsDeadPeer(textPtr)) {
+ return TCL_OK;
+ }
/*
* If the specified position is the extra line at the end of the text,
* round it back to the last real line.
*/
- if (TkBTreeLinesTo(textPtr, index.linePtr)
- == TkBTreeNumLines(index.tree, textPtr)) {
- TkTextIndexBackChars(textPtr, &index, 1, &index, COUNT_INDICES);
+ if (TkTextIndexGetLine(&index) == TkBTreeGetLastLine(textPtr)) {
+ TkTextIndexSetToLastChar2(&index, TkTextIndexGetLine(&index)->prevPtr);
}
/*
@@ -5661,12 +9762,11 @@ TkTextSeeCmd(
}
/*
- * Find the display line containing the desired index. dlPtr may be NULL
- * if the widget is not mapped. [Bug #641778]
+ * Take into account that the desired index is past the visible text.
+ * It's also possible that the widget is not yet mapped.
*/
- dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index);
- if (dlPtr == NULL) {
+ if (!(dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, &index))) {
return TCL_OK;
}
@@ -5678,12 +9778,10 @@ TkTextSeeCmd(
* they are elided.
*/
- byteCount = TkTextIndexCountBytes(textPtr, &dlPtr->index, &index);
- for (chunkPtr = dlPtr->chunkPtr; chunkPtr != NULL ;
+ byteCount = TkTextIndexCountBytes(&dlPtr->index, &index);
+ for (chunkPtr = dlPtr->chunkPtr;
+ chunkPtr && byteCount >= chunkPtr->numBytes;
chunkPtr = chunkPtr->nextPtr) {
- if (byteCount < chunkPtr->numBytes) {
- break;
- }
byteCount -= chunkPtr->numBytes;
}
@@ -5693,12 +9791,13 @@ TkTextSeeCmd(
* region.
*/
- if (chunkPtr != NULL) {
- chunkPtr->bboxProc(textPtr, chunkPtr, byteCount,
+ if (chunkPtr) {
+ chunkPtr->layoutProcs->bboxProc(
+ textPtr, chunkPtr, byteCount,
dlPtr->y + dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
- dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
- &height);
+ dlPtr->baseline - dlPtr->spaceAbove,
+ &x, &y, &width, &height);
delta = x - dInfoPtr->curXPixelOffset;
oneThird = lineWidth/3;
if (delta < 0) {
@@ -5720,10 +9819,7 @@ TkTextSeeCmd(
}
}
dInfoPtr->flags |= DINFO_OUT_OF_DATE;
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- dInfoPtr->flags |= REDRAW_PENDING;
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
+ DisplayTextWhenIdle(textPtr);
return TCL_OK;
}
@@ -5750,12 +9846,11 @@ TkTextXviewCmd(
TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "xview". */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "xview". */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- int type, count;
+ int count;
double fraction;
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
@@ -5763,48 +9858,33 @@ TkTextXviewCmd(
}
if (objc == 2) {
- GetXView(interp, textPtr, 0);
+ GetXView(interp, textPtr, false);
return TCL_OK;
}
- type = TextGetScrollInfoObj(interp, textPtr, objc, objv,
- &fraction, &count);
- switch (type) {
- case TKTEXT_SCROLL_ERROR:
+ switch (TextGetScrollInfoObj(interp, textPtr, objc, objv, &fraction, &count)) {
+ case SCROLL_ERROR:
return TCL_ERROR;
- case TKTEXT_SCROLL_MOVETO:
- if (fraction > 1.0) {
- fraction = 1.0;
- }
- if (fraction < 0) {
- fraction = 0;
- }
- dInfoPtr->newXPixelOffset = (int)
- (fraction * dInfoPtr->maxLength + 0.5);
+ case SCROLL_MOVETO:
+ dInfoPtr->newXPixelOffset = (int) (MIN(1.0, MAX(0.0, fraction))*dInfoPtr->maxLength + 0.5);
break;
- case TKTEXT_SCROLL_PAGES: {
+ case SCROLL_PAGES: {
int pixelsPerPage;
- pixelsPerPage = (dInfoPtr->maxX-dInfoPtr->x) - 2*textPtr->charWidth;
- if (pixelsPerPage < 1) {
- pixelsPerPage = 1;
- }
- dInfoPtr->newXPixelOffset += pixelsPerPage * count;
+ pixelsPerPage = dInfoPtr->maxX - dInfoPtr->x - 2*textPtr->charWidth;
+ dInfoPtr->newXPixelOffset += count*MAX(1, pixelsPerPage);
break;
}
- case TKTEXT_SCROLL_UNITS:
- dInfoPtr->newXPixelOffset += count * textPtr->charWidth;
+ case SCROLL_UNITS:
+ dInfoPtr->newXPixelOffset += count*textPtr->charWidth;
break;
- case TKTEXT_SCROLL_PIXELS:
+ case SCROLL_PIXELS:
dInfoPtr->newXPixelOffset += count;
break;
}
dInfoPtr->flags |= DINFO_OUT_OF_DATE;
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- dInfoPtr->flags |= REDRAW_PENDING;
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
+ DisplayTextWhenIdle(textPtr);
return TCL_OK;
}
@@ -5827,70 +9907,50 @@ TkTextXviewCmd(
static void
YScrollByPixels(
- TkText *textPtr, /* Widget to scroll. */
- int offset) /* Amount by which to scroll, in pixels.
- * Positive means that information later in
- * text becomes visible, negative means that
- * information earlier in the text becomes
- * visible. */
+ TkText *textPtr, /* Widget to scroll. */
+ int offset) /* Amount by which to scroll, in pixels. Positive means that
+ * information later in text becomes visible, negative means
+ * that information earlier in the text becomes visible. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
if (offset < 0) {
/*
* Now we want to measure up this number of pixels from the top of the
- * screen. But the top line may not be totally visible. Note that
- * 'count' is negative here.
+ * screen. But the top line may not be totally visible.
*/
- offset -= CalculateDisplayLineHeight(textPtr,
- &textPtr->topIndex, NULL, NULL) - dInfoPtr->topPixelOffset;
- MeasureUp(textPtr, &textPtr->topIndex, -offset,
- &textPtr->topIndex, &dInfoPtr->newTopPixelOffset);
- } else if (offset > 0) {
- DLine *dlPtr;
- TkTextLine *lastLinePtr;
- TkTextIndex newIdx;
+ offset -= CalculateDisplayLineHeight(textPtr, &textPtr->topIndex, NULL);
+ offset += dInfoPtr->topPixelOffset;
+ if (!MeasureUp(textPtr, &textPtr->topIndex, -offset,
+ &textPtr->topIndex, &dInfoPtr->newTopPixelOffset)) {
+ return; /* already at top, we cannot scroll */
+ }
+ } else if (offset > 0) {
/*
* Scrolling down by pixels. Layout lines starting at the top index
* and count through the desired vertical distance.
*/
- lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
offset += dInfoPtr->topPixelOffset;
- dInfoPtr->newTopPixelOffset = 0;
- while (offset > 0) {
- dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
- dlPtr->nextPtr = NULL;
- TkTextIndexForwBytes(textPtr, &textPtr->topIndex,
- dlPtr->byteCount, &newIdx);
- if (offset <= dlPtr->height) {
- /*
- * Adjust the top overlap accordingly.
- */
-
- dInfoPtr->newTopPixelOffset = offset;
- }
- offset -= dlPtr->height;
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
- if (newIdx.linePtr == lastLinePtr || offset <= 0) {
- break;
- }
- textPtr->topIndex = newIdx;
+ if (!MeasureDown(textPtr, &textPtr->topIndex, offset, &dInfoPtr->newTopPixelOffset, true)) {
+ return; /* already at end, we cannot scroll */
}
+ TkTextIndexToByteIndex(&textPtr->topIndex);
} else {
/*
* offset = 0, so no scrolling required.
*/
-
return;
}
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
- dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
+
+ assert(TkTextIndexIsEndOfText(&textPtr->topIndex) ?
+ dInfoPtr->newTopPixelOffset == 0 :
+ dInfoPtr->newTopPixelOffset < CalculateDisplayLineHeight(textPtr, &textPtr->topIndex, NULL));
+
+ DisplayTextWhenIdle(textPtr);
+ dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
}
/*
@@ -5910,122 +9970,184 @@ YScrollByPixels(
*----------------------------------------------------------------------
*/
-static void
-YScrollByLines(
- TkText *textPtr, /* Widget to scroll. */
- int offset) /* Amount by which to scroll, in display
- * lines. Positive means that information
- * later in text becomes visible, negative
- * means that information earlier in the text
- * becomes visible. */
-{
- int i, bytesToCount, lineNum;
- TkTextIndex newIdx, index;
- TkTextLine *lastLinePtr;
- TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- DLine *dlPtr, *lowestPtr;
+static bool
+ScrollUp(
+ TkText *textPtr, /* Widget to scroll. */
+ unsigned offset) /* Amount by which to scroll, in display lines. */
+{
+ TkTextLine *linePtr;
+ unsigned byteOffset;
+ DisplayInfo info;
+ bool upToDate;
+
+ assert(offset > 0);
+
+ /*
+ * Scrolling up, to show earlier information in the text.
+ */
+
+ if (AlreadyAtTop(textPtr)) {
+ return false;
+ }
+
+ if (TkTextIndexIsStartOfText(&textPtr->dInfoPtr->dLinePtr->index)) {
+ textPtr->dInfoPtr->newTopPixelOffset = 0;
+ return true;
+ }
+
+ upToDate = TestIfLinesUpToDate(&textPtr->topIndex);
+ linePtr = ComputeDisplayLineInfo(textPtr, &textPtr->topIndex, &info);
+
+ if (upToDate) {
+ const TkTextDispLineInfo *dispLineInfo;
+
+ assert(!info.dLinePtr);
- if (offset < 0) {
/*
- * Must scroll up (to show earlier information in the text). The code
- * below is similar to that in MeasureUp, except that it counts lines
- * instead of pixels.
+ * The display line information is complete for the required range, so
+ * use it for finding the requested display line.
*/
- bytesToCount = textPtr->topIndex.byteIndex + 1;
- index.tree = textPtr->sharedTextPtr->tree;
- offset--; /* Skip line containing topIndex. */
- for (lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr);
- lineNum >= 0; lineNum--) {
- index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineNum);
- index.byteIndex = 0;
- lowestPtr = NULL;
- do {
- dlPtr = LayoutDLine(textPtr, &index);
- dlPtr->nextPtr = lowestPtr;
- lowestPtr = dlPtr;
- TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount,&index);
- bytesToCount -= dlPtr->byteCount;
- } while ((bytesToCount > 0)
- && (index.linePtr == dlPtr->index.linePtr));
-
- for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
- offset++;
- if (offset == 0) {
- textPtr->topIndex = dlPtr->index;
-
- /*
- * topIndex is the start of a logical line. However, if
- * the eol of the previous logical line is elided, then
- * topIndex may be elsewhere than the first character of
- * a display line, which is unwanted. Adjust to the start
- * of the display line, if needed.
- * topIndex is the start of a display line that is or is
- * not the start of a logical line. If it is the start of
- * a logical line, we must check whether this line is
- * merged with the previous logical line, and if so we
- * must adjust topIndex to the start of the display line.
- */
- if (!IsStartOfNotMergedLine(textPtr, &textPtr->topIndex)) {
- TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex,
- 0, NULL);
- }
+ linePtr = TkBTreePrevDisplayLine(textPtr, linePtr, &info.displayLineNo, offset);
+ dispLineInfo = TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo;
+ byteOffset = dispLineInfo ? dispLineInfo->entry[info.displayLineNo].byteOffset : 0;
+ } else {
+ TkTextLine *firstLinePtr;
+ TkTextIndex index;
- break;
- }
- }
+ /*
+ * The display line information is incomplete, so we do a search line by line.
+ * The computed display lines will be saved for displaying.
+ */
- /*
- * Discard the display lines, then either return or prepare for
- * the next display line to lay out.
- */
+ firstLinePtr = TkBTreeGetStartLine(textPtr);
+ index = textPtr->topIndex;
+ SaveDisplayLines(textPtr, &info, false);
+ info.numDispLines = info.displayLineNo + 1;
- FreeDLines(textPtr, lowestPtr, NULL, DLINE_FREE);
- if (offset >= 0) {
- goto scheduleUpdate;
+ while (true) {
+ if (info.numDispLines > offset) {
+ byteOffset = (info.entry - offset)->byteOffset;
+ break;
}
- bytesToCount = INT_MAX;
+ offset -= info.numDispLines;
+ if (linePtr == firstLinePtr) {
+ byteOffset = 0;
+ break;
+ }
+ TkTextIndexSetToLastChar2(&index, linePtr->prevPtr);
+ linePtr = ComputeDisplayLineInfo(textPtr, &index, &info);
+ SaveDisplayLines(textPtr, &info, false);
+ assert(!TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo
+ || info.entry == TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo->entry +
+ info.numDispLines - 1);
}
+ }
+
+ TkTextIndexSetToStartOfLine2(&textPtr->topIndex, linePtr);
+ TkTextIndexForwBytes(textPtr, &textPtr->topIndex, byteOffset, &textPtr->topIndex);
+
+ return true;
+}
+
+static bool
+ScrollDown(
+ TkText *textPtr, /* Widget to scroll. */
+ unsigned offset) /* Amount by which to scroll, in display lines. */
+{
+ TkTextLine *linePtr;
+ unsigned byteOffset;
+ DisplayInfo info;
+ bool upToDate;
+
+ assert(offset > 0);
+
+ /*
+ * Scrolling down, to show later information in the text.
+ */
+
+ if (AlreadyAtBottom(textPtr)) {
+ return false;
+ }
+
+ upToDate = TkRangeListIsEmpty(textPtr->dInfoPtr->lineMetricUpdateRanges);
+ linePtr = ComputeDisplayLineInfo(textPtr, &textPtr->topIndex, &info);
+
+ if (upToDate) {
+ const TkTextDispLineInfo *dispLineInfo;
+
+ assert(!info.dLinePtr);
/*
- * Ran off the beginning of the text. Return the first character in
- * the text, and make sure we haven't left anything overlapping the
- * top window border.
+ * The display line information is complete for the required range, so
+ * use it for finding the requested display line.
*/
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &textPtr->topIndex);
- dInfoPtr->newTopPixelOffset = 0;
+ linePtr = TkBTreeNextDisplayLine(textPtr, linePtr, &info.displayLineNo, offset);
+ dispLineInfo = TkBTreeLinePixelInfo(textPtr, linePtr)->dispLineInfo;
+ byteOffset = dispLineInfo ? dispLineInfo->entry[info.displayLineNo].byteOffset : 0;
} else {
+ TkTextLine *lastLinePtr;
+
/*
- * Scrolling down, to show later information in the text. Just count
- * lines from the current top of the window.
+ * The display line information is incomplete, so we do a search line by line.
+ * The computed display lines will be saved for displaying.
*/
- lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr));
- for (i = 0; i < offset; i++) {
- dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
- if (dlPtr->length == 0 && dlPtr->height == 0) {
- offset++;
- }
- dlPtr->nextPtr = NULL;
- TkTextIndexForwBytes(textPtr, &textPtr->topIndex,
- dlPtr->byteCount, &newIdx);
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE);
- if (newIdx.linePtr == lastLinePtr) {
+ lastLinePtr = TkBTreeGetLastLine(textPtr);
+ ComputeMissingMetric(textPtr, &info, THRESHOLD_LINE_OFFSET, offset);
+ SaveDisplayLines(textPtr, &info, true);
+ info.numDispLines -= info.displayLineNo;
+
+ while (true) {
+ if (info.numDispLines == offset) {
+ byteOffset = 0;
+ linePtr = linePtr->nextPtr;
+ break;
+ }
+ if (info.numDispLines > offset) {
+ byteOffset = (info.entry + offset)->byteOffset;
break;
}
- textPtr->topIndex = newIdx;
+ offset -= info.numDispLines;
+ if (TkTextIndexGetLine(&info.index) == lastLinePtr) {
+ byteOffset = (info.entry + info.numDispLines - 1)->byteOffset;
+ break;
+ }
+ linePtr = ComputeDisplayLineInfo(textPtr, &info.index, &info);
+ ComputeMissingMetric(textPtr, &info, THRESHOLD_LINE_OFFSET, offset);
+ SaveDisplayLines(textPtr, &info, true);
}
}
- scheduleUpdate:
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- Tcl_DoWhenIdle(DisplayText, textPtr);
+ TkTextIndexSetToStartOfLine2(&textPtr->topIndex, linePtr);
+ TkTextIndexForwBytes(textPtr, &textPtr->topIndex, byteOffset, &textPtr->topIndex);
+ return true;
+}
+
+static void
+YScrollByLines(
+ TkText *textPtr, /* Widget to scroll. */
+ int offset) /* Amount by which to scroll, in display lines. Positive means
+ * that information later in text becomes visible, negative
+ * means that information earlier in the text becomes visible. */
+{
+ assert(textPtr);
+
+ if (offset < 0) {
+ if (!ScrollUp(textPtr, -offset)) {
+ return;
+ }
+ } else if (offset > 0) {
+ if (!ScrollDown(textPtr, offset)) {
+ return;
+ }
+ } else {
+ return;
}
- dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
+
+ DisplayTextWhenIdle(textPtr);
+ textPtr->dInfoPtr->flags |= DINFO_OUT_OF_DATE|REPICK_NEEDED;
}
/*
@@ -6046,17 +10168,82 @@ YScrollByLines(
*--------------------------------------------------------------
*/
+static int
+MakePixelIndex(
+ TkText *textPtr, /* The text widget. */
+ unsigned pixelIndex, /* Pixel-index of desired line (0 means first pixel of first
+ * line of text). */
+ TkTextIndex *indexPtr) /* Structure to fill in. */
+{
+ TkTextLine *linePtr;
+ TkTextLine *lastLinePtr;
+ int32_t pixelOffset;
+
+ assert(!TkTextIsDeadPeer(textPtr));
+
+ TkTextIndexClear(indexPtr, textPtr);
+ linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree, textPtr, pixelIndex, &pixelOffset);
+ lastLinePtr = TkBTreeGetLastLine(textPtr);
+
+ if (linePtr != lastLinePtr) {
+ int byteOffset = FindDisplayLineOffset(textPtr, linePtr, &pixelOffset);
+ TkTextIndexSetByteIndex2(indexPtr, linePtr, byteOffset);
+ } else {
+ assert(lastLinePtr->prevPtr); /* MakePixelIndex will not be called if peer is empty */
+ linePtr = TkBTreeGetLogicalLine(textPtr->sharedTextPtr, textPtr, linePtr->prevPtr);
+ TkTextIndexSetToLastChar2(indexPtr, linePtr);
+ FindDisplayLineStartEnd(textPtr, indexPtr, DISP_LINE_START, DLINE_CACHE);
+ pixelOffset = CalculateDisplayLineHeight(textPtr, indexPtr, NULL) - 1;
+ }
+
+ return MAX(0, pixelOffset);
+}
+
+static void
+Repick(
+ ClientData clientData) /* Information about widget. */
+{
+ TkText *textPtr = (TkText *) clientData;
+
+ if (!TkTextReleaseIfDestroyed(textPtr)) {
+ textPtr->dInfoPtr->flags &= ~REPICK_NEEDED;
+ textPtr->dInfoPtr->currChunkPtr = NULL;
+ textPtr->dInfoPtr->repickTimer = NULL;
+ textPtr->dontRepick = false;
+ TkTextPickCurrent(textPtr, &textPtr->pickEvent);
+ }
+}
+
+static void
+DelayRepick(
+ TkText *textPtr)
+{
+ assert(textPtr->dInfoPtr->flags & REPICK_NEEDED);
+
+ if (textPtr->responsiveness > 0) {
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+
+ if (dInfoPtr->repickTimer) {
+ Tcl_DeleteTimerHandler(dInfoPtr->repickTimer);
+ } else {
+ textPtr->refCount += 1;
+ }
+ textPtr->dontRepick = true;
+ dInfoPtr->flags &= ~REPICK_NEEDED;
+ dInfoPtr->repickTimer = Tcl_CreateTimerHandler(textPtr->responsiveness, Repick, textPtr);
+ }
+}
+
int
TkTextYviewCmd(
TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "yview". */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "yview". */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- int pickPlace, type;
+ int pickPlace;
int pixels, count;
int switchLength;
double fraction;
@@ -6067,7 +10254,7 @@ TkTextYviewCmd(
}
if (objc == 2) {
- GetYView(interp, textPtr, 0);
+ GetYView(interp, textPtr, false);
return TCL_OK;
}
@@ -6077,11 +10264,9 @@ TkTextYviewCmd(
pickPlace = 0;
if (Tcl_GetString(objv[2])[0] == '-') {
- register const char *switchStr =
- Tcl_GetStringFromObj(objv[2], &switchLength);
+ const char *switchStr = Tcl_GetStringFromObj(objv[2], &switchLength);
- if ((switchLength >= 2) && (strncmp(switchStr, "-pickplace",
- (unsigned) switchLength) == 0)) {
+ if (switchLength >= 2 && strncmp(switchStr, "-pickplace", switchLength) == 0) {
pickPlace = 1;
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index");
@@ -6089,122 +10274,116 @@ TkTextYviewCmd(
}
}
}
- if ((objc == 3) || pickPlace) {
+
+ if (objc == 3 || pickPlace) {
int lineNum;
- if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineNum, 0, &index);
+ if (Tcl_GetIntFromObj(interp, objv[2 + pickPlace], &lineNum) == TCL_OK) {
+ TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, 0, &index);
TkTextSetYView(textPtr, &index, 0);
- return TCL_OK;
- }
+ } else {
+ /*
+ * The argument must be a regular text index.
+ */
+ Tcl_ResetResult(interp);
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2 + pickPlace], &index)) {
+ return TCL_ERROR;
+ }
+ TkTextSetYView(textPtr, &index, pickPlace ? TK_TEXT_PICKPLACE : 0);
+ }
+ } else {
/*
- * The argument must be a regular text index.
+ * New syntax: dispatch based on objv[2].
*/
- Tcl_ResetResult(interp);
- if (TkTextGetObjIndex(interp, textPtr, objv[2+pickPlace],
- &index) != TCL_OK) {
+ switch (TextGetScrollInfoObj(interp, textPtr, objc, objv, &fraction, &count)) {
+ case SCROLL_ERROR:
return TCL_ERROR;
- }
- TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0));
- return TCL_OK;
- }
+ case SCROLL_MOVETO: {
+ int numPixels = TkBTreeNumPixels(textPtr);
+ int topMostPixel;
- /*
- * New syntax: dispatch based on objv[2].
- */
-
- type = TextGetScrollInfoObj(interp, textPtr, objc,objv, &fraction, &count);
- switch (type) {
- case TKTEXT_SCROLL_ERROR:
- return TCL_ERROR;
- case TKTEXT_SCROLL_MOVETO: {
- int numPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree,
- textPtr);
- int topMostPixel;
+ if (numPixels == 0 || TkTextIsDeadPeer(textPtr)) {
+ /*
+ * If the window is totally empty no scrolling is needed, and the
+ * MakePixelIndex call below will fail.
+ */
+ break;
+ }
+ if (fraction > 1.0) {
+ fraction = 1.0;
+ } else if (fraction < 0.0) {
+ fraction = 0.0;
+ }
- if (numPixels == 0) {
/*
- * If the window is totally empty no scrolling is needed, and the
- * TkTextMakePixelIndex call below will fail.
+ * Calculate the pixel count for the new topmost pixel in the topmost
+ * line of the window. Note that the interpretation of 'fraction' is
+ * that it counts from 0 (top pixel in buffer) to 1.0 (one pixel past
+ * the last pixel in buffer).
*/
- break;
- }
- if (fraction > 1.0) {
- fraction = 1.0;
- }
- if (fraction < 0) {
- fraction = 0;
- }
+ topMostPixel = MAX(0, MIN((int) (fraction*numPixels + 0.5), numPixels - 1));
- /*
- * Calculate the pixel count for the new topmost pixel in the topmost
- * line of the window. Note that the interpretation of 'fraction' is
- * that it counts from 0 (top pixel in buffer) to 1.0 (one pixel past
- * the last pixel in buffer).
- */
+ /*
+ * This function returns the number of pixels by which the given line
+ * should overlap the top of the visible screen.
+ *
+ * This is then used to provide smooth scrolling.
+ */
- topMostPixel = (int) (0.5 + fraction * numPixels);
- if (topMostPixel >= numPixels) {
- topMostPixel = numPixels -1;
+ pixels = MakePixelIndex(textPtr, topMostPixel, &index);
+ TkTextSetYView(textPtr, &index, pixels);
+ break;
}
-
- /*
- * This function returns the number of pixels by which the given line
- * should overlap the top of the visible screen.
- *
- * This is then used to provide smooth scrolling.
- */
-
- pixels = TkTextMakePixelIndex(textPtr, topMostPixel, &index);
- TkTextSetYView(textPtr, &index, pixels);
- break;
- }
- case TKTEXT_SCROLL_PAGES: {
- /*
- * Scroll up or down by screenfuls. Actually, use the window height
- * minus two lines, so that there's some overlap between adjacent
- * pages.
- */
-
- int height = dInfoPtr->maxY - dInfoPtr->y;
-
- if (textPtr->charHeight * 4 >= height) {
+ case SCROLL_PAGES: {
/*
- * A single line is more than a quarter of the display. We choose
- * to scroll by 3/4 of the height instead.
+ * Scroll up or down by screenfuls. Actually, use the window height
+ * minus two lines, so that there's some overlap between adjacent
+ * pages.
*/
- pixels = 3*height/4;
- if (pixels < textPtr->charHeight) {
+ int height = dInfoPtr->maxY - dInfoPtr->y;
+
+ if (textPtr->lineHeight*4 >= height) {
/*
- * But, if 3/4 of the height is actually less than a single
- * typical character height, then scroll by the minimum of the
- * linespace or the total height.
+ * A single line is more than a quarter of the display. We choose
+ * to scroll by 3/4 of the height instead.
*/
- if (textPtr->charHeight < height) {
- pixels = textPtr->charHeight;
- } else {
- pixels = height;
+ pixels = 3*height/4;
+ if (pixels < textPtr->lineHeight) {
+ /*
+ * But, if 3/4 of the height is actually less than a single
+ * typical character height, then scroll by the minimum of the
+ * linespace or the total height.
+ */
+
+ if (textPtr->lineHeight < height) {
+ pixels = textPtr->lineHeight;
+ } else {
+ pixels = height;
+ }
}
+ pixels *= count;
+ } else {
+ pixels = (height - 2*textPtr->lineHeight)*count;
}
- pixels *= count;
- } else {
- pixels = (height - 2*textPtr->charHeight)*count;
+ YScrollByPixels(textPtr, pixels);
+ break;
+ }
+ case SCROLL_PIXELS:
+ YScrollByPixels(textPtr, count);
+ break;
+ case SCROLL_UNITS:
+ YScrollByLines(textPtr, count);
+ break;
}
- YScrollByPixels(textPtr, pixels);
- break;
}
- case TKTEXT_SCROLL_PIXELS:
- YScrollByPixels(textPtr, count);
- break;
- case TKTEXT_SCROLL_UNITS:
- YScrollByLines(textPtr, count);
- break;
+
+ if (dInfoPtr->flags & REPICK_NEEDED) {
+ DelayRepick(textPtr);
}
return TCL_OK;
}
@@ -6212,35 +10391,6 @@ TkTextYviewCmd(
/*
*--------------------------------------------------------------
*
- * TkTextPendingsync --
- *
- * This function checks if any line heights are not up-to-date.
- *
- * Results:
- * Returns a boolean true if it is the case, or false if all line
- * heights are up-to-date.
- *
- * Side effects:
- * None.
- *
- *--------------------------------------------------------------
- */
-
-Bool
-TkTextPendingsync(
- TkText *textPtr) /* Information about text widget. */
-{
- TextDInfo *dInfoPtr = textPtr->dInfoPtr;
-
- return (
- ((dInfoPtr->metricEpoch == -1) &&
- (dInfoPtr->lastMetricUpdateLine == dInfoPtr->currentMetricUpdateLine)) ?
- 0 : 1);
-}
-
-/*
- *--------------------------------------------------------------
- *
* TkTextScanCmd --
*
* This function is invoked to process the "scan" option for the widget
@@ -6258,25 +10408,22 @@ TkTextPendingsync(
int
TkTextScanCmd(
- register TkText *textPtr, /* Information about text widget. */
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "scan". */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "scan". */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextIndex index;
int c, x, y, totalScroll, gain=10;
size_t length;
- if ((objc != 5) && (objc != 6)) {
+ if (objc != 5 && objc != 6) {
Tcl_WrongNumArgs(interp, 2, objv, "mark x y");
- Tcl_AppendResult(interp, " or \"", Tcl_GetString(objv[0]),
- " scan dragto x y ?gain?\"", NULL);
+ Tcl_AppendResult(interp, " or \"", Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", NULL);
/*
- * Ought to be:
- * Tcl_WrongNumArgs(interp, 2, objc, "dragto x y ?gain?");
+ * Ought to be: Tcl_WrongNumArgs(interp, 2, objc, "dragto x y ?gain?");
*/
return TCL_ERROR;
}
@@ -6286,12 +10433,12 @@ TkTextScanCmd(
if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
return TCL_ERROR;
}
- if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK)) {
+ if (objc == 6 && Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK) {
return TCL_ERROR;
}
c = Tcl_GetString(objv[2])[0];
length = strlen(Tcl_GetString(objv[2]));
- if (c=='d' && strncmp(Tcl_GetString(objv[2]), "dragto", length)==0) {
+ if (c == 'd' && strncmp(Tcl_GetString(objv[2]), "dragto", length) == 0) {
int newX, maxX;
/*
@@ -6321,30 +10468,24 @@ TkTextScanCmd(
totalScroll = gain*(dInfoPtr->scanMarkY - y);
if (totalScroll != dInfoPtr->scanTotalYScroll) {
index = textPtr->topIndex;
- YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll);
+ YScrollByPixels(textPtr, totalScroll - dInfoPtr->scanTotalYScroll);
dInfoPtr->scanTotalYScroll = totalScroll;
- if ((index.linePtr == textPtr->topIndex.linePtr) &&
- (index.byteIndex == textPtr->topIndex.byteIndex)) {
+ if (TkTextIndexIsEqual(&index, &textPtr->topIndex)) {
dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = y;
}
}
dInfoPtr->flags |= DINFO_OUT_OF_DATE;
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- dInfoPtr->flags |= REDRAW_PENDING;
- Tcl_DoWhenIdle(DisplayText, textPtr);
- }
- } else if (c=='m' && strncmp(Tcl_GetString(objv[2]), "mark", length)==0) {
+ DisplayTextWhenIdle(textPtr);
+ } else if (c == 'm' && strncmp(Tcl_GetString(objv[2]), "mark", length) == 0) {
dInfoPtr->scanMarkXPixel = dInfoPtr->newXPixelOffset;
dInfoPtr->scanMarkX = x;
dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = y;
} else {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "bad scan option \"%s\": must be mark or dragto",
- Tcl_GetString(objv[2])));
- Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "scan option",
- Tcl_GetString(objv[2]), NULL);
+ "bad scan option \"%s\": must be mark or dragto", Tcl_GetString(objv[2])));
+ Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "INDEX", "scan option", Tcl_GetString(objv[2]), NULL);
return TCL_ERROR;
}
return TCL_OK;
@@ -6377,32 +10518,32 @@ TkTextScanCmd(
static void
GetXView(
- Tcl_Interp *interp, /* If "report" is FALSE, string describing
- * visible range gets stored in the interp's
- * result. */
+ Tcl_Interp *interp, /* If "report" is FALSE, string describing visible range gets stored
+ * in the interp's result. */
TkText *textPtr, /* Information about text widget. */
- int report) /* Non-zero means report info to scrollbar if
- * it has changed. */
+ bool report) /* 'true' means report info to scrollbar if it has changed. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
double first, last;
+ int xMin, xMax;
int code;
Tcl_Obj *listObj;
if (dInfoPtr->maxLength > 0) {
- first = ((double) dInfoPtr->curXPixelOffset)
- / dInfoPtr->maxLength;
- last = ((double) (dInfoPtr->curXPixelOffset + dInfoPtr->maxX
- - dInfoPtr->x))/dInfoPtr->maxLength;
+ first = ((double) dInfoPtr->curXPixelOffset)/dInfoPtr->maxLength;
+ last = ((double) (dInfoPtr->curXPixelOffset + dInfoPtr->maxX - dInfoPtr->x))/dInfoPtr->maxLength;
if (last > 1.0) {
last = 1.0;
}
+ xMin = dInfoPtr->curXPixelOffset;
+ xMax = xMin + dInfoPtr->maxX - dInfoPtr->x;
} else {
- first = 0;
+ first = 0.0;
last = 1.0;
+ xMin = xMax = dInfoPtr->curXPixelOffset;
}
if (!report) {
- listObj = Tcl_NewListObj(0, NULL);
+ listObj = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
Tcl_SetObjResult(interp, listObj);
@@ -6412,17 +10553,21 @@ GetXView(
FP_EQUAL_SCALE(last, dInfoPtr->xScrollLast, dInfoPtr->maxLength)) {
return;
}
+
dInfoPtr->xScrollFirst = first;
dInfoPtr->xScrollLast = last;
- if (textPtr->xScrollCmd != NULL) {
- char buf1[TCL_DOUBLE_SPACE+1];
- char buf2[TCL_DOUBLE_SPACE+1];
+ dInfoPtr->curPixelPos.xFirst = xMin;
+ dInfoPtr->curPixelPos.xLast = xMax;
+
+ if (textPtr->xScrollCmd) {
+ char buf1[TCL_DOUBLE_SPACE + 1];
+ char buf2[TCL_DOUBLE_SPACE + 1];
Tcl_DString buf;
buf1[0] = ' ';
buf2[0] = ' ';
- Tcl_PrintDouble(NULL, first, buf1+1);
- Tcl_PrintDouble(NULL, last, buf2+1);
+ Tcl_PrintDouble(NULL, first, buf1 + 1);
+ Tcl_PrintDouble(NULL, last, buf2 + 1);
Tcl_DStringInit(&buf);
Tcl_DStringAppend(&buf, textPtr->xScrollCmd, -1);
Tcl_DStringAppend(&buf, buf1, -1);
@@ -6469,93 +10614,17 @@ GetXView(
*----------------------------------------------------------------------
*/
-static int
+static unsigned
GetYPixelCount(
- TkText *textPtr, /* Information about text widget. */
- DLine *dlPtr) /* Information about the layout of a given
- * index. */
+ TkText *textPtr, /* Information about text widget. */
+ DLine *dlPtr) /* Information about the layout of a given index. */
{
- TkTextLine *linePtr = dlPtr->index.linePtr;
- int count;
-
- /*
- * Get the pixel count to the top of dlPtr's logical line. The rest of the
- * function is then concerned with updating 'count' for any difference
- * between the top of the logical line and the display line.
- */
-
- count = TkBTreePixelsTo(textPtr, linePtr);
-
- /*
- * For the common case where this dlPtr is also the start of the logical
- * line, we can return right away.
- */
-
- if (IsStartOfNotMergedLine(textPtr, &dlPtr->index)) {
- return count;
- }
-
- /*
- * Add on the logical line's height to reach one pixel beyond the bottom
- * of the logical line. And then subtract off the heights of all the
- * display lines from dlPtr to the end of its logical line.
- *
- * A different approach would be to lay things out from the start of the
- * logical line until we reach dlPtr, but since none of those are
- * pre-calculated, it'll usually take a lot longer. (But there are cases
- * where it would be more efficient: say if we're on the second of 1000
- * wrapped lines all from a single logical line - but that sort of
- * optimization is left for the future).
- */
-
- count += TkBTreeLinePixelCount(textPtr, linePtr);
-
- do {
- count -= dlPtr->height;
- if (dlPtr->nextPtr == NULL) {
- /*
- * We've run out of pre-calculated display lines, so we have to
- * lay them out ourselves until the end of the logical line. Here
- * is where we could be clever and ask: what's faster, to layout
- * all lines from here to line-end, or all lines from the original
- * dlPtr to the line-start? We just assume the former.
- */
-
- TkTextIndex index;
- int notFirst = 0;
-
- while (1) {
- TkTextIndexForwBytes(textPtr, &dlPtr->index,
- dlPtr->byteCount, &index);
- if (notFirst) {
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
- }
- if (index.linePtr != linePtr) {
- break;
- }
- dlPtr = LayoutDLine(textPtr, &index);
-
- if (tkTextDebug) {
- char string[TK_POS_CHARS];
-
- /*
- * Debugging is enabled, so keep a log of all the lines
- * whose height was recalculated. The test suite uses this
- * information.
- */
-
- TkTextPrintIndex(textPtr, &index, string);
- LOG("tk_textHeightCalc", string);
- }
- count -= dlPtr->height;
- notFirst = 1;
- }
- break;
- }
- dlPtr = dlPtr->nextPtr;
- } while (dlPtr->index.linePtr == linePtr);
+ TkTextLine *linePtr;
+ DisplayInfo info;
- return count;
+ linePtr = ComputeDisplayLineInfo(textPtr, &dlPtr->index, &info);
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
+ return TkBTreePixelsTo(textPtr, linePtr) + info.entry->pixels - info.entry->height;
}
/*
@@ -6584,30 +10653,30 @@ GetYPixelCount(
static void
GetYView(
- Tcl_Interp *interp, /* If "report" is FALSE, string describing
- * visible range gets stored in the interp's
- * result. */
+ Tcl_Interp *interp, /* If "report" is 'false', string describing visible range gets
+ * stored in the interp's result. */
TkText *textPtr, /* Information about text widget. */
- int report) /* Non-zero means report info to scrollbar if
- * it has changed. */
+ bool report) /* 'true' means report info to scrollbar if it has changed. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
double first, last;
DLine *dlPtr;
int totalPixels, code, count;
+ int yMin, yMax;
Tcl_Obj *listObj;
dlPtr = dInfoPtr->dLinePtr;
- if (dlPtr == NULL) {
+ if (!dlPtr) {
return;
}
- totalPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, textPtr);
+ totalPixels = TkBTreeNumPixels(textPtr);
if (totalPixels == 0) {
first = 0.0;
last = 1.0;
+ yMin = yMax = dInfoPtr->topPixelOffset;
} else {
/*
* Get the pixel count for the first visible pixel of the first
@@ -6615,7 +10684,7 @@ GetYView(
* then we use 'topPixelOffset' to get the difference.
*/
- count = GetYPixelCount(textPtr, dlPtr);
+ count = yMin = GetYPixelCount(textPtr, dlPtr);
first = (count + dInfoPtr->topPixelOffset) / (double) totalPixels;
/*
@@ -6625,7 +10694,7 @@ GetYView(
* 'totalPixels' and not 'totalPixels-1'.
*/
- while (1) {
+ while (dlPtr) {
int extra;
count += dlPtr->height;
@@ -6640,9 +10709,6 @@ GetYView(
count -= extra;
break;
}
- if (dlPtr->nextPtr == NULL) {
- break;
- }
dlPtr = dlPtr->nextPtr;
}
@@ -6677,47 +10743,53 @@ GetYView(
Tcl_Panic("Counted more pixels (%d) than expected (%d) total "
"pixels in text widget scroll bar calculation.", count,
totalPixels);
+#elif 0 /* TODO: still happens sometimes, why? */
+ fprintf(stderr, "warning: Counted more pixels (%d) than expected (%d)\n",
+ count, totalPixels);
#endif
+
count = totalPixels;
}
- last = ((double) count)/((double)totalPixels);
+ yMax = count;
+ last = ((double) count)/((double) totalPixels);
}
if (!report) {
- listObj = Tcl_NewListObj(0,NULL);
+ listObj = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
Tcl_SetObjResult(interp, listObj);
- return;
- }
-
- if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) &&
- FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) {
- return;
- }
-
- dInfoPtr->yScrollFirst = first;
- dInfoPtr->yScrollLast = last;
- if (textPtr->yScrollCmd != NULL) {
- char buf1[TCL_DOUBLE_SPACE+1];
- char buf2[TCL_DOUBLE_SPACE+1];
- Tcl_DString buf;
-
- buf1[0] = ' ';
- buf2[0] = ' ';
- Tcl_PrintDouble(NULL, first, buf1+1);
- Tcl_PrintDouble(NULL, last, buf2+1);
- Tcl_DStringInit(&buf);
- Tcl_DStringAppend(&buf, textPtr->yScrollCmd, -1);
- Tcl_DStringAppend(&buf, buf1, -1);
- Tcl_DStringAppend(&buf, buf2, -1);
- code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0);
- Tcl_DStringFree(&buf);
- if (code != TCL_OK) {
- Tcl_AddErrorInfo(interp,
- "\n (vertical scrolling command executed by text)");
- Tcl_BackgroundException(interp, code);
+ } else {
+ dInfoPtr->curPixelPos.yFirst = yMin + dInfoPtr->topPixelOffset;
+ dInfoPtr->curPixelPos.yLast = yMax + dInfoPtr->topPixelOffset;
+
+ if (!FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) ||
+ !FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) {
+ dInfoPtr->yScrollFirst = first;
+ dInfoPtr->yScrollLast = last;
+
+ if (textPtr->yScrollCmd) {
+ char buf1[TCL_DOUBLE_SPACE + 1];
+ char buf2[TCL_DOUBLE_SPACE + 1];
+ Tcl_DString buf;
+
+ buf1[0] = ' ';
+ buf2[0] = ' ';
+ Tcl_PrintDouble(NULL, first, buf1 + 1);
+ Tcl_PrintDouble(NULL, last, buf2 + 1);
+ Tcl_DStringInit(&buf);
+ Tcl_DStringAppend(&buf, textPtr->yScrollCmd, -1);
+ Tcl_DStringAppend(&buf, buf1, -1);
+ Tcl_DStringAppend(&buf, buf2, -1);
+ code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0);
+ Tcl_DStringFree(&buf);
+ if (code != TCL_OK) {
+ Tcl_AddErrorInfo(interp,
+ "\n (vertical scrolling command executed by text)");
+ Tcl_BackgroundException(interp, code);
+ }
+ }
}
}
}
@@ -6744,17 +10816,59 @@ static void
AsyncUpdateYScrollbar(
ClientData clientData) /* Information about widget. */
{
- register TkText *textPtr = clientData;
+ TkText *textPtr = clientData;
- textPtr->dInfoPtr->scrollbarTimer = NULL;
+ if (!TkTextReleaseIfDestroyed(textPtr)) {
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- if (!(textPtr->flags & DESTROYED)) {
- GetYView(textPtr->interp, textPtr, 1);
+ if (!dInfoPtr->insideLineMetricUpdate) {
+ textPtr->dInfoPtr->scrollbarTimer = NULL;
+ GetYView(textPtr->interp, textPtr, true);
+ }
}
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FindCachedDLine --
+ *
+ * This function is called to find the cached line for given text
+ * index.
+ *
+ * Results:
+ * The return value is a pointer to the cached DLine found, or NULL
+ * if not available.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
+static DLine *
+FindCachedDLine(
+ TkText *textPtr,
+ const TkTextIndex *indexPtr)
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ DLine *dlPtr;
+
+ for (dlPtr = dInfoPtr->cachedDLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
+ if (TkBTreeLinePixelInfo(textPtr, TkTextIndexGetLine(&dlPtr->index))->epoch
+ == dInfoPtr->lineMetricUpdateEpoch
+ && TkTextIndexCompare(indexPtr, &dlPtr->index) >= 0) {
+ TkTextIndex index = dlPtr->index;
+
+ TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index);
+ if (TkTextIndexCompare(indexPtr, &index) < 0) {
+ DEBUG(stats.numHits++);
+ return dlPtr;
+ }
+ }
}
+
+ return NULL;
}
/*
@@ -6779,22 +10893,19 @@ AsyncUpdateYScrollbar(
static DLine *
FindDLine(
TkText *textPtr, /* Widget record for text widget. */
- register DLine *dlPtr, /* Pointer to first in list of DLines to
- * search. */
+ DLine *dlPtr, /* Pointer to first in list of DLines to search. */
const TkTextIndex *indexPtr)/* Index of desired character. */
{
- DLine *dlPtrPrev;
- TkTextIndex indexPtr2;
+ DLine *lastDlPtr;
- if (dlPtr == NULL) {
+ if (!dlPtr) {
return NULL;
}
- if (TkBTreeLinesTo(NULL, indexPtr->linePtr)
- < TkBTreeLinesTo(NULL, dlPtr->index.linePtr)) {
+
+ if (TkTextIndexGetLineNumber(indexPtr, NULL) < TkTextIndexGetLineNumber(&dlPtr->index, NULL)) {
/*
* The first display line is already past the desired line.
*/
-
return dlPtr;
}
@@ -6805,60 +10916,55 @@ FindDLine(
* display line is after the desired index.
*/
- while (TkTextIndexCmp(&dlPtr->index,indexPtr) < 0) {
- dlPtrPrev = dlPtr;
+ while (TkTextIndexCompare(&dlPtr->index, indexPtr) < 0) {
+ lastDlPtr = dlPtr;
dlPtr = dlPtr->nextPtr;
- if (dlPtr == NULL) {
+ if (!dlPtr) {
+ TkTextIndex index2;
/*
* We're past the last display line, either because the desired
* index lies past the visible text, or because the desired index
- * is on the last display line.
+ * is on the last display line showing the last logical line.
*/
- indexPtr2 = dlPtrPrev->index;
- TkTextIndexForwBytes(textPtr, &indexPtr2, dlPtrPrev->byteCount,
- &indexPtr2);
- if (TkTextIndexCmp(&indexPtr2,indexPtr) > 0) {
+ index2 = lastDlPtr->index;
+ TkTextIndexForwBytes(textPtr, &index2, lastDlPtr->byteCount, &index2);
+ if (TkTextIndexCompare(&index2, indexPtr) > 0) {
/*
- * The desired index is on the last display line.
- * --> return this display line.
+ * The desired index is on the last display line, hence return this display line.
*/
- dlPtr = dlPtrPrev;
+ dlPtr = lastDlPtr;
+ break;
} else {
/*
- * The desired index is past the visible text. There is no
- * display line displaying something at the desired index.
- * --> return NULL.
+ * The desired index is past the visible text. There is no display line
+ * displaying something at the desired index, hence return NULL.
*/
+ return NULL;
}
- break;
}
- if (TkTextIndexCmp(&dlPtr->index,indexPtr) > 0) {
+ if (TkTextIndexCompare(&dlPtr->index, indexPtr) > 0) {
/*
* If we're here then we would normally expect that:
- * dlPtrPrev->index <= indexPtr < dlPtr->index
+ * lastDlPtr->index <= indexPtr < dlPtr->index
* i.e. we have found the searched display line being dlPtr.
* However it is possible that some DLines were unlinked
* previously, leading to a situation where going through
* the list of display lines skips display lines that did
* exist just a moment ago.
*/
- indexPtr2 = dlPtrPrev->index;
- TkTextIndexForwBytes(textPtr, &indexPtr2, dlPtrPrev->byteCount,
- &indexPtr2);
- if (TkTextIndexCmp(&indexPtr2,indexPtr) > 0) {
+
+ TkTextIndex index;
+ TkTextIndexForwBytes(textPtr, &lastDlPtr->index, lastDlPtr->byteCount, &index);
+ if (TkTextIndexCompare(&index, indexPtr) > 0) {
/*
- * Confirmed:
- * dlPtrPrev->index <= indexPtr < dlPtr->index
- * --> return dlPtrPrev.
+ * Confirmed: lastDlPtr->index <= indexPtr < dlPtr->index
*/
- dlPtr = dlPtrPrev;
+ dlPtr = lastDlPtr;
} else {
/*
- * The last (rightmost) index shown by dlPtrPrev is still
- * before the desired index. This may be because there was
- * previously a display line between dlPtrPrev and dlPtr
- * and this display line has been unlinked.
- * --> return dlPtr.
+ * The last (rightmost) index shown by dlPtrPrev is still before the desired
+ * index. This may be because there was previously a display line between
+ * dlPtrPrev and dlPtr and this display line has been unlinked.
*/
}
break;
@@ -6871,15 +10977,12 @@ FindDLine(
/*
*----------------------------------------------------------------------
*
- * IsStartOfNotMergedLine --
+ * TkTextGetFirstXPixel --
*
- * This function checks whether the given index is the start of a
- * logical line that is not merged with the previous logical line
- * (due to elision of the eol of the previous line).
+ * Get first x-pixel position in current widget.
*
* Results:
- * Returns whether the given index denotes the first index of a
-* logical line not merged with its previous line.
+ * Returns first x-pixel.
*
* Side effects:
* None.
@@ -6887,35 +10990,132 @@ FindDLine(
*----------------------------------------------------------------------
*/
-static int
-IsStartOfNotMergedLine(
- TkText *textPtr, /* Widget record for text widget. */
- const TkTextIndex *indexPtr) /* Index to check. */
+int
+TkTextGetFirstXPixel(
+ const TkText *textPtr) /* Widget record for text widget. */
{
- TkTextIndex indexPtr2;
+ assert(textPtr);
+ return textPtr->dInfoPtr->x;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextGetFirstYPixel --
+ *
+ * Get first y-pixel position in current widget.
+ *
+ * Results:
+ * Returns first y-pixel.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- if (indexPtr->byteIndex != 0) {
- /*
- * Not the start of a logical line.
- */
- return 0;
- }
+int
+TkTextGetFirstYPixel(
+ const TkText *textPtr) /* Widget record for text widget. */
+{
+ assert(textPtr);
+ return textPtr->dInfoPtr->y;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextGetLastXPixel --
+ *
+ * Get last x-pixel position in current widget.
+ *
+ * Results:
+ * Returns last x-pixel.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- if (TkTextIndexBackBytes(textPtr, indexPtr, 1, &indexPtr2)) {
- /*
- * indexPtr is the first index of the text widget.
- */
- return 1;
- }
+int
+TkTextGetLastXPixel(
+ const TkText *textPtr) /* Widget record for text widget. */
+{
+ assert(textPtr);
+ return textPtr->dInfoPtr->maxX - 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextGetLastYPixel --
+ *
+ * Get last y-pixel position in current widget.
+ *
+ * Results:
+ * Returns last y-pixel.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- if (!TkTextIsElided(textPtr, &indexPtr2, NULL)) {
- /*
- * The eol of the line just before indexPtr is elided.
- */
- return 1;
- }
+int
+TkTextGetLastYPixel(
+ const TkText *textPtr) /* Widget record for text widget. */
+{
+ assert(textPtr);
+ return textPtr->dInfoPtr->maxY - 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextCountVisibleImages --
+ *
+ * Return the number of visible embedded images.
+ *
+ * Results:
+ * Returns number of visible embedded images.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- return 0;
+unsigned
+TkTextCountVisibleImages(
+ const TkText *textPtr) /* Widget record for text widget. */
+{
+ assert(textPtr);
+ return textPtr->dInfoPtr->countImages;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextCountVisibleWindows --
+ *
+ * Return the number of visible embedded windows.
+ *
+ * Results:
+ * Returns number of visible embedded windows.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+unsigned
+TkTextCountVisibleWindows(
+ const TkText *textPtr) /* Widget record for text widget. */
+{
+ assert(textPtr);
+ return textPtr->dInfoPtr->countWindows;
}
/*
@@ -6928,7 +11128,9 @@ IsStartOfNotMergedLine(
*
* Results:
* The index at *indexPtr is modified to refer to the character on the
- * display that is closest to (x,y).
+ * display that is closest to (x,y). It returns whether we have found
+ * the same chunk as before, this implies that the tags belonging to
+ * this chunk did not change.
*
* Side effects:
* None.
@@ -6936,21 +11138,22 @@ IsStartOfNotMergedLine(
*----------------------------------------------------------------------
*/
-void
+bool
TkTextPixelIndex(
TkText *textPtr, /* Widget record for text widget. */
- int x, int y, /* Pixel coordinates of point in widget's
- * window. */
- TkTextIndex *indexPtr, /* This index gets filled in with the index of
- * the character nearest to (x,y). */
- int *nearest) /* If non-NULL then gets set to 0 if (x,y) is
- * actually over the returned index, and 1 if
- * it is just nearby (e.g. if x,y is on the
- * border of the widget). */
+ int x, int y, /* Pixel coordinates of point in widget's window. */
+ TkTextIndex *indexPtr, /* This index gets filled in with the index of the character
+ * nearest to (x,y). */
+ bool *nearest) /* If non-NULL then gets set to false if (x,y) is actually over the
+ * returned index, and true if it is just nearby (e.g. if x,y is on
+ * the border of the widget). */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- register DLine *dlPtr, *validDlPtr;
- int nearby = 0;
+ DLine *dlPtr = NULL;
+ DLine *currDLinePtr;
+ TkTextDispChunk *currChunkPtr;
+ bool nearby = false;
+ unsigned epoch;
/*
* Make sure that all of the layout information about what's displayed
@@ -6969,66 +11172,161 @@ TkTextPixelIndex(
if (y < dInfoPtr->y) {
y = dInfoPtr->y;
- x = dInfoPtr->x;
- nearby = 1;
+ nearby = true;
}
if (x >= dInfoPtr->maxX) {
x = dInfoPtr->maxX - 1;
- nearby = 1;
+ nearby = true;
}
if (x < dInfoPtr->x) {
x = dInfoPtr->x;
- nearby = 1;
+ nearby = true;
}
/*
* Find the display line containing the desired y-coordinate.
*/
- if (dInfoPtr->dLinePtr == NULL) {
- if (nearest != NULL) {
- *nearest = 1;
+ if (!dInfoPtr->dLinePtr) {
+ if (nearest) {
+ *nearest = true;
}
*indexPtr = textPtr->topIndex;
- return;
+ return true;
}
- for (dlPtr = validDlPtr = dInfoPtr->dLinePtr;
- y >= (dlPtr->y + dlPtr->height);
- dlPtr = dlPtr->nextPtr) {
- if (dlPtr->chunkPtr != NULL) {
- validDlPtr = dlPtr;
- }
- if (dlPtr->nextPtr == NULL) {
- /*
- * Y-coordinate is off the bottom of the displayed text. Use the
- * last character on the last line.
- */
- x = dInfoPtr->maxX - 1;
- nearby = 1;
- break;
+ epoch = TkBTreeEpoch(textPtr->sharedTextPtr->tree);
+ currChunkPtr = dInfoPtr->currChunkPtr;
+
+ if (currChunkPtr && dInfoPtr->currChunkIndex.stateEpoch == epoch) {
+ currDLinePtr = dInfoPtr->currDLinePtr;
+
+ assert(currChunkPtr->stylePtr); /* otherwise the chunk has been expired */
+
+ if (currDLinePtr->y <= y && y < currDLinePtr->y + currDLinePtr->height) {
+ int rx = x - dInfoPtr->x + dInfoPtr->curXPixelOffset;
+
+ if (currChunkPtr->x <= rx && rx < currChunkPtr->x + currChunkPtr->width) {
+ /*
+ * We have luck, it's inside the cache.
+ */
+
+ *indexPtr = dInfoPtr->currChunkIndex;
+ DLineIndexOfX(textPtr, currChunkPtr, x, indexPtr);
+ if (nearest) {
+ *nearest = nearby;
+ }
+ return true;
+ }
+
+ dlPtr = currDLinePtr;
}
}
- if (dlPtr->chunkPtr == NULL) {
- dlPtr = validDlPtr;
+
+ if (!dlPtr) {
+ DLine *validDlPtr = dInfoPtr->dLinePtr;
+
+ for (dlPtr = validDlPtr; y >= dlPtr->y + dlPtr->height; dlPtr = dlPtr->nextPtr) {
+ if (dlPtr->chunkPtr) {
+ validDlPtr = dlPtr;
+ }
+ if (!dlPtr->nextPtr) {
+ /*
+ * Y-coordinate is off the bottom of the displayed text. Use the
+ * last character on the last line.
+ */
+
+ if (nearest) {
+ *nearest = true;
+ }
+ dInfoPtr->currChunkPtr = NULL;
+ *indexPtr = dlPtr->index;
+ assert(dlPtr->byteCount > 0);
+ TkTextIndexForwBytes(textPtr, indexPtr, dlPtr->byteCount - 1, indexPtr);
+ return false;
+ }
+ }
+ if (!dlPtr->chunkPtr) {
+ dlPtr = validDlPtr;
+ }
}
- if (nearest != NULL) {
+ currChunkPtr = DLineChunkOfX(textPtr, dlPtr, x, indexPtr, &nearby);
+
+ if (nearest) {
*nearest = nearby;
}
- DlineIndexOfX(textPtr, dlPtr, x, indexPtr);
+ if (!nearby) {
+ /*
+ * Cache the result.
+ */
+
+ dInfoPtr->currChunkIndex = *indexPtr;
+ TkTextIndexSetEpoch(&dInfoPtr->currChunkIndex, epoch); /* price it as actual */
+ dInfoPtr->currChunkPtr = currChunkPtr;
+ dInfoPtr->currDLinePtr = dlPtr;
+ } else {
+ dInfoPtr->currChunkPtr = NULL;
+ }
+
+ DLineIndexOfX(textPtr, currChunkPtr, x, indexPtr);
+ return false;
}
/*
*----------------------------------------------------------------------
*
- * DlineIndexOfX --
+ * DLineIndexOfX --
+ *
+ * Given an x coordinate in a display line, increase the byte position
+ * of the index according to the character closest to that location.
+ *
+ * Together with DLineChunkOfX this is effectively the opposite of
+ * DLineXOfIndex.
+ *
+ * Note: use DLineChunkOfX for the computation of the chunk.
+ *
+ * Results:
+ * The index at *indexPtr is modified to refer to the character on the
+ * display line that is closest to x.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DLineIndexOfX(
+ TkText *textPtr, /* Widget record for text widget. */
+ TkTextDispChunk *chunkPtr, /* Chunk which contains the character. */
+ int x, /* Pixel x coordinate of point in widget's window. */
+ TkTextIndex *indexPtr) /* This byte offset of this index will be increased according
+ * to the character position. */
+{
+ /*
+ * If the chunk has more than one byte in it, ask it which character is at
+ * the desired location. In this case we can manipulate
+ * 'indexPtr->byteIndex' directly, because we know we're staying inside a
+ * single logical line.
+ */
+
+ if (chunkPtr && chunkPtr->numBytes > 1) {
+ x -= textPtr->dInfoPtr->x - textPtr->dInfoPtr->curXPixelOffset;
+ TkTextIndexAddToByteIndex(indexPtr, chunkPtr->layoutProcs->measureProc(chunkPtr, x));
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DLineChunkOfX --
*
* Given an x coordinate in a display line, find the index of the
* character closest to that location.
*
- * This is effectively the opposite of DlineXOfIndex.
+ * This is effectively the opposite of DLineXOfIndex.
*
* Results:
* The index at *indexPtr is modified to refer to the character on the
@@ -7040,76 +11338,65 @@ TkTextPixelIndex(
*----------------------------------------------------------------------
*/
-static void
-DlineIndexOfX(
+static TkTextDispChunk *
+DLineChunkOfX(
TkText *textPtr, /* Widget record for text widget. */
- DLine *dlPtr, /* Display information for this display
- * line. */
- int x, /* Pixel x coordinate of point in widget's
- * window. */
- TkTextIndex *indexPtr) /* This index gets filled in with the index of
- * the character nearest to x. */
+ DLine *dlPtr, /* Display information for this display line. */
+ int x, /* Pixel x coordinate of point in widget's window. */
+ TkTextIndex *indexPtr, /* This index gets filled in with the index of the character
+ * nearest to x. */
+ bool *nearby) /* If non-NULL then gets set to true if (x,y) is not actually over the
+ * returned index, but never set to false. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- register TkTextDispChunk *chunkPtr;
+ TkTextDispChunk *chunkPtr;
+ TkTextDispChunkSection *sectionPtr;
+ unsigned countBytes;
/*
- * Scan through the line's chunks to find the one that contains the
- * desired x-coordinate. Before doing this, translate the x-coordinate
- * from the coordinate system of the window to the coordinate system of
- * the line (to take account of x-scrolling).
+ * Scan through the line's chunks to find the one that contains the desired x-coordinate.
+ * Before doing this, translate the x-coordinate from the coordinate system of the window
+ * to the coordinate system of the line (to take account of x-scrolling).
*/
- *indexPtr = dlPtr->index;
- x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset;
chunkPtr = dlPtr->chunkPtr;
+ *indexPtr = dlPtr->index;
- if (chunkPtr == NULL || x == 0) {
- /*
- * This may occur if everything is elided, or if we're simply already
- * at the beginning of the line.
- */
-
- return;
+ if (!chunkPtr) {
+ /* this may happen if everything is elided */
+ if (nearby) {
+ *nearby = true;
+ }
+ return chunkPtr;
}
- while (x >= (chunkPtr->x + chunkPtr->width)) {
- /*
- * Note that this forward then backward movement of the index can be
- * problematic at the end of the buffer (we can't move forward, and
- * then when we move backward, we do, leading to the wrong position).
- * Hence when x == 0 we take special action above.
- */
-
- if (TkTextIndexForwBytes(NULL,indexPtr,chunkPtr->numBytes,indexPtr)) {
- /*
- * We've reached the end of the text.
- */
+ x -= dInfoPtr->x - dInfoPtr->curXPixelOffset;
- TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES);
- return;
+ if (x < chunkPtr->x) {
+ if (chunkPtr->stylePtr->sValuePtr->indentBg) {
+ /* if -indentbackground is enabled, then do not trigger when hovering the margin */
+ *nearby = true;
}
- if (chunkPtr->nextPtr == NULL) {
- /*
- * We've reached the end of the display line.
- */
+ return chunkPtr;
+ }
- TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, COUNT_INDICES);
- return;
- }
- chunkPtr = chunkPtr->nextPtr;
+ sectionPtr = chunkPtr->sectionPtr;
+ countBytes = chunkPtr->byteOffset;
+
+ while (sectionPtr->nextPtr && x >= sectionPtr->nextPtr->chunkPtr->x) {
+ countBytes += sectionPtr->numBytes;
+ sectionPtr = sectionPtr->nextPtr;
}
- /*
- * If the chunk has more than one byte in it, ask it which character is at
- * the desired location. In this case we can manipulate
- * 'indexPtr->byteIndex' directly, because we know we're staying inside a
- * single logical line.
- */
+ chunkPtr = sectionPtr->chunkPtr;
- if (chunkPtr->numBytes > 1) {
- indexPtr->byteIndex += chunkPtr->measureProc(chunkPtr, x);
+ while (chunkPtr->nextPtr && x >= chunkPtr->x + chunkPtr->width) {
+ countBytes += chunkPtr->numBytes;
+ chunkPtr = chunkPtr->nextPtr;
}
+
+ TkTextIndexForwBytes(textPtr, indexPtr, countBytes, indexPtr);
+ return chunkPtr;
}
/*
@@ -7135,29 +11422,60 @@ DlineIndexOfX(
void
TkTextIndexOfX(
TkText *textPtr, /* Widget record for text widget. */
- int x, /* The x coordinate for which we want the
- * index. */
- TkTextIndex *indexPtr) /* Index of display line start, which will be
- * adjusted to the index under the given x
- * coordinate. */
+ int x, /* The x coordinate for which we want the index. */
+ TkTextIndex *indexPtr) /* Index of display line start, which will be adjusted to the
+ * index under the given x coordinate. */
{
- DLine *dlPtr = LayoutDLine(textPtr, indexPtr);
- DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x
- - textPtr->dInfoPtr->curXPixelOffset, indexPtr);
- FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+ TextDInfo *dInfoPtr;
+ DLine *dlPtr;
+
+ assert(textPtr);
+
+ if (TkTextIndexGetLine(indexPtr) == TkBTreeGetLastLine(textPtr)) {
+ return;
+ }
+
+ dInfoPtr = textPtr->dInfoPtr;
+ dlPtr = FindCachedDLine(textPtr, indexPtr);
+
+ if (!dlPtr
+ && !(dInfoPtr->flags & DINFO_OUT_OF_DATE)
+ && TkTextIndexCompare(indexPtr, &textPtr->topIndex) >= 0) {
+ dlPtr = FindDLine(textPtr, dInfoPtr->dLinePtr, indexPtr);
+ }
+ if (!dlPtr) {
+ DisplayInfo info;
+
+ ComputeDisplayLineInfo(textPtr, indexPtr, &info);
+ if (!(dlPtr = info.lastDLinePtr)) {
+ TkTextIndex index = *indexPtr;
+
+ /* we need display line start */
+ TkTextIndexBackBytes(textPtr, &index, info.byteOffset, &index);
+ dlPtr = LayoutDLine(&index, info.displayLineNo);
+ } else if ((info.lastDLinePtr = info.lastDLinePtr->prevPtr)) {
+ dlPtr->prevPtr = info.lastDLinePtr->nextPtr = NULL;
+ } else {
+ info.dLinePtr = NULL;
+ }
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_CACHE);
+ FreeDLines(textPtr, info.dLinePtr, NULL, DLINE_FREE_TEMP);
+ }
+ x += dInfoPtr->x - dInfoPtr->curXPixelOffset;
+ DLineIndexOfX(textPtr, DLineChunkOfX(textPtr, dlPtr, x, indexPtr, NULL), x, indexPtr);
}
/*
*----------------------------------------------------------------------
*
- * DlineXOfIndex --
+ * DLineXOfIndex --
*
* Given a relative byte index on a given display line (i.e. the number
* of byte indices from the beginning of the given display line), find
* the x coordinate of that index within the abstract display line,
* without adjusting for the x-scroll state of the line.
*
- * This is effectively the opposite of DlineIndexOfX.
+ * This is effectively the opposite of DLineIndexOfX.
*
* NB. The 'byteIndex' is relative to the display line, NOT the logical
* line.
@@ -7172,42 +11490,50 @@ TkTextIndexOfX(
*/
static int
-DlineXOfIndex(
+DLineXOfIndex(
TkText *textPtr, /* Widget record for text widget. */
- DLine *dlPtr, /* Display information for this display
- * line. */
- int byteIndex) /* The byte index for which we want the
- * coordinate. */
+ DLine *dlPtr, /* Display information for this display line. */
+ int byteIndex) /* The byte index for which we want the coordinate. */
{
- register TkTextDispChunk *chunkPtr = dlPtr->chunkPtr;
- int x = 0;
+ TkTextDispChunkSection *sectionPtr, *nextPtr;
+ TkTextDispChunk *chunkPtr;
+ int x;
- if (byteIndex == 0 || chunkPtr == NULL) {
- return x;
+ if (byteIndex == 0 || !(sectionPtr = dlPtr->chunkPtr->sectionPtr)) {
+ return 0;
+ }
+
+ while (byteIndex >= sectionPtr->numBytes && (nextPtr = sectionPtr->nextPtr)) {
+ byteIndex -= sectionPtr->numBytes;
+ sectionPtr = nextPtr;
}
+ chunkPtr = sectionPtr->chunkPtr;
+ assert(chunkPtr);
+
/*
- * Scan through the line's chunks to find the one that contains the
- * desired byte index.
+ * Scan through the line's chunks to find the one that contains the desired byte index.
*/
- chunkPtr = dlPtr->chunkPtr;
- while (byteIndex > 0) {
+ x = 0;
+
+ while (true) {
if (byteIndex < chunkPtr->numBytes) {
- int y, width, height;
+ int unused;
- chunkPtr->bboxProc(textPtr, chunkPtr, byteIndex,
+ x = chunkPtr->x;
+ chunkPtr->layoutProcs->bboxProc(textPtr, chunkPtr, byteIndex,
dlPtr->y + dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
- dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
- &height);
+ dlPtr->baseline - dlPtr->spaceAbove, &x, &unused, &unused,
+ &unused);
break;
}
- byteIndex -= chunkPtr->numBytes;
- if (chunkPtr->nextPtr == NULL || byteIndex == 0) {
+ if (!chunkPtr->nextPtr || byteIndex == chunkPtr->numBytes) {
x = chunkPtr->x + chunkPtr->width;
break;
}
+ byteIndex -= chunkPtr->numBytes;
chunkPtr = chunkPtr->nextPtr;
}
@@ -7238,18 +11564,17 @@ int
TkTextIndexBbox(
TkText *textPtr, /* Widget record for text widget. */
const TkTextIndex *indexPtr,/* Index whose bounding box is desired. */
- int *xPtr, int *yPtr, /* Filled with index's upper-left
- * coordinate. */
+ int *xPtr, int *yPtr, /* Filled with index's upper-left coordinate. */
int *widthPtr, int *heightPtr,
/* Filled in with index's dimensions. */
- int *charWidthPtr) /* If the 'index' is at the end of a display
- * line and therefore takes up a very large
- * width, this is used to return the smaller
+ int *charWidthPtr) /* If the 'index' is at the end of a display line and therefore
+ * takes up a very large width, this is used to return the smaller
* width actually desired by the index. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
- register TkTextDispChunk *chunkPtr;
+ TkTextDispChunk *chunkPtr;
+ TkTextDispChunkSection *sectionPtr;
int byteCount;
/*
@@ -7269,12 +11594,12 @@ TkTextIndexBbox(
/*
* Two cases shall be trapped here because the logic later really
* needs dlPtr to be the display line containing indexPtr:
- * 1. if no display line contains the desired index (NULL dlPtr)
+ * 1. if no display line contains the desired index (NULL dlPtr or no chunk)
* 2. if indexPtr is before the first display line, in which case
* dlPtr currently points to the first display line
*/
- if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
+ if (!dlPtr || !dlPtr->chunkPtr || TkTextIndexCompare(&dlPtr->index, indexPtr) > 0) {
return -1;
}
@@ -7286,15 +11611,23 @@ TkTextIndexBbox(
* they are elided.
*/
- byteCount = TkTextIndexCountBytes(textPtr, &dlPtr->index, indexPtr);
- for (chunkPtr = dlPtr->chunkPtr; ; chunkPtr = chunkPtr->nextPtr) {
- if (chunkPtr == NULL) {
+ byteCount = TkTextIndexCountBytes(&dlPtr->index, indexPtr);
+ sectionPtr = dlPtr->chunkPtr->sectionPtr;
+
+ while (byteCount >= sectionPtr->numBytes) {
+ byteCount -= sectionPtr->numBytes;
+ if (!(sectionPtr = sectionPtr->nextPtr)) {
return -1;
}
- if (byteCount < chunkPtr->numBytes) {
- break;
- }
+ }
+
+ chunkPtr = sectionPtr->chunkPtr;
+
+ while (byteCount >= chunkPtr->numBytes) {
byteCount -= chunkPtr->numBytes;
+ if (!(chunkPtr = chunkPtr->nextPtr)) {
+ return -1;
+ }
}
/*
@@ -7304,19 +11637,19 @@ TkTextIndexBbox(
* coordinate on the screen. Translate it to reflect horizontal scrolling.
*/
- chunkPtr->bboxProc(textPtr, chunkPtr, byteCount,
+ chunkPtr->layoutProcs->bboxProc(textPtr, chunkPtr, byteCount,
dlPtr->y + dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
heightPtr);
*xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset;
- if ((byteCount == chunkPtr->numBytes-1) && (chunkPtr->nextPtr == NULL)) {
+
+ if (byteCount == chunkPtr->numBytes - 1 && !chunkPtr->nextPtr) {
/*
- * Last character in display line. Give it all the space up to the
- * line.
+ * Last character in display line. Give it all the space up to the line.
*/
- if (charWidthPtr != NULL) {
+ if (charWidthPtr) {
*charWidthPtr = dInfoPtr->maxX - *xPtr;
if (*charWidthPtr > textPtr->charWidth) {
*charWidthPtr = textPtr->charWidth;
@@ -7327,10 +11660,11 @@ TkTextIndexBbox(
}
*widthPtr = dInfoPtr->maxX - *xPtr;
} else {
- if (charWidthPtr != NULL) {
+ if (charWidthPtr) {
*charWidthPtr = *widthPtr;
}
}
+
if (*widthPtr == 0) {
/*
* With zero width (e.g. elided text) we just need to make sure it is
@@ -7341,22 +11675,25 @@ TkTextIndexBbox(
return -1;
}
} else {
- if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
+ if (*xPtr + *widthPtr <= dInfoPtr->x) {
return -1;
}
}
- if ((*xPtr + *widthPtr) > dInfoPtr->maxX) {
+
+ if (*xPtr + *widthPtr > dInfoPtr->maxX) {
*widthPtr = dInfoPtr->maxX - *xPtr;
if (*widthPtr <= 0) {
return -1;
}
}
- if ((*yPtr + *heightPtr) > dInfoPtr->maxY) {
+
+ if (*yPtr + *heightPtr > dInfoPtr->maxY) {
*heightPtr = dInfoPtr->maxY - *yPtr;
if (*heightPtr <= 0) {
return -1;
}
}
+
return 0;
}
@@ -7369,8 +11706,8 @@ TkTextIndexBbox(
* that character.
*
* Results:
- * Zero is returned if the character is on the screen. -1 means the
- * character isn't on the screen. If the return value is 0, then
+ * 'true' is returned if the character is on the screen. 'false' means
+ * the character isn't on the screen. If the return value is 'true', then
* information is returned in the variables pointed to by xPtr, yPtr,
* widthPtr, heightPtr, and basePtr.
*
@@ -7380,13 +11717,11 @@ TkTextIndexBbox(
*----------------------------------------------------------------------
*/
-int
-TkTextDLineInfo(
+bool
+TkTextGetDLineInfo(
TkText *textPtr, /* Widget record for text widget. */
- const TkTextIndex *indexPtr,/* Index of character whose bounding box is
- * desired. */
- int *xPtr, int *yPtr, /* Filled with line's upper-left
- * coordinate. */
+ const TkTextIndex *indexPtr,/* Index of character whose bounding box is desired. */
+ int *xPtr, int *yPtr, /* Filled with line's upper-left coordinate. */
int *widthPtr, int *heightPtr,
/* Filled in with line's dimensions. */
int *basePtr) /* Filled in with the baseline position,
@@ -7418,21 +11753,21 @@ TkTextDLineInfo(
* dlPtr currently points to the first display line
*/
- if ((dlPtr == NULL) || (TkTextIndexCmp(&dlPtr->index, indexPtr) > 0)) {
- return -1;
+ if (!dlPtr || TkTextIndexCompare(&dlPtr->index, indexPtr) > 0) {
+ return false;
}
- dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0);
+ dlx = dlPtr->chunkPtr ? dlPtr->chunkPtr->x : 0;
*xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx;
*widthPtr = dlPtr->length - dlx;
*yPtr = dlPtr->y;
- if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
+ if (dlPtr->y + dlPtr->height > dInfoPtr->maxY) {
*heightPtr = dInfoPtr->maxY - dlPtr->y;
} else {
*heightPtr = dlPtr->height;
}
*basePtr = dlPtr->baseline;
- return 0;
+ return true;
}
/*
@@ -7443,20 +11778,14 @@ static void
ElideBboxProc(
TkText *textPtr,
TkTextDispChunk *chunkPtr, /* Chunk containing desired char. */
- int index, /* Index of desired character within the
- * chunk. */
- int y, /* Topmost pixel in area allocated for this
- * line. */
+ int index, /* Index of desired character within the chunk. */
+ int y, /* Topmost pixel in area allocated for this line. */
int lineHeight, /* Height of line, in pixels. */
- int baseline, /* Location of line's baseline, in pixels
- * measured down from y. */
- int *xPtr, int *yPtr, /* Gets filled in with coords of character's
- * upper-left pixel. X-coord is in same
- * coordinate system as chunkPtr->x. */
- int *widthPtr, /* Gets filled in with width of character, in
- * pixels. */
- int *heightPtr) /* Gets filled in with height of character, in
- * pixels. */
+ int baseline, /* Location of line's baseline, in pixels measured down from y. */
+ int *xPtr, int *yPtr, /* Gets filled in with coords of character's upper-left pixel.
+ * X-coord is in same coordinate system as chunkPtr->x. */
+ int *widthPtr, /* Gets filled in with width of character, in pixels. */
+ int *heightPtr) /* Gets filled in with height of character, in pixels. */
{
*xPtr = chunkPtr->x;
*yPtr = y;
@@ -7470,584 +11799,9 @@ ElideBboxProc(
static int
ElideMeasureProc(
TkTextDispChunk *chunkPtr, /* Chunk containing desired coord. */
- int x) /* X-coordinate, in same coordinate system as
- * chunkPtr->x. */
-{
- return 0 /*chunkPtr->numBytes - 1*/;
-}
-
-/*
- *--------------------------------------------------------------
- *
- * TkTextCharLayoutProc --
- *
- * This function is the "layoutProc" for character segments.
- *
- * Results:
- * If there is something to display for the chunk then a non-zero value
- * is returned and the fields of chunkPtr will be filled in (see the
- * declaration of TkTextDispChunk in tkText.h for details). If zero is
- * returned it means that no characters from this chunk fit in the
- * window. If -1 is returned it means that this segment just doesn't need
- * to be displayed (never happens for text).
- *
- * Side effects:
- * Memory is allocated to hold additional information about the chunk.
- *
- *--------------------------------------------------------------
- */
-
-int
-TkTextCharLayoutProc(
- TkText *textPtr, /* Text widget being layed out. */
- TkTextIndex *indexPtr, /* Index of first character to lay out
- * (corresponds to segPtr and offset). */
- TkTextSegment *segPtr, /* Segment being layed out. */
- int byteOffset, /* Byte offset within segment of first
- * character to consider. */
- int maxX, /* Chunk must not occupy pixels at this
- * position or higher. */
- int maxBytes, /* Chunk must not include more than this many
- * characters. */
- int noCharsYet, /* Non-zero means no characters have been
- * assigned to this display line yet. */
- TkWrapMode wrapMode, /* How to handle line wrapping:
- * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or
- * TEXT_WRAPMODE_WORD. */
- register TkTextDispChunk *chunkPtr)
- /* Structure to fill in with information about
- * this chunk. The x field has already been
- * set by the caller. */
-{
- Tk_Font tkfont;
- int nextX, bytesThatFit, count;
- CharInfo *ciPtr;
- char *p;
- TkTextSegment *nextPtr;
- Tk_FontMetrics fm;
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- const char *line;
- int lineOffset;
- BaseCharInfo *bciPtr;
- Tcl_DString *baseString;
-#endif
-
- /*
- * Figure out how many characters will fit in the space we've got. Include
- * the next character, even though it won't fit completely, if any of the
- * following is true:
- * (a) the chunk contains no characters and the display line contains no
- * characters yet (i.e. the line isn't wide enough to hold even a
- * single character).
- * (b) at least one pixel of the character is visible, we have not
- * already exceeded the character limit, and the next character is a
- * white space character.
- * In the specific case of 'word' wrapping mode however, include all space
- * characters following the characters that fit in the space we've got,
- * even if no pixel of them is visible.
- */
-
- p = segPtr->body.chars + byteOffset;
- tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
-
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- if (baseCharChunkPtr == NULL) {
- baseCharChunkPtr = chunkPtr;
- bciPtr = ckalloc(sizeof(BaseCharInfo));
- baseString = &bciPtr->baseChars;
- Tcl_DStringInit(baseString);
- bciPtr->width = 0;
-
- ciPtr = &bciPtr->ci;
- } else {
- bciPtr = baseCharChunkPtr->clientData;
- ciPtr = ckalloc(sizeof(CharInfo));
- baseString = &bciPtr->baseChars;
- }
-
- lineOffset = Tcl_DStringLength(baseString);
- line = Tcl_DStringAppend(baseString,p,maxBytes);
-
- chunkPtr->clientData = ciPtr;
- ciPtr->baseChunkPtr = baseCharChunkPtr;
- ciPtr->baseOffset = lineOffset;
- ciPtr->chars = NULL;
- ciPtr->numBytes = 0;
-
- bytesThatFit = CharChunkMeasureChars(chunkPtr, line,
- lineOffset + maxBytes, lineOffset, -1, chunkPtr->x, maxX,
- TK_ISOLATE_END, &nextX);
-#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
- bytesThatFit = CharChunkMeasureChars(chunkPtr, p, maxBytes, 0, -1,
- chunkPtr->x, maxX, TK_ISOLATE_END, &nextX);
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
-
- if (bytesThatFit < maxBytes) {
- if ((bytesThatFit == 0) && noCharsYet) {
- int ch;
- int chLen = TkUtfToUniChar(p, &ch);
-
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- bytesThatFit = CharChunkMeasureChars(chunkPtr, line,
- lineOffset+chLen, lineOffset, -1, chunkPtr->x, -1, 0,
- &nextX);
-#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
- bytesThatFit = CharChunkMeasureChars(chunkPtr, p, chLen, 0, -1,
- chunkPtr->x, -1, 0, &nextX);
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
- }
- if ((nextX < maxX) && ((p[bytesThatFit] == ' ')
- || (p[bytesThatFit] == '\t'))) {
- /*
- * Space characters are funny, in that they are considered to fit
- * if there is at least one pixel of space left on the line. Just
- * give the space character whatever space is left.
- */
-
- nextX = maxX;
- bytesThatFit++;
- }
- if (wrapMode == TEXT_WRAPMODE_WORD) {
- while (p[bytesThatFit] == ' ') {
- /*
- * Space characters that would go at the beginning of the
- * next line are allocated to the current line. This gives
- * the effect of trimming white spaces that would otherwise
- * be seen at the beginning of wrapped lines.
- * Note that testing for '\t' is useless here because the
- * chunk always includes at most one trailing \t, see
- * LayoutDLine.
- */
-
- bytesThatFit++;
- }
- }
- if (p[bytesThatFit] == '\n') {
- /*
- * A newline character takes up no space, so if the previous
- * character fits then so does the newline.
- */
-
- bytesThatFit++;
- }
- if (bytesThatFit == 0) {
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- chunkPtr->clientData = NULL;
- if (chunkPtr == baseCharChunkPtr) {
- baseCharChunkPtr = NULL;
- Tcl_DStringFree(baseString);
- } else {
- Tcl_DStringSetLength(baseString,lineOffset);
- }
- ckfree(ciPtr);
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
- return 0;
- }
- }
-
- Tk_GetFontMetrics(tkfont, &fm);
-
- /*
- * Fill in the chunk structure and allocate and initialize a CharInfo
- * structure. If the last character is a newline then don't bother to
- * display it.
- */
-
- chunkPtr->displayProc = CharDisplayProc;
- chunkPtr->undisplayProc = CharUndisplayProc;
- chunkPtr->measureProc = CharMeasureProc;
- chunkPtr->bboxProc = CharBboxProc;
- chunkPtr->numBytes = bytesThatFit;
- chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset;
- chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset;
- chunkPtr->minHeight = 0;
- chunkPtr->width = nextX - chunkPtr->x;
- chunkPtr->breakIndex = -1;
-
-#if !TK_LAYOUT_WITH_BASE_CHUNKS
- ciPtr = ckalloc((Tk_Offset(CharInfo, chars) + 1) + bytesThatFit);
- chunkPtr->clientData = ciPtr;
- memcpy(ciPtr->chars, p, (unsigned) bytesThatFit);
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
-
- ciPtr->numBytes = bytesThatFit;
- if (p[bytesThatFit - 1] == '\n') {
- ciPtr->numBytes--;
- }
-
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- /*
- * Final update for the current base chunk data.
- */
-
- Tcl_DStringSetLength(baseString,lineOffset+ciPtr->numBytes);
- bciPtr->width = nextX - baseCharChunkPtr->x;
-
- /*
- * Finalize the base chunk if this chunk ends in a tab, which definitly
- * breaks the context and needs to be handled on a higher level.
- */
-
- if (ciPtr->numBytes > 0 && p[ciPtr->numBytes - 1] == '\t') {
- FinalizeBaseChunk(chunkPtr);
- }
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
-
- /*
- * Compute a break location. If we're in word wrap mode, a break can occur
- * after any space character, or at the end of the chunk if the next
- * segment (ignoring those with zero size) is not a character segment.
- */
-
- if (wrapMode != TEXT_WRAPMODE_WORD) {
- chunkPtr->breakIndex = chunkPtr->numBytes;
- } else {
- for (count = bytesThatFit, p += bytesThatFit - 1; count > 0;
- count--, p--) {
- /*
- * Don't use isspace(); effects are unpredictable and can lead to
- * odd word-wrapping problems on some platforms. Also don't use
- * Tcl_UniCharIsSpace here either, as it identifies non-breaking
- * spaces as places to break. What we actually want is only the
- * ASCII space characters, so use them explicitly...
- */
-
- switch (*p) {
- case '\t': case '\n': case '\v': case '\f': case '\r': case ' ':
- chunkPtr->breakIndex = count;
- goto checkForNextChunk;
- }
- }
- checkForNextChunk:
- if ((bytesThatFit + byteOffset) == segPtr->size) {
- for (nextPtr = segPtr->nextPtr; nextPtr != NULL;
- nextPtr = nextPtr->nextPtr) {
- if (nextPtr->size != 0) {
- if (nextPtr->typePtr != &tkTextCharType) {
- chunkPtr->breakIndex = chunkPtr->numBytes;
- }
- break;
- }
- }
- }
- }
- return 1;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * CharChunkMeasureChars --
- *
- * Determine the number of characters from a char chunk that will fit in
- * the given horizontal span.
- *
- * This is the same as MeasureChars (which see), but in the context of a
- * char chunk, i.e. on a higher level of abstraction. Use this function
- * whereever possible instead of plain MeasureChars, so that the right
- * context is used automatically.
- *
- * Results:
- * The return value is the number of bytes from the range of start to end
- * in source that fit in the span given by startX and maxX. *nextXPtr is
- * filled in with the x-coordinate at which the first character that
- * didn't fit would be drawn, if it were to be drawn.
- *
- * Side effects:
- * None.
- *--------------------------------------------------------------
- */
-
-static int
-CharChunkMeasureChars(
- TkTextDispChunk *chunkPtr, /* Chunk from which to measure. */
- const char *chars, /* Chars to use, instead of the chunk's own.
- * Used by the layoutproc during chunk setup.
- * All other callers use NULL. Not
- * NUL-terminated. */
- int charsLen, /* Length of the "chars" parameter. */
- int start, int end, /* The range of chars to measure inside the
- * chunk (or inside the additional chars). */
- int startX, /* Starting x coordinate where the measured
- * span will begin. */
- int maxX, /* Maximum pixel width of the span. May be -1
- * for unlimited. */
- int flags, /* Flags to pass to MeasureChars. */
- int *nextXPtr) /* The function puts the newly calculated
- * right border x-position of the span
- * here. */
+ int x) /* X-coordinate, in same coordinate system as chunkPtr->x. */
{
- Tk_Font tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
- CharInfo *ciPtr = chunkPtr->clientData;
-
-#if !TK_LAYOUT_WITH_BASE_CHUNKS
- if (chars == NULL) {
- chars = ciPtr->chars;
- charsLen = ciPtr->numBytes;
- }
- if (end == -1) {
- end = charsLen;
- }
-
- return MeasureChars(tkfont, chars, charsLen, start, end-start,
- startX, maxX, flags, nextXPtr);
-#else /* TK_LAYOUT_WITH_BASE_CHUNKS */
- {
- int xDisplacement;
- int fit, bstart = start, bend = end;
-
- if (chars == NULL) {
- Tcl_DString *baseChars = &((BaseCharInfo *)
- ciPtr->baseChunkPtr->clientData)->baseChars;
-
- chars = Tcl_DStringValue(baseChars);
- charsLen = Tcl_DStringLength(baseChars);
- bstart += ciPtr->baseOffset;
- if (bend == -1) {
- bend = ciPtr->baseOffset + ciPtr->numBytes;
- } else {
- bend += ciPtr->baseOffset;
- }
- } else if (bend == -1) {
- bend = charsLen;
- }
-
- if (bstart == ciPtr->baseOffset) {
- xDisplacement = startX - chunkPtr->x;
- } else {
- int widthUntilStart = 0;
-
- MeasureChars(tkfont, chars, charsLen, 0, bstart,
- 0, -1, 0, &widthUntilStart);
- xDisplacement = startX - widthUntilStart - ciPtr->baseChunkPtr->x;
- }
-
- fit = MeasureChars(tkfont, chars, charsLen, 0, bend,
- ciPtr->baseChunkPtr->x + xDisplacement, maxX, flags, nextXPtr);
-
- if (fit < bstart) {
- return 0;
- } else {
- return fit - bstart;
- }
- }
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
-}
-
-/*
- *--------------------------------------------------------------
- *
- * CharDisplayProc --
- *
- * This function is called to display a character chunk on the screen or
- * in an off-screen pixmap.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Graphics are drawn.
- *
- *--------------------------------------------------------------
- */
-
-static void
-CharDisplayProc(
- TkText *textPtr,
- TkTextDispChunk *chunkPtr, /* Chunk that is to be drawn. */
- int x, /* X-position in dst at which to draw this
- * chunk (may differ from the x-position in
- * the chunk because of scrolling). */
- int y, /* Y-position at which to draw this chunk in
- * dst. */
- int height, /* Total height of line. */
- int baseline, /* Offset of baseline from y. */
- Display *display, /* Display to use for drawing. */
- Drawable dst, /* Pixmap or window in which to draw chunk. */
- int screenY) /* Y-coordinate in text window that
- * corresponds to y. */
-{
- CharInfo *ciPtr = chunkPtr->clientData;
- const char *string;
- TextStyle *stylePtr;
- StyleValues *sValuePtr;
- int numBytes, offsetBytes, offsetX;
-#if TK_DRAW_IN_CONTEXT
- BaseCharInfo *bciPtr;
-#endif /* TK_DRAW_IN_CONTEXT */
-
- if ((x + chunkPtr->width) <= 0) {
- /*
- * The chunk is off-screen.
- */
-
- return;
- }
-
-#if TK_DRAW_IN_CONTEXT
- bciPtr = ciPtr->baseChunkPtr->clientData;
- numBytes = Tcl_DStringLength(&bciPtr->baseChars);
- string = Tcl_DStringValue(&bciPtr->baseChars);
-
-#elif TK_LAYOUT_WITH_BASE_CHUNKS
- if (ciPtr->baseChunkPtr != chunkPtr) {
- /*
- * Without context drawing only base chunks display their foreground.
- */
-
- return;
- }
-
- numBytes = Tcl_DStringLength(&((BaseCharInfo *) ciPtr)->baseChars);
- string = ciPtr->chars;
-
-#else /* !TK_LAYOUT_WITH_BASE_CHUNKS */
- numBytes = ciPtr->numBytes;
- string = ciPtr->chars;
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
-
- stylePtr = chunkPtr->stylePtr;
- sValuePtr = stylePtr->sValuePtr;
-
- /*
- * If the text sticks out way to the left of the window, skip over the
- * characters that aren't in the visible part of the window. This is
- * essential if x is very negative (such as less than 32K); otherwise
- * overflow problems will occur in servers that use 16-bit arithmetic,
- * like X.
- */
-
- offsetX = x;
- offsetBytes = 0;
- if (x < 0) {
- offsetBytes = CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1,
- x, 0, 0, &offsetX);
- }
-
- /*
- * Draw the text, underline, and overstrike for this chunk.
- */
-
- if (!sValuePtr->elide && (numBytes > offsetBytes)
- && (stylePtr->fgGC != None)) {
-#if TK_DRAW_IN_CONTEXT
- int start = ciPtr->baseOffset + offsetBytes;
- int len = ciPtr->numBytes - offsetBytes;
- int xDisplacement = x - chunkPtr->x;
-
- if ((len > 0) && (string[start + len - 1] == '\t')) {
- len--;
- }
- if (len <= 0) {
- return;
- }
-
- TkpDrawCharsInContext(display, dst, stylePtr->fgGC, sValuePtr->tkfont,
- string, numBytes, start, len,
- ciPtr->baseChunkPtr->x + xDisplacement,
- y + baseline - sValuePtr->offset);
-
- if (sValuePtr->underline) {
- TkUnderlineCharsInContext(display, dst, stylePtr->ulGC,
- sValuePtr->tkfont, string, numBytes,
- ciPtr->baseChunkPtr->x + xDisplacement,
- y + baseline - sValuePtr->offset,
- start, start+len);
- }
- if (sValuePtr->overstrike) {
- Tk_FontMetrics fm;
-
- Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
- TkUnderlineCharsInContext(display, dst, stylePtr->ovGC,
- sValuePtr->tkfont, string, numBytes,
- ciPtr->baseChunkPtr->x + xDisplacement,
- y + baseline - sValuePtr->offset
- - fm.descent - (fm.ascent * 3) / 10,
- start, start+len);
- }
-#else /* !TK_DRAW_IN_CONTEXT */
- string += offsetBytes;
- numBytes -= offsetBytes;
-
- if ((numBytes > 0) && (string[numBytes - 1] == '\t')) {
- numBytes--;
- }
- Tk_DrawChars(display, dst, stylePtr->fgGC, sValuePtr->tkfont, string,
- numBytes, offsetX, y + baseline - sValuePtr->offset);
- if (sValuePtr->underline) {
- Tk_UnderlineChars(display, dst, stylePtr->ulGC, sValuePtr->tkfont,
- string, offsetX,
- y + baseline - sValuePtr->offset,
- 0, numBytes);
-
- }
- if (sValuePtr->overstrike) {
- Tk_FontMetrics fm;
-
- Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
- Tk_UnderlineChars(display, dst, stylePtr->ovGC, sValuePtr->tkfont,
- string, offsetX,
- y + baseline - sValuePtr->offset
- - fm.descent - (fm.ascent * 3) / 10,
- 0, numBytes);
- }
-#endif /* TK_DRAW_IN_CONTEXT */
- }
-}
-
-/*
- *--------------------------------------------------------------
- *
- * CharUndisplayProc --
- *
- * This function is called when a character chunk is no longer going to
- * be displayed. It frees up resources that were allocated to display the
- * chunk.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Memory and other resources get freed.
- *
- *--------------------------------------------------------------
- */
-
-static void
-CharUndisplayProc(
- TkText *textPtr, /* Overall information about text widget. */
- TkTextDispChunk *chunkPtr) /* Chunk that is about to be freed. */
-{
- CharInfo *ciPtr = chunkPtr->clientData;
-
- if (ciPtr) {
-#if TK_LAYOUT_WITH_BASE_CHUNKS
- if (chunkPtr == ciPtr->baseChunkPtr) {
- /*
- * Basechunks are undisplayed first, when DLines are freed or
- * partially freed, so this makes sure we don't access their data
- * any more.
- */
-
- FreeBaseChunk(chunkPtr);
- } else if (ciPtr->baseChunkPtr != NULL) {
- /*
- * When other char chunks are undisplayed, drop their characters
- * from the base chunk. This usually happens, when they are last
- * in a line and need to be re-layed out.
- */
-
- RemoveFromBaseChunk(chunkPtr);
- }
-
- ciPtr->baseChunkPtr = NULL;
- ciPtr->chars = NULL;
- ciPtr->numBytes = 0;
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
-
- ckfree(ciPtr);
- chunkPtr->clientData = NULL;
- }
+ return 0;
}
/*
@@ -8071,13 +11825,9 @@ CharUndisplayProc(
static int
CharMeasureProc(
TkTextDispChunk *chunkPtr, /* Chunk containing desired coord. */
- int x) /* X-coordinate, in same coordinate system as
- * chunkPtr->x. */
+ int x) /* X-coordinate, in same coordinate system as chunkPtr->x. */
{
- int endX;
-
- return CharChunkMeasureChars(chunkPtr, NULL, 0, 0, chunkPtr->numBytes-1,
- chunkPtr->x, x, 0, &endX); /* CHAR OFFSET */
+ return CharChunkMeasureChars(chunkPtr, NULL, 0, 0, chunkPtr->numBytes - 1, chunkPtr->x, x, 0, NULL);
}
/*
@@ -8106,29 +11856,23 @@ static void
CharBboxProc(
TkText *textPtr,
TkTextDispChunk *chunkPtr, /* Chunk containing desired char. */
- int byteIndex, /* Byte offset of desired character within the
- * chunk. */
- int y, /* Topmost pixel in area allocated for this
- * line. */
+ int byteIndex, /* Byte offset of desired character within the chunk */
+ int y, /* Topmost pixel in area allocated for this line. */
int lineHeight, /* Height of line, in pixels. */
- int baseline, /* Location of line's baseline, in pixels
- * measured down from y. */
- int *xPtr, int *yPtr, /* Gets filled in with coords of character's
- * upper-left pixel. X-coord is in same
- * coordinate system as chunkPtr->x. */
- int *widthPtr, /* Gets filled in with width of character, in
- * pixels. */
- int *heightPtr) /* Gets filled in with height of character, in
- * pixels. */
+ int baseline, /* Location of line's baseline, in pixels measured down from y. */
+ int *xPtr, int *yPtr, /* Gets filled in with coords of character's upper-left pixel.
+ * X-coord is in same coordinate system as chunkPtr->x. */
+ int *widthPtr, /* Gets filled in with width of character, in pixels. */
+ int *heightPtr) /* Gets filled in with height of character, in pixels. */
{
CharInfo *ciPtr = chunkPtr->clientData;
- int maxX;
+ int offset = ciPtr->baseOffset + byteIndex;
+ int maxX = chunkPtr->width + chunkPtr->x;
+ int nextX;
- maxX = chunkPtr->width + chunkPtr->x;
- CharChunkMeasureChars(chunkPtr, NULL, 0, 0, byteIndex,
- chunkPtr->x, -1, 0, xPtr);
+ CharChunkMeasureChars(chunkPtr, NULL, 0, 0, byteIndex, chunkPtr->x, -1, 0, xPtr);
- if (byteIndex == ciPtr->numBytes) {
+ if (byteIndex >= ciPtr->numBytes) {
/*
* This situation only happens if the last character in a line is a
* space character, in which case it absorbs all of the extra space in
@@ -8136,8 +11880,7 @@ CharBboxProc(
*/
*widthPtr = maxX - *xPtr;
- } else if ((ciPtr->chars[byteIndex] == '\t')
- && (byteIndex == ciPtr->numBytes - 1)) {
+ } else if (ciPtr->u.chars[offset] == '\t' && byteIndex == ciPtr->numBytes - 1) {
/*
* The desired character is a tab character that terminates a chunk;
* give it all the space left in the chunk.
@@ -8145,14 +11888,40 @@ CharBboxProc(
*widthPtr = maxX - *xPtr;
} else {
- CharChunkMeasureChars(chunkPtr, NULL, 0, byteIndex, byteIndex+1,
- *xPtr, -1, 0, widthPtr);
- if (*widthPtr > maxX) {
+ CharChunkMeasureChars(chunkPtr, NULL, 0, byteIndex, byteIndex + 1, *xPtr, -1, 0, &nextX);
+
+ if (nextX >= maxX) {
*widthPtr = maxX - *xPtr;
} else {
- *widthPtr -= *xPtr;
+ *widthPtr = nextX - *xPtr;
+
+ if (chunkPtr->additionalWidth && IsExpandableSpace(ciPtr->u.chars + offset)) {
+ /*
+ * We've expanded some spaces for full line justification. Compute the
+ * width of this specific space character.
+ */
+
+ const char *base = ciPtr->u.chars + ciPtr->baseOffset;
+ const char *q = ciPtr->u.chars + offset;
+ unsigned numSpaces = chunkPtr->numSpaces;
+ unsigned remaining = chunkPtr->additionalWidth;
+
+ do {
+ unsigned space = (remaining + numSpaces - 1)/numSpaces;
+
+ *widthPtr += space;
+ remaining -= space;
+ assert(numSpaces > 0);
+ numSpaces -= 1;
+ if (base == q) {
+ break;
+ }
+ q = Tcl_UtfPrev(q, ciPtr->u.chars);
+ } while (IsExpandableSpace(q));
+ }
}
}
+
*yPtr = y + baseline - chunkPtr->minAscent;
*heightPtr = chunkPtr->minAscent + chunkPtr->minDescent;
}
@@ -8177,34 +11946,68 @@ CharBboxProc(
*----------------------------------------------------------------------
*/
+static TkTextDispChunk *
+FindEndOfTab(
+ TkTextDispChunk *chunkPtr,
+ int *decimalPtr)
+{
+ TkTextDispChunk *decimalChunkPtr = NULL;
+ bool gotDigit = false;
+
+ *decimalPtr = 0;
+
+ for ( ; chunkPtr; chunkPtr = chunkPtr->nextPtr) {
+ if (IsCharChunk(chunkPtr)) {
+ CharInfo *ciPtr = chunkPtr->clientData;
+ const char *s = ciPtr->u.chars + ciPtr->baseOffset;
+ const char *p;
+ int i;
+
+ for (p = s, i = 0; i < ciPtr->numBytes; ++p, ++i) {
+ if (isdigit(*p)) {
+ gotDigit = true;
+ } else if (*p == '.' || *p == ',') {
+ *decimalPtr = p - s;
+ decimalChunkPtr = chunkPtr;
+ } else if (gotDigit) {
+ if (!decimalChunkPtr) {
+ *decimalPtr = p - s;
+ decimalChunkPtr = chunkPtr;
+ }
+ return decimalChunkPtr;
+ }
+ }
+ }
+ }
+ return decimalChunkPtr;
+}
+
static void
AdjustForTab(
- TkText *textPtr, /* Information about the text widget as a
- * whole. */
- TkTextTabArray *tabArrayPtr,/* Information about the tab stops that apply
- * to this line. May be NULL to indicate
- * default tabbing (every 8 chars). */
- int index, /* Index of current tab stop. */
- TkTextDispChunk *chunkPtr) /* Chunk whose last character is the tab; the
- * following chunks contain information to be
- * shifted right. */
-{
- int x, desired, delta, width, decimal, i, gotDigit;
- TkTextDispChunk *chunkPtr2, *decimalChunkPtr;
- CharInfo *ciPtr;
- int tabX, spaceWidth;
- const char *p;
+ LayoutData *data)
+{
+ int x, desired = 0, delta, width;
+ TkTextDispChunk *chunkPtr, *nextChunkPtr, *chPtr;
+ TkTextTabArray *tabArrayPtr;
+ TkText *textPtr;
+ int tabX, tabIndex;
TkTextTabAlign alignment;
- if (chunkPtr->nextPtr == NULL) {
- /*
- * Nothing after the actual tab; just return.
- */
+ assert(data->tabIndex >= 0);
+ assert(data->tabChunkPtr);
+ chunkPtr = data->tabChunkPtr;
+ nextChunkPtr = chunkPtr->nextPtr;
+
+ if (!nextChunkPtr) {
+ /* Nothing after the actual tab; just return. */
return;
}
- x = chunkPtr->nextPtr->x;
+ tabIndex = data->tabIndex;
+ textPtr = data->textPtr;
+ tabArrayPtr = data->tabArrayPtr;
+ x = nextChunkPtr->x;
/*
* If no tab information has been given, assuming tab stops are at 8
@@ -8212,132 +12015,95 @@ AdjustForTab(
* wordprocessor tab style.
*/
- if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
+ if (!tabArrayPtr || tabArrayPtr->numTabs == 0) {
/*
* No tab information has been given, so use the default
* interpretation of tabs.
*/
- if (textPtr->tabStyle == TK_TEXT_TABSTYLE_TABULAR) {
- int tabWidth = Tk_TextWidth(textPtr->tkfont, "0", 1) * 8;
- if (tabWidth == 0) {
- tabWidth = 1;
- }
+ unsigned tabWidth = textPtr->charWidth*8;
+
+ tabWidth = MAX(1, tabWidth);
- desired = tabWidth * (index + 1);
+ if (textPtr->tabStyle == TK_TEXT_TABSTYLE_TABULAR) {
+ desired = tabWidth*(tabIndex + 1);
} else {
- desired = NextTabStop(textPtr->tkfont, x, 0);
+ desired = NextTabStop(tabWidth, x, 0);
}
+ } else {
+ if (tabIndex < tabArrayPtr->numTabs) {
+ alignment = tabArrayPtr->tabs[tabIndex].alignment;
+ tabX = tabArrayPtr->tabs[tabIndex].location;
+ } else {
+ /*
+ * Ran out of tab stops; compute a tab position by extrapolating from
+ * the last two tab positions.
+ */
- goto update;
- }
+ tabX = (int) (tabArrayPtr->lastTab +
+ (tabIndex + 1 - tabArrayPtr->numTabs)*tabArrayPtr->tabIncrement + 0.5);
+ alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs - 1].alignment;
+ }
- if (index < tabArrayPtr->numTabs) {
- alignment = tabArrayPtr->tabs[index].alignment;
- tabX = tabArrayPtr->tabs[index].location;
- } else {
- /*
- * Ran out of tab stops; compute a tab position by extrapolating from
- * the last two tab positions.
- */
+ switch (alignment) {
+ case LEFT:
+ desired = tabX;
+ break;
- tabX = (int) (tabArrayPtr->lastTab +
- (index + 1 - tabArrayPtr->numTabs)*tabArrayPtr->tabIncrement +
- 0.5);
- alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
- }
+ case CENTER:
+ case RIGHT:
+ /*
+ * Compute the width of all the information in the tab group, then use
+ * it to pick a desired location.
+ */
- if (alignment == LEFT) {
- desired = tabX;
- goto update;
- }
+ width = 0;
+ for (chPtr = nextChunkPtr; chPtr; chPtr = chPtr->nextPtr) {
+ width += chPtr->width;
+ }
+ desired = tabX - (alignment == CENTER ? width/2 : width);
+ break;
- if ((alignment == CENTER) || (alignment == RIGHT)) {
- /*
- * Compute the width of all the information in the tab group, then use
- * it to pick a desired location.
- */
+ case NUMERIC: {
+ /*
+ * Search through the text to be tabbed, looking for the last ',' or
+ * '.' before the first character that isn't a number, comma, period,
+ * or sign.
+ */
- width = 0;
- for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
- chunkPtr2 = chunkPtr2->nextPtr) {
- width += chunkPtr2->width;
- }
- if (alignment == CENTER) {
- desired = tabX - width/2;
- } else {
- desired = tabX - width;
- }
- goto update;
- }
+ int decimal;
+ TkTextDispChunk *decimalChunkPtr = FindEndOfTab(nextChunkPtr, &decimal);
- /*
- * Must be numeric alignment. Search through the text to be tabbed,
- * looking for the last , or . before the first character that isn't a
- * number, comma, period, or sign.
- */
+ if (decimalChunkPtr) {
+ int curX;
- decimalChunkPtr = NULL;
- decimal = gotDigit = 0;
- for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
- chunkPtr2 = chunkPtr2->nextPtr) {
- if (chunkPtr2->displayProc != CharDisplayProc) {
- continue;
- }
- ciPtr = chunkPtr2->clientData;
- for (p = ciPtr->chars, i = 0; i < ciPtr->numBytes; p++, i++) {
- if (isdigit(UCHAR(*p))) {
- gotDigit = 1;
- } else if ((*p == '.') || (*p == ',')) {
- decimal = p-ciPtr->chars;
- decimalChunkPtr = chunkPtr2;
- } else if (gotDigit) {
- if (decimalChunkPtr == NULL) {
- decimal = p-ciPtr->chars;
- decimalChunkPtr = chunkPtr2;
+ CharChunkMeasureChars(decimalChunkPtr, NULL, 0, 0, decimal,
+ decimalChunkPtr->x, -1, 0, &curX);
+ desired = tabX - (curX - x);
+ } else {
+ /*
+ * There wasn't a decimal point. Right justify the text.
+ */
+
+ width = 0;
+ for (chPtr = nextChunkPtr; chPtr; chPtr = chPtr->nextPtr) {
+ width += chPtr->width;
}
- goto endOfNumber;
+ desired = tabX - width;
}
}
+ }
}
- endOfNumber:
- if (decimalChunkPtr != NULL) {
- int curX;
-
- ciPtr = decimalChunkPtr->clientData;
- CharChunkMeasureChars(decimalChunkPtr, NULL, 0, 0, decimal,
- decimalChunkPtr->x, -1, 0, &curX);
- desired = tabX - (curX - x);
- goto update;
- }
-
- /*
- * There wasn't a decimal point. Right justify the text.
- */
-
- width = 0;
- for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
- chunkPtr2 = chunkPtr2->nextPtr) {
- width += chunkPtr2->width;
- }
- desired = tabX - width;
-
/*
* Shift all of the chunks to the right so that the left edge is at the
* desired location, then expand the chunk containing the tab. Be sure
* that the tab occupies at least the width of a space character.
*/
- update:
- delta = desired - x;
- MeasureChars(textPtr->tkfont, " ", 1, 0, 1, 0, -1, 0, &spaceWidth);
- if (delta < spaceWidth) {
- delta = spaceWidth;
- }
- for (chunkPtr2 = chunkPtr->nextPtr; chunkPtr2 != NULL;
- chunkPtr2 = chunkPtr2->nextPtr) {
- chunkPtr2->x += delta;
+ delta = MAX(textPtr->spaceWidth, desired - x);
+ for (chPtr = nextChunkPtr; chPtr; chPtr = chPtr->nextPtr) {
+ chPtr->x += delta;
}
chunkPtr->width += delta;
}
@@ -8345,19 +12111,19 @@ AdjustForTab(
/*
*----------------------------------------------------------------------
*
- * SizeOfTab --
+ * ComputeSizeOfTab --
*
- * This returns an estimate of the amount of white space that will be
- * consumed by a tab.
+ * This estimates the amount of white space that will be consumed by
+ * a tab.
*
* Results:
- * The return value is the minimum number of pixels that will be occupied
- * by the next tab of tabArrayPtr, assuming that the current position on
- * the line is x and the end of the line is maxX. The 'next tab' is
- * determined by a combination of the current position (x) which it must
- * be equal to or beyond, and the tab count in indexPtr.
+ * The 'current tab' is the minimum number of pixels that will be occupied
+ * by the next tab of tabArrayPtr, assuming that the current position on the
+ * line is x and the end of the line is maxX. The 'next tab' is determined
+ * by a combination of the current position (x) which it must be equal to or
+ * beyond, and the tab count in indexPtr.
*
- * For numeric tabs, this is a conservative estimate. The return value is
+ * For numeric tabs, this is a conservative estimate. The 'current tab' is
* always >= 0.
*
* Side effects:
@@ -8366,38 +12132,26 @@ AdjustForTab(
*----------------------------------------------------------------------
*/
-static int
-SizeOfTab(
- TkText *textPtr, /* Information about the text widget as a
- * whole. */
- int tabStyle, /* One of TK_TEXT_TABSTYLE_TABULAR
- * or TK_TEXT_TABSTYLE_WORDPROCESSOR. */
- TkTextTabArray *tabArrayPtr,/* Information about the tab stops that apply
- * to this line. NULL means use default
- * tabbing (every 8 chars.) */
- int *indexPtr, /* Contains index of previous tab stop, will
- * be updated to reflect the number of stops
- * used. */
- int x, /* Current x-location in line. */
- int maxX) /* X-location of pixel just past the right
- * edge of the line. */
-{
- int tabX, result, index, spaceWidth, tabWidth;
+static void
+ComputeSizeOfTab(
+ LayoutData *data)
+{
+ TkText *textPtr;
+ TkTextTabArray *tabArrayPtr;
+ unsigned tabX, tabWidth;
TkTextTabAlign alignment;
- index = *indexPtr;
+ textPtr = data->textPtr;
+ tabArrayPtr = data->tabArrayPtr;
- if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
+ if (!tabArrayPtr || tabArrayPtr->numTabs == 0) {
/*
* We're using a default tab spacing of 8 characters.
*/
- tabWidth = Tk_TextWidth(textPtr->tkfont, "0", 1) * 8;
- if (tabWidth == 0) {
- tabWidth = 1;
- }
+ tabWidth = MAX(1, textPtr->charWidth*8);
} else {
- tabWidth = 0; /* Avoid compiler error. */
+ tabWidth = 0;
}
do {
@@ -8405,27 +12159,27 @@ SizeOfTab(
* We were given the count before this tab, so increment it first.
*/
- index++;
+ data->tabIndex += 1;
- if ((tabArrayPtr == NULL) || (tabArrayPtr->numTabs == 0)) {
+ if (!tabArrayPtr || tabArrayPtr->numTabs == 0) {
/*
* We're using a default tab spacing calculated above.
*/
- tabX = tabWidth * (index + 1);
+ tabX = tabWidth*(data->tabIndex + 1);
alignment = LEFT;
- } else if (index < tabArrayPtr->numTabs) {
- tabX = tabArrayPtr->tabs[index].location;
- alignment = tabArrayPtr->tabs[index].alignment;
+ } else if (data->tabIndex < tabArrayPtr->numTabs) {
+ tabX = tabArrayPtr->tabs[data->tabIndex].location;
+ alignment = tabArrayPtr->tabs[data->tabIndex].alignment;
} else {
/*
* Ran out of tab stops; compute a tab position by extrapolating.
*/
tabX = (int) (tabArrayPtr->lastTab
- + (index + 1 - tabArrayPtr->numTabs)
- * tabArrayPtr->tabIncrement + 0.5);
- alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs-1].alignment;
+ + (data->tabIndex + 1 - tabArrayPtr->numTabs)*tabArrayPtr->tabIncrement
+ + 0.5);
+ alignment = tabArrayPtr->tabs[tabArrayPtr->numTabs - 1].alignment;
}
/*
@@ -8435,52 +12189,46 @@ SizeOfTab(
* With 'wordprocessor' style tabs, we must obviously continue until
* we reach the text tab stop.
*
- * With 'tabular' style tabs, we always use the index'th tab stop.
+ * With 'tabular' style tabs, we always use the data->tabIndex'th tab stop.
*/
- } while (tabX <= x && (tabStyle == TK_TEXT_TABSTYLE_WORDPROCESSOR));
+ } while (tabX <= data->x && data->tabStyle == TK_TEXT_TABSTYLE_WORDPROCESSOR);
/*
* Inform our caller of how many tab stops we've used up.
*/
- *indexPtr = index;
-
- if (alignment == CENTER) {
+ switch (alignment) {
+ case CENTER:
/*
* Be very careful in the arithmetic below, because maxX may be the
* largest positive number: watch out for integer overflow.
*/
- if ((maxX-tabX) < (tabX - x)) {
- result = (maxX - x) - 2*(maxX - tabX);
+ if (data->maxX - tabX < tabX - data->x) {
+ data->tabSize = data->maxX - data->x - 2*(data->maxX - tabX);
} else {
- result = 0;
+ data->tabSize = 0;
}
- goto done;
- }
- if (alignment == RIGHT) {
- result = 0;
- goto done;
- }
+ break;
- /*
- * Note: this treats NUMERIC alignment the same as LEFT alignment, which
- * is somewhat conservative. However, it's pretty tricky at this point to
- * figure out exactly where the damn decimal point will be.
- */
+ case RIGHT:
+ data->tabSize = 0;
+ break;
- if (tabX > x) {
- result = tabX - x;
- } else {
- result = 0;
- }
+ case LEFT:
+ case NUMERIC:
+ /*
+ * Note: this treats NUMERIC alignment the same as LEFT alignment, which
+ * is somewhat conservative. However, it's pretty tricky at this point to
+ * figure out exactly where the damn decimal point will be.
+ */
- done:
- MeasureChars(textPtr->tkfont, " ", 1, 0, 1, 0, -1, 0, &spaceWidth);
- if (result < spaceWidth) {
- result = spaceWidth;
+ data->tabSize = tabX - data->x;
+ assert(textPtr->spaceWidth > 0); /* ensure positive size */
+ break;
}
- return result;
+
+ data->tabSize = MAX(data->tabSize, textPtr->spaceWidth);
}
/*
@@ -8504,24 +12252,19 @@ SizeOfTab(
static int
NextTabStop(
- Tk_Font tkfont, /* Font in which chunk that contains tab stop
- * will be drawn. */
- int x, /* X-position in pixels where last character
- * was drawn. The next tab stop occurs
- * somewhere after this location. */
- int tabOrigin) /* The origin for tab stops. May be non-zero
- * if text has been scrolled. */
+ unsigned tabWidth, /* Default tab width of the widget. */
+ int x, /* X-position in pixels where last character was drawn. The next
+ * tab stop occurs somewhere after this location. */
+ int tabOrigin) /* The origin for tab stops. May be non-zero if text has been
+ * scrolled. */
{
- int tabWidth, rem;
+ int rem;
- tabWidth = Tk_TextWidth(tkfont, "0", 1) * 8;
- if (tabWidth == 0) {
- tabWidth = 1;
- }
+ assert(tabWidth > 0);
+ tabWidth *= 8;
x += tabWidth;
- rem = (x - tabOrigin) % tabWidth;
- if (rem < 0) {
+ if ((rem = (x - tabOrigin) % tabWidth) < 0) {
rem += tabWidth;
}
x -= rem;
@@ -8556,40 +12299,71 @@ NextTabStop(
*--------------------------------------------------------------
*/
+#if TK_DRAW_IN_CONTEXT
+
+static int
+TkpMeasureChars(
+ Tk_Font tkfont,
+ const char *source,
+ int numBytes,
+ int rangeStart,
+ int rangeLength,
+ int maxLength,
+ int flags,
+ int *lengthPtr)
+{
+ return TkpMeasureCharsInContext(tkfont, source, numBytes, rangeStart,
+ rangeLength, maxLength, flags, lengthPtr);
+}
+
+#else /* if !TK_DRAW_IN_CONTEXT */
+
+static int
+TkpMeasureChars(
+ Tk_Font tkfont,
+ const char *source,
+ int numBytes,
+ int rangeStart,
+ int rangeLength,
+ int maxLength,
+ int flags,
+ int *lengthPtr)
+{
+ return Tk_MeasureChars(tkfont, source + rangeStart, rangeLength, maxLength, flags, lengthPtr);
+}
+
+#endif /* TK_DRAW_IN_CONTEXT */
+
static int
MeasureChars(
Tk_Font tkfont, /* Font in which to draw characters. */
- const char *source, /* Characters to be displayed. Need not be
- * NULL-terminated. */
- int maxBytes, /* Maximum # of bytes to consider from
- * source. */
+ const char *source, /* Characters to be displayed. Need not be NUL-terminated. */
+ int maxBytes, /* Maximum # of bytes to consider from source. */
int rangeStart, int rangeLength,
/* Range of bytes to consider in source.*/
- int startX, /* X-position at which first character will be
- * drawn. */
- int maxX, /* Don't consider any character that would
- * cross this x-position. */
+ int startX, /* X-position at which first character will be drawn. */
+ int maxX, /* Don't consider any character that would cross this x-position. */
int flags, /* Flags to pass to Tk_MeasureChars. */
- int *nextXPtr) /* Return x-position of terminating character
- * here. */
+ int *nextXPtr) /* Return x-position of terminating character here, can be NULL. */
{
int curX, width, ch;
const char *special, *end, *start;
- ch = 0; /* lint. */
+ ch = 0;
curX = startX;
start = source + rangeStart;
end = start + rangeLength;
special = start;
+
while (start < end) {
if (start >= special) {
/*
* Find the next special character in the string.
*/
- for (special = start; special < end; special++) {
+ for (special = start; special < end; ++special) {
ch = *special;
- if ((ch == '\t') || (ch == '\n')) {
+ if (ch == '\t' || ch == '\n') {
break;
}
}
@@ -8600,18 +12374,11 @@ MeasureChars(
* string). Process characters between start and special.
*/
- if ((maxX >= 0) && (curX >= maxX)) {
+ if (maxX >= 0 && curX >= maxX) {
break;
}
-#if TK_DRAW_IN_CONTEXT
- start += TkpMeasureCharsInContext(tkfont, source, maxBytes,
- start - source, special - start,
- maxX >= 0 ? maxX - curX : -1, flags, &width);
-#else
- (void) maxBytes;
- start += Tk_MeasureChars(tkfont, start, special - start,
+ start += TkpMeasureChars(tkfont, source, maxBytes, start - source, special - start,
maxX >= 0 ? maxX - curX : -1, flags, &width);
-#endif /* TK_DRAW_IN_CONTEXT */
curX += width;
if (start < special) {
/*
@@ -8624,12 +12391,14 @@ MeasureChars(
if (ch != '\t') {
break;
}
- start++;
+ start += 1;
}
}
- *nextXPtr = curX;
- return start - (source+rangeStart);
+ if (nextXPtr) {
+ *nextXPtr = curX;
+ }
+ return start - (source + rangeStart);
}
/*
@@ -8645,13 +12414,13 @@ MeasureChars(
* more flexible in this regard.
*
* Results:
- * The return value is either TKTEXT_SCROLL_MOVETO, TKTEXT_SCROLL_PAGES,
- * TKTEXT_SCROLL_UNITS, TKTEXT_SCROLL_PIXELS or TKTEXT_SCROLL_ERROR. This
+ * The return value is either SCROLL_MOVETO, SCROLL_PAGES,
+ * SCROLL_UNITS, SCROLL_PIXELS or SCROLL_ERROR. This
* indicates whether the command was successfully parsed and what form
- * the command took. If TKTEXT_SCROLL_MOVETO, *dblPtr is filled in with
- * the desired position; if TKTEXT_SCROLL_PAGES, TKTEXT_SCROLL_PIXELS or
- * TKTEXT_SCROLL_UNITS, *intPtr is filled in with the number of
- * pages/pixels/lines to move (may be negative); if TKTEXT_SCROLL_ERROR,
+ * the command took. If SCROLL_MOVETO, *dblPtr is filled in with
+ * the desired position; if SCROLL_PAGES, SCROLL_PIXELS or
+ * SCROLL_UNITS, *intPtr is filled in with the number of
+ * pages/pixels/lines to move (may be negative); if SCROLL_ERROR,
* the interp's result contains an error message.
*
* Side effects:
@@ -8660,16 +12429,15 @@ MeasureChars(
*----------------------------------------------------------------------
*/
-static int
+static ScrollMethod
TextGetScrollInfoObj(
Tcl_Interp *interp, /* Used for error reporting. */
TkText *textPtr, /* Information about the text widget. */
int objc, /* # arguments for command. */
Tcl_Obj *const objv[], /* Arguments for command. */
- double *dblPtr, /* Filled in with argument "moveto" option, if
- * any. */
- int *intPtr) /* Filled in with number of pages or lines or
- * pixels to scroll, if any. */
+ double *dblPtr, /* Filled in with argument "moveto" option, if any. */
+ int *intPtr) /* Filled in with number of pages or lines or pixels to scroll,
+ * if any. */
{
static const char *const subcommands[] = {
"moveto", "scroll", NULL
@@ -8685,324 +12453,1152 @@ TextGetScrollInfoObj(
};
int index;
- if (Tcl_GetIndexFromObjStruct(interp, objv[2], subcommands,
- sizeof(char *), "option", 0, &index) != TCL_OK) {
- return TKTEXT_SCROLL_ERROR;
+ if (Tcl_GetIndexFromObjStruct(interp, objv[2], subcommands, sizeof(char *), "option", 0, &index)
+ != TCL_OK) {
+ return SCROLL_ERROR;
}
switch ((enum viewSubcmds) index) {
case VIEW_MOVETO:
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "fraction");
- return TKTEXT_SCROLL_ERROR;
+ return SCROLL_ERROR;
}
if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) {
- return TKTEXT_SCROLL_ERROR;
+ return SCROLL_ERROR;
}
- return TKTEXT_SCROLL_MOVETO;
+ return SCROLL_MOVETO;
case VIEW_SCROLL:
if (objc != 5) {
Tcl_WrongNumArgs(interp, 3, objv, "number units|pages|pixels");
- return TKTEXT_SCROLL_ERROR;
+ return SCROLL_ERROR;
}
- if (Tcl_GetIndexFromObjStruct(interp, objv[4], units,
- sizeof(char *), "argument", 0, &index) != TCL_OK) {
- return TKTEXT_SCROLL_ERROR;
+ if (Tcl_GetIndexFromObjStruct(interp, objv[4], units, sizeof(char *), "argument", 0, &index)
+ != TCL_OK) {
+ return SCROLL_ERROR;
}
switch ((enum viewUnits) index) {
case VIEW_SCROLL_PAGES:
if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
- return TKTEXT_SCROLL_ERROR;
+ return SCROLL_ERROR;
}
- return TKTEXT_SCROLL_PAGES;
+ return SCROLL_PAGES;
case VIEW_SCROLL_PIXELS:
- if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3],
- intPtr) != TCL_OK) {
- return TKTEXT_SCROLL_ERROR;
+ if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3], intPtr) != TCL_OK) {
+ return SCROLL_ERROR;
}
- return TKTEXT_SCROLL_PIXELS;
+ return SCROLL_PIXELS;
case VIEW_SCROLL_UNITS:
if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
- return TKTEXT_SCROLL_ERROR;
+ return SCROLL_ERROR;
}
- return TKTEXT_SCROLL_UNITS;
+ return SCROLL_UNITS;
}
}
- Tcl_Panic("unexpected switch fallthrough");
- return TKTEXT_SCROLL_ERROR;
+ assert(!"unexpected switch fallthrough");
+ return SCROLL_ERROR; /* should be never reached */
}
-#if TK_LAYOUT_WITH_BASE_CHUNKS
/*
*----------------------------------------------------------------------
*
- * FinalizeBaseChunk --
+ * AllocCharInfo --
*
- * This procedure makes sure that all the chunks of the stretch are
- * up-to-date. It is invoked when the LayoutProc has been called for all
- * chunks and the base chunk is stable.
+ * Allocate new char info struct. We are using a pool of char info
+ * structs.
*
* Results:
- * None.
+ * The newly allocated struct, or a free char info struct from
+ * pool.
*
* Side effects:
- * The CharInfo.chars of all dependent chunks point into
- * BaseCharInfo.baseChars for easy access (and compatibility).
+ * May allocate some memory.
*
*----------------------------------------------------------------------
*/
-static void
-FinalizeBaseChunk(
- TkTextDispChunk *addChunkPtr)
- /* An additional chunk to add to the stretch,
- * even though it may not be in the linked
- * list yet. Used by the LayoutProc, otherwise
- * NULL. */
-{
- const char *baseChars;
- TkTextDispChunk *chunkPtr;
+static CharInfo *
+AllocCharInfo(
+ TkText *textPtr)
+{
+ TextDInfo *dInfoPtr;
CharInfo *ciPtr;
-#if TK_DRAW_IN_CONTEXT
- int widthAdjust = 0;
- int newwidth;
-#endif /* TK_DRAW_IN_CONTEXT */
- if (baseCharChunkPtr == NULL) {
- return;
+ assert(textPtr);
+
+ dInfoPtr = textPtr->dInfoPtr;
+ if ((ciPtr = dInfoPtr->charInfoPoolPtr)) {
+ dInfoPtr->charInfoPoolPtr = dInfoPtr->charInfoPoolPtr->u.next;
+ } else {
+ ciPtr = malloc(sizeof(CharInfo));
+ DEBUG_ALLOC(tkTextCountNewCharInfo++);
}
- baseChars = Tcl_DStringValue(
- &((BaseCharInfo *) baseCharChunkPtr->clientData)->baseChars);
+ return ciPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeCharInfo --
+ *
+ * Put back given char info to pool.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeCharInfo(
+ TkText *textPtr,
+ CharInfo *ciPtr)
+{
+ TextDInfo *dInfoPtr;
- for (chunkPtr = baseCharChunkPtr; chunkPtr != NULL;
- chunkPtr = chunkPtr->nextPtr) {
-#if TK_DRAW_IN_CONTEXT
- chunkPtr->x += widthAdjust;
-#endif /* TK_DRAW_IN_CONTEXT */
+ assert(textPtr);
+ assert(ciPtr);
- if (chunkPtr->displayProc != CharDisplayProc) {
- continue;
+ TkBTreeFreeSegment(ciPtr->segPtr);
+ dInfoPtr = textPtr->dInfoPtr;
+ ciPtr->u.next = dInfoPtr->charInfoPoolPtr;
+ dInfoPtr->charInfoPoolPtr = ciPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ComputeBreakIndex --
+ *
+ * Compute a break location. If we're in word wrap mode, a break
+ * can occurr after any space character, or at the end of the chunk
+ * if the the next segment (ignoring those with zero size) is not a
+ * character segment.
+ *
+ * Results:
+ * The computed break location.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+ComputeBreakIndex(
+ TkText *textPtr,
+ const TkTextDispChunk *chunkPtr,
+ TkTextSegment *segPtr,
+ int byteOffset,
+ TkWrapMode wrapMode,
+ TkTextSpaceMode spaceMode)
+{
+ switch (wrapMode) {
+ case TEXT_WRAPMODE_NONE:
+ break;
+ case TEXT_WRAPMODE_CHAR:
+ case TEXT_WRAPMODE_NULL:
+ return chunkPtr->numBytes;
+ case TEXT_WRAPMODE_WORD:
+ case TEXT_WRAPMODE_CODEPOINT: {
+ TkTextSegment *nextPtr;
+ const char *p;
+ int count;
+
+ if (segPtr->typePtr == &tkTextHyphenType) {
+ return 1;
+ }
+
+ if (chunkPtr->numBytes + byteOffset == segPtr->size) {
+ for (nextPtr = segPtr->nextPtr; nextPtr; nextPtr = nextPtr->nextPtr) {
+ if (nextPtr->size > 0) {
+ if (!(nextPtr->typePtr->group & (SEG_GROUP_CHAR|SEG_GROUP_HYPHEN))) {
+ return chunkPtr->numBytes;
+ }
+ break;
+ } else if (nextPtr->typePtr == &tkTextBranchType) {
+ nextPtr = nextPtr->body.branch.nextPtr->nextPtr;
+ }
+ }
}
- ciPtr = chunkPtr->clientData;
- if (ciPtr->baseChunkPtr != baseCharChunkPtr) {
- break;
+
+ count = chunkPtr->numBytes;
+ if (chunkPtr->endsWithSyllable) {
+ assert(chunkPtr->numBytes > 0);
+ count -= 1;
}
- ciPtr->chars = baseChars + ciPtr->baseOffset;
+ p = segPtr->body.chars + byteOffset + count - 1;
-#if TK_DRAW_IN_CONTEXT
- newwidth = 0;
- CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, 0, -1, 0, &newwidth);
- if (newwidth < chunkPtr->width) {
- widthAdjust += newwidth - chunkPtr->width;
- chunkPtr->width = newwidth;
+ if (wrapMode == TEXT_WRAPMODE_WORD) {
+ /*
+ * Don't use isspace(); effects are unpredictable (because the result
+ * is locale dependent) and can lead to odd word-wrapping problems on
+ * some platforms. Also don't use Tcl_UniCharIsSpace here either, this
+ * can be used when displaying Markup in read-only mode (except the
+ * non-breaking space), but in text data there is a difference between
+ * ASCII spaces and all other spaces, and this difference must be
+ * visible for the user (line break makes the spaces indistinguishable).
+ * Keep in mind that the text widget will also be used for editing
+ * text. What we actually want is only the ASCII space characters, so
+ * use them explicitly...
+ *
+ * NOTE: don't break at hyphen character (U+002D), because the meaning
+ * of this character is contextual. The user has to use the "codepoint"
+ * wrap mode if he want's line breaking at hard hyphen characters.
+ */
+
+ for ( ; count > 0; --count, --p) {
+ switch (*p) {
+ case ' ':
+ if (spaceMode == TEXT_SPACEMODE_EXACT) {
+ return -1;
+ }
+ /* fallthru */
+ case '\t': case '\n': case '\v': case '\f': case '\r':
+ return count;
+ }
+ }
+ } else {
+ const char *brks;
+ int i;
+
+ if (*p == '\n') {
+ return count; /* catch special case end of line */
+ }
+
+ brks = chunkPtr->brks;
+ i = count - 1;
+
+ /* In this case the break locations must be already computed. */
+ assert(brks);
+
+ for ( ; i >= 0; --i, --p) {
+ if (brks[i] == LINEBREAK_ALLOWBREAK) {
+ if (*p == ' ' && spaceMode == TEXT_SPACEMODE_EXACT) {
+ return -1;
+ }
+ return i + 1;
+ }
+ }
}
-#endif /* TK_DRAW_IN_CONTEXT */
+ break;
+ }
}
- if (addChunkPtr != NULL) {
- ciPtr = addChunkPtr->clientData;
- ciPtr->chars = baseChars + ciPtr->baseOffset;
+ return -1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextCheckDisplayLineConsistency --
+ *
+ * This function is called for consistency checking of display line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If anything suspicious is found in the display lines, the function
+ * panics.
+ *
+ *----------------------------------------------------------------------
+ */
-#if TK_DRAW_IN_CONTEXT
- addChunkPtr->x += widthAdjust;
- CharChunkMeasureChars(addChunkPtr, NULL, 0, 0, -1, 0, -1, 0,
- &addChunkPtr->width);
-#endif /* TK_DRAW_IN_CONTEXT */
+void
+TkTextCheckDisplayLineConsistency(
+ const TkText *textPtr)
+{
+ DLine *dlPtr;
+
+ for (dlPtr = textPtr->dInfoPtr->dLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
+ if (dlPtr->chunkPtr) {
+ const TkTextLine *linePtr = TkTextIndexGetLine(&dlPtr->index);
+
+ if (!linePtr->parentPtr || linePtr->parentPtr == (void *) 0x61616161) {
+ Tcl_Panic("CheckDisplayLineConsisteny: expired index in display line");
+ }
+ }
}
- baseCharChunkPtr = NULL;
+ for (dlPtr = textPtr->dInfoPtr->savedDLinePtr; dlPtr; dlPtr = dlPtr->nextPtr) {
+ if (dlPtr->chunkPtr) {
+ const TkTextLine *linePtr = TkTextIndexGetLine(&dlPtr->index);
+
+ if (!linePtr->parentPtr || linePtr->parentPtr == (void *) 0x61616161) {
+ Tcl_Panic("CheckDisplayLineConsisteny: expired index in saved display line");
+ }
+ }
+ }
+
+ dlPtr = textPtr->dInfoPtr->cachedDLinePtr;
+ if (dlPtr && dlPtr->chunkPtr) {
+ const TkTextLine *linePtr = TkTextIndexGetLine(&dlPtr->index);
+
+ if (!linePtr->parentPtr || linePtr->parentPtr == (void *) 0x61616161) {
+ Tcl_Panic("CheckDisplayLineConsisteny: expired index in cached display line");
+ }
+ }
}
/*
*----------------------------------------------------------------------
*
- * FreeBaseChunk --
+ * CheckLineMetricConsistency --
*
- * This procedure makes sure that all the chunks of the stretch are
- * disconnected from the base chunk and the base chunk specific data is
- * freed. It is invoked from the UndisplayProc. The procedure doesn't
- * ckfree the base chunk clientData itself, that's up to the main
- * UndisplayProc.
+ * This function is called for consistency checking of display line
+ * metric information. Call this function only if all line metrics
+ * are up-to-date.
*
* Results:
* None.
*
* Side effects:
- * The CharInfo.chars of all dependent chunks are set to NULL. Memory
- * that belongs specifically to the base chunk is freed.
+ * If anything suspicious is found in the display line metric information,
+ * the function panics.
*
*----------------------------------------------------------------------
*/
static void
-FreeBaseChunk(
- TkTextDispChunk *baseChunkPtr)
- /* The base chunk of the stretch and head of
- * the linked list. */
+CheckLineMetricConsistency(
+ const TkText *textPtr)
{
- TkTextDispChunk *chunkPtr;
- CharInfo *ciPtr;
+ const TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ unsigned epoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
+ const TkTextLine *lastLinePtr;
+ const TkTextLine *linePtr;
+ unsigned lineNum = 0;
+ unsigned reference;
+
+ assert(textPtr->pixelReference >= 0);
- if (baseCharChunkPtr == baseChunkPtr) {
- baseCharChunkPtr = NULL;
+ linePtr = TkBTreeGetStartLine(textPtr);
+ lastLinePtr = TkBTreeGetLastLine(textPtr);
+
+ if (textPtr->dInfoPtr->firstLineNo != TkBTreeLinesTo(sharedTextPtr->tree, NULL, linePtr, NULL)) {
+ Tcl_Panic("CheckLineMetricConsistency: firstLineNo is not up-to-date");
+ }
+ if (textPtr->dInfoPtr->lastLineNo != TkBTreeLinesTo(sharedTextPtr->tree, NULL, lastLinePtr, NULL)) {
+ Tcl_Panic("CheckLineMetricConsistency: lastLineNo is not up-to-date");
}
- for (chunkPtr=baseChunkPtr; chunkPtr!=NULL; chunkPtr=chunkPtr->nextPtr) {
- if (chunkPtr->undisplayProc != CharUndisplayProc) {
- continue;
+ reference = textPtr->pixelReference;
+
+ while (linePtr != lastLinePtr) {
+ const TkTextPixelInfo *pixelInfo = linePtr->pixelInfo + reference;
+ const TkTextDispLineInfo *dispLineInfo = pixelInfo->dispLineInfo;
+
+ if ((pixelInfo->epoch & EPOCH_MASK) != epoch) {
+ Tcl_Panic("CheckLineMetricConsistency: line metric info is not up-to-date");
}
- ciPtr = chunkPtr->clientData;
- if (ciPtr->baseChunkPtr != baseChunkPtr) {
- break;
+ if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
+ Tcl_Panic("CheckLineMetricConsistency: computation of this line is not yet complete");
}
- ciPtr->baseChunkPtr = NULL;
- ciPtr->chars = NULL;
- }
+ linePtr = linePtr->nextPtr;
+ lineNum += 1;
+
+ while (linePtr != lastLinePtr && !linePtr->logicalLine) {
+ const TkTextPixelInfo *pixelInfo = linePtr->pixelInfo + reference;
+
+ if ((pixelInfo->epoch & EPOCH_MASK) != epoch) {
+ Tcl_Panic("CheckLineMetricConsistency: line metric info is not up-to-date");
+ }
+ if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
+ Tcl_Panic("CheckLineMetricConsistency: partial flag shouldn't be set");
+ }
+ if (pixelInfo->dispLineInfo) {
+ Tcl_Panic("CheckLineMetricConsistency: merged line should not have display line info");
+ }
+ if (pixelInfo->height > 0) {
+ Tcl_Panic("CheckLineMetricConsistency: merged line should not have a height");
+ }
- if (baseChunkPtr) {
- Tcl_DStringFree(&((BaseCharInfo *) baseChunkPtr->clientData)->baseChars);
+ linePtr = linePtr->nextPtr;
+ lineNum += 1;
+ }
+
+ if (!lastLinePtr->nextPtr) {
+ const TkTextPixelInfo *pixelInfo = lastLinePtr->pixelInfo + reference;
+
+ if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
+ Tcl_Panic("CheckLineMetricConsistency: partial flag shouldn't be set in last line");
+ }
+ if (pixelInfo->dispLineInfo) {
+ Tcl_Panic("CheckLineMetricConsistency: last line should not have display line info");
+ }
+ if (pixelInfo->height > 0) {
+ Tcl_Panic("CheckLineMetricConsistency: last line should not have a height");
+ }
+ }
+
+ if (dispLineInfo) {
+ unsigned pixels = 0;
+ unsigned k;
+
+ if (dispLineInfo->numDispLines == 1) {
+ Tcl_Panic("CheckLineMetricConsistency: this line should not have display line info");
+ }
+ for (k = 0; k < dispLineInfo->numDispLines; ++k) {
+ const TkTextDispLineEntry *entry = dispLineInfo->entry + k;
+
+ if (k == 0 && entry->byteOffset != 0) {
+ Tcl_Panic("CheckLineMetricConsistency: first display line (line %d) should "
+ "have byte offset zero", lineNum);
+ }
+ if ((entry + 1)->byteOffset <= entry->byteOffset) {
+ Tcl_Panic("CheckLineMetricConsistency: display line (line %d) has invalid byte "
+ "offset %d (previous is %d)", lineNum, (entry + 1)->byteOffset,
+ entry->byteOffset);
+ }
+ if (entry->height == 0) {
+ Tcl_Panic("CheckLineMetricConsistency: display line (%d) has zero height", lineNum);
+ }
+ pixels += entry->height;
+ }
+ if (pixels != pixelInfo->height) {
+ Tcl_Panic("CheckLineMetricConsistency: sum of display line pixels is wrong (line %d)",
+ lineNum);
+ }
+ }
}
}
/*
*----------------------------------------------------------------------
*
- * IsSameFGStyle --
+ * TkTextCheckLineMetricUpdate --
*
- * Compare the foreground attributes of two styles. Specifically must
- * consider: foreground color, font, font style and font decorations,
- * elide, "offset" and foreground stipple. Do *not* consider: background
- * color, border, relief or background stipple.
- *
- * If we use TkpDrawCharsInContext(), we also don't need to check
- * foreground color, font decorations, elide, offset and foreground
- * stipple, so all that is left is font (including font size and font
- * style) and "offset".
+ * This function is called for consistency checking of display line
+ * metric update information.
*
* Results:
- * 1 if the two styles match, 0 otherwise.
+ * None.
*
* Side effects:
- * None.
+ * If anything suspicious is found in the display line metric update
+ * information, the function panics.
*
*----------------------------------------------------------------------
*/
+void
+TkTextCheckLineMetricUpdate(
+ const TkText *textPtr)
+{
+ const TkRangeList *ranges;
+ const TkRange *range;
+ TkTextBTree tree;
+ unsigned epoch;
+ int n, total;
+
+ assert(textPtr);
+
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ return;
+ }
+ if (!textPtr->endMarker->sectionPtr || !textPtr->startMarker->sectionPtr) {
+ /*
+ * Called inside unlink of start/end marker, in this case we cannot check
+ * (and we don't need a check here).
+ */
+ return;
+ }
+
+ ranges = textPtr->dInfoPtr->lineMetricUpdateRanges;
+ tree = textPtr->sharedTextPtr->tree;
+ total = TkBTreeNumLines(tree, textPtr);
+
+ if (!TkRangeListIsEmpty(ranges) && TkRangeListHigh(ranges) >= total) {
+ Tcl_Panic("TkTextCheckLineMetricUpdate: line %d is out of range (max=%d)\n",
+ TkRangeListHigh(ranges), total);
+ }
+
+ range = TkRangeListFirst(ranges);
+ epoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
+
+ for (n = 0; n < total - 1; ++n) {
+ const TkTextPixelInfo *pixelInfo;
+
+ if (range && range->low == n) {
+ n = range->high;
+ range = TkRangeListNext(ranges, range);
+ continue;
+ }
+
+ pixelInfo = TkBTreeLinePixelInfo(textPtr, TkBTreeFindLine(tree, textPtr, n));
+
+ if (pixelInfo->epoch && (pixelInfo->epoch & EPOCH_MASK) != epoch) {
+ Tcl_Panic("TkTextCheckLineMetricUpdate: line %d is not up-to-date\n", n);
+ }
+ if (pixelInfo->epoch & PARTIAL_COMPUTED_BIT) {
+ Tcl_Panic("TkTextCheckLineMetricUpdate: line metric computation (line %d) is not "
+ "yet complete\n", n);
+ }
+ }
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * CharChunkMeasureChars --
+ *
+ * Determine the number of characters from a char chunk that will fit in
+ * the given horizontal span.
+ *
+ * This is the same as MeasureChars (which see), but in the context of a
+ * char chunk, i.e. on a higher level of abstraction. Use this function
+ * whereever possible instead of plain MeasureChars, so that the right
+ * context is used automatically.
+ *
+ * Results:
+ * The return value is the number of bytes from the range of start to end
+ * in source that fit in the span given by startX and maxX. *nextXPtr is
+ * filled in with the x-coordinate at which the first character that
+ * didn't fit would be drawn, if it were to be drawn.
+ *
+ * Side effects:
+ * None.
+ *--------------------------------------------------------------
+ */
+
static int
-IsSameFGStyle(
- TextStyle *style1,
- TextStyle *style2)
+CharChunkMeasureChars(
+ TkTextDispChunk *chunkPtr, /* Chunk from which to measure. */
+ const char *chars, /* Chars to use, instead of the chunk's own. Used by the layoutproc
+ * during chunk setup. All other callers use NULL. Not NUL-terminated. */
+ int charsLen, /* Length of the "chars" parameter. */
+ int start, int end, /* The range of chars to measure inside the chunk (or inside the
+ * additional chars). */
+ int startX, /* Starting x coordinate where the measured span will begin. */
+ int maxX, /* Maximum pixel width of the span. May be -1 for unlimited. */
+ int flags, /* Flags to pass to MeasureChars. */
+ int *nextXPtr) /* The function puts the newly calculated right border x-position of
+ * the span here; can be NULL. */
{
- StyleValues *sv1;
- StyleValues *sv2;
+ Tk_Font tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
+ CharInfo *ciPtr = chunkPtr->clientData;
+ int fit, rangeStart;
- if (style1 == style2) {
- return 1;
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+
+ int widthUntilStart = 0;
+
+ assert(chunkPtr->baseChunkPtr);
+
+ if (!chars) {
+ const Tcl_DString *baseChars = &chunkPtr->baseChunkPtr->baseChars;
+
+ chars = Tcl_DStringValue(baseChars);
+ charsLen = Tcl_DStringLength(baseChars);
+ start += ciPtr->baseOffset;
+ if (end == -1) {
+ assert(ciPtr->numBytes >= chunkPtr->wrappedAtSpace);
+ end = ciPtr->baseOffset + ciPtr->numBytes - chunkPtr->wrappedAtSpace;
+ } else {
+ end += ciPtr->baseOffset;
+ }
+ if (chunkPtr->wrappedAtSpace) {
+ assert(charsLen >= 1);
+ charsLen -= 1;
+ }
}
-#if !TK_DRAW_IN_CONTEXT
- if (
-#ifdef MAC_OSX_TK
- !TkMacOSXCompareColors(style1->fgGC->foreground,
- style2->fgGC->foreground)
+ if (start != ciPtr->baseOffset) {
+ MeasureChars(tkfont, chars, charsLen, 0, start, 0, -1, 0, &widthUntilStart);
+ }
+
+ startX = chunkPtr->baseChunkPtr->x + (startX - widthUntilStart - chunkPtr->x);
+ rangeStart = 0;
+
#else
- style1->fgGC->foreground != style2->fgGC->foreground
+
+ rangeStart = start;
+
+ if (!chars) {
+ chars = ciPtr->u.chars;
+ charsLen = ciPtr->numBytes;
+ }
+
#endif
- ) {
- return 0;
+
+ if (end == -1) {
+ end = charsLen;
}
-#endif /* !TK_DRAW_IN_CONTEXT */
- sv1 = style1->sValuePtr;
- sv2 = style2->sValuePtr;
+ fit = MeasureChars(tkfont, chars, charsLen, rangeStart, end - rangeStart,
+ startX, maxX, flags, nextXPtr);
-#if TK_DRAW_IN_CONTEXT
- return sv1->tkfont == sv2->tkfont && sv1->offset == sv2->offset;
-#else
- return sv1->tkfont == sv2->tkfont
- && sv1->underline == sv2->underline
- && sv1->overstrike == sv2->overstrike
- && sv1->elide == sv2->elide
- && sv1->offset == sv2->offset
- && sv1->fgStipple == sv1->fgStipple;
-#endif /* TK_DRAW_IN_CONTEXT */
+ return MAX(0, fit - start);
}
/*
- *----------------------------------------------------------------------
+ *--------------------------------------------------------------
*
- * RemoveFromBaseChunk --
+ * TkTextCharLayoutProc --
*
- * This procedure removes a chunk from the stretch as a result of
- * UndisplayProc. The chunk in question should be the last in a stretch.
- * This happens during re-layouting of the break position.
+ * This function is the "layoutProc" for character segments.
*
* Results:
- * None.
+ * If there is something to display for the chunk then a non-zero value
+ * is returned and the fields of chunkPtr will be filled in (see the
+ * declaration of TkTextDispChunk in tkText.h for details). If zero is
+ * returned it means that no characters from this chunk fit in the
+ * window. If -1 is returned it means that this segment just doesn't need
+ * to be displayed (never happens for text).
*
* Side effects:
- * The characters that belong to this chunk are removed from the base
- * chunk. It is assumed that LayoutProc and FinalizeBaseChunk are called
- * next to repair any damage that this causes to the integrity of the
- * stretch and the other chunks. For that reason the base chunk is also
- * put into baseCharChunkPtr automatically, so that LayoutProc can resume
- * correctly.
+ * Memory is allocated to hold additional information about the chunk.
*
- *----------------------------------------------------------------------
+ *--------------------------------------------------------------
*/
-static void
-RemoveFromBaseChunk(
- TkTextDispChunk *chunkPtr) /* The chunk to remove from the end of the
- * stretch. */
+static bool
+EndsWithSyllable(
+ TkTextSegment *segPtr)
{
+ if (segPtr->typePtr->group == SEG_GROUP_CHAR) {
+ for (segPtr = segPtr->nextPtr; segPtr; segPtr = segPtr->nextPtr) {
+ switch (segPtr->typePtr->group) {
+ case SEG_GROUP_MARK:
+ break;
+ case SEG_GROUP_HYPHEN:
+ return true;
+ case SEG_GROUP_BRANCH:
+ if (segPtr->typePtr == &tkTextBranchType) {
+ segPtr = segPtr->body.branch.nextPtr;
+ break;
+ }
+ /* fallthru */
+ default:
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+int
+TkTextCharLayoutProc(
+ const TkTextIndex *indexPtr,/* Index of first character to lay out (corresponds to segPtr and
+ * offset). */
+ TkTextSegment *segPtr, /* Segment being layed out. */
+ int byteOffset, /* Byte offset within segment of first character to consider. */
+ int maxX, /* Chunk must not occupy pixels at this position or higher. */
+ int maxBytes, /* Chunk must not include more than this many characters. */
+ bool noCharsYet, /* 'true' means no characters have been assigned to this display
+ * line yet. */
+ TkWrapMode wrapMode, /* How to handle line wrapping: TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE,
+ * TEXT_WRAPMODE_WORD, or TEXT_WRAPMODE_CODEPOINT. */
+ TkTextSpaceMode spaceMode, /* How to handle displaying spaces. Must be TEXT_SPACEMODE_NONE,
+ * TEXT_SPACEMODE_EXACT, or TEXT_SPACEMODE_TRIM. */
+ TkTextDispChunk *chunkPtr) /* Structure to fill in with information about this chunk. The x
+ * field has already been set by the caller. */
+{
+ Tk_Font tkfont;
+ int nextX, bytesThatFit;
+ Tk_FontMetrics fm;
CharInfo *ciPtr;
- BaseCharInfo *bciPtr;
+ char const *p;
- if (chunkPtr->displayProc != CharDisplayProc) {
-#ifdef DEBUG_LAYOUT_WITH_BASE_CHUNKS
- fprintf(stderr,"RemoveFromBaseChunk called with wrong chunk type\n");
-#endif
- return;
- }
+ assert(indexPtr->textPtr);
+ assert(chunkPtr->clientData);
/*
- * Reinstitute this base chunk for re-layout.
+ * Figure out how many characters will fit in the space we've got. Include
+ * the next character, even though it won't fit completely, if any of the
+ * following is true:
+ *
+ * (a) the chunk contains no characters and the display line contains no
+ * characters yet (i.e. the line isn't wide enough to hold even a
+ * single character).
+ *
+ * (b) at least one pixel of the character is visible, we have not
+ * already exceeded the character limit, and the next character is a
+ * white space character.
*/
+ tkfont = chunkPtr->stylePtr->sValuePtr->tkfont;
ciPtr = chunkPtr->clientData;
- baseCharChunkPtr = ciPtr->baseChunkPtr;
+ chunkPtr->layoutProcs = &layoutCharProcs;
+ p = segPtr->body.chars + byteOffset;
+
+ bytesThatFit = CharChunkMeasureChars(chunkPtr, ciPtr->u.chars, ciPtr->baseOffset + maxBytes,
+ ciPtr->baseOffset, -1, chunkPtr->x, maxX, TK_ISOLATE_END, &nextX);
/*
- * Remove the chunk data from the base chunk data.
+ * NOTE: do not trim white spaces at the end of line, it would be impossible
+ * for the user to see typos like mistakenly typing two consecutive spaces.
*/
- bciPtr = baseCharChunkPtr->clientData;
+ if (bytesThatFit < maxBytes) {
+ if (bytesThatFit == 0 && noCharsYet) {
+ int ch, chLen = TkUtfToUniChar(p, &ch);
+
+ /*
+ * At least one character should be contained in current display line.
+ */
+
+ bytesThatFit = CharChunkMeasureChars(chunkPtr, ciPtr->u.chars, ciPtr->baseOffset + chLen,
+ ciPtr->baseOffset, -1, chunkPtr->x, -1, 0, &nextX);
+ }
+ if (spaceMode == TEXT_SPACEMODE_TRIM) {
+ while (isblank(p[bytesThatFit])) {
+ bytesThatFit += 1;
+ }
+ }
+ if (p[bytesThatFit] == '\n') {
+ /*
+ * A newline character takes up no space, so if the previous
+ * character fits then so does the newline.
+ */
+
+ bytesThatFit += 1;
+ } else if (spaceMode == TEXT_SPACEMODE_NONE
+ && nextX <= maxX
+ && ((1 << wrapMode) & ((1 << TEXT_WRAPMODE_WORD) | (1 << TEXT_WRAPMODE_CODEPOINT)))
+ && isblank(p[bytesThatFit])
+ && !(bytesThatFit == 0
+ && chunkPtr->prevCharChunkPtr
+ && chunkPtr->prevCharChunkPtr->wrappedAtSpace)) {
+ /*
+ * Space characters are funny, in that they are considered to fit at the end
+ * of the line. Just give the space character whatever space is left.
+ */
+
+ nextX = maxX;
+ bytesThatFit += 1;
+
+ /* Do not wrap next chunk in this line. */
+ chunkPtr->wrappedAtSpace = true;
+ }
+ if (bytesThatFit == 0) {
+ return 0;
+ }
+ }
+
+ Tk_GetFontMetrics(tkfont, &fm);
+
+ /*
+ * Fill in the chunk structure and allocate and initialize a CharInfo structure. If the
+ * last character is a newline then don't bother to display it.
+ */
+
+ chunkPtr->endsWithSyllable =
+ p[bytesThatFit] == '\0' && indexPtr->textPtr->hyphenate && EndsWithSyllable(segPtr);
+ chunkPtr->numBytes = bytesThatFit;
+ chunkPtr->segByteOffset = byteOffset;
+ chunkPtr->minAscent = fm.ascent + chunkPtr->stylePtr->sValuePtr->offset;
+ chunkPtr->minDescent = fm.descent - chunkPtr->stylePtr->sValuePtr->offset;
+ chunkPtr->minHeight = 0;
+ chunkPtr->width = nextX - chunkPtr->x;
+ chunkPtr->breakIndex =
+ ComputeBreakIndex(indexPtr->textPtr, chunkPtr, segPtr, byteOffset, wrapMode, spaceMode);
+
+ ciPtr->numBytes = chunkPtr->numBytes;
+ return 1;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * CharDisplayProc --
+ *
+ * This function is called to display a character chunk on the screen or
+ * in an off-screen pixmap.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Graphics are drawn.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+CharDisplayProc(
+ TkText *textPtr,
+ TkTextDispChunk *chunkPtr, /* Chunk that is to be drawn. */
+ int x, /* X-position in dst at which to draw this chunk (may differ from
+ * the x-position in the chunk because of scrolling). */
+ int y, /* Y-position at which to draw this chunk in dst. */
+ int height, /* Total height of line. */
+ int baseline, /* Offset of baseline from y. */
+ Display *display, /* Display to use for drawing. */
+ Drawable dst, /* Pixmap or window in which to draw chunk. */
+ int screenY) /* Y-coordinate in text window that corresponds to y. */
+{
+ assert(chunkPtr->width == 0 || !chunkPtr->stylePtr->sValuePtr->elide);
+
+ if (chunkPtr->width > 0 && x + chunkPtr->width > 0) {
+ /* The chunk has displayable content, and is not off-screen. */
+ DisplayChars(textPtr, chunkPtr, x, y, baseline, display, dst);
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * CharUndisplayProc --
+ *
+ * This function is called when a character chunk is no longer going to
+ * be displayed. It frees up resources that were allocated to display the
+ * chunk.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory and other resources get freed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+CharUndisplayProc(
+ TkText *textPtr, /* Overall information about text widget. */
+ TkTextDispChunk *chunkPtr) /* Chunk that is about to be freed. */
+{
+ CharInfo *ciPtr = chunkPtr->clientData;
+
+ if (!ciPtr) {
+ return;
+ }
+
+#if TK_LAYOUT_WITH_BASE_CHUNKS
+ {
+ TkTextDispChunk *baseChunkPtr = chunkPtr->baseChunkPtr;
+
+ if (chunkPtr == baseChunkPtr) {
+ /*
+ * Base chunks are undisplayed first, when DLines are freed or
+ * partially freed, so this makes sure we don't access their data
+ * any more.
+ */
+
+ Tcl_DStringFree(&baseChunkPtr->baseChars);
+ DEBUG_ALLOC(tkTextCountDestroyBaseChars++);
+ } else if (baseChunkPtr && ciPtr->numBytes > 0) {
+ /*
+ * When other char chunks are undisplayed, drop their characters
+ * from the base chunk. This usually happens, when they are last
+ * in a line and need to be re-layed out.
+ */
+
+ assert(ciPtr->baseOffset + ciPtr->numBytes == Tcl_DStringLength(&baseChunkPtr->baseChars));
+ Tcl_DStringSetLength(&baseChunkPtr->baseChars, ciPtr->baseOffset);
+ baseChunkPtr->baseWidth = 0;
+ }
+
+ if (chunkPtr->prevPtr) {
+ chunkPtr->x -= chunkPtr->prevPtr->xAdjustment;
+ }
-#ifdef DEBUG_LAYOUT_WITH_BASE_CHUNKS
- if ((ciPtr->baseOffset + ciPtr->numBytes)
- != Tcl_DStringLength(&bciPtr->baseChars)) {
- fprintf(stderr,"RemoveFromBaseChunk called with wrong chunk "
- "(not last)\n");
+ chunkPtr->baseChunkPtr = NULL;
}
#endif
- Tcl_DStringSetLength(&bciPtr->baseChars, ciPtr->baseOffset);
+ FreeCharInfo(textPtr, ciPtr);
+ chunkPtr->clientData = NULL;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * HyphenUndisplayProc --
+ *
+ * This function is called when a hyphen chunk is no longer going to
+ * be displayed. It frees up resources that were allocated to display the
+ * chunk.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory and other resources get freed.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+HyphenUndisplayProc(
+ TkText *textPtr, /* Overall information about text widget. */
+ TkTextDispChunk *chunkPtr) /* Chunk that is about to be freed. */
+{
+ TkTextSegment *hyphenPtr = chunkPtr->clientData;
+
+ if (hyphenPtr) {
+ TkBTreeFreeSegment(hyphenPtr);
+ }
+ chunkPtr->clientData = NULL;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * DisplayChars --
+ *
+ * This function is called to display characters on the screen or
+ * in an off-screen pixmap.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Graphics are drawn.
+ *
+ *--------------------------------------------------------------
+ */
+
+static GC
+GetForegroundGC(
+ const TkText *textPtr,
+ const TkTextDispChunk *chunkPtr)
+{
+ const TkTextSegment *segPtr = ((const CharInfo *) chunkPtr->clientData)->segPtr;
+
+ if (segPtr == textPtr->dInfoPtr->endOfLineSegPtr) {
+ if (chunkPtr->stylePtr->eolGC != None) {
+ return chunkPtr->stylePtr->eolGC;
+ }
+ } else if (segPtr->typePtr == &tkTextHyphenType) {
+ if (chunkPtr->stylePtr->hyphenGC != None) {
+ return chunkPtr->stylePtr->hyphenGC;
+ }
+ }
+ return chunkPtr->stylePtr->fgGC;
+}
+
+#if TK_DRAW_IN_CONTEXT
+# if defined(_WIN32) || defined(__UNIX__)
+
+/*****************************************************************************
+ * We need this function for the emulation of context drawing, in this way the
+ * context support can be pre-tested on platforms without sub-pixel accuracy.
+ *****************************************************************************/
+
+static void
+DrawCharsInContext(
+ Display *display, /* Display on which to draw. */
+ Drawable drawable, /* Window or pixmap in which to draw. */
+ GC gc, /* Graphics context for drawing characters. */
+ Tk_Font tkfont, /* Font in which characters will be drawn; must be the same as font used
+ * in GC. */
+ const char *source, /* UTF-8 string to be displayed. Need not be nul terminated. All Tk
+ * meta-characters (tabs, control characters, and newlines) should be
+ * stripped out of the string that is passed to this function. If they are
+ * not stripped out, they will be displayed as regular printing characters. */
+ int numBytes, /* Number of bytes in string. */
+ int rangeStart, /* Index of first byte to draw. */
+ int rangeLength, /* Length of range to draw in bytes. */
+ int x, int y, /* Coordinates at which to place origin of the whole (not just the range)
+ * string when drawing. */
+ int xOffset) /* Offset to x-coordinate, required for emulation of context drawing. */
+{
+ Tk_DrawChars(display, drawable, gc, tkfont, source + rangeStart, rangeLength, xOffset, y);
+}
+
+# else /* if !(defined(_WIN32) || defined(__UNIX__)) */
+
+static void
+DrawCharsInContext(
+ Display *display, /* Display on which to draw. */
+ Drawable drawable, /* Window or pixmap in which to draw. */
+ GC gc, /* Graphics context for drawing characters. */
+ Tk_Font tkfont, /* Font in which characters will be drawn; must be the same as font used
+ * in GC. */
+ const char *source, /* UTF-8 string to be displayed. Need not be nul terminated. All Tk
+ * meta-characters (tabs, control characters, and newlines) should be
+ * stripped out of the string that is passed to this function. If they are
+ * not stripped out, they will be displayed as regular printing characters. */
+ int numBytes, /* Number of bytes in string. */
+ int rangeStart, /* Index of first byte to draw. */
+ int rangeLength, /* Length of range to draw in bytes. */
+ int x, int y, /* Coordinates at which to place origin of the whole (not just the range)
+ * string when drawing. */
+ int xOffset) /* Offset to x-coordinate, not needed here. */
+{
+ TkpDrawCharsInContext(display, drawable, gc, tkfont,
+ source, numBytes, rangeStart, rangeLength, x, y);
+}
+
+# endif /* defined(_WIN32) || defined(__UNIX__) */
+
+static void
+DrawChars(
+ TkText *textPtr,
+ TkTextDispChunk *chunkPtr, /* Display the content of this chunk. */
+ int x, /* X-position in dst at which to draw. */
+ int y, /* Y-position at which to draw. */
+ int offsetX, /* Offset in x-direction. */
+ int offsetBytes, /* Offset in display string. */
+ Display *display, /* Display to use for drawing. */
+ Drawable dst) /* Pixmap or window in which to draw chunk. */
+{
+ const TkTextDispChunk *baseChunkPtr;
+ unsigned numBytes;
+
+ assert(chunkPtr->baseChunkPtr);
+
+ baseChunkPtr = chunkPtr->baseChunkPtr;
+ numBytes = Tcl_DStringLength(&baseChunkPtr->baseChars);
+
+ if (numBytes > offsetBytes) {
+ const char *string;
+ const CharInfo *ciPtr;
+ const TextStyle *stylePtr;
+ const StyleValues *sValuePtr;
+ int xDisplacement, start, len;
+ GC fgGC;
+
+ string = Tcl_DStringValue(&baseChunkPtr->baseChars);
+ ciPtr = chunkPtr->clientData;
+ start = ciPtr->baseOffset + offsetBytes;
+ len = ciPtr->numBytes - offsetBytes;
+
+ assert(ciPtr->numBytes >= offsetBytes);
+
+ if (len == 0 || (string[start + len - 1] == '\t' && --len == 0)) {
+ return;
+ }
+
+ stylePtr = chunkPtr->stylePtr;
+ sValuePtr = stylePtr->sValuePtr;
+ ciPtr = chunkPtr->clientData;
+ xDisplacement = x - chunkPtr->x;
+ fgGC = GetForegroundGC(textPtr, chunkPtr);
+
+ /*
+ * Draw the text, underline, and overstrike for this chunk.
+ */
+
+ DrawCharsInContext(display, dst, fgGC, sValuePtr->tkfont, string, numBytes,
+ start, len, baseChunkPtr->x + xDisplacement, y - sValuePtr->offset,
+ chunkPtr->x + textPtr->dInfoPtr->x);
+
+ if (sValuePtr->underline) {
+ TkUnderlineCharsInContext(display, dst, stylePtr->ulGC, sValuePtr->tkfont, string,
+ numBytes, baseChunkPtr->x + xDisplacement, y - sValuePtr->offset,
+ start, start + len);
+ }
+ if (sValuePtr->overstrike) {
+ Tk_FontMetrics fm;
+
+ Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
+ TkUnderlineCharsInContext(display, dst, stylePtr->ovGC, sValuePtr->tkfont, string,
+ numBytes, baseChunkPtr->x + xDisplacement,
+ y - sValuePtr->offset - fm.descent - (fm.ascent*3)/10,
+ start, start + len);
+ }
+ }
+}
+
+#else /* if !TK_DRAW_IN_CONTEXT */
+
+static void
+DrawChars(
+ TkText *textPtr,
+ TkTextDispChunk *chunkPtr, /* Display the content of this chunk. */
+ int x, /* X-position in dst at which to draw. */
+ int y, /* Y-position at which to draw. */
+ int offsetX, /* Offset from x. */
+ int offsetBytes, /* Offset in display string. */
+ Display *display, /* Display to use for drawing. */
+ Drawable dst) /* Pixmap or window in which to draw chunk. */
+{
+ const CharInfo *ciPtr;
+ int numBytes;
+
+ ciPtr = chunkPtr->clientData;
+ numBytes = ciPtr->numBytes;
+
+ assert(offsetBytes >= ciPtr->baseOffset);
+
+ if (numBytes > offsetBytes) {
+ const TextStyle *stylePtr = chunkPtr->stylePtr;
+
+ if (stylePtr->fgGC != None) {
+ const StyleValues *sValuePtr;
+ const char *string;
+ GC fgGC;
+
+ string = ciPtr->u.chars + offsetBytes;
+ numBytes -= offsetBytes;
+
+ if (string[numBytes - 1] == '\t' && --numBytes == 0) {
+ return;
+ }
+
+ sValuePtr = stylePtr->sValuePtr;
+ fgGC = GetForegroundGC(textPtr, chunkPtr);
+
+ /*
+ * Draw the text, underline, and overstrike for this chunk.
+ */
+
+ Tk_DrawChars(display, dst, fgGC, sValuePtr->tkfont, string, numBytes,
+ offsetX, y - sValuePtr->offset);
+ if (sValuePtr->underline) {
+ Tk_UnderlineChars(display, dst, stylePtr->ulGC, sValuePtr->tkfont,
+ string, offsetX, y - sValuePtr->offset, 0, numBytes);
+
+ }
+ if (sValuePtr->overstrike) {
+ Tk_FontMetrics fm;
+
+ Tk_GetFontMetrics(sValuePtr->tkfont, &fm);
+ Tk_UnderlineChars(display, dst, stylePtr->ovGC, sValuePtr->tkfont, string, offsetX,
+ y - sValuePtr->offset - fm.descent - (fm.ascent*3)/10, 0, numBytes);
+ }
+ }
+ }
+}
+
+#endif /* TK_DRAW_IN_CONTEXT */
+
+static void
+DisplayChars(
+ TkText *textPtr,
+ TkTextDispChunk *chunkPtr, /* Display the content of this chunk. */
+ int x, /* X-position in dst at which to draw. */
+ int y, /* Y-position at which to draw. */
+ int baseline, /* Offset of baseline from y. */
+ Display *display, /* Display to use for drawing. */
+ Drawable dst) /* Pixmap or window in which to draw chunk. */
+{
+ const TextStyle *stylePtr = chunkPtr->stylePtr;
+ int offsetBytes, offsetX;
+
+ assert(!stylePtr->sValuePtr->elide);
+
+ if (stylePtr->fgGC == None) {
+ return;
+ }
/*
- * Invalidate the stored pixel width of the base chunk.
+ * If the text sticks out way to the left of the window, skip over the
+ * characters that aren't in the visible part of the window. This is
+ * essential if x is very negative (such as less than 32K); otherwise
+ * overflow problems will occur in servers that use 16-bit arithmetic,
+ * like X.
*/
- bciPtr->width = -1;
+ offsetX = x;
+ offsetBytes = (x >= 0) ? CharChunkMeasureChars(chunkPtr, NULL, 0, 0, -1, x, 0, 0, &offsetX) : 0;
+ DrawChars(textPtr, chunkPtr, x, y + baseline, offsetX, offsetBytes, display, dst);
}
-#endif /* TK_LAYOUT_WITH_BASE_CHUNKS */
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
- * fill-column: 78
+ * fill-column: 105
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/generic/tkTextImage.c b/generic/tkTextImage.c
index 41dd448..e7895d7 100644
--- a/generic/tkTextImage.c
+++ b/generic/tkTextImage.c
@@ -5,6 +5,7 @@
* widgets. It also implements the "image" widget command for texts.
*
* Copyright (c) 1997 Sun Microsystems, Inc.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -12,43 +13,111 @@
#include "tkPort.h"
#include "tkText.h"
+#include "tkTextTagSet.h"
+#include "tkTextUndo.h"
+#include <assert.h>
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? a : b)
+#endif
+#ifndef MAX
+# define MAX(a,b) ((a) < (b) ? b : a)
+#endif
+
+#if NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
/*
- * Macro that determines the size of an embedded image segment:
+ * Support of tk8.5.
*/
-
-#define EI_SEG_SIZE \
- ((unsigned) (Tk_Offset(TkTextSegment, body) + sizeof(TkTextEmbImage)))
+#ifdef CONST
+# undef CONST
+#endif
+#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5
+# define CONST
+#else
+# define CONST const
+#endif
/*
* Prototypes for functions defined in this file:
*/
-static TkTextSegment * EmbImageCleanupProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static void EmbImageCheckProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static void EmbImageBboxProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr, int index, int y,
- int lineHeight, int baseline, int *xPtr, int *yPtr,
- int *widthPtr, int *heightPtr);
-static int EmbImageConfigure(TkText *textPtr,
- TkTextSegment *eiPtr, int objc,
+static void EmbImageCheckProc(const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr);
+static Tcl_Obj * EmbImageInspectProc(const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr);
+static void EmbImageBboxProc(TkText *textPtr, TkTextDispChunk *chunkPtr, int index, int y,
+ int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr,
+ int *heightPtr);
+static int EmbImageConfigure(TkText *textPtr, TkTextSegment *eiPtr, int *maskPtr, int objc,
Tcl_Obj *const objv[]);
-static int EmbImageDeleteProc(TkTextSegment *segPtr,
- TkTextLine *linePtr, int treeGone);
-static void EmbImageDisplayProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr, int x, int y,
- int lineHeight, int baseline, Display *display,
- Drawable dst, int screenY);
-static int EmbImageLayoutProc(TkText *textPtr,
- TkTextIndex *indexPtr, TkTextSegment *segPtr,
- int offset, int maxX, int maxChars,
- int noCharsYet, TkWrapMode wrapMode,
- TkTextDispChunk *chunkPtr);
-static void EmbImageProc(ClientData clientData, int x, int y,
- int width, int height, int imageWidth,
- int imageHeight);
+static bool EmbImageDeleteProc(TkTextBTree tree, TkTextSegment *segPtr, int treeGone);
+static void EmbImageRestoreProc(TkTextSegment *segPtr);
+static void EmbImageDisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr, int x, int y,
+ int lineHeight, int baseline, Display *display, Drawable dst, int screenY);
+static int EmbImageLayoutProc(const TkTextIndex *indexPtr, TkTextSegment *segPtr,
+ int offset, int maxX, int maxChars, bool noCharsYet, TkWrapMode wrapMode,
+ TkTextSpaceMode spaceMode, TkTextDispChunk *chunkPtr);
+static void EmbImageProc(ClientData clientData, int x, int y, int width, int height,
+ int imageWidth, int imageHeight);
+static TkTextSegment * MakeImage(TkText *textPtr);
+static void ReleaseImage(TkTextSegment *eiPtr);
+
+static const TkTextDispChunkProcs layoutImageProcs = {
+ TEXT_DISP_IMAGE, /* type */
+ EmbImageDisplayProc, /* displayProc */
+ NULL, /* undisplayProc */
+ NULL, /* measureProc */
+ EmbImageBboxProc, /* bboxProc */
+};
+
+/*
+ * We need some private undo/redo stuff.
+ */
+
+static void UndoLinkSegmentPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void RedoLinkSegmentPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void UndoLinkSegmentDestroy(TkSharedText *, TkTextUndoToken *, bool);
+static void UndoLinkSegmentGetRange(const TkSharedText *, const TkTextUndoToken *,
+ TkTextIndex *, TkTextIndex *);
+static void RedoLinkSegmentGetRange(const TkSharedText *, const TkTextUndoToken *,
+ TkTextIndex *, TkTextIndex *);
+static Tcl_Obj *UndoLinkSegmentGetCommand(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *UndoLinkSegmentInspect(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *RedoLinkSegmentInspect(const TkSharedText *, const TkTextUndoToken *);
+
+static const Tk_UndoType undoTokenLinkSegmentType = {
+ TK_TEXT_UNDO_IMAGE, /* action */
+ UndoLinkSegmentGetCommand, /* commandProc */
+ UndoLinkSegmentPerform, /* undoProc */
+ UndoLinkSegmentDestroy, /* destroyProc */
+ UndoLinkSegmentGetRange, /* rangeProc */
+ UndoLinkSegmentInspect /* inspectProc */
+};
+
+static const Tk_UndoType redoTokenLinkSegmentType = {
+ TK_TEXT_REDO_IMAGE, /* action */
+ UndoLinkSegmentGetCommand, /* commandProc */
+ RedoLinkSegmentPerform, /* undoProc */
+ UndoLinkSegmentDestroy, /* destroyProc */
+ RedoLinkSegmentGetRange, /* rangeProc */
+ RedoLinkSegmentInspect /* inspectProc */
+};
+
+typedef struct UndoTokenLinkSegment {
+ const Tk_UndoType *undoType;
+ TkTextSegment *segPtr;
+} UndoTokenLinkSegment;
+
+typedef struct RedoTokenLinkSegment {
+ const Tk_UndoType *undoType;
+ TkTextSegment *segPtr;
+ TkTextUndoIndex index;
+} RedoTokenLinkSegment;
/*
* The following structure declares the "embedded image" segment type.
@@ -56,20 +125,20 @@ static void EmbImageProc(ClientData clientData, int x, int y,
const Tk_SegType tkTextEmbImageType = {
"image", /* name */
- 0, /* leftGravity */
- NULL, /* splitProc */
+ SEG_GROUP_IMAGE, /* group */
+ GRAVITY_NEUTRAL, /* gravity */
EmbImageDeleteProc, /* deleteProc */
- EmbImageCleanupProc, /* cleanupProc */
- NULL, /* lineChangeProc */
+ EmbImageRestoreProc, /* restoreProc */
EmbImageLayoutProc, /* layoutProc */
- EmbImageCheckProc /* checkProc */
+ EmbImageCheckProc, /* checkProc */
+ EmbImageInspectProc /* inspectProc */
};
/*
* Definitions for alignment values:
*/
-static const char *const alignStrings[] = {
+static const char *CONST alignStrings[] = {
"baseline", "bottom", "center", "top", NULL
};
@@ -83,20 +152,188 @@ typedef enum {
static const Tk_OptionSpec optionSpecs[] = {
{TK_OPTION_STRING_TABLE, "-align", NULL, NULL,
- "center", -1, Tk_Offset(TkTextEmbImage, align),
- 0, alignStrings, 0},
+ "center", -1, Tk_Offset(TkTextEmbImage, align), 0, alignStrings, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-padx", NULL, NULL,
"0", -1, Tk_Offset(TkTextEmbImage, padX), 0, 0, 0},
{TK_OPTION_PIXELS, "-pady", NULL, NULL,
- "0", -1, Tk_Offset(TkTextEmbImage, padY), 0, 0, 0},
+ "0", -1, Tk_Offset(TkTextEmbImage, padY), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING, "-image", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextEmbImage, imageString),
- TK_OPTION_NULL_OK, 0, 0},
+ NULL, -1, Tk_Offset(TkTextEmbImage, imageString), TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING, "-name", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextEmbImage, imageName),
- TK_OPTION_NULL_OK, 0, 0},
+ NULL, -1, Tk_Offset(TkTextEmbImage, imageName), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0}
};
+
+DEBUG_ALLOC(extern unsigned tkTextCountDestroySegment);
+DEBUG_ALLOC(extern unsigned tkTextCountNewUndoToken);
+DEBUG_ALLOC(extern unsigned tkTextCountNewSegment);
+
+/*
+ * Some helper functions.
+ */
+
+static void
+TextChanged(
+ TkSharedText *sharedTextPtr,
+ TkTextIndex *indexPtr,
+ int mask)
+{
+ TkTextChanged(sharedTextPtr, NULL, indexPtr, indexPtr);
+
+ if (mask & TK_TEXT_LINE_GEOMETRY) {
+ TkTextInvalidateLineMetrics(sharedTextPtr, NULL,
+ TkTextIndexGetLine(indexPtr), 0, TK_TEXT_INVALIDATE_ONLY);
+ }
+}
+
+static void
+GetIndex(
+ const TkSharedText *sharedTextPtr,
+ TkTextSegment *segPtr,
+ TkTextIndex *indexPtr)
+{
+ TkTextIndexClear2(indexPtr, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(indexPtr, segPtr);
+}
+
+/*
+ * Some functions for the undo/redo mechanism.
+ */
+
+static Tcl_Obj *
+UndoLinkSegmentGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("image", -1));
+ return objPtr;
+}
+
+static Tcl_Obj *
+UndoLinkSegmentInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenLinkSegment *token = (const UndoTokenLinkSegment *) item;
+ Tcl_Obj *objPtr = UndoLinkSegmentGetCommand(sharedTextPtr, item);
+ char buf[TK_POS_CHARS];
+ TkTextIndex index;
+
+ GetIndex(sharedTextPtr, token->segPtr, &index);
+ TkTextIndexPrint(sharedTextPtr, NULL, &index, buf);
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(buf, -1));
+ return objPtr;
+}
+
+static void
+UndoLinkSegmentPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ const UndoTokenLinkSegment *token = (const UndoTokenLinkSegment *) undoInfo->token;
+ TkTextSegment *segPtr = token->segPtr;
+ TkTextIndex index;
+
+ if (redoInfo) {
+ RedoTokenLinkSegment *redoToken;
+ redoToken = malloc(sizeof(RedoTokenLinkSegment));
+ redoToken->undoType = &redoTokenLinkSegmentType;
+ TkBTreeMakeUndoIndex(sharedTextPtr, segPtr, &redoToken->index);
+ redoInfo->token = (TkTextUndoToken *) redoToken;
+ (redoToken->segPtr = segPtr)->refCount += 1;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ }
+
+ GetIndex(sharedTextPtr, segPtr, &index);
+ TextChanged(sharedTextPtr, &index, TK_TEXT_LINE_GEOMETRY);
+ TkBTreeUnlinkSegment(sharedTextPtr, segPtr);
+ EmbImageDeleteProc(sharedTextPtr->tree, segPtr, 0);
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+}
+
+static void
+UndoLinkSegmentDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *item,
+ bool reused)
+{
+ UndoTokenLinkSegment *token = (UndoTokenLinkSegment *) item;
+
+ assert(!reused);
+
+ if (--token->segPtr->refCount == 0) {
+ ReleaseImage(token->segPtr);
+ }
+}
+
+static void
+UndoLinkSegmentGetRange(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item,
+ TkTextIndex *startIndex,
+ TkTextIndex *endIndex)
+{
+ const UndoTokenLinkSegment *token = (const UndoTokenLinkSegment *) item;
+
+ GetIndex(sharedTextPtr, token->segPtr, startIndex);
+ *endIndex = *startIndex;
+}
+
+static Tcl_Obj *
+RedoLinkSegmentInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const RedoTokenLinkSegment *token = (const RedoTokenLinkSegment *) item;
+ Tcl_Obj *objPtr = EmbImageInspectProc(sharedTextPtr, token->segPtr);
+ char buf[TK_POS_CHARS];
+ TkTextIndex index;
+ Tcl_Obj *idxPtr;
+
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->index, &index);
+ TkTextIndexPrint(sharedTextPtr, NULL, &index, buf);
+ idxPtr = Tcl_NewStringObj(buf, -1);
+ Tcl_ListObjReplace(NULL, objPtr, 0, 0, 1, &idxPtr);
+ return objPtr;
+}
+
+static void
+RedoLinkSegmentPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ RedoTokenLinkSegment *token = (RedoTokenLinkSegment *) undoInfo->token;
+ TkTextIndex index;
+
+ TkBTreeReInsertSegment(sharedTextPtr, &token->index, token->segPtr);
+
+ if (redoInfo) {
+ redoInfo->token = undoInfo->token;
+ token->undoType = &undoTokenLinkSegmentType;
+ }
+
+ GetIndex(sharedTextPtr, token->segPtr, &index);
+ TextChanged(sharedTextPtr, &index, TK_TEXT_LINE_GEOMETRY);
+ token->segPtr->refCount += 1;
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+}
+
+static void
+RedoLinkSegmentGetRange(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item,
+ TkTextIndex *startIndex,
+ TkTextIndex *endIndex)
+{
+ const RedoTokenLinkSegment *token = (const RedoTokenLinkSegment *) item;
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->index, startIndex);
+ *endIndex = *startIndex;
+}
/*
*--------------------------------------------------------------
@@ -115,9 +352,18 @@ static const Tk_OptionSpec optionSpecs[] = {
*--------------------------------------------------------------
*/
+static bool
+Displayed(
+ const TkTextEmbImage *img,
+ const TkText *peer)
+{
+ return peer->pixelReference < img->numClients
+ && !TkQTreeRectIsEmpty(&img->bbox[peer->pixelReference]);
+}
+
int
TkTextImageCmd(
- register TkText *textPtr, /* Information about text widget. */
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
@@ -125,24 +371,80 @@ TkTextImageCmd(
* objv[1] is "image". */
{
int idx;
- register TkTextSegment *eiPtr;
+ TkTextSegment *eiPtr;
+ TkSharedText *sharedTextPtr;
TkTextIndex index;
- static const char *const optionStrings[] = {
- "cget", "configure", "create", "names", NULL
+ static const char *CONST optionStrings[] = {
+ "bind", "cget", "configure", "create", "names", NULL
};
enum opts {
- CMD_CGET, CMD_CONF, CMD_CREATE, CMD_NAMES
+ CMD_BIND, CMD_CGET, CMD_CONF, CMD_CREATE, CMD_NAMES
};
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
+ Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?");
return TCL_ERROR;
}
- if (Tcl_GetIndexFromObjStruct(interp, objv[2], optionStrings,
- sizeof(char *), "option", 0, &idx) != TCL_OK) {
+ if (Tcl_GetIndexFromObj(interp, objv[2], optionStrings, "option", 0, &idx) != TCL_OK) {
return TCL_ERROR;
}
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+
switch ((enum opts) idx) {
+ case CMD_BIND: {
+ TkTextEmbImage *img;
+ int rc;
+
+ if (objc < 4 || objc > 6) {
+ Tcl_WrongNumArgs(interp, 3, objv, "index ?sequence? ?command?");
+ return TCL_ERROR;
+ }
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
+ return TCL_ERROR;
+ }
+ eiPtr = TkTextIndexGetContentSegment(&index, NULL);
+ if (eiPtr->typePtr != &tkTextEmbImageType) {
+ Tcl_AppendResult(interp, "no embedded image at index \"",
+ Tcl_GetString(objv[3]), "\"", NULL);
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_IMAGE", NULL);
+ return TCL_ERROR;
+ }
+ img = &eiPtr->body.ei;
+ rc = TkTextBindEvent(interp, objc - 4, objv + 4, sharedTextPtr,
+ &sharedTextPtr->imageBindingTable, img->name);
+ if (rc == TCL_OK && !img->haveBindings) {
+ img->haveBindings = true;
+
+ if (!textPtr->imageBboxTree) {
+ TkText *peer;
+
+ for (peer = sharedTextPtr->peers; peer; peer = peer->next) {
+ if (Displayed(img, peer)) {
+ TkQTreeRect bbox;
+ int dx, dy;
+
+ /*
+ * This image is already displayed, so we have to insert the bounding
+ * box of this image in the lookup tree, but this tree must be
+ * configured before we can add the bbox.
+ */
+
+ TkTextGetViewOffset(peer, &dx, &dy);
+ TkQTreeRectSet(&bbox, dx, dy,
+ Tk_Width(peer->tkwin) + dx, Tk_Height(peer->tkwin) + dy);
+ TkQTreeConfigure(&peer->imageBboxTree, &bbox);
+ peer->configureBboxTree = false;
+ TkQTreeInsertRect(peer->imageBboxTree, &img->bbox[peer->pixelReference],
+ (TkQTreeUid) img, 0);
+ } else if (!peer->imageBboxTree) {
+ peer->configureBboxTree = true;
+ }
+ }
+ }
+ }
+ return rc;
+ }
case CMD_CGET: {
Tcl_Obj *objPtr;
@@ -150,20 +452,18 @@ TkTextImageCmd(
Tcl_WrongNumArgs(interp, 3, objv, "index option");
return TCL_ERROR;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
return TCL_ERROR;
}
- eiPtr = TkTextIndexToSeg(&index, NULL);
+ eiPtr = TkTextIndexGetContentSegment(&index, NULL);
if (eiPtr->typePtr != &tkTextEmbImageType) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "no embedded image at index \"%s\"",
- Tcl_GetString(objv[3])));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_IMAGE", NULL);
+ Tcl_AppendResult(interp, "no embedded image at index \"",
+ Tcl_GetString(objv[3]), "\"", NULL);
return TCL_ERROR;
}
objPtr = Tk_GetOptionValue(interp, (char *) &eiPtr->body.ei,
eiPtr->body.ei.optionTable, objv[4], textPtr->tkwin);
- if (objPtr == NULL) {
+ if (!objPtr) {
return TCL_ERROR;
} else {
Tcl_SetObjResult(interp, objPtr);
@@ -172,131 +472,251 @@ TkTextImageCmd(
}
case CMD_CONF:
if (objc < 4) {
- Tcl_WrongNumArgs(interp, 3, objv, "index ?-option value ...?");
+ Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?");
return TCL_ERROR;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
return TCL_ERROR;
}
- eiPtr = TkTextIndexToSeg(&index, NULL);
+ eiPtr = TkTextIndexGetContentSegment(&index, NULL);
if (eiPtr->typePtr != &tkTextEmbImageType) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "no embedded image at index \"%s\"",
- Tcl_GetString(objv[3])));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_IMAGE", NULL);
+ Tcl_AppendResult(interp, "no embedded image at index \"",
+ Tcl_GetString(objv[3]), "\"", NULL);
return TCL_ERROR;
}
if (objc <= 5) {
Tcl_Obj *objPtr = Tk_GetOptionInfo(interp,
(char *) &eiPtr->body.ei, eiPtr->body.ei.optionTable,
- (objc == 5) ? objv[4] : NULL, textPtr->tkwin);
+ objc == 5 ? objv[4] : NULL, textPtr->tkwin);
- if (objPtr == NULL) {
+ if (!objPtr) {
return TCL_ERROR;
} else {
Tcl_SetObjResult(interp, objPtr);
return TCL_OK;
}
} else {
- TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index);
+ int mask;
+ int rc = EmbImageConfigure(textPtr, eiPtr, &mask, objc - 4, objv + 4);
+ TextChanged(sharedTextPtr, &index, mask);
+ return rc;
+ }
+ case CMD_CREATE: {
+ int mask;
/*
- * It's probably not true that all window configuration can change
- * the line height, so we could be more efficient here and only
- * call this when necessary.
+ * Add a new image. Find where to put the new image, and mark that
+ * position for redisplay.
*/
- TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL,
- index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
- return EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4);
- }
- case CMD_CREATE: {
- int lineIndex;
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?");
+ return TCL_ERROR;
+ }
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
+ return TCL_ERROR;
+ }
- /*
- * Add a new image. Find where to put the new image, and mark that
- * position for redisplay.
- */
+ if (textPtr->state == TK_TEXT_STATE_DISABLED) {
+#if !SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("attempt to modify disabled widget"));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NOT_ALLOWED", NULL);
+ return TCL_ERROR;
+#endif /* SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET */
+ }
- if (objc < 4) {
- Tcl_WrongNumArgs(interp, 3, objv, "index ?-option value ...?");
- return TCL_ERROR;
- }
- if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
- return TCL_ERROR;
- }
+ /*
+ * Don't allow insertions on the last line of the text.
+ */
- /*
- * Don't allow insertions on the last (dummy) line of the text.
- */
+ if (!TkTextIndexEnsureBeforeLastChar(&index)) {
+#if SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET
+ return TCL_OK;
+#else
+ Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(
+ "cannot insert image into dead peer", -1));
+ Tcl_SetErrorCode(textPtr->interp, "TK", "TEXT", "IMAGE_CREATE_USAGE", NULL);
+ return TCL_ERROR;
+#endif
+ }
- lineIndex = TkBTreeLinesTo(textPtr, index.linePtr);
- if (lineIndex == TkBTreeNumLines(textPtr->sharedTextPtr->tree,
- textPtr)) {
- lineIndex--;
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineIndex, 1000000, &index);
- }
+ /*
+ * Create the new image segment and initialize it.
+ */
- /*
- * Create the new image segment and initialize it.
- */
+ eiPtr = MakeImage(textPtr);
- eiPtr = ckalloc(EI_SEG_SIZE);
- eiPtr->typePtr = &tkTextEmbImageType;
- eiPtr->size = 1;
- eiPtr->body.ei.sharedTextPtr = textPtr->sharedTextPtr;
- eiPtr->body.ei.linePtr = NULL;
- eiPtr->body.ei.imageName = NULL;
- eiPtr->body.ei.imageString = NULL;
- eiPtr->body.ei.name = NULL;
- eiPtr->body.ei.image = NULL;
- eiPtr->body.ei.align = ALIGN_CENTER;
- eiPtr->body.ei.padX = eiPtr->body.ei.padY = 0;
- eiPtr->body.ei.chunkCount = 0;
- eiPtr->body.ei.optionTable = Tk_CreateOptionTable(interp, optionSpecs);
+ /*
+ * Link the segment into the text widget, then configure it (delete it
+ * again if the configuration fails).
+ */
- /*
- * Link the segment into the text widget, then configure it (delete it
- * again if the configuration fails).
- */
+ TkBTreeLinkSegment(sharedTextPtr, eiPtr, &index);
+ if (EmbImageConfigure(textPtr, eiPtr, &mask, objc - 4, objv + 4) != TCL_OK) {
+ TkBTreeUnlinkSegment(sharedTextPtr, eiPtr);
+ EmbImageDeleteProc(sharedTextPtr->tree, eiPtr, 0);
+ return TCL_ERROR;
+ }
+ TextChanged(sharedTextPtr, &index, mask);
- TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index);
- TkBTreeLinkSegment(eiPtr, &index);
- if (EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4) != TCL_OK) {
- TkTextIndex index2;
+ if (!TkTextUndoStackIsFull(sharedTextPtr->undoStack)) {
+ UndoTokenLinkSegment *token;
- TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
- TkBTreeDeleteIndexRange(textPtr->sharedTextPtr->tree, &index, &index2);
- return TCL_ERROR;
+ assert(sharedTextPtr->undoStack);
+ assert(eiPtr->typePtr == &tkTextEmbImageType);
+
+ token = malloc(sizeof(UndoTokenLinkSegment));
+ token->undoType = &undoTokenLinkSegmentType;
+ token->segPtr = eiPtr;
+ eiPtr->refCount += 1;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+
+ TkTextPushUndoToken(sharedTextPtr, token, 0);
+ }
+
+ TkTextUpdateAlteredFlag(sharedTextPtr);
}
- TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL,
- index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
return TCL_OK;
- }
case CMD_NAMES: {
Tcl_HashSearch search;
Tcl_HashEntry *hPtr;
- Tcl_Obj *resultObj;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- resultObj = Tcl_NewObj();
- for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->imageTable,
- &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
- Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr),
- -1));
+ for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->imageTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
+ Tcl_AppendElement(interp, Tcl_GetHashKey(&sharedTextPtr->imageTable, hPtr));
}
- Tcl_SetObjResult(interp, resultObj);
return TCL_OK;
}
- default:
- Tcl_Panic("unexpected switch fallthrough");
}
- return TCL_ERROR;
+ assert(!"unexpected switch fallthrough");
+ return TCL_ERROR; /* shouldn't be reached */
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextImageAddClient --
+ *
+ * This function is called to provide the image binding
+ * support of a client.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+void
+TkTextImageAddClient(
+ TkSharedText *sharedTextPtr,
+ TkText *textPtr)
+{
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr;
+ TkText *peer;
+
+ for (peer = sharedTextPtr->peers; peer; peer = peer->next) {
+ if (peer != textPtr && (peer->imageBboxTree || peer->configureBboxTree)) {
+ textPtr->configureBboxTree = true;
+ }
+ }
+
+ for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->imageTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
+ TkTextSegment *eiPtr = Tcl_GetHashValue(hPtr);
+ TkTextEmbImage *img = &eiPtr->body.ei;
+
+ if (img->numClients > textPtr->pixelReference) {
+ memset(&img->bbox[textPtr->pixelReference], 0, sizeof(img->bbox[0]));
+ }
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * MakeImage --
+ *
+ * This function is called to create an image segment.
+ *
+ * Results:
+ * The return value is the newly created image.
+ *
+ * Side effects:
+ * Some memory will be allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static TkTextSegment *
+MakeImage(
+ TkText *textPtr) /* Information about text widget that contains embedded image. */
+{
+ TkTextSegment *eiPtr;
+
+ eiPtr = memset(malloc(SEG_SIZE(TkTextEmbImage)), 0, SEG_SIZE(TkTextEmbImage));
+ eiPtr->typePtr = &tkTextEmbImageType;
+ eiPtr->size = 1;
+ eiPtr->refCount = 1;
+ eiPtr->body.ei.sharedTextPtr = textPtr->sharedTextPtr;
+ eiPtr->body.ei.align = ALIGN_CENTER;
+ eiPtr->body.ei.optionTable = Tk_CreateOptionTable(textPtr->interp, optionSpecs);
+ DEBUG_ALLOC(tkTextCountNewSegment++);
+
+ return eiPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextMakeImage --
+ *
+ * This function is called to create an image segment.
+ *
+ * Results:
+ * The return value is a standard Tcl result. If TCL_ERROR is returned,
+ * then the interp's result contains an error message.
+ *
+ * Side effects:
+ * Some memory will be allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+TkTextSegment *
+TkTextMakeImage(
+ TkText *textPtr, /* Information about text widget that contains embedded image. */
+ Tcl_Obj *options) /* Options for this image. */
+{
+ TkTextSegment *eiPtr;
+ Tcl_Obj **objv;
+ int objc;
+
+ assert(options);
+
+ if (Tcl_ListObjGetElements(textPtr->interp, options, &objc, &objv) != TCL_OK) {
+ return NULL;
+ }
+
+ eiPtr = MakeImage(textPtr);
+
+ if (EmbImageConfigure(textPtr, eiPtr, NULL, objc, objv) == TCL_OK) {
+ Tcl_ResetResult(textPtr->interp);
+ } else {
+ EmbImageDeleteProc(textPtr->sharedTextPtr->tree, eiPtr, 0);
+ eiPtr = NULL;
+ }
+
+ return eiPtr;
}
/*
@@ -309,7 +729,7 @@ TkTextImageCmd(
*
* Results:
* The return value is a standard Tcl result. If TCL_ERROR is returned,
- * then the interp's result contains an error message..
+ * then the interp's result contains an error message.
*
* Side effects:
* Configuration information for the embedded image changes, such as
@@ -318,28 +738,65 @@ TkTextImageCmd(
*--------------------------------------------------------------
*/
+static void
+SetImageName(
+ TkText *textPtr,
+ TkTextSegment *eiPtr,
+ const char *name)
+{
+ Tcl_DString newName;
+ TkTextEmbImage *img;
+ int dummy, length;
+
+ assert(name);
+ assert(!eiPtr->body.ei.name);
+ assert(!eiPtr->body.ei.hPtr);
+
+ /*
+ * Create an unique name for this image.
+ */
+
+ Tcl_DStringInit(&newName);
+ while (Tcl_FindHashEntry(&textPtr->sharedTextPtr->imageTable, name)) {
+ char buf[4 + TCL_INTEGER_SPACE];
+ snprintf(buf, sizeof(buf), "#%d", ++textPtr->sharedTextPtr->imageCount);
+ Tcl_DStringTrunc(&newName, 0);
+ Tcl_DStringAppend(&newName, name, -1);
+ Tcl_DStringAppend(&newName, buf, -1);
+ name = Tcl_DStringValue(&newName);
+ }
+ length = strlen(name);
+
+ img = &eiPtr->body.ei;
+ img->hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->imageTable, name, &dummy);
+ textPtr->sharedTextPtr->numImages += 1;
+ Tcl_SetHashValue(img->hPtr, eiPtr);
+ img->name = malloc(length + 1);
+ memcpy(img->name, name, length + 1);
+ Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(name, -1));
+ Tcl_DStringFree(&newName);
+}
+
static int
EmbImageConfigure(
- TkText *textPtr, /* Information about text widget that contains
- * embedded image. */
+ TkText *textPtr, /* Information about text widget that contains embedded image. */
TkTextSegment *eiPtr, /* Embedded image to be configured. */
+ int *maskPtr, /* Return the bit-wise OR of the typeMask fields of affected options,
+ * can be NULL. */
int objc, /* Number of strings in objv. */
- Tcl_Obj *const objv[]) /* Array of strings describing configuration
- * options. */
+ Tcl_Obj *const objv[]) /* Array of strings describing configuration options. */
{
Tk_Image image;
- Tcl_DString newName;
- Tcl_HashEntry *hPtr;
- Tcl_HashSearch search;
char *name;
- int dummy;
- int count = 0; /* The counter for picking a unique name */
- int conflict = 0; /* True if we have a name conflict */
- size_t len; /* length of image name */
-
- if (Tk_SetOptions(textPtr->interp, (char *) &eiPtr->body.ei,
- eiPtr->body.ei.optionTable,
- objc, objv, textPtr->tkwin, NULL, NULL) != TCL_OK) {
+ int width;
+ TkTextEmbImage *img = &eiPtr->body.ei;
+
+ if (maskPtr) {
+ *maskPtr = 0;
+ }
+
+ if (Tk_SetOptions(textPtr->interp, (char *) img, img->optionTable, objc, objv, textPtr->tkwin,
+ NULL, maskPtr) != TCL_OK) {
return TCL_ERROR;
}
@@ -350,80 +807,134 @@ EmbImageConfigure(
* changed.
*/
- if (eiPtr->body.ei.imageString != NULL) {
- image = Tk_GetImage(textPtr->interp, textPtr->tkwin,
- eiPtr->body.ei.imageString, EmbImageProc, eiPtr);
- if (image == NULL) {
+ if (img->imageString) {
+ image = Tk_GetImage(textPtr->interp, textPtr->tkwin, img->imageString, EmbImageProc, eiPtr);
+ if (!image) {
return TCL_ERROR;
}
} else {
image = NULL;
}
- if (eiPtr->body.ei.image != NULL) {
- Tk_FreeImage(eiPtr->body.ei.image);
+ if (img->image) {
+ Tk_FreeImage(img->image);
}
- eiPtr->body.ei.image = image;
-
- if (eiPtr->body.ei.name != NULL) {
- return TCL_OK;
+ if ((img->image = image)) {
+ Tk_SizeOfImage(image, &width, &img->imgHeight);
}
- /*
- * Find a unique name for this image. Use imageName (or imageString) if
- * available, otherwise tack on a #nn and use it. If a name is already
- * associated with this image, delete the name.
- */
+ if (!img->name) {
+ if (!(name = img->imageName) && !(name = img->imageString)) {
+ Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(
+ "Either a \"-name\" or a \"-image\" argument must be"
+ " provided to the \"image create\" subcommand", -1));
+ Tcl_SetErrorCode(textPtr->interp, "TK", "TEXT", "IMAGE_CREATE_USAGE", NULL);
+ return TCL_ERROR;
+ }
- name = eiPtr->body.ei.imageName;
- if (name == NULL) {
- name = eiPtr->body.ei.imageString;
- }
- if (name == NULL) {
- Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(
- "Either a \"-name\" or a \"-image\" argument must be"
- " provided to the \"image create\" subcommand", -1));
- Tcl_SetErrorCode(textPtr->interp, "TK", "TEXT", "IMAGE_CREATE_USAGE",
- NULL);
- return TCL_ERROR;
+ Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(img->name, -1));
+ SetImageName(textPtr, eiPtr, name);
}
- len = strlen(name);
- for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->imageTable,
- &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- char *haveName =
- Tcl_GetHashKey(&textPtr->sharedTextPtr->imageTable, hPtr);
- if (strncmp(name, haveName, len) == 0) {
- int newVal = 0;
+ return TCL_OK;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * EmbImageInspectProc --
+ *
+ * This function is invoked to build the information for
+ * "inspect".
+ *
+ * Results:
+ * Return a TCL object containing the information for
+ * "inspect".
+ *
+ * Side effects:
+ * Storage is allocated.
+ *
+ *--------------------------------------------------------------
+ */
- sscanf(haveName+len, "#%d", &newVal);
- if (newVal > count) {
- count = newVal;
- }
- if (len == strlen(haveName)) {
- conflict = 1;
- }
- }
+static Tcl_Obj *
+EmbImageInspectProc(
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_Obj *objPtr2 = Tcl_NewObj();
+ TkTextTag **tagLookup = sharedTextPtr->tagLookup;
+ const TkTextTagSet *tagInfoPtr = segPtr->tagInfoPtr;
+ unsigned i = TkTextTagSetFindFirst(tagInfoPtr);
+ const TkTextEmbImage *img = &segPtr->body.ei;
+ Tcl_DString opts;
+
+ assert(sharedTextPtr->peers);
+
+ for ( ; i != TK_TEXT_TAG_SET_NPOS; i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ const TkTextTag *tagPtr = tagLookup[i];
+ Tcl_ListObjAppendElement(NULL, objPtr2, Tcl_NewStringObj(tagPtr->name, -1));
}
- Tcl_DStringInit(&newName);
- Tcl_DStringAppend(&newName, name, -1);
+ Tcl_DStringInit(&opts);
+ TkTextInspectOptions(sharedTextPtr->peers, &segPtr->body.ei, segPtr->body.ei.optionTable,
+ &opts, false, false);
- if (conflict) {
- char buf[4 + TCL_INTEGER_SPACE];
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(segPtr->typePtr->name, -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(img->name, -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, objPtr2);
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(Tcl_DStringValue(&opts),
+ Tcl_DStringLength(&opts)));
- sprintf(buf, "#%d", count+1);
- Tcl_DStringAppend(&newName, buf, -1);
+ Tcl_DStringFree(&opts);
+ return objPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * ReleaseImage --
+ *
+ * This function is releasing the embedded image all it's
+ * associated resources.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The embedded image is deleted, and any resources
+ * associated with it are released.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+ReleaseImage(
+ TkTextSegment *eiPtr)
+{
+ TkTextEmbImage *img = &eiPtr->body.ei;
+
+ if (img->image) {
+ Tk_FreeImage(img->image);
+ }
+ if (img->sharedTextPtr->imageBindingTable) {
+ Tk_DeleteAllBindings(img->sharedTextPtr->imageBindingTable, (ClientData) img->name);
}
- name = Tcl_DStringValue(&newName);
- hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->imageTable, name,
- &dummy);
- Tcl_SetHashValue(hPtr, eiPtr);
- Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(name, -1));
- eiPtr->body.ei.name = ckalloc(Tcl_DStringLength(&newName) + 1);
- strcpy(eiPtr->body.ei.name, name);
- Tcl_DStringFree(&newName);
- return TCL_OK;
+ /*
+ * No need to supply a tkwin argument, since we have no window-specific options.
+ */
+
+ Tk_FreeConfigOptions((char *) img, img->optionTable, NULL);
+ if (img->name) {
+ free(img->name);
+ }
+ if (img->bbox) {
+ free(img->bbox);
+ }
+ TkTextTagSetDecrRefCount(eiPtr->tagInfoPtr);
+ FREE_SEGMENT(eiPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
}
/*
@@ -431,11 +942,11 @@ EmbImageConfigure(
*
* EmbImageDeleteProc --
*
- * This function is invoked by the text B-tree code whenever an embedded
- * image lies in a range of characters being deleted.
+ * This function is invoked by the text B-tree code whenever an
+ * embedded image lies in a range of characters being deleted.
*
* Results:
- * Returns 0 to indicate that the deletion has been accepted.
+ * Returns true to indicate that the deletion has been accepted.
*
* Side effects:
* The embedded image is deleted, if it exists, and any resources
@@ -444,70 +955,97 @@ EmbImageConfigure(
*--------------------------------------------------------------
*/
- /* ARGSUSED */
-static int
+static bool
EmbImageDeleteProc(
+ TkTextBTree tree,
TkTextSegment *eiPtr, /* Segment being deleted. */
- TkTextLine *linePtr, /* Line containing segment. */
- int treeGone) /* Non-zero means the entire tree is being
- * deleted, so everything must get cleaned
- * up. */
+ int flags) /* Flags controlling the deletion. */
{
- Tcl_HashEntry *hPtr;
+ TkTextEmbImage *img = &eiPtr->body.ei;
- if (eiPtr->body.ei.image != NULL) {
- hPtr = Tcl_FindHashEntry(&eiPtr->body.ei.sharedTextPtr->imageTable,
- eiPtr->body.ei.name);
- if (hPtr != NULL) {
- /*
- * (It's possible for there to be no hash table entry for this
- * image, if an error occurred while creating the image segment
- * but before the image got added to the table)
- */
+ assert(eiPtr->refCount > 0);
- Tcl_DeleteHashEntry(hPtr);
- }
- Tk_FreeImage(eiPtr->body.ei.image);
+ if (img->hPtr) {
+ img->sharedTextPtr->numImages -= 1;
+ Tcl_DeleteHashEntry(img->hPtr);
+ img->hPtr = NULL;
}
/*
- * No need to supply a tkwin argument, since we have no window-specific
- * options.
+ * Remove this image from bounding box tree in all peers, and clear
+ * the information about the currently hovered image if necessary.
*/
- Tk_FreeConfigOptions((char *) &eiPtr->body.ei, eiPtr->body.ei.optionTable,
- NULL);
- if (eiPtr->body.ei.name) {
- ckfree(eiPtr->body.ei.name);
+ if (img->haveBindings) {
+ TkText *peer = img->sharedTextPtr->peers;
+
+ for ( ; peer; peer = peer->next) {
+ if (!(peer->flags & DESTROYED && Displayed(img, peer))) {
+ if (peer->hoveredImageArrSize) {
+ unsigned i;
+
+ for (i = 0; i < peer->hoveredImageArrSize; ++i) {
+ if (peer->hoveredImageArr[i] == img) {
+ /*
+ * One problem here, the mouse leave event will not be
+ * triggered anymore. The user should avoid this situation.
+ */
+ memmove(peer->hoveredImageArr + i, peer->hoveredImageArr + i + 1,
+ --peer->hoveredImageArrSize - i);
+ break;
+ }
+ }
+ }
+ if (peer->imageBboxTree) {
+ TkQTreeDeleteRect(peer->imageBboxTree, &img->bbox[peer->pixelReference],
+ (TkQTreeUid) img);
+ }
+ }
+ }
}
- ckfree(eiPtr);
- return 0;
+
+ if (--eiPtr->refCount == 0) {
+ ReleaseImage(eiPtr);
+ }
+
+ return true;
}
/*
*--------------------------------------------------------------
*
- * EmbImageCleanupProc --
+ * EmbImageRestoreProc --
*
- * This function is invoked by the B-tree code whenever a segment
- * containing an embedded image is moved from one line to another.
+ * This function is called when an image segment will be
+ * restored from the undo chain.
*
* Results:
* None.
*
* Side effects:
- * The linePtr field of the segment gets updated.
+ * The name of the mark will be freed, and the mark will be
+ * re-entered into the hash table.
*
*--------------------------------------------------------------
*/
-static TkTextSegment *
-EmbImageCleanupProc(
- TkTextSegment *eiPtr, /* Mark segment that's being moved. */
- TkTextLine *linePtr) /* Line that now contains segment. */
+static void
+EmbImageRestoreProc(
+ TkTextSegment *eiPtr) /* Segment to reuse. */
{
- eiPtr->body.ei.linePtr = linePtr;
- return eiPtr;
+ TkTextEmbImage *img = &eiPtr->body.ei;
+ int isNew;
+
+ if (img->image) {
+ assert(!img->hPtr);
+ img->hPtr = Tcl_CreateHashEntry(&img->sharedTextPtr->imageTable, img->name, &isNew);
+ img->sharedTextPtr->numImages += 1;
+ assert(isNew);
+ Tcl_SetHashValue(img->hPtr, eiPtr);
+ }
+ if (img->bbox) {
+ memset(img->bbox, 0, img->numClients * sizeof(img->bbox[0]));
+ }
}
/*
@@ -527,48 +1065,43 @@ EmbImageCleanupProc(
*--------------------------------------------------------------
*/
- /*ARGSUSED*/
static int
EmbImageLayoutProc(
- TkText *textPtr, /* Text widget being layed out. */
- TkTextIndex *indexPtr, /* Identifies first character in chunk. */
+ const TkTextIndex *indexPtr,/* Identifies first character in chunk. */
TkTextSegment *eiPtr, /* Segment corresponding to indexPtr. */
- int offset, /* Offset within segPtr corresponding to
- * indexPtr (always 0). */
- int maxX, /* Chunk must not occupy pixels at this
- * position or higher. */
- int maxChars, /* Chunk must not include more than this many
- * characters. */
- int noCharsYet, /* Non-zero means no characters have been
- * assigned to this line yet. */
+ int offset, /* Offset within segPtr corresponding to indexPtr (always 0). */
+ int maxX, /* Chunk must not occupy pixels at this position or higher. */
+ int maxChars, /* Chunk must not include more than this many characters. */
+ bool noCharsYet, /* 'true' means no characters have been assigned to this line yet. */
TkWrapMode wrapMode, /* Wrap mode to use for line:
- * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or
- * TEXT_WRAPMODE_WORD. */
- register TkTextDispChunk *chunkPtr)
- /* Structure to fill in with information about
- * this chunk. The x field has already been
- * set by the caller. */
+ * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or TEXT_WRAPMODE_WORD. */
+ TkTextSpaceMode spaceMode, /* Not used. */
+ TkTextDispChunk *chunkPtr) /* Structure to fill in with information about this chunk. The x
+ * field has already been set by the caller. */
{
+ TkTextEmbImage *img = &eiPtr->body.ei;
int width, height;
- if (offset != 0) {
- Tcl_Panic("Non-zero offset in EmbImageLayoutProc");
- }
+ assert(indexPtr->textPtr);
+ assert(offset == 0);
/*
* See if there's room for this image on this line.
*/
- if (eiPtr->body.ei.image == NULL) {
+ if (!img->image) {
width = 0;
height = 0;
+ img->imgHeight = 0;
} else {
- Tk_SizeOfImage(eiPtr->body.ei.image, &width, &height);
- width += 2*eiPtr->body.ei.padX;
- height += 2*eiPtr->body.ei.padY;
+ Tk_SizeOfImage(img->image, &width, &height);
+ img->imgHeight = height;
+ width += 2*img->padX;
+ height += 2*img->padY;
}
- if ((width > (maxX - chunkPtr->x))
- && !noCharsYet && (textPtr->wrapMode != TEXT_WRAPMODE_NONE)) {
+ if ((width > maxX - chunkPtr->x)
+ && !noCharsYet
+ && (indexPtr->textPtr->wrapMode != TEXT_WRAPMODE_NONE)) {
return 0;
}
@@ -576,14 +1109,11 @@ EmbImageLayoutProc(
* Fill in the chunk structure.
*/
- chunkPtr->displayProc = EmbImageDisplayProc;
- chunkPtr->undisplayProc = NULL;
- chunkPtr->measureProc = NULL;
- chunkPtr->bboxProc = EmbImageBboxProc;
+ chunkPtr->layoutProcs = &layoutImageProcs;
chunkPtr->numBytes = 1;
- if (eiPtr->body.ei.align == ALIGN_BASELINE) {
- chunkPtr->minAscent = height - eiPtr->body.ei.padY;
- chunkPtr->minDescent = eiPtr->body.ei.padY;
+ if (img->align == ALIGN_BASELINE) {
+ chunkPtr->minAscent = height - img->padY;
+ chunkPtr->minDescent = img->padY;
chunkPtr->minHeight = 0;
} else {
chunkPtr->minAscent = 0;
@@ -591,10 +1121,8 @@ EmbImageLayoutProc(
chunkPtr->minHeight = height;
}
chunkPtr->width = width;
- chunkPtr->breakIndex = -1;
- chunkPtr->breakIndex = 1;
+ chunkPtr->breakIndex = (wrapMode == TEXT_WRAPMODE_NONE) ? -1 : 1;
chunkPtr->clientData = eiPtr;
- eiPtr->body.ei.chunkCount += 1;
return 1;
}
@@ -618,15 +1146,14 @@ EmbImageLayoutProc(
static void
EmbImageCheckProc(
- TkTextSegment *eiPtr, /* Segment to check. */
- TkTextLine *linePtr) /* Line containing segment. */
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *eiPtr) /* Segment to check. */
{
- if (eiPtr->nextPtr == NULL) {
+ if (!eiPtr->nextPtr) {
Tcl_Panic("EmbImageCheckProc: embedded image is last segment in line");
}
if (eiPtr->size != 1) {
- Tcl_Panic("EmbImageCheckProc: embedded image has size %d",
- eiPtr->size);
+ Tcl_Panic("EmbImageCheckProc: embedded image has size %d", eiPtr->size);
}
}
@@ -662,18 +1189,17 @@ EmbImageDisplayProc(
int baseline, /* Offset of baseline from y. */
Display *display, /* Display to use for drawing. */
Drawable dst, /* Pixmap or window in which to draw */
- int screenY) /* Y-coordinate in text window that
- * corresponds to y. */
+ int screenY) /* Y-coordinate in text window that corresponds to y. */
{
TkTextSegment *eiPtr = chunkPtr->clientData;
+ TkTextEmbImage *img = &eiPtr->body.ei;
int lineX, imageX, imageY, width, height;
+ TkQTreeRect oldBbox;
+ TkQTreeRect *bbox;
Tk_Image image;
+ int dx, dy;
- image = eiPtr->body.ei.image;
- if (image == NULL) {
- return;
- }
- if ((x + chunkPtr->width) <= 0) {
+ if (!(image = img->image)) {
return;
}
@@ -682,11 +1208,62 @@ EmbImageDisplayProc(
* account the align value for the image.
*/
- EmbImageBboxProc(textPtr, chunkPtr, 0, y, lineHeight, baseline, &lineX,
- &imageY, &width, &height);
+ EmbImageBboxProc(textPtr, chunkPtr, 0, y, lineHeight, baseline, &lineX, &imageY, &width, &height);
imageX = lineX - chunkPtr->x + x;
- Tk_RedrawImage(image, 0, 0, width, height, dst, imageX, imageY);
+ TkTextGetViewOffset(textPtr, &dx, &dy);
+
+ if (textPtr->configureBboxTree) {
+ TkQTreeRect bbox;
+
+ /*
+ * The view of the widget has changed. This is the appropriate place to
+ * re-configure the bounding box tree.
+ */
+
+ TkQTreeRectSet(&bbox, dx, dy, Tk_Width(textPtr->tkwin) + dx, Tk_Height(textPtr->tkwin) + dy);
+ TkQTreeConfigure(&textPtr->imageBboxTree, &bbox);
+ textPtr->configureBboxTree = false;
+ }
+
+ if (img->numClients <= textPtr->pixelReference) {
+ unsigned numClients = textPtr->pixelReference + 1;
+
+ assert((img->numClients == 0) == !img->bbox);
+ img->bbox = realloc(img->bbox, numClients * sizeof(img->bbox[0]));
+ memset(img->bbox + img->numClients, 0, (numClients - img->numClients) * sizeof(img->bbox[0]));
+ img->numClients = numClients;
+ }
+
+ /*
+ * Update the bounding box, used for detection of mouse hovering.
+ */
+
+ bbox = &img->bbox[textPtr->pixelReference];
+ oldBbox = *bbox;
+ bbox->xmin = imageX + dx;
+ bbox->xmax = bbox->xmin + width;
+ bbox->ymin = screenY + imageY + dy;
+ bbox->ymax = bbox->ymin + height;
+
+ if (img->haveBindings && textPtr->imageBboxTree) {
+ const TkQTreeRect *oldBboxPtr = TkQTreeRectIsEmpty(&oldBbox) ? NULL : &oldBbox;
+
+ if (!TkQTreeRectIsEmpty(bbox)) {
+ TkQTreeUpdateRect(textPtr->imageBboxTree, oldBboxPtr, bbox, (TkQTreeUid) img, 0);
+ } else if (oldBboxPtr) {
+ /* Possibly this case is not possible at all, but we want to be sure. */
+ TkQTreeDeleteRect(textPtr->imageBboxTree, oldBboxPtr, (TkQTreeUid) img);
+ }
+ }
+
+ if (x + chunkPtr->width > 0) {
+ /*
+ * Finally, redraw the image if inside widget area.
+ */
+
+ Tk_RedrawImage(image, 0, 0, width, height, dst, imageX, imageY);
+ }
}
/*
@@ -715,42 +1292,35 @@ static void
EmbImageBboxProc(
TkText *textPtr,
TkTextDispChunk *chunkPtr, /* Chunk containing desired char. */
- int index, /* Index of desired character within the
- * chunk. */
- int y, /* Topmost pixel in area allocated for this
- * line. */
+ int index, /* Index of desired character within the chunk. */
+ int y, /* Topmost pixel in area allocated for this line. */
int lineHeight, /* Total height of line. */
- int baseline, /* Location of line's baseline, in pixels
- * measured down from y. */
- int *xPtr, int *yPtr, /* Gets filled in with coords of character's
- * upper-left pixel. */
- int *widthPtr, /* Gets filled in with width of image, in
- * pixels. */
- int *heightPtr) /* Gets filled in with height of image, in
- * pixels. */
+ int baseline, /* Location of line's baseline, in pixels measured down from y. */
+ int *xPtr, int *yPtr, /* Gets filled in with coords of character's upper-left pixel. */
+ int *widthPtr, /* Gets filled in with width of image, in pixels. */
+ int *heightPtr) /* Gets filled in with height of image, in pixels. */
{
TkTextSegment *eiPtr = chunkPtr->clientData;
- Tk_Image image;
+ TkTextEmbImage *img = &eiPtr->body.ei;
+ Tk_Image image = img->image;
- image = eiPtr->body.ei.image;
- if (image != NULL) {
+ if (image) {
Tk_SizeOfImage(image, widthPtr, heightPtr);
} else {
- *widthPtr = 0;
- *heightPtr = 0;
+ *widthPtr = *heightPtr = 0;
}
- *xPtr = chunkPtr->x + eiPtr->body.ei.padX;
+ *xPtr = chunkPtr->x + img->padX;
- switch (eiPtr->body.ei.align) {
+ switch (img->align) {
case ALIGN_BOTTOM:
- *yPtr = y + (lineHeight - *heightPtr - eiPtr->body.ei.padY);
+ *yPtr = y + (lineHeight - *heightPtr - img->padY);
break;
case ALIGN_CENTER:
*yPtr = y + (lineHeight - *heightPtr)/2;
break;
case ALIGN_TOP:
- *yPtr = y + eiPtr->body.ei.padY;
+ *yPtr = y + img->padY;
break;
case ALIGN_BASELINE:
*yPtr = y + (baseline - *heightPtr);
@@ -767,8 +1337,8 @@ EmbImageBboxProc(
* index corresponding to the image's position in the text.
*
* Results:
- * The return value is 1 if there is an embedded image by the given name
- * in the text widget, 0 otherwise. If the image exists, *indexPtr is
+ * The return value is true if there is an embedded image by the given name
+ * in the text widget, false otherwise. If the image exists, *indexPtr is
* filled in with its index.
*
* Side effects:
@@ -777,7 +1347,7 @@ EmbImageBboxProc(
*--------------------------------------------------------------
*/
-int
+bool
TkTextImageIndex(
TkText *textPtr, /* Text widget containing image. */
const char *name, /* Name of image. */
@@ -786,19 +1356,15 @@ TkTextImageIndex(
Tcl_HashEntry *hPtr;
TkTextSegment *eiPtr;
- if (textPtr == NULL) {
- return 0;
- }
+ assert(textPtr);
- hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->imageTable, name);
- if (hPtr == NULL) {
- return 0;
+ if (!(hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->imageTable, name))) {
+ return false;
}
eiPtr = Tcl_GetHashValue(hPtr);
- indexPtr->tree = textPtr->sharedTextPtr->tree;
- indexPtr->linePtr = eiPtr->body.ei.linePtr;
- indexPtr->byteIndex = TkTextSegToOffset(eiPtr, indexPtr->linePtr);
- return 1;
+ TkTextIndexClear(indexPtr, textPtr);
+ TkTextIndexSetSegment(indexPtr, eiPtr);
+ return true;
}
/*
@@ -821,29 +1387,23 @@ TkTextImageIndex(
static void
EmbImageProc(
ClientData clientData, /* Pointer to widget record. */
- int x, int y, /* Upper left pixel (within image) that must
- * be redisplayed. */
- int width, int height, /* Dimensions of area to redisplay (may be
- * <= 0). */
+ int x, int y, /* Upper left pixel (within image) that must be redisplayed. */
+ int width, int height, /* Dimensions of area to redisplay (may be <= 0). */
int imgWidth, int imgHeight)/* New dimensions of image. */
{
TkTextSegment *eiPtr = clientData;
- TkTextIndex index;
+ TkTextEmbImage *img = &eiPtr->body.ei;
- index.tree = eiPtr->body.ei.sharedTextPtr->tree;
- index.linePtr = eiPtr->body.ei.linePtr;
- index.byteIndex = TkTextSegToOffset(eiPtr, eiPtr->body.ei.linePtr);
- TkTextChanged(eiPtr->body.ei.sharedTextPtr, NULL, &index, &index);
+ if (img->hPtr) {
+ TkTextIndex index;
+ int mask;
- /*
- * It's probably not true that all image changes can change the line
- * height, so we could be more efficient here and only call this when
- * necessary.
- */
-
- TkTextInvalidateLineMetrics(eiPtr->body.ei.sharedTextPtr, NULL,
- index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
+ assert(img->image);
+ GetIndex(img->sharedTextPtr, eiPtr, &index);
+ mask = (img->imgHeight == imgHeight) ? 0 : TK_TEXT_LINE_GEOMETRY;
+ TextChanged(img->sharedTextPtr, &index, mask);
+ }
}
/*
@@ -852,4 +1412,5 @@ EmbImageProc(
* c-basic-offset: 4
* fill-column: 78
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/generic/tkTextIndex.c b/generic/tkTextIndex.c
index cfe230c..c58ba17 100644
--- a/generic/tkTextIndex.c
+++ b/generic/tkTextIndex.c
@@ -1,11 +1,11 @@
/*
* tkTextIndex.c --
*
- * This module provides functions that manipulate indices for text
- * widgets.
+ * This module provides functions that manipulate indices for text widgets.
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -14,344 +14,1674 @@
#include "default.h"
#include "tkInt.h"
#include "tkText.h"
+#include <stdlib.h>
+#include <assert.h>
+
+#ifndef MAX
+# define MAX(a,b) (((int) a) < ((int) b) ? b : a)
+#endif
+#ifndef MIN
+# define MIN(a,b) (((int) a) < ((int) b) ? a : b)
+#endif
+
+#if NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
/*
- * Index to use to select last character in line (very large integer):
+ * Modifiers for index parsing: 'display', 'any' or nothing.
*/
-#define LAST_CHAR 1000000
+enum { TKINDEX_NONE, TKINDEX_DISPLAY, TKINDEX_CHAR };
/*
- * Modifiers for index parsing: 'display', 'any' or nothing.
+ * Forward declarations for functions defined later in this file:
*/
-#define TKINDEX_NONE 0
-#define TKINDEX_DISPLAY 1
-#define TKINDEX_ANY 2
+static const char * ForwBack(TkText *textPtr, const char *string, TkTextIndex *indexPtr);
+static const char * StartEnd(TkText *textPtr, const char *string, TkTextIndex *indexPtr);
+static bool GetIndex(Tcl_Interp *interp, TkSharedText *sharedTextPtr, TkText *textPtr,
+ const char *string, TkTextIndex *indexPtr);
+static TkTextSegment * IndexToSeg(const TkTextIndex *indexPtr, int *offsetPtr);
+static int SegToIndex(const TkTextLine *linePtr, const TkTextSegment *segPtr);
/*
- * Forward declarations for functions defined later in this file:
+ * This object is no longer in use anymore.
+ *
+ * The cache of indices has been eliminated, because it has worked in only
+ * one case: the user has given a numeric index. But this case is quite seldom,
+ * and the overhead for caching is not considerably faster than the mapping
+ * to an internal index. And the trivial indices, like 1.0 or 1.end, will be
+ * mapped very fast. Furthermore the revised version is using a section
+ * structure for acceleration.
*/
+#if TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 5
+const
+#endif /* end of backport to 8.5 */
+Tcl_ObjType tkTextIndexType = {
+ "textindex",/* name */
+ NULL, /* freeIntRepProc */
+ NULL, /* dupIntRepProc */
+ NULL, /* updateStringProc */
+ NULL /* setFromAnyProc */
+};
-static const char * ForwBack(TkText *textPtr, const char *string,
- TkTextIndex *indexPtr);
-static const char * StartEnd(TkText *textPtr, const char *string,
- TkTextIndex *indexPtr);
-static int GetIndex(Tcl_Interp *interp, TkSharedText *sharedPtr,
- TkText *textPtr, const char *string,
- TkTextIndex *indexPtr, int *canCachePtr);
-static int IndexCountBytesOrdered(const TkText *textPtr,
- const TkTextIndex *indexPtr1,
- const TkTextIndex *indexPtr2);
+/*
+ * A note about sizeof(char). Due to the specification of sizeof in C99,
+ * sizeof(char) is always 1, see section 6.5.3.4:
+ *
+ * When applied to an operand that has type char, unsigned char, or
+ * signed char, (or a qualified version thereof) the result is 1.
+ *
+ * This means that the expression "== sizeof(char)" is not required, the
+ * expression "== 1" is good as well.
+ */
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexIsEmpty --
+ *
+ * Return whether the given index is empty (still unset, or invalid).
+ *
+ * Results:
+ * True if empty, false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+bool
+TkTextIndexIsEmpty(
+ const TkTextIndex *indexPtr)
+{
+ assert(indexPtr);
+ return indexPtr->priv.byteIndex == -1 && !indexPtr->priv.segPtr;
+}
+
/*
- * The "textindex" Tcl_Obj definition:
+ *----------------------------------------------------------------------
+ *
+ * TkTextGetIndexFromObj --
+ *
+ * Create new text index from given position.
+ *
+ * Results:
+ * Returns true if and only if the index could be created.
+ *
+ * Side effects:
+ * Store the new text index in 'indexPtr'.
+ *
+ *----------------------------------------------------------------------
*/
-static void DupTextIndexInternalRep(Tcl_Obj *srcPtr,
- Tcl_Obj *copyPtr);
-static void FreeTextIndexInternalRep(Tcl_Obj *listPtr);
-static void UpdateStringOfTextIndex(Tcl_Obj *objPtr);
+bool
+TkTextGetIndexFromObj(
+ Tcl_Interp *interp, /* Use this for error reporting. */
+ TkText *textPtr, /* Information about text widget, can be NULL. */
+ Tcl_Obj *objPtr, /* Object containing description of position. */
+ TkTextIndex *indexPtr) /* Store the result here. */
+{
+ assert(textPtr);
+ return GetIndex(interp, textPtr->sharedTextPtr, textPtr, Tcl_GetString(objPtr), indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetLine --
+ *
+ * Set the line pointer of this index.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+#if !NDEBUG
+static bool
+CheckLine(
+ const TkTextIndex *indexPtr,
+ const TkTextLine *linePtr)
+{
+ assert(linePtr);
+
+ if (indexPtr->stateEpoch == TkBTreeEpoch(indexPtr->tree)) {
+ if (indexPtr->priv.segPtr
+ && indexPtr->priv.segPtr->sectionPtr->linePtr != indexPtr->priv.linePtr) {
+ return false;
+ }
+ if (indexPtr->priv.lineNo != -1
+ && indexPtr->priv.lineNo !=
+ TkBTreeLinesTo(indexPtr->tree, NULL, indexPtr->priv.linePtr, NULL)) {
+ return false;
+ }
+ if (indexPtr->priv.lineNoRel != -1
+ && indexPtr->priv.lineNoRel !=
+ TkBTreeLinesTo(indexPtr->tree, indexPtr->textPtr, indexPtr->priv.linePtr, NULL)) {
+ return false;
+ }
+ }
+
+ if (!indexPtr->discardConsistencyCheck && indexPtr->textPtr) {
+ const TkTextLine *startLine = TkBTreeGetStartLine(indexPtr->textPtr);
+ const TkTextLine *endLine = TkBTreeGetLastLine(indexPtr->textPtr);
+ int lineNo = TkBTreeLinesTo(indexPtr->tree, NULL, linePtr, NULL);
+
+ if (lineNo < TkBTreeLinesTo(indexPtr->tree, NULL, startLine, NULL)) {
+ return false;
+ }
+ if (lineNo > TkBTreeLinesTo(indexPtr->tree, NULL, endLine, NULL)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+#endif
+
+static int
+FindStartByteIndex(
+ const TkTextIndex *indexPtr)
+{
+ const TkText *textPtr = indexPtr->textPtr;
+ const TkTextSegment *segPtr;
+ const TkTextSection *sectionPtr;
+ int byteIndex;
+
+ if (!textPtr) {
+ return 0;
+ }
+
+ if (textPtr->startMarker == TkBTreeGetShared(indexPtr->tree)->startMarker) {
+ return 0;
+ }
+
+ segPtr = textPtr->startMarker;
+ sectionPtr = segPtr->sectionPtr;
+ byteIndex = 0;
+
+ if (sectionPtr->linePtr == indexPtr->priv.linePtr) {
+ while (segPtr && sectionPtr == segPtr->sectionPtr) {
+ byteIndex += segPtr->size;
+ segPtr = segPtr->prevPtr;
+ }
+ while (sectionPtr->prevPtr) {
+ sectionPtr = sectionPtr->prevPtr;
+ byteIndex += sectionPtr->size;
+ }
+ }
+
+ return byteIndex;
+}
+
+static bool
+DontNeedSpecialStartLineTreatment(
+ const TkTextIndex *indexPtr)
+{
+ const TkText *textPtr = indexPtr->textPtr;
+
+ return !textPtr
+ || textPtr->startMarker == TkBTreeGetShared(indexPtr->tree)->startMarker
+ || indexPtr->priv.linePtr != textPtr->startMarker->sectionPtr->linePtr;
+}
+
+void
+TkTextIndexSetLine(
+ TkTextIndex *indexPtr,
+ TkTextLine *linePtr)
+{
+ assert(linePtr);
+ assert(indexPtr->tree);
+ assert(CheckLine(indexPtr, linePtr));
+
+ indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ indexPtr->priv.segPtr = NULL;
+ indexPtr->priv.byteIndex = -1;
+ if ((indexPtr->priv.linePtr = linePtr)) {
+ assert(linePtr->parentPtr); /* expired? */
+
+ if (DontNeedSpecialStartLineTreatment(indexPtr)) {
+ indexPtr->priv.byteIndex = 0;
+ } else {
+ indexPtr->priv.segPtr = indexPtr->textPtr->startMarker;
+ indexPtr->priv.isCharSegment = false;
+ indexPtr->priv.byteIndex = FindStartByteIndex(indexPtr);
+ }
+ }
+}
+
/*
- * Accessor macros for the "textindex" type.
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetPosition --
+ *
+ * Set the byte index and the segment, the user is responsible
+ * for proper values.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
*/
-#define GET_TEXTINDEX(objPtr) \
- ((TkTextIndex *) (objPtr)->internalRep.twoPtrValue.ptr1)
-#define GET_INDEXEPOCH(objPtr) \
- (PTR2INT((objPtr)->internalRep.twoPtrValue.ptr2))
-#define SET_TEXTINDEX(objPtr, indexPtr) \
- ((objPtr)->internalRep.twoPtrValue.ptr1 = (void *) (indexPtr))
-#define SET_INDEXEPOCH(objPtr, epoch) \
- ((objPtr)->internalRep.twoPtrValue.ptr2 = INT2PTR(epoch))
+#if !NDEBUG
+static bool
+CheckByteIndex(
+ const TkTextIndex *indexPtr,
+ const TkTextLine *linePtr,
+ int byteIndex)
+{
+ const TkText *textPtr = indexPtr->textPtr;
+
+ if (byteIndex == -1 && (byteIndex = indexPtr->priv.byteIndex) == -1) {
+ assert(indexPtr->priv.segPtr);
+ assert(!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch);
+ byteIndex = SegToIndex(indexPtr->priv.linePtr, indexPtr->priv.segPtr);
+ }
+
+ if (!indexPtr->discardConsistencyCheck && textPtr) {
+ if (linePtr == textPtr->startMarker->sectionPtr->linePtr) {
+ if (byteIndex < FindStartByteIndex(indexPtr)) {
+ return false;
+ }
+ }
+ if (linePtr == textPtr->endMarker->sectionPtr->linePtr) {
+ return byteIndex <= SegToIndex(linePtr, textPtr->endMarker);
+ }
+ if (linePtr == textPtr->endMarker->sectionPtr->linePtr->nextPtr) {
+ return byteIndex == 0;
+ }
+ }
+
+ return byteIndex < linePtr->size;
+}
+#endif /* !NDEBUG */
+
+void
+TkTextIndexSetPosition(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ int byteIndex, /* New byte index. */
+ TkTextSegment *segPtr) /* The segment which belongs to the byte index. */
+{
+ assert(indexPtr->tree);
+ assert(byteIndex >= 0);
+ assert(segPtr);
+ assert(segPtr->typePtr); /* expired? */
+ assert(segPtr->sectionPtr); /* linked? */
+ assert(CheckLine(indexPtr, segPtr->sectionPtr->linePtr));
+ assert(CheckByteIndex(indexPtr, segPtr->sectionPtr->linePtr, byteIndex));
+
+ indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
+ indexPtr->priv.linePtr = segPtr->sectionPtr->linePtr;
+ indexPtr->priv.byteIndex = byteIndex;
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ indexPtr->priv.segPtr = segPtr;
+ indexPtr->priv.isCharSegment = segPtr->typePtr == &tkTextCharType;
+
+#if !NDEBUG
+ {
+ int pos = SegToIndex(indexPtr->priv.linePtr, segPtr);
+
+ if (segPtr->typePtr == &tkTextCharType) {
+ assert(byteIndex - pos < segPtr->size);
+ } else {
+ assert(pos == byteIndex);
+ }
+ }
+#endif /* !NDEBUG */
+}
/*
- * Define the 'textindex' object type, which Tk uses to represent indices in
- * text widgets internally.
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetByteIndex --
+ *
+ * Set the byte index. We allow to set to the start of the next
+ * line (this means that argument byteIndex is equal to line size),
+ * required that the next line exists.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
*/
-const Tcl_ObjType tkTextIndexType = {
- "textindex", /* name */
- FreeTextIndexInternalRep, /* freeIntRepProc */
- DupTextIndexInternalRep, /* dupIntRepProc */
- NULL, /* updateStringProc */
- NULL /* setFromAnyProc */
-};
+static bool
+DontNeedSpecialEndLineTreatment(
+ const TkTextIndex *indexPtr)
+{
+ const TkText *textPtr = indexPtr->textPtr;
+
+ return !textPtr
+ || textPtr->endMarker == TkBTreeGetShared(indexPtr->tree)->endMarker
+ || indexPtr->priv.linePtr != textPtr->endMarker->sectionPtr->linePtr;
+}
-static void
-FreeTextIndexInternalRep(
- Tcl_Obj *indexObjPtr) /* TextIndex object with internal rep to
- * free. */
+static int
+FindEndByteIndex(
+ const TkTextIndex *indexPtr)
{
- TkTextIndex *indexPtr = GET_TEXTINDEX(indexObjPtr);
+ /*
+ * We do not handle the special case with last line, because CheckLine is testing this.
+ */
- if (indexPtr->textPtr != NULL) {
- if (indexPtr->textPtr->refCount-- <= 1) {
- /*
- * The text widget has been deleted and we need to free it now.
- */
+ if (indexPtr->textPtr && indexPtr->priv.linePtr == TkBTreeGetLastLine(indexPtr->textPtr)) {
+ return 0;
+ }
+ if (DontNeedSpecialEndLineTreatment(indexPtr)) {
+ return indexPtr->priv.linePtr->size - 1;
+ }
+ return SegToIndex(indexPtr->priv.linePtr, indexPtr->textPtr->endMarker);
+}
- ckfree(indexPtr->textPtr);
+void
+TkTextIndexSetByteIndex(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ int byteIndex) /* New byte index. */
+{
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(byteIndex >= 0);
+
+ if (byteIndex == FindEndByteIndex(indexPtr) + 1) {
+ assert(indexPtr->priv.linePtr->nextPtr);
+ indexPtr->priv.linePtr = indexPtr->priv.linePtr->nextPtr;
+ indexPtr->priv.byteIndex = 0;
+ indexPtr->priv.segPtr = NULL;
+ if (indexPtr->priv.lineNo >= 0) {
+ indexPtr->priv.lineNo += 1;
+ }
+ if (indexPtr->priv.lineNoRel >= 0) {
+ indexPtr->priv.lineNoRel += 1;
}
+ } else {
+ indexPtr->priv.byteIndex = byteIndex;
+ indexPtr->priv.segPtr = NULL;
}
- ckfree(indexPtr);
- indexObjPtr->typePtr = NULL;
+
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+ assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, byteIndex));
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetByteIndex2 --
+ *
+ * Set the new line pointer and the byte index. We allow to set to
+ * the start of the next line (this means that argument byteIndex
+ * is equal to line size), required that the next line exists.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
-static void
-DupTextIndexInternalRep(
- Tcl_Obj *srcPtr, /* TextIndex obj with internal rep to copy. */
- Tcl_Obj *copyPtr) /* TextIndex obj with internal rep to set. */
+void
+TkTextIndexSetByteIndex2(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkTextLine *linePtr, /* Pointer to line. */
+ int byteIndex) /* New byte index. */
{
- int epoch;
- TkTextIndex *dupIndexPtr, *indexPtr;
-
- dupIndexPtr = ckalloc(sizeof(TkTextIndex));
- indexPtr = GET_TEXTINDEX(srcPtr);
- epoch = GET_INDEXEPOCH(srcPtr);
-
- dupIndexPtr->tree = indexPtr->tree;
- dupIndexPtr->linePtr = indexPtr->linePtr;
- dupIndexPtr->byteIndex = indexPtr->byteIndex;
- dupIndexPtr->textPtr = indexPtr->textPtr;
- if (dupIndexPtr->textPtr != NULL) {
- dupIndexPtr->textPtr->refCount++;
- }
- SET_TEXTINDEX(copyPtr, dupIndexPtr);
- SET_INDEXEPOCH(copyPtr, epoch);
- copyPtr->typePtr = &tkTextIndexType;
+ assert(indexPtr->tree);
+ assert(linePtr);
+ assert(linePtr->parentPtr); /* expired? */
+ assert(byteIndex >= 0);
+
+ if (indexPtr->priv.linePtr != linePtr) {
+ indexPtr->priv.linePtr = linePtr;
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ }
+ TkTextIndexSetByteIndex(indexPtr, byteIndex);
}
/*
- * This will not be called except by TkTextNewIndexObj below. This is because
- * if a TkTextIndex is no longer valid, it is not possible to regenerate the
- * string representation.
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetSegment --
+ *
+ * Set the segment pointer.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The byte index will be cleared.
+ *
+ *----------------------------------------------------------------------
*/
-static void
-UpdateStringOfTextIndex(
- Tcl_Obj *objPtr)
+void
+TkTextIndexSetSegment(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkTextSegment *segPtr) /* Pointer to segment. */
{
- char buffer[TK_POS_CHARS];
- register int len;
- const TkTextIndex *indexPtr = GET_TEXTINDEX(objPtr);
+ assert(indexPtr->tree);
+ assert(segPtr);
+ assert(segPtr->typePtr); /* expired? */
+ assert(segPtr->sectionPtr); /* linked? */
+ assert(CheckLine(indexPtr, segPtr->sectionPtr->linePtr));
+
+ indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
+ indexPtr->priv.linePtr = segPtr->sectionPtr->linePtr;
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ indexPtr->priv.segPtr = segPtr;
- len = TkTextPrintIndex(indexPtr->textPtr, indexPtr, buffer);
+ if (segPtr->typePtr == &tkTextCharType) {
+ indexPtr->priv.byteIndex = SegToIndex(indexPtr->priv.linePtr, segPtr);
+ indexPtr->priv.isCharSegment = true;
+ } else {
+ indexPtr->priv.byteIndex = -1;
+ indexPtr->priv.isCharSegment = false;
+ }
- objPtr->bytes = ckalloc(len + 1);
- strcpy(objPtr->bytes, buffer);
- objPtr->length = len;
+ assert(CheckByteIndex(indexPtr, segPtr->sectionPtr->linePtr, -1));
}
/*
- *---------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*
- * MakeObjIndex --
+ * TkTextIndexSetToStartOfLine --
*
- * This function generates a Tcl_Obj description of an index, suitable
- * for reading in again later. If the 'textPtr' is NULL then we still
- * generate an index object, but it's internal description is deemed
- * non-cacheable, and therefore effectively useless (apart from as a
- * temporary memory storage). This is used for indices whose meaning is
- * very temporary (like @0,0 or the name of a mark or tag). The mapping
- * from such strings/objects to actual TkTextIndex pointers is not stable
- * to minor text widget changes which we do not track (we track
- * insertions and deletions).
+ * Set this index to the start of the line.
*
* Results:
- * A pointer to an allocated TkTextIndex which will be freed
- * automatically when the Tcl_Obj is used for other purposes.
+ * None.
*
* Side effects:
- * A small amount of memory is allocated.
+ * None.
*
- *---------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*/
-static TkTextIndex *
-MakeObjIndex(
- TkText *textPtr, /* Information about text widget. */
- Tcl_Obj *objPtr, /* Object containing description of
- * position. */
- const TkTextIndex *origPtr) /* Pointer to index. */
+void
+TkTextIndexSetToStartOfLine(
+ TkTextIndex *indexPtr) /* Pointer to index. */
{
- TkTextIndex *indexPtr = ckalloc(sizeof(TkTextIndex));
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+
+ indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
+ indexPtr->priv.segPtr = NULL;
+ indexPtr->priv.byteIndex = FindStartByteIndex(indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetToStartOfLine2 --
+ *
+ * Set the new line pointer, and set this index to the start of the line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextIndexSetToStartOfLine2(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkTextLine *linePtr) /* Pointer to line. */
+{
+ assert(indexPtr->tree);
+ assert(linePtr);
+ assert(linePtr->parentPtr); /* expired? */
+ assert(CheckLine(indexPtr, linePtr));
+
+ indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
+ indexPtr->priv.linePtr = linePtr;
+ indexPtr->priv.segPtr = NULL;
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ indexPtr->priv.byteIndex = FindStartByteIndex(indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetToEndOfLine2 --
+ *
+ * Set the new line pointer, and set this index to the end of the line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextIndexSetToEndOfLine2(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkTextLine *linePtr) /* Pointer to line. */
+{
+ assert(indexPtr->tree);
+ assert(linePtr);
+ assert(linePtr->parentPtr); /* expired? */
+ assert(linePtr->nextPtr);
+ assert(CheckLine(indexPtr, linePtr->nextPtr));
+
+ indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
+ indexPtr->priv.segPtr = NULL;
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ indexPtr->priv.linePtr = linePtr->nextPtr;
+ indexPtr->priv.byteIndex = 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetToLastChar --
+ *
+ * Set this index to one byte before the end of the line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextIndexSetToLastChar(
+ TkTextIndex *indexPtr) /* Pointer to index. */
+{
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+
+ indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
+ indexPtr->priv.byteIndex = FindEndByteIndex(indexPtr);
+ indexPtr->priv.segPtr = NULL;
+
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetupToStartOfText --
+ *
+ * Setup this index to the start of the text.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextIndexSetupToStartOfText(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkText *textPtr, /* Text widget for this index, can be NULL. */
+ TkTextBTree tree) /* B-tree for this index. */
+{
+ assert(indexPtr);
+ assert(tree);
- indexPtr->tree = origPtr->tree;
- indexPtr->linePtr = origPtr->linePtr;
- indexPtr->byteIndex = origPtr->byteIndex;
- SET_TEXTINDEX(objPtr, indexPtr);
- objPtr->typePtr = &tkTextIndexType;
indexPtr->textPtr = textPtr;
+ indexPtr->tree = tree;
+ indexPtr->stateEpoch = TkBTreeEpoch(tree);
+ indexPtr->priv.lineNo = textPtr ? -1 : 0;
+ indexPtr->priv.lineNoRel = 0;
+ indexPtr->priv.isCharSegment = false;
+ DEBUG(indexPtr->discardConsistencyCheck = false);
+
+ if (textPtr) {
+ indexPtr->priv.segPtr = textPtr->startMarker;
+ indexPtr->priv.linePtr = indexPtr->priv.segPtr->sectionPtr->linePtr;
+ indexPtr->priv.byteIndex = FindStartByteIndex(indexPtr);
+ } else {
+ indexPtr->priv.segPtr = TkBTreeGetShared(tree)->startMarker;
+ indexPtr->priv.linePtr = indexPtr->priv.segPtr->sectionPtr->linePtr;
+ indexPtr->priv.byteIndex = 0;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetupToEndOfText --
+ *
+ * Setup this index to the end of the text. If a peer is given,
+ * then this is the start of last line in this peer, otherwise
+ * it's the start of the very last line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- if (textPtr != NULL) {
- textPtr->refCount++;
- SET_INDEXEPOCH(objPtr, textPtr->sharedTextPtr->stateEpoch);
+void
+TkTextIndexSetupToEndOfText(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkText *textPtr, /* Text widget for this index, can be NULL. */
+ TkTextBTree tree) /* B-tree for this index. */
+{
+ assert(indexPtr);
+ assert(tree);
+
+ indexPtr->textPtr = textPtr;
+ indexPtr->tree = tree;
+ indexPtr->stateEpoch = TkBTreeEpoch(tree);
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ DEBUG(indexPtr->discardConsistencyCheck = false);
+
+ if (!textPtr) {
+ indexPtr->priv.segPtr = TkBTreeGetShared(tree)->endMarker;
+ indexPtr->priv.isCharSegment = false;
+ indexPtr->priv.linePtr = indexPtr->priv.segPtr->sectionPtr->linePtr;
+ indexPtr->priv.byteIndex = 0;
} else {
- SET_INDEXEPOCH(objPtr, 0);
+ indexPtr->priv.linePtr = TkBTreeGetLastLine(textPtr);
+ indexPtr->priv.segPtr = indexPtr->priv.linePtr->segPtr;
+ indexPtr->priv.isCharSegment = indexPtr->priv.segPtr->typePtr == &tkTextCharType;
+ indexPtr->priv.byteIndex = 0;
}
- return indexPtr;
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexGetByteIndex --
+ *
+ * Get the byte index.
+ *
+ * Results:
+ * The byte index.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
-const TkTextIndex *
-TkTextGetIndexFromObj(
- Tcl_Interp *interp, /* Use this for error reporting. */
- TkText *textPtr, /* Information about text widget. */
- Tcl_Obj *objPtr) /* Object containing description of
- * position. */
+int
+TkTextIndexGetByteIndex(
+ const TkTextIndex *indexPtr) /* Pointer to index. */
{
- TkTextIndex index;
- TkTextIndex *indexPtr = NULL;
- int cache;
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+
+ if (indexPtr->priv.byteIndex == -1) {
+ assert(indexPtr->priv.segPtr);
+ assert(!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch);
+ assert(indexPtr->priv.segPtr->typePtr); /* expired? */
+ assert(indexPtr->priv.segPtr->sectionPtr); /* linked? */
+ assert(indexPtr->priv.segPtr->sectionPtr->linePtr == indexPtr->priv.linePtr);
+ /* is mutable due to concept */
+ ((TkTextIndex *)indexPtr)->priv.byteIndex =
+ SegToIndex(indexPtr->priv.linePtr, indexPtr->priv.segPtr);
+ }
+ return indexPtr->priv.byteIndex;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexToByteIndex --
+ *
+ * Force the conversion from segment pointer to byte index. This
+ * will unset the segment pointer.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The segment pointer will be unset.
+ *
+ *----------------------------------------------------------------------
+ */
- if (objPtr->typePtr == &tkTextIndexType) {
- int epoch;
+void
+TkTextIndexToByteIndex(
+ TkTextIndex *indexPtr) /* Pointer to index. */
+{
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
- indexPtr = GET_TEXTINDEX(objPtr);
- epoch = GET_INDEXEPOCH(objPtr);
+ if (indexPtr->priv.byteIndex == -1) {
+ (void) TkTextIndexGetByteIndex(indexPtr);
+ }
+ indexPtr->priv.segPtr = NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexClear --
+ *
+ * Clear all attributes, and set up the corresponding text pointer.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The given index will be in an invalid state, the TkIndexGet*
+ * functions cannot be used.
+ *
+ *----------------------------------------------------------------------
+ */
- if (epoch == textPtr->sharedTextPtr->stateEpoch) {
- if (indexPtr->textPtr == textPtr) {
- return indexPtr;
+void
+TkTextIndexClear(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkText *textPtr) /* Overall information for text widget. */
+{
+ assert(textPtr);
+
+ indexPtr->textPtr = textPtr;
+ indexPtr->tree = textPtr->sharedTextPtr->tree;
+ indexPtr->stateEpoch = 0;
+ indexPtr->priv.linePtr = NULL;
+ indexPtr->priv.segPtr = NULL;
+ indexPtr->priv.byteIndex = -1;
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ indexPtr->priv.isCharSegment = false;
+ DEBUG(indexPtr->discardConsistencyCheck = false);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexClear2 --
+ *
+ * Clear all attributes, and set up the corresponding tree.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The given index will be in an invalid state, the TkIndexGet*
+ * functions cannot be used.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextIndexClear2(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkText *textPtr, /* Overall information for text widget, can be NULL */
+ TkTextBTree tree) /* B-tree for this index. */
+{
+ assert(textPtr || tree);
+ assert(!textPtr || !tree || textPtr->sharedTextPtr->tree == tree);
+
+ indexPtr->textPtr = textPtr;
+ indexPtr->tree = tree ? tree : textPtr->sharedTextPtr->tree;
+ indexPtr->stateEpoch = 0;
+ indexPtr->priv.linePtr = NULL;
+ indexPtr->priv.segPtr = NULL;
+ indexPtr->priv.byteIndex = -1;
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ indexPtr->priv.isCharSegment = false;
+ DEBUG(indexPtr->discardConsistencyCheck = false);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexGetLineNumber --
+ *
+ * Get the line number.
+ *
+ * Results:
+ * The line number.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+unsigned
+TkTextIndexGetLineNumber(
+ const TkTextIndex *indexPtr,
+ const TkText *textPtr) /* we want the line number belonging to this peer, can be NULL */
+{
+ unsigned epoch;
+ int32_t *lineNo;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(!textPtr || indexPtr->textPtr == textPtr);
+
+ lineNo = (int32_t *) (textPtr ? &indexPtr->priv.lineNoRel : &indexPtr->priv.lineNo);
+ epoch = TkBTreeEpoch(indexPtr->tree);
+
+ if (*lineNo == -1 || indexPtr->stateEpoch != epoch) {
+ TkTextIndex *iPtr = (TkTextIndex *) indexPtr;
+
+ if (iPtr->priv.byteIndex == -1) {
+ assert(iPtr->priv.segPtr);
+ assert(!iPtr->priv.isCharSegment || indexPtr->stateEpoch == epoch);
+ iPtr->priv.byteIndex = SegToIndex(iPtr->priv.linePtr, iPtr->priv.segPtr);
+ assert(CheckByteIndex(iPtr, iPtr->priv.linePtr, iPtr->priv.byteIndex));
+ }
+ TkTextIndexSetEpoch(iPtr, epoch);
+ *lineNo = TkBTreeLinesTo(iPtr->tree, textPtr, iPtr->priv.linePtr, NULL);
+ } else {
+ assert(*lineNo == TkBTreeLinesTo(indexPtr->tree, textPtr, indexPtr->priv.linePtr, NULL));
+ }
+
+ return *lineNo;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexRebuild --
+ *
+ * Rebuild the index after possible modifications, it is required
+ * that TkTextIndexSave has been called before.
+ *
+ * Results:
+ * Returns whether the original line/byte position could be restored.
+ * This does not meean that we have the same content at this position,
+ * this only means that the we have the same position as before.
+ *
+ * Side effects:
+ * Adjust the line and the byte offset, if required.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextIndexRebuild(
+ TkTextIndex *indexPtr)
+{
+ TkTextLine *linePtr;
+ int byteIndex;
+ int lineNo;
+ bool rc;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.lineNo >= 0 || indexPtr->priv.lineNoRel >= 0);
+ assert(indexPtr->priv.byteIndex >= 0);
+
+ if (indexPtr->stateEpoch == TkBTreeEpoch(indexPtr->tree)) {
+ return true; /* still up-to-date */
+ }
+
+ if (indexPtr->priv.lineNo >= 0) {
+ lineNo = MIN(TkBTreeNumLines(indexPtr->tree, NULL), indexPtr->priv.lineNo);
+ linePtr = TkBTreeFindLine(indexPtr->tree, NULL, lineNo);
+ indexPtr->priv.lineNo = lineNo;
+ } else {
+ lineNo = MIN(TkBTreeNumLines(indexPtr->tree, indexPtr->textPtr), indexPtr->priv.lineNoRel);
+ linePtr = TkBTreeFindLine(indexPtr->tree, indexPtr->textPtr, lineNo);
+ indexPtr->priv.lineNoRel = lineNo;
+ }
+
+ if (!(rc = (linePtr == indexPtr->priv.linePtr))) {
+ indexPtr->priv.linePtr = linePtr;
+ }
+ byteIndex = MIN(indexPtr->priv.byteIndex, FindEndByteIndex(indexPtr));
+ if (byteIndex != indexPtr->priv.byteIndex) {
+ rc = false;
+ }
+ indexPtr->priv.byteIndex = byteIndex;
+ indexPtr->priv.segPtr = NULL;
+
+ return rc;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexRestrictToStartRange --
+ *
+ * If given index is beyond the range of the widget (at left side),
+ * then this index will be set to start range.
+ *
+ * Results:
+ * Returns -1 if the index is earlier than start of range, 0 if index
+ * is at start of range, and +1 if index is after start of range.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextIndexRestrictToStartRange(
+ TkTextIndex *indexPtr)
+{
+ TkText *textPtr = indexPtr->textPtr;
+ TkTextIndex start;
+ int cmp;
+
+ assert(indexPtr->tree);
+
+ if (!textPtr || textPtr->startMarker == textPtr->sharedTextPtr->startMarker) {
+ return TkTextIndexIsStartOfText(indexPtr) ? 0 : 1;
+ }
+
+ start = *indexPtr;
+ TkTextIndexSetSegment(&start, textPtr->startMarker);
+
+ if ((cmp = TkTextIndexCompare(indexPtr, &start)) < 0) {
+ *indexPtr = start;
+ cmp = -1;
+ }
+
+ return cmp;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexRestrictToEndRange --
+ *
+ * If given index is beyond the range of the widget (at right side),
+ * then this index will be set to end range.
+ *
+ * Results:
+ * Returns +1 if the index has exceeded the range, 0 if index was at
+ * end of range, and -1 if index is earlier than end of range.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextIndexRestrictToEndRange(
+ TkTextIndex *indexPtr)
+{
+ TkText *textPtr = indexPtr->textPtr;
+ TkTextIndex last;
+ int cmp;
+
+ assert(indexPtr->tree);
+
+ if (!textPtr || textPtr->endMarker == textPtr->sharedTextPtr->endMarker) {
+ return TkTextIndexIsEndOfText(indexPtr) ? 0 : -1;
+ }
+
+ last = *indexPtr;
+ TkTextIndexSetByteIndex2(&last, TkBTreeGetLastLine(textPtr), 0);
+
+ if ((cmp = TkTextIndexCompare(indexPtr, &last)) > 0) {
+ *indexPtr = last;
+ cmp = 1;
+ } else if (cmp < 0) {
+ TkTextIndex end = *indexPtr;
+ TkTextIndexSetSegment(&end, textPtr->endMarker);
+ if (TkTextIndexCompare(indexPtr, &end) > 0) {
+ *indexPtr = last;
+ cmp = 0;
+ } else {
+ cmp = -1;
+ }
+ }
+
+ return cmp;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexEnsureBeforeLastChar --
+ *
+ * If given index is on last line, then this index will be set to
+ * the position of the last character in second last line.
+ *
+ * Results:
+ * Returns 'true' if the index is now before last character position.
+ * This is not possible if the peer is empty, and in this case this
+ * function returns 'false'.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextIndexEnsureBeforeLastChar(
+ TkTextIndex *indexPtr)
+{
+ TkText *textPtr = indexPtr->textPtr;
+ const TkTextLine *lastLinePtr;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->textPtr);
+
+ if (TkTextIsDeadPeer(indexPtr->textPtr)) {
+ return false;
+ }
+
+ lastLinePtr = TkBTreeGetLastLine(textPtr);
+
+ if (lastLinePtr == indexPtr->priv.linePtr
+ && (!textPtr || lastLinePtr != textPtr->startMarker->sectionPtr->linePtr)) {
+ TkTextIndexSetToLastChar2(indexPtr, lastLinePtr->prevPtr);
+ }
+
+ return true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexGetContentSegment --
+ *
+ * Get the pointer to the segment at this byte position which
+ * contains any content (chars, image, or window).
+ *
+ * This is the equivalent to the older (and eliminated) function
+ * TkTextIndexToSeg.
+ *
+ * Results:
+ * Pointer to a segment with size > 0.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+TkTextSegment *
+TkTextIndexGetContentSegment(
+ const TkTextIndex *indexPtr,/* Pointer to index. */
+ int *offset) /* Get offset in segment, can be NULL. */
+{
+ TkTextSegment *segPtr;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+
+ if ((segPtr = indexPtr->priv.segPtr)
+ && (!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch)) {
+ while (segPtr->size == 0) {
+ segPtr = segPtr->nextPtr;
+ }
+
+ if (offset) {
+ if (indexPtr->priv.byteIndex == -1) {
+ *offset = 0;
+ } else {
+ int byteIndex = SegToIndex(indexPtr->priv.linePtr, segPtr);
+ assert(byteIndex <= indexPtr->priv.byteIndex);
+ assert(indexPtr->priv.byteIndex < byteIndex + segPtr->size);
+ *offset = indexPtr->priv.byteIndex - byteIndex;
}
+ assert(*offset >= 0);
+ assert(*offset < segPtr->size);
+ }
+ } else {
+ int myOffset;
+
+ assert(indexPtr->priv.byteIndex >= 0);
+ segPtr = IndexToSeg(indexPtr, &myOffset);
+ if (myOffset == 0) {
+ TkTextIndex *iPtr = (TkTextIndex *) indexPtr; /* mutable due to concept */
+ iPtr->priv.segPtr = segPtr;
+ iPtr->priv.isCharSegment = segPtr->typePtr == &tkTextCharType;
+ }
+ if (offset) {
+ *offset = myOffset;
}
}
- /*
- * The object is either not an index type or referred to a different text
- * widget, or referred to the correct widget, but it is out of date (text
- * has been added/deleted since).
- */
+ return segPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexGetFirstSegment --
+ *
+ * Get the pointer to first segment at this byte position.
+ *
+ * Results:
+ * Pointer to a segment.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
- if (GetIndex(interp, NULL, textPtr, Tcl_GetString(objPtr), &index,
- &cache) != TCL_OK) {
- return NULL;
+TkTextSegment *
+TkTextIndexGetFirstSegment(
+ const TkTextIndex *indexPtr,/* Pointer to index. */
+ int *offset) /* Get offset in segment, can be NULL. */
+{
+ TkTextSegment *segPtr;
+ TkTextSegment *prevPtr;
+ int myOffset;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+
+ if ((segPtr = indexPtr->priv.segPtr)
+ && (!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch)) {
+ if (indexPtr->priv.byteIndex >= 0) {
+ myOffset = indexPtr->priv.byteIndex - SegToIndex(indexPtr->priv.linePtr, segPtr);
+ assert(myOffset >= 0);
+ assert(segPtr->size == 0 || myOffset < segPtr->size);
+ } else {
+ myOffset = 0;
+ }
+ } else {
+ assert(indexPtr->priv.byteIndex >= 0);
+ segPtr = IndexToSeg(indexPtr, &myOffset);
}
- if (objPtr->typePtr != NULL) {
- if (objPtr->bytes == NULL) {
- objPtr->typePtr->updateStringProc(objPtr);
+ assert(segPtr->typePtr); /* expired? */
+ assert(segPtr->sectionPtr); /* linked? */
+ assert(segPtr->sectionPtr->linePtr == indexPtr->priv.linePtr);
+
+ if (myOffset == 0) {
+ TkTextIndex *iPtr;
+
+ while ((prevPtr = segPtr->prevPtr) && prevPtr->size == 0) {
+ segPtr = prevPtr;
}
- if (objPtr->typePtr->freeIntRepProc != NULL) {
- objPtr->typePtr->freeIntRepProc(objPtr);
+
+ iPtr = (TkTextIndex *) indexPtr; /* mutable due to concept */
+ iPtr->priv.segPtr = segPtr;
+ iPtr->priv.isCharSegment = segPtr->typePtr == &tkTextCharType;
+ }
+ if (offset) {
+ *offset = myOffset;
+ }
+
+ return segPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexIsStartOfLine --
+ *
+ * Test whether this index refers to the start of a line.
+ *
+ * Results:
+ * Returns true if the start of a line is referred, zero otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextIndexIsStartOfLine(
+ const TkTextIndex *indexPtr)
+{
+ const TkTextSegment *segPtr;
+ const TkTextSegment *startPtr;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+
+ if (indexPtr->priv.byteIndex >= 0) {
+ return FindStartByteIndex(indexPtr) == indexPtr->priv.byteIndex;
+ }
+
+ assert(indexPtr->priv.segPtr);
+ assert(!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch);
+ assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
+
+ startPtr = indexPtr->textPtr ? indexPtr->textPtr->startMarker : NULL;
+ segPtr = indexPtr->priv.segPtr;
+ if (segPtr->size > 0) {
+ segPtr = segPtr->prevPtr;
+ }
+ while (segPtr && segPtr->size == 0) {
+ if (segPtr == startPtr) {
+ return true;
}
+ segPtr = segPtr->prevPtr;
}
- return MakeObjIndex((cache ? textPtr : NULL), objPtr, &index);
+ return !segPtr;
}
/*
- *---------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*
- * TkTextNewIndexObj --
+ * TkTextIndexIsEndOfLine --
*
- * This function generates a Tcl_Obj description of an index, suitable
- * for reading in again later. The index generated is effectively stable
- * to all except insertion/deletion operations on the widget.
+ * Test whether this index refers to the end of the line.
*
* Results:
- * A new Tcl_Obj with refCount zero.
+ * Returns true if the end of the line is referred, false otherwise.
*
* Side effects:
- * A small amount of memory is allocated.
+ * None.
*
- *---------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*/
-Tcl_Obj *
-TkTextNewIndexObj(
- TkText *textPtr, /* Text widget for this index */
- const TkTextIndex *indexPtr)/* Pointer to index. */
+bool
+TkTextIndexIsEndOfLine(
+ const TkTextIndex *indexPtr)
{
- Tcl_Obj *retVal;
+ const TkTextSegment *segPtr;
+ const TkTextSegment *endPtr;
- retVal = Tcl_NewObj();
- retVal->bytes = NULL;
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+ assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
- /*
- * Assumption that the above call returns an object with:
- * retVal->typePtr == NULL
- */
+ if (indexPtr->priv.byteIndex >= 0) {
+ return indexPtr->priv.byteIndex == FindEndByteIndex(indexPtr);
+ }
- MakeObjIndex(textPtr, retVal, indexPtr);
+ assert(indexPtr->priv.segPtr);
+ assert(!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch);
- /*
- * Unfortunately, it isn't possible for us to regenerate the string
- * representation so we have to create it here, while we can be sure the
- * contents of the index are still valid.
- */
+ if (indexPtr->priv.linePtr == TkBTreeGetLastLine(indexPtr->textPtr)) {
+ return true;
+ }
+
+ segPtr = indexPtr->priv.segPtr;
+
+ if (DontNeedSpecialEndLineTreatment(indexPtr)) {
+ while (segPtr->size == 0) {
+ segPtr = segPtr->nextPtr;
+ }
+ return segPtr->size == 1 && segPtr == indexPtr->priv.linePtr->lastPtr;
+ }
- UpdateStringOfTextIndex(retVal);
- return retVal;
+ assert(indexPtr->textPtr);
+ assert(indexPtr->textPtr->endMarker != indexPtr->textPtr->sharedTextPtr->endMarker);
+
+ endPtr = indexPtr->textPtr->endMarker;
+ while (segPtr->size == 0) {
+ if (segPtr == endPtr) {
+ return true;
+ }
+ segPtr = segPtr->nextPtr;
+ }
+
+ return false;
}
/*
- *---------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*
- * TkTextMakePixelIndex --
+ * TkTextIndexIsStartOfText --
*
- * Given a pixel index and a byte index, look things up in the B-tree and
- * fill in a TkTextIndex structure.
+ * Test whether this index refers to the start of the text.
*
- * The valid input range for pixelIndex is from 0 to the number of pixels
- * in the widget-1. Anything outside that range will be rounded to the
- * closest acceptable value.
+ * Results:
+ * Returns true if the start of the text is referred, false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextIndexIsStartOfText(
+ const TkTextIndex *indexPtr)
+{
+ const TkText *textPtr = indexPtr->textPtr;
+ const TkTextSegment *segPtr;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+ assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
+
+ segPtr = textPtr ? textPtr->startMarker : TkBTreeGetShared(indexPtr->tree)->startMarker;
+ return indexPtr->priv.linePtr == segPtr->sectionPtr->linePtr && TkTextIndexIsStartOfLine(indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexIsEndOfText --
+ *
+ * Test whether this index refers to the end of the text.
*
* Results:
+ * Returns true if the end of the text is referred, false otherwise.
*
- * The structure at *indexPtr is filled in with information about the
- * character at pixelIndex (or the closest existing character, if the
- * specified one doesn't exist), and the number of excess pixels is
- * returned as a result. This means if the given pixel index is exactly
- * correct for the top-edge of the indexPtr, then zero will be returned,
- * and otherwise we will return the calculation 'desired pixelIndex' -
- * 'actual pixel index of indexPtr'.
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextIndexIsEndOfText(
+ const TkTextIndex *indexPtr)
+{
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+ assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
+
+ if (indexPtr->textPtr) {
+ return indexPtr->priv.linePtr == TkBTreeGetLastLine(indexPtr->textPtr);
+ }
+ return !indexPtr->priv.linePtr->nextPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexIsEqual --
+ *
+ * Test whether both given indicies are referring the same byte
+ * index. Such a test makes sense only if both indices are
+ * belonging to the same line.
+ *
+ * Results:
+ * Return true if both indices are equal, otherwise false will be returned.
*
* Side effects:
* None.
*
- *---------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*/
-int
-TkTextMakePixelIndex(
- TkText *textPtr, /* The Text Widget */
- int pixelIndex, /* Pixel-index of desired line (0 means first
- * pixel of first line of text). */
- TkTextIndex *indexPtr) /* Structure to fill in. */
+bool
+TkTextIndexIsEqual(
+ const TkTextIndex *indexPtr1, /* Pointer to index. */
+ const TkTextIndex *indexPtr2) /* Pointer to index. */
{
- int pixelOffset = 0;
+ const TkTextSegment *segPtr1;
+ const TkTextSegment *segPtr2;
- indexPtr->tree = textPtr->sharedTextPtr->tree;
- indexPtr->textPtr = textPtr;
+ assert(indexPtr1->priv.linePtr);
+ assert(indexPtr2->priv.linePtr);
+ assert(indexPtr1->priv.linePtr->parentPtr); /* expired? */
+ assert(indexPtr2->priv.linePtr->parentPtr); /* expired? */
- if (pixelIndex < 0) {
- pixelIndex = 0;
+ if (indexPtr1->priv.linePtr != indexPtr2->priv.linePtr) {
+ return false;
}
- indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree,
- textPtr, pixelIndex, &pixelOffset);
- /*
- * 'pixelIndex' was too large, so we try again, just to find the last
- * pixel in the window.
- */
+ if ((segPtr1 = TkTextIndexGetSegment(indexPtr1))) {
+ if ((segPtr2 = TkTextIndexGetSegment(indexPtr2))) {
+ while (segPtr1->prevPtr && segPtr1->prevPtr->size == 0) {
+ segPtr1 = segPtr1->prevPtr;
+ }
+ while (segPtr2->prevPtr && segPtr2->prevPtr->size == 0) {
+ segPtr2 = segPtr2->prevPtr;
+ }
+ return segPtr1 == segPtr2;
+ }
+ }
+
+ return TkTextIndexGetByteIndex(indexPtr1) == TkTextIndexGetByteIndex(indexPtr2);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexCompare --
+ *
+ * Compare two indicies.
+ *
+ * Results:
+ * It returns an integer less than, equal to, or greater than zero if
+ * indexPtr1 is found, respectively, to be less than, to match, or be
+ * greater than indexPtr2.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextIndexCompare(
+ const TkTextIndex *indexPtr1, /* Pointer to index. */
+ const TkTextIndex *indexPtr2) /* Pointer to index. */
+{
+ const TkTextSection *sectionPtr1;
+ const TkTextSection *sectionPtr2;
+ const TkTextSegment *segPtr1;
+ const TkTextSegment *segPtr2;
+
+ assert(indexPtr1->priv.linePtr);
+ assert(indexPtr2->priv.linePtr);
+ assert(indexPtr1->priv.linePtr->parentPtr); /* expired? */
+ assert(indexPtr2->priv.linePtr->parentPtr); /* expired? */
- if (indexPtr->linePtr == NULL) {
- int lastMinusOne = TkBTreeNumPixels(textPtr->sharedTextPtr->tree,
- textPtr)-1;
+ if (indexPtr1->priv.linePtr != indexPtr2->priv.linePtr) {
+ int lineNo1 = TkTextIndexGetLineNumber(indexPtr1, NULL);
+ int lineNo2 = TkTextIndexGetLineNumber(indexPtr2, NULL);
- indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree,
- textPtr, lastMinusOne, &pixelOffset);
- indexPtr->byteIndex = 0;
- return pixelOffset;
+ return lineNo1 - lineNo2;
+ }
+ if (indexPtr1->priv.byteIndex >= 0 && indexPtr2->priv.byteIndex >= 0) {
+ return indexPtr1->priv.byteIndex - indexPtr2->priv.byteIndex;
+ }
+
+ if (!(segPtr1 = TkTextIndexGetSegment(indexPtr1)) || !(segPtr2 = TkTextIndexGetSegment(indexPtr2))) {
+ return TkTextIndexGetByteIndex(indexPtr1) - TkTextIndexGetByteIndex(indexPtr2);
}
- indexPtr->byteIndex = 0;
- if (pixelOffset <= 0) {
+ assert(!indexPtr1->priv.isCharSegment || TkBTreeEpoch(indexPtr1->tree) == indexPtr1->stateEpoch);
+ assert(!indexPtr2->priv.isCharSegment || TkBTreeEpoch(indexPtr2->tree) == indexPtr2->stateEpoch);
+
+ segPtr1 = indexPtr1->priv.segPtr;
+ segPtr2 = indexPtr2->priv.segPtr;
+ while (segPtr1->size == 0) {
+ segPtr1 = segPtr1->nextPtr;
+ }
+ while (segPtr2->size == 0) {
+ segPtr2 = segPtr2->nextPtr;
+ }
+ if (segPtr1 == segPtr2) {
return 0;
}
- return TkTextMeasureDown(textPtr, indexPtr, pixelOffset);
+ sectionPtr1 = indexPtr1->priv.segPtr->sectionPtr;
+ sectionPtr2 = indexPtr2->priv.segPtr->sectionPtr;
+ if (sectionPtr1 != sectionPtr2) {
+ while (sectionPtr1 && sectionPtr1 != sectionPtr2) {
+ sectionPtr1 = sectionPtr1->nextPtr;
+ }
+ return sectionPtr1 ? -1 : +1;
+ }
+ segPtr1 = indexPtr1->priv.segPtr;
+ segPtr2 = indexPtr2->priv.segPtr;
+ while (segPtr1 != segPtr2) {
+ if (!(segPtr1 = segPtr1->nextPtr) || segPtr1->sectionPtr != sectionPtr1) {
+ return +1;
+ }
+ }
+ return -1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexAddToByteIndex --
+ *
+ * Add given byte offset to byte index.
+ *
+ * Note that this function allows that the byte index will reach the
+ * size of the line, in this case the line will be advanced, and the
+ * byte index will be set to zero.
+ *
+ * Results:
+ * Returns whether we're on same line.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextIndexAddToByteIndex(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ int byteOffset) /* Add this offset. */
+{
+ bool rc = true;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+ assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
+
+ if (byteOffset == 0) {
+ return true;
+ }
+
+ if (indexPtr->priv.byteIndex == -1) {
+ (void) TkTextIndexGetByteIndex(indexPtr);
+ }
+
+ if (byteOffset > 0) {
+ if ((indexPtr->priv.byteIndex += byteOffset) > FindEndByteIndex(indexPtr)) {
+ assert(indexPtr->priv.linePtr->nextPtr);
+ assert(indexPtr->priv.byteIndex <= indexPtr->priv.linePtr->size);
+ indexPtr->priv.linePtr = indexPtr->priv.linePtr->nextPtr;
+ if (indexPtr->priv.lineNo >= 0) {
+ indexPtr->priv.lineNo += 1;
+ }
+ if (indexPtr->priv.lineNoRel >= 0) {
+ indexPtr->priv.lineNoRel += 1;
+ }
+ indexPtr->priv.byteIndex = 0;
+ rc = false;
+ }
+ } else {
+ assert(-byteOffset <= indexPtr->priv.byteIndex);
+ indexPtr->priv.byteIndex += byteOffset;
+ }
+
+ indexPtr->priv.segPtr = NULL;
+
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+ assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
+
+ return rc;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TkpTextIndexDump --
+ *
+ * This function is for debugging only, printing the given index
+ * on stdout.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+#if !NDEBUG
+
+void
+TkpTextIndexDump(
+ TkText *textPtr, /* May be NULL. */
+ const TkTextIndex *indexPtr)/* Pointer to index. */
+{
+ char buf[TK_POS_CHARS];
+ TkTextIndexPrint(TkTextIndexGetShared(indexPtr), textPtr, indexPtr, buf);
+ printf("%s\n", buf);
+}
+
+#endif /* !NDEBUG */
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TkTextNewIndexObj --
+ *
+ * This function generates a Tcl_Obj description of an index, suitable
+ * for reading in again later. The index generated is effectively stable
+ * to all except insertion/deletion operations on the widget.
+ *
+ * Results:
+ * A new Tcl_String with refCount zero.
+ *
+ * Side effects:
+ * A small amount of memory is allocated.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+TkTextNewIndexObj(
+ const TkTextIndex *indexPtr)/* Pointer to index. */
+{
+ char buffer[TK_POS_CHARS];
+ int len;
+
+ assert(indexPtr->textPtr);
+
+ len = TkTextPrintIndex(indexPtr->textPtr, indexPtr, buffer);
+ return Tcl_NewStringObj(buffer, len);
}
/*
@@ -376,74 +1706,113 @@ TkTextMakePixelIndex(
TkTextIndex *
TkTextMakeByteIndex(
- TkTextBTree tree, /* Tree that lineIndex and byteIndex refer
- * to. */
- const TkText *textPtr,
- int lineIndex, /* Index of desired line (0 means first line
- * of text). */
+ TkTextBTree tree, /* Tree that lineIndex and byteIndex refer TkTextBTree tree, to. */
+ const TkText *textPtr, /* Client that lineIndex and byteIndex refer to, can be NULL. */
+ int lineIndex, /* Index of desired line (0 means first line of text). */
int byteIndex, /* Byte index of desired character. */
TkTextIndex *indexPtr) /* Structure to fill in. */
{
TkTextSegment *segPtr;
- int index;
- const char *p, *start;
- Tcl_UniChar ch;
+ TkTextSection *sectionPtr;
+ TkTextLine *linePtr;
+ int index, nextIndex;
+
+ TkTextIndexClear2(indexPtr, (TkText *) textPtr, tree);
- indexPtr->tree = tree;
if (lineIndex < 0) {
- lineIndex = 0;
- byteIndex = 0;
+ TkTextIndexSetupToStartOfText(indexPtr, (TkText *) textPtr, tree);
+ return indexPtr;
}
+
+ if (!(linePtr = TkBTreeFindLine(tree, textPtr, lineIndex))) {
+ TkTextIndexSetupToEndOfText(indexPtr, (TkText *) textPtr, tree);
+ return indexPtr;
+ }
+
if (byteIndex < 0) {
byteIndex = 0;
}
- indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex);
- if (indexPtr->linePtr == NULL) {
- indexPtr->linePtr = TkBTreeFindLine(tree, textPtr,
- TkBTreeNumLines(tree, textPtr));
- byteIndex = 0;
+
+ if (textPtr) {
+ if (textPtr->startMarker != textPtr->sharedTextPtr->startMarker
+ && textPtr->startMarker->sectionPtr->linePtr == linePtr) {
+ int startByteIndex;
+
+ TkTextIndexSetSegment(indexPtr, textPtr->startMarker);
+ startByteIndex = FindStartByteIndex(indexPtr);
+ if (byteIndex <= startByteIndex) {
+ return indexPtr;
+ }
+ }
+ if (textPtr->endMarker != textPtr->sharedTextPtr->endMarker
+ && textPtr->endMarker->sectionPtr->linePtr == linePtr) {
+ int endByteIndex;
+
+ TkTextIndexSetSegment(indexPtr, textPtr->endMarker);
+ endByteIndex = FindEndByteIndex(indexPtr);
+ if (endByteIndex <= byteIndex) {
+ return indexPtr;
+ }
+ }
}
+
+ indexPtr->priv.linePtr = linePtr;
+
if (byteIndex == 0) {
- indexPtr->byteIndex = byteIndex;
+ /* this is catching a frequent case */
+ TkTextIndexSetByteIndex(indexPtr, 0);
return indexPtr;
}
+ if (byteIndex >= linePtr->size) {
+ /*
+ * Use the index of the last character in the line. Since the last
+ * character on the line is guaranteed to be a '\n', we can back
+ * up one byte.
+ *
+ * Note that it is already guaranteed that we do not exceed the position
+ * of the end marker.
+ */
+ TkTextIndexSetByteIndex(indexPtr, linePtr->size - 1);
+ return indexPtr;
+ }
+
+ indexPtr->priv.byteIndex = byteIndex;
+ index = 0;
+
+ sectionPtr = linePtr->segPtr->sectionPtr;
+ while ((nextIndex = index + sectionPtr->size) <= byteIndex) {
+ index = nextIndex;
+ sectionPtr = sectionPtr->nextPtr;
+ assert(sectionPtr);
+ }
+
+ segPtr = sectionPtr->segPtr;
+ while ((nextIndex = index + segPtr->size) < byteIndex) {
+ index = nextIndex;
+ segPtr = segPtr->nextPtr;
+ assert(segPtr);
+ }
+
/*
- * Verify that the index is within the range of the line and points to a
- * valid character boundary.
+ * Verify that the index points to a valid character boundary.
*/
- index = 0;
- for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
- if (segPtr == NULL) {
- /*
- * Use the index of the last character in the line. Since the last
- * character on the line is guaranteed to be a '\n', we can back
- * up a constant sizeof(char) bytes.
- */
+ if (segPtr->typePtr == &tkTextCharType && byteIndex > index && index + segPtr->size > byteIndex) {
+ const char *p = segPtr->body.chars + (byteIndex - index);
- indexPtr->byteIndex = index - sizeof(char);
- break;
- }
- if (index + segPtr->size > byteIndex) {
- indexPtr->byteIndex = byteIndex;
- if ((byteIndex > index) && (segPtr->typePtr == &tkTextCharType)) {
- /*
- * Prevent UTF-8 character from being split up by ensuring
- * that byteIndex falls on a character boundary. If the index
- * falls in the middle of a UTF-8 character, it will be
- * adjusted to the end of that UTF-8 character.
- */
+ /*
+ * Prevent UTF-8 character from being split up by ensuring that byteIndex
+ * falls on a character boundary. If index falls in the middle of a UTF-8
+ * character, it will be adjusted to the end of that UTF-8 character.
+ */
- start = segPtr->body.chars + (byteIndex - index);
- p = Tcl_UtfPrev(start, segPtr->body.chars);
- p += Tcl_UtfToUniChar(p, &ch);
- indexPtr->byteIndex += p - start;
- }
- break;
+ while ((*p & 0xc0) == 0x80) {
+ ++p;
+ indexPtr->priv.byteIndex += 1;
}
- index += segPtr->size;
}
+
return indexPtr;
}
@@ -467,34 +1836,88 @@ TkTextMakeByteIndex(
*---------------------------------------------------------------------------
*/
+static unsigned
+CountCharsInSeg(
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr->typePtr == &tkTextCharType);
+ return Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
+}
+
TkTextIndex *
TkTextMakeCharIndex(
- TkTextBTree tree, /* Tree that lineIndex and charIndex refer
- * to. */
- TkText *textPtr,
- int lineIndex, /* Index of desired line (0 means first line
- * of text). */
+ TkTextBTree tree, /* Tree that lineIndex and charIndex refer to. */
+ TkText *textPtr, /* Client that lineIndex and charIndex refer to, can be NULL. */
+ int lineIndex, /* Index of desired line (0 means first line of text). */
int charIndex, /* Index of desired character. */
TkTextIndex *indexPtr) /* Structure to fill in. */
{
- register TkTextSegment *segPtr;
+ TkTextSegment *segPtr, *lastPtr;
+ TkTextLine *linePtr;
char *p, *start, *end;
int index, offset;
- Tcl_UniChar ch;
- indexPtr->tree = tree;
+ TkTextIndexClear2(indexPtr, textPtr, tree);
+
if (lineIndex < 0) {
- lineIndex = 0;
- charIndex = 0;
+ TkTextIndexSetupToStartOfText(indexPtr, textPtr, tree);
+ return indexPtr;
+ }
+
+ if (!(linePtr = TkBTreeFindLine(tree, textPtr, lineIndex))) {
+ TkTextIndexSetupToEndOfText(indexPtr, textPtr, tree);
+ return indexPtr;
}
- if (charIndex < 0) {
- charIndex = 0;
+
+ indexPtr->priv.linePtr = linePtr;
+
+ if (charIndex >= linePtr->size - 1) {
+ /* this is catching a frequent case */
+ TkTextIndexSetToLastChar(indexPtr);
+ return indexPtr;
}
- indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex);
- if (indexPtr->linePtr == NULL) {
- indexPtr->linePtr = TkBTreeFindLine(tree, textPtr,
- TkBTreeNumLines(tree, textPtr));
- charIndex = 0;
+
+ if (charIndex <= 0) {
+ /* this is catching a frequent case */
+ TkTextIndexSetToStartOfLine(indexPtr);
+ return indexPtr;
+ }
+
+ if (textPtr && textPtr->endMarker->sectionPtr->linePtr == linePtr) {
+ lastPtr = textPtr->endMarker;
+ } else {
+ lastPtr = NULL;
+ }
+
+ if (!textPtr
+ || textPtr->startMarker == TkBTreeGetShared(indexPtr->tree)->startMarker
+ || linePtr != textPtr->startMarker->sectionPtr->linePtr) {
+ segPtr = linePtr->segPtr;
+ index = 0;
+ } else {
+ TkTextSegment *startPtr;
+
+ /*
+ * We have to skip some segments not belonging to this peer.
+ */
+
+ TkTextIndexSetSegment(indexPtr, textPtr->startMarker);
+ startPtr = TkTextIndexGetFirstSegment(indexPtr, NULL);
+
+ for (segPtr = linePtr->segPtr; segPtr != startPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ charIndex -= CountCharsInSeg(segPtr);
+ } else {
+ assert(segPtr->size <= 1);
+ charIndex -= segPtr->size;
+ }
+ if (charIndex <= 0) {
+ return indexPtr;
+ }
+ }
+
+ index = TkTextIndexGetByteIndex(indexPtr);
+ indexPtr->priv.segPtr = NULL;
}
/*
@@ -502,50 +1925,53 @@ TkTextMakeCharIndex(
* the index of the last character in the line.
*/
- index = 0;
- for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
- if (segPtr == NULL) {
- /*
- * Use the index of the last character in the line. Since the last
- * character on the line is guaranteed to be a '\n', we can back
- * up a constant sizeof(char) bytes.
- */
+ while (segPtr != lastPtr) {
+ if (segPtr->tagInfoPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ int ch;
- indexPtr->byteIndex = index - sizeof(char);
- break;
- }
- if (segPtr->typePtr == &tkTextCharType) {
- /*
- * Turn character offset into a byte offset.
- */
+ /*
+ * Turn character offset into a byte offset.
+ */
+
+ start = segPtr->body.chars;
+ end = start + segPtr->size;
- start = segPtr->body.chars;
- end = start + segPtr->size;
- for (p = start; p < end; p += offset) {
- if (charIndex == 0) {
- indexPtr->byteIndex = index;
- return indexPtr;
+ for (p = start; p < end; p += offset) {
+ if (charIndex == 0) {
+ indexPtr->priv.byteIndex = index;
+ return indexPtr;
+ }
+ charIndex -= 1;
+ offset = TkUtfToUniChar(p, &ch);
+ index += offset;
}
- charIndex--;
- offset = Tcl_UtfToUniChar(p, &ch);
- index += offset;
- }
- } else {
- if (charIndex < segPtr->size) {
- indexPtr->byteIndex = index;
- break;
+ } else if (charIndex < segPtr->size) {
+ indexPtr->priv.byteIndex = index;
+ return indexPtr;
+ } else {
+ assert(segPtr->size == 1);
+ charIndex -= 1;
+ index += 1;
}
- charIndex -= segPtr->size;
- index += segPtr->size;
+ }
+ if (!(segPtr = segPtr->nextPtr)) {
+ /*
+ * Use the index of the last character in the line.
+ */
+ TkTextIndexSetToLastChar(indexPtr);
+ return indexPtr;
}
}
+
+ indexPtr->priv.byteIndex = index;
return indexPtr;
}
/*
*---------------------------------------------------------------------------
*
- * TkTextIndexToSeg --
+ * IndexToSeg --
*
* Given an index, this function returns the segment and offset within
* segment for the index.
@@ -562,22 +1988,68 @@ TkTextMakeCharIndex(
*---------------------------------------------------------------------------
*/
-TkTextSegment *
-TkTextIndexToSeg(
+static TkTextSegment *
+IndexToSeg(
const TkTextIndex *indexPtr,/* Text index. */
- int *offsetPtr) /* Where to store offset within segment, or
- * NULL if offset isn't wanted. */
+ int *offsetPtr) /* Where to store offset within segment, or NULL if offset isn't
+ * wanted. */
{
+ TkTextSection *sectionPtr;
TkTextSegment *segPtr;
- int offset;
+ TkTextLine *linePtr;
+ int index;
- for (offset = indexPtr->byteIndex, segPtr = indexPtr->linePtr->segPtr;
- offset >= segPtr->size;
- offset -= segPtr->size, segPtr = segPtr->nextPtr) {
- /* Empty loop body. */
+ assert(indexPtr->priv.byteIndex >= 0);
+ assert(indexPtr->priv.byteIndex < indexPtr->priv.linePtr->size);
+
+ index = indexPtr->priv.byteIndex;
+ linePtr = indexPtr->priv.linePtr;
+
+ /*
+ * Speed up a frequent use case.
+ */
+
+ if (index == 0) {
+ segPtr = linePtr->segPtr;
+ while (segPtr->size == 0) {
+ segPtr = segPtr->nextPtr;
+ }
+ if (offsetPtr) {
+ *offsetPtr = 0;
+ }
+ return segPtr;
+ }
+
+ /*
+ * Speed up a frequent use case.
+ */
+
+ if (index == linePtr->size - 1) {
+ assert(linePtr->lastPtr->typePtr == &tkTextCharType);
+ if (offsetPtr) {
+ *offsetPtr = linePtr->lastPtr->size - 1;
+ }
+ return linePtr->lastPtr;
+ }
+
+ /*
+ * Now we iterate through the section an segment structure until we reach the
+ * wanted byte index.
+ */
+
+ sectionPtr = linePtr->segPtr->sectionPtr;
+ for ( ; index >= sectionPtr->size; sectionPtr = sectionPtr->nextPtr) {
+ index -= sectionPtr->size;
+ assert(sectionPtr->nextPtr);
}
- if (offsetPtr != NULL) {
- *offsetPtr = offset;
+ for (segPtr = sectionPtr->segPtr; index >= segPtr->size; segPtr = segPtr->nextPtr) {
+ index -= segPtr->size;
+ assert(segPtr->nextPtr);
+ }
+ assert(segPtr->size > 0);
+
+ if (offsetPtr) {
+ *offsetPtr = index;
}
return segPtr;
}
@@ -585,10 +2057,13 @@ TkTextIndexToSeg(
/*
*---------------------------------------------------------------------------
*
- * TkTextSegToOffset --
+ * SegToIndex --
+ *
+ * Given a segment pointer, this function returns the offset of the
+ * segment within its line.
*
- * Given a segment pointer and the line containing it, this function
- * returns the offset of the segment within its line.
+ * This function assumes that we want the index to the current line,
+ * and not the index from the start of the logical line.
*
* Results:
* The return value is the offset (within its line) of the first
@@ -600,60 +2075,64 @@ TkTextIndexToSeg(
*---------------------------------------------------------------------------
*/
-int
-TkTextSegToOffset(
- const TkTextSegment *segPtr,/* Segment whose offset is desired. */
- const TkTextLine *linePtr) /* Line containing segPtr. */
+static int
+SegToIndex(
+ const TkTextLine *linePtr,
+ const TkTextSegment *segPtr)/* Segment whose offset is desired. */
{
+ const TkTextSection *sectionPtr;
const TkTextSegment *segPtr2;
- int offset = 0;
+ int offset;
+
+ assert(segPtr->sectionPtr); /* otherwise not linked */
+ assert(segPtr->sectionPtr->linePtr == linePtr);
+
+ sectionPtr = linePtr->segPtr->sectionPtr; /* first segment in line */
+
+ /*
+ * Speed up frequent use cases.
+ */
+
+ if (segPtr == sectionPtr->segPtr) {
+ return 0;
+ }
+
+ if (segPtr == linePtr->lastPtr) {
+ return linePtr->size - segPtr->size;
+ }
- for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr;
- segPtr2 = segPtr2->nextPtr) {
+ /*
+ * Now we iterate through the section an segment structure until we reach the
+ * given segment.
+ */
+
+ offset = 0;
+
+ for ( ; sectionPtr != segPtr->sectionPtr; sectionPtr = sectionPtr->nextPtr) {
+ offset += sectionPtr->size;
+ assert(sectionPtr->nextPtr);
+ }
+ for (segPtr2 = segPtr->sectionPtr->segPtr; segPtr2 != segPtr; segPtr2 = segPtr2->nextPtr) {
offset += segPtr2->size;
+ assert(segPtr2->nextPtr);
}
+
return offset;
}
-
/*
*---------------------------------------------------------------------------
*
- * TkTextGetObjIndex --
- *
- * Simpler wrapper around the string based function, but could be
- * enhanced with a new object type in the future.
+ * TkTextSegToIndex --
*
- * Results:
- * see TkTextGetIndex
+ * Given a segment pointer, this function returns the offset of the
+ * segment within its line.
*
- * Side effects:
- * None.
- *
- *---------------------------------------------------------------------------
- */
-
-int
-TkTextGetObjIndex(
- Tcl_Interp *interp, /* Use this for error reporting. */
- TkText *textPtr, /* Information about text widget. */
- Tcl_Obj *idxObj, /* Object containing textual description of
- * position. */
- TkTextIndex *indexPtr) /* Index structure to fill in. */
-{
- return GetIndex(interp, NULL, textPtr, Tcl_GetString(idxObj), indexPtr,
- NULL);
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * TkTextSharedGetObjIndex --
- *
- * Simpler wrapper around the string based function, but could be
- * enhanced with a new object type in the future.
+ * This function assumes that we want the index to the current line,
+ * and not the index from the start of the logical line.
*
* Results:
- * see TkTextGetIndex
+ * The return value is the offset (within its line) of the first
+ * character in segPtr.
*
* Side effects:
* None.
@@ -662,15 +2141,10 @@ TkTextGetObjIndex(
*/
int
-TkTextSharedGetObjIndex(
- Tcl_Interp *interp, /* Use this for error reporting. */
- TkSharedText *sharedTextPtr,/* Information about text widget. */
- Tcl_Obj *idxObj, /* Object containing textual description of
- * position. */
- TkTextIndex *indexPtr) /* Index structure to fill in. */
+TkTextSegToIndex(
+ const TkTextSegment *segPtr)/* Segment whose offset is desired. */
{
- return GetIndex(interp, sharedTextPtr, NULL, Tcl_GetString(idxObj),
- indexPtr, NULL);
+ return SegToIndex(segPtr->sectionPtr->linePtr, segPtr);
}
/*
@@ -699,7 +2173,8 @@ TkTextGetIndex(
const char *string, /* Textual description of position. */
TkTextIndex *indexPtr) /* Index structure to fill in. */
{
- return GetIndex(interp, NULL, textPtr, string, indexPtr, NULL);
+ assert(textPtr);
+ return GetIndex(interp, textPtr->sharedTextPtr, textPtr, string, indexPtr) ? TCL_OK : TCL_ERROR;
}
/*
@@ -710,14 +2185,9 @@ TkTextGetIndex(
* Given a string, return the index that is described.
*
* Results:
- * The return value is a standard Tcl return result. If TCL_OK is
- * returned, then everything went well and the index at *indexPtr is
- * filled in; otherwise TCL_ERROR is returned and an error message is
- * left in the interp's result.
- *
- * If *canCachePtr is non-NULL, and everything went well, the integer it
- * points to is set to 1 if the indexPtr is something which can be
- * cached, and zero otherwise.
+ * If 'true' is returned, then everything went well and the index at
+ * *indexPtr is filled in; otherwise 'false' is returned and an error
+ * message is left in the interp's result.
*
* Side effects:
* None.
@@ -725,57 +2195,90 @@ TkTextGetIndex(
*---------------------------------------------------------------------------
*/
-static int
+static unsigned
+SkipSegments(
+ TkTextSegment *startMarkPtr)
+{
+ TkTextSegment *segPtr = startMarkPtr->sectionPtr->linePtr->segPtr;
+ unsigned charIndex = 0;
+
+ /* Skip chars not belonging to this text widget. */
+
+ for ( ; segPtr != startMarkPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->tagInfoPtr) {
+ charIndex += (segPtr->typePtr == &tkTextCharType) ? CountCharsInSeg(segPtr) : 1;
+ }
+ }
+
+ return charIndex;
+}
+
+static bool
GetIndex(
Tcl_Interp *interp, /* Use this for error reporting. */
- TkSharedText *sharedPtr,
+ TkSharedText *sharedTextPtr,/* Pointer to shared resource. */
TkText *textPtr, /* Information about text widget. */
const char *string, /* Textual description of position. */
- TkTextIndex *indexPtr, /* Index structure to fill in. */
- int *canCachePtr) /* Pointer to integer to store whether we can
- * cache the index (or NULL). */
+ TkTextIndex *indexPtr) /* Index structure to fill in. */
{
char *p, *end, *endOfBase;
TkTextIndex first, last;
- int wantLast, result;
char c;
const char *cp;
+ char *myString;
Tcl_DString copy;
- int canCache = 0;
+ bool tagFound;
+ bool skipMark;
+ bool result;
- if (sharedPtr == NULL) {
- sharedPtr = textPtr->sharedTextPtr;
- }
+ assert(textPtr);
+ assert(sharedTextPtr);
/*
- *---------------------------------------------------------------------
- * Stage 1: check to see if the index consists of nothing but a mark
- * name, an embedded window or an embedded image. We do this check
- * now even though it's also done later, in order to allow mark names,
- * embedded window names or image names that include funny characters
- * such as spaces or "+1c".
- *---------------------------------------------------------------------
+ * The documentation about indices says:
+ *
+ * The base for an index must have one of the following forms:
+ *
+ * <line.<char>
+ * @<x>,<y>
+ * begin
+ * end
+ * <mark>
+ * <tag>.first
+ * <tag>.last
+ * <pathName>
+ * <imageName>
+ *
+ * Furthermore the documentation says:
+ *
+ * If the base could match more than one of the above forms, such as a mark and imageName
+ * both having the same value, then the form earlier in the above list takes precedence.
*/
- if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) {
- goto done;
- }
-
- if (TkTextWindowIndex(textPtr, string, indexPtr) != 0) {
- goto done;
- }
+#if BEGIN_DOES_NOT_BELONG_TO_BASE
+ /*
+ *------------------------------------------------
+ * Stage 0: for portability reasons keyword "begin" has the lowest
+ * precedence (but this should be corrected in a future version).
+ *------------------------------------------------
+ */
- if (TkTextImageIndex(textPtr, string, indexPtr) != 0) {
- goto done;
+ if (string[0] == 'b' && strncmp(string, "begin", 5)) {
+ if (TkTextMarkNameToIndex(textPtr, string, indexPtr)
+ || TkTextWindowIndex(textPtr, string, indexPtr)
+ || TkTextImageIndex(textPtr, string, indexPtr)) {
+ return true;
+ }
}
+#endif /* BEGIN_DOES_NOT_BELONG_TO_BASE */
/*
*------------------------------------------------
- * Stage 2: start again by parsing the base index.
+ * Stage 1: start by parsing the base index.
*------------------------------------------------
*/
- indexPtr->tree = sharedPtr->tree;
+ TkTextIndexClear(indexPtr, textPtr);
/*
* First look for the form "tag.first" or "tag.last" where "tag" is the
@@ -786,68 +2289,80 @@ GetIndex(
*/
Tcl_DStringInit(&copy);
- p = strrchr(Tcl_DStringAppend(&copy, string, -1), '.');
- if (p != NULL) {
+ myString = Tcl_DStringAppend(&copy, string, -1);
+ p = strrchr(myString, '.');
+ skipMark = false;
+
+ if (p) {
TkTextSearch search;
TkTextTag *tagPtr;
Tcl_HashEntry *hPtr = NULL;
const char *tagName;
-
- if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) {
- wantLast = 0;
- endOfBase = p+6;
- } else if ((p[1] == 'l') && (strncmp(p+1, "last", 4) == 0)) {
- wantLast = 1;
- endOfBase = p+5;
+ bool wantLast;
+
+ if (p[1] == 'f' && strncmp(p + 1, "first", 5) == 0) {
+ wantLast = false;
+ endOfBase = p + 6;
+ } else if (p[1] == 'l' && strncmp(p + 1, "last", 4) == 0) {
+ wantLast = true;
+ endOfBase = p + 5;
} else {
goto tryxy;
}
+ /*
+ * Marks have a higher precedence than tag.first or tag.last, so we will
+ * search for marks before proceeding with tags.
+ */
+
+ if (TkTextMarkNameToIndex(textPtr, string, indexPtr)) {
+ Tcl_DStringFree(&copy);
+ return true;
+ }
+
+ skipMark = true;
tagPtr = NULL;
- tagName = Tcl_DStringValue(&copy);
- if (((p - tagName) == 3) && !strncmp(tagName, "sel", 3)) {
+ tagName = myString;
+ if (p - tagName == 3 && strncmp(tagName, "sel", 3) == 0) {
/*
* Special case for sel tag which is not stored in the hash table.
*/
-
tagPtr = textPtr->selTagPtr;
} else {
- *p = 0;
- hPtr = Tcl_FindHashEntry(&sharedPtr->tagTable, tagName);
+ *p = '\0';
+ hPtr = Tcl_FindHashEntry(&sharedTextPtr->tagTable, tagName);
*p = '.';
- if (hPtr != NULL) {
+ if (hPtr) {
tagPtr = Tcl_GetHashValue(hPtr);
}
}
- if (tagPtr == NULL) {
+ if (!tagPtr) {
goto tryxy;
}
- TkTextMakeByteIndex(sharedPtr->tree, textPtr, 0, 0, &first);
- TkTextMakeByteIndex(sharedPtr->tree, textPtr,
- TkBTreeNumLines(sharedPtr->tree, textPtr), 0, &last);
- TkBTreeStartSearch(&first, &last, tagPtr, &search);
- if (!TkBTreeCharTagged(&first, tagPtr) && !TkBTreeNextTag(&search)) {
+ TkTextIndexSetupToStartOfText(&first, textPtr, sharedTextPtr->tree);
+ TkTextIndexSetupToEndOfText(&last, textPtr, sharedTextPtr->tree);
+ if (wantLast) {
+ TkBTreeStartSearchBack(&last, &first, tagPtr, &search, SEARCH_EITHER_TAGON_TAGOFF);
+ tagFound = TkBTreePrevTag(&search);
+ } else {
+ TkBTreeStartSearch(&first, &last, tagPtr, &search, SEARCH_NEXT_TAGON);
+ tagFound = TkBTreeNextTag(&search);
+ }
+ if (!tagFound) {
if (tagPtr == textPtr->selTagPtr) {
tagName = "sel";
- } else if (hPtr != NULL) {
- tagName = Tcl_GetHashKey(&sharedPtr->tagTable, hPtr);
+ } else if (hPtr) {
+ tagName = Tcl_GetHashKey(&sharedTextPtr->tagTable, hPtr);
}
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "text doesn't contain any characters tagged with \"%s\"",
- tagName));
- Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_INDEX", tagName,
- NULL);
+ "text doesn't contain any characters tagged with \"%s\"", tagName));
+ Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_INDEX", tagName, NULL);
Tcl_DStringFree(&copy);
- return TCL_ERROR;
+ return false;
}
*indexPtr = search.curIndex;
- if (wantLast) {
- while (TkBTreeNextTag(&search)) {
- *indexPtr = search.curIndex;
- }
- }
goto gotBase;
}
@@ -859,22 +2374,38 @@ GetIndex(
int x, y;
- cp = string+1;
- x = strtol(cp, &end, 0);
- if ((end == cp) || (*end != ',')) {
- goto error;
+ cp = string + 1;
+ if (*cp == 'f' && strncmp(cp, "first,", 6) == 0) {
+ x = TkTextGetFirstXPixel(textPtr);
+ end = (char *) cp + 5;
+ } else if (*cp == 'l' && strncmp(cp, "last,", 5) == 0) {
+ x = TkTextGetLastXPixel(textPtr);
+ end = (char *) cp + 4;
+ } else {
+ x = strtol(cp, &end, 0);
+ if (end == cp || *end != ',') {
+ goto noBaseFound;
+ }
}
- cp = end+1;
- y = strtol(cp, &end, 0);
- if (end == cp) {
- goto error;
+ cp = end + 1;
+ if (*cp == 'f' && strcmp(cp, "first") == 0) {
+ y = TkTextGetFirstYPixel(textPtr);
+ end += 6;
+ } else if (*cp == 'l' && strcmp(cp, "last") == 0) {
+ y = TkTextGetLastYPixel(textPtr);
+ end += 5;
+ } else {
+ y = strtol(cp, &end, 0);
+ if (end == cp) {
+ goto noBaseFound;
+ }
}
TkTextPixelIndex(textPtr, x, y, indexPtr, NULL);
endOfBase = end;
goto gotBase;
}
- if (isdigit(UCHAR(string[0])) || (string[0] == '-')) {
+ if (isdigit(string[0]) || string[0] == '-') {
int lineIndex, charIndex;
/*
@@ -882,28 +2413,32 @@ GetIndex(
*/
lineIndex = strtol(string, &end, 0) - 1;
- if ((end == string) || (*end != '.')) {
- goto error;
- }
- p = end+1;
- if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) {
- charIndex = LAST_CHAR;
- endOfBase = p+3;
+ if (end == string || *end != '.') {
+ goto noBaseFound;
+ }
+ p = end + 1;
+ if (*p == 'e' && strncmp(p, "end", 3) == 0) {
+ charIndex = INT_MAX;
+ endOfBase = p + 3;
+ } else if (*p == 'b' && strncmp(p, "begin", 5) == 0) {
+ charIndex = 0;
+ endOfBase = p + 5;
} else {
charIndex = strtol(p, &end, 0);
if (end == p) {
- goto error;
+ goto noBaseFound;
}
endOfBase = end;
}
- TkTextMakeCharIndex(sharedPtr->tree, textPtr, lineIndex, charIndex,
- indexPtr);
- canCache = 1;
+ if (lineIndex == 0 && textPtr->startMarker != sharedTextPtr->startMarker) {
+ charIndex += SkipSegments(textPtr->startMarker);
+ }
+ TkTextMakeCharIndex(sharedTextPtr->tree, textPtr, lineIndex, charIndex, indexPtr);
goto gotBase;
}
- for (p = Tcl_DStringValue(&copy); *p != 0; p++) {
- if (isspace(UCHAR(*p)) || (*p == '+') || (*p == '-')) {
+ for (p = myString; *p != 0; ++p) {
+ if (isspace(*p) || *p == '+' || *p == '-') {
break;
}
}
@@ -914,55 +2449,69 @@ GetIndex(
*/
c = *endOfBase;
- *endOfBase = 0;
- result = TkTextWindowIndex(textPtr, Tcl_DStringValue(&copy), indexPtr);
+ *endOfBase = '\0';
+ result = TkTextWindowIndex(textPtr, myString, indexPtr);
*endOfBase = c;
- if (result != 0) {
+ if (result) {
goto gotBase;
}
}
- if ((string[0] == 'e')
- && (strncmp(string, "end",
- (size_t) (endOfBase-Tcl_DStringValue(&copy))) == 0)) {
+ if (string[0] == 'b' && endOfBase - myString == 5 && strncmp(string, "begin", 5) == 0) {
/*
- * Base position is end of text.
+ * Base position is start of text.
*/
- TkTextMakeByteIndex(sharedPtr->tree, textPtr,
- TkBTreeNumLines(sharedPtr->tree, textPtr), 0, indexPtr);
- canCache = 1;
+ TkTextIndexSetupToStartOfText(indexPtr, textPtr, sharedTextPtr->tree);
goto gotBase;
- } else {
+ }
+ if (string[0] == 'e' && endOfBase - myString == 3 && strncmp(string, "end", 3) == 0) {
/*
- * See if the base position is the name of a mark.
+ * Base position is end of text.
*/
- c = *endOfBase;
- *endOfBase = 0;
- result = TkTextMarkNameToIndex(textPtr, Tcl_DStringValue(&copy),
- indexPtr);
+ TkTextIndexSetupToEndOfText(indexPtr, textPtr, sharedTextPtr->tree);
+ goto gotBase;
+ }
+
+ /*
+ * See if the base position is the name of a mark.
+ */
+
+ c = *endOfBase;
+ *endOfBase = '\0';
+ result = TkTextMarkNameToIndex(textPtr, myString, indexPtr);
+ if (result) {
*endOfBase = c;
- if (result == TCL_OK) {
- goto gotBase;
- }
+ goto gotBase;
+ }
- /*
- * See if the base position is the name of an embedded image.
- */
+ /*
+ * See if the base position is the name of an embedded image.
+ */
- c = *endOfBase;
- *endOfBase = 0;
- result = TkTextImageIndex(textPtr, Tcl_DStringValue(&copy), indexPtr);
- *endOfBase = c;
- if (result != 0) {
- goto gotBase;
- }
+ result = TkTextImageIndex(textPtr, myString, indexPtr);
+ *endOfBase = c;
+ if (result) {
+ goto gotBase;
+ }
+
+ noBaseFound:
+ if ((!skipMark && TkTextMarkNameToIndex(textPtr, string, indexPtr))
+ || TkTextWindowIndex(textPtr, string, indexPtr)
+ || TkTextImageIndex(textPtr, string, indexPtr)) {
+ Tcl_DStringFree(&copy);
+ return true;
}
- goto error;
+
+ Tcl_DStringFree(&copy);
+ Tcl_ResetResult(interp);
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad text index \"%s\"", string));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_INDEX", NULL);
+ return false;
/*
*-------------------------------------------------------------------
- * Stage 3: process zero or more modifiers. Each modifier is either a
+ * Stage 2: process zero or more modifiers. Each modifier is either a
* keyword like "wordend" or "linestart", or it has the form "op count
* units" where op is + or -, count is a number, and units is "chars" or
* "lines".
@@ -971,45 +2520,32 @@ GetIndex(
gotBase:
cp = endOfBase;
- while (1) {
- while (isspace(UCHAR(*cp))) {
+
+ while (true) {
+ while (isspace(*cp)) {
cp++;
}
- if (*cp == 0) {
+ if (*cp == '\0') {
break;
}
-
- if ((*cp == '+') || (*cp == '-')) {
+ if (*cp == '+' || *cp == '-') {
cp = ForwBack(textPtr, cp, indexPtr);
} else {
cp = StartEnd(textPtr, cp, indexPtr);
}
- if (cp == NULL) {
- goto error;
+ if (!cp) {
+ goto noBaseFound;
}
}
- Tcl_DStringFree(&copy);
-
- done:
- if (canCachePtr != NULL) {
- *canCachePtr = canCache;
- }
- if (indexPtr->linePtr == NULL) {
- Tcl_Panic("Bad index created");
- }
- return TCL_OK;
- error:
Tcl_DStringFree(&copy);
- Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad text index \"%s\"", string));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_INDEX", NULL);
- return TCL_ERROR;
+ return true;
}
/*
*---------------------------------------------------------------------------
*
- * TkTextPrintIndex --
+ * TkTextIndexPrint --
*
* This function generates a string description of an index, suitable for
* reading in again later.
@@ -1025,102 +2561,89 @@ GetIndex(
*/
int
-TkTextPrintIndex(
- const TkText *textPtr,
+TkTextIndexPrint(
+ const TkSharedText *sharedTextPtr,
+ /* Pointer to shared resource. */
+ const TkText *textPtr, /* Information about text widget, can be NULL. */
const TkTextIndex *indexPtr,/* Pointer to index. */
- char *string) /* Place to store the position. Must have at
- * least TK_POS_CHARS characters. */
+ char *string) /* Place to store the position. Must have at least TK_POS_CHARS
+ * characters. */
{
- TkTextSegment *segPtr;
- TkTextLine *linePtr;
- int numBytes, charIndex;
+ const TkTextSegment *segPtr;
+ const TkTextLine *linePtr;
+ const TkTextSegment *startMarker;
+ int charIndex;
+
+ assert(sharedTextPtr);
+ assert(indexPtr);
+ assert(string);
+ assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
+ assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
- numBytes = indexPtr->byteIndex;
charIndex = 0;
- linePtr = indexPtr->linePtr;
+ linePtr = indexPtr->priv.linePtr;
+ startMarker = textPtr ? textPtr->startMarker : sharedTextPtr->startMarker;
+ segPtr = (linePtr == startMarker->sectionPtr->linePtr) ? startMarker : linePtr->segPtr;
- for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
- if (segPtr == NULL) {
- /*
- * Two logical lines merged into one display line through eliding
- * of a newline.
- */
+ /*
+ * Too bad that we cannot use the section structure here.
+ *
+ * The user of the Tk text widget is encouraged to work with marks,
+ * in this way the expensive mappings between char indices and byte
+ * indices can be avoided.
+ */
- linePtr = TkBTreeNextLine(NULL, linePtr);
- segPtr = linePtr->segPtr;
- }
- if (numBytes <= segPtr->size) {
- break;
- }
- if (segPtr->typePtr == &tkTextCharType) {
- charIndex += Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
- } else {
- charIndex += segPtr->size;
+ if (indexPtr->priv.segPtr && !indexPtr->priv.isCharSegment) {
+ TkTextSegment *lastPtr = indexPtr->priv.segPtr;
+
+ assert(indexPtr->priv.segPtr->typePtr);
+
+ while (lastPtr->size == 0) {
+ lastPtr = lastPtr->nextPtr;
}
- numBytes -= segPtr->size;
- }
- if (segPtr->typePtr == &tkTextCharType) {
- charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes);
+ for ( ; segPtr != lastPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ charIndex += CountCharsInSeg(segPtr);
+ } else {
+ assert(segPtr->size <= 1);
+ charIndex += segPtr->size;
+ }
+ assert(segPtr->nextPtr);
+ }
} else {
- charIndex += numBytes;
- }
+ int numBytes = TkTextIndexGetByteIndex(indexPtr);
- return sprintf(string, "%d.%d",
- TkBTreeLinesTo(textPtr, indexPtr->linePtr) + 1, charIndex);
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * TkTextIndexCmp --
- *
- * Compare two indices to see which one is earlier in the text.
- *
- * Results:
- * The return value is 0 if index1Ptr and index2Ptr refer to the same
- * position in the file, -1 if index1Ptr refers to an earlier position
- * than index2Ptr, and 1 otherwise.
- *
- * Side effects:
- * None.
- *
- *---------------------------------------------------------------------------
- */
+ if (segPtr == startMarker && startMarker != sharedTextPtr->startMarker) {
+ numBytes -= TkTextSegToIndex(startMarker);
+ }
-int
-TkTextIndexCmp(
- const TkTextIndex*index1Ptr,/* First index. */
- const TkTextIndex*index2Ptr)/* Second index. */
-{
- int line1, line2;
+ assert(numBytes >= 0);
+ assert(numBytes < linePtr->size);
- if (index1Ptr->linePtr == index2Ptr->linePtr) {
- if (index1Ptr->byteIndex < index2Ptr->byteIndex) {
- return -1;
- } else if (index1Ptr->byteIndex > index2Ptr->byteIndex) {
- return 1;
- } else {
- return 0;
+ for ( ; numBytes > segPtr->size; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ charIndex += CountCharsInSeg(segPtr);
+ } else {
+ assert(segPtr->size <= 1);
+ charIndex += segPtr->size;
+ }
+ numBytes -= segPtr->size;
+ assert(segPtr->nextPtr);
}
- }
- /*
- * Assumption here that it is ok for comparisons to reflect the full
- * B-tree and not just the portion that is available to any client. This
- * should be true because the only indexPtr's we should be given are ones
- * which are valid for the current client.
- */
-
- line1 = TkBTreeLinesTo(NULL, index1Ptr->linePtr);
- line2 = TkBTreeLinesTo(NULL, index2Ptr->linePtr);
- if (line1 < line2) {
- return -1;
- }
- if (line1 > line2) {
- return 1;
+ if (numBytes) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes);
+ } else {
+ assert(segPtr->size <= 1);
+ charIndex += numBytes;
+ }
+ }
}
- return 0;
+
+ return snprintf(string, TK_POS_CHARS, "%d.%d",
+ TkBTreeLinesTo(indexPtr->tree, textPtr, linePtr, NULL) + 1, charIndex);
}
/*
@@ -1151,17 +2674,19 @@ ForwBack(
* or "-" that starts modifier. */
TkTextIndex *indexPtr) /* Index to update as specified in string. */
{
- register const char *p, *units;
+ const char *p, *units;
char *end;
int count, lineIndex, modifier;
size_t length;
+ assert(textPtr);
+
/*
* Get the count (how many units forward or backward).
*/
- p = string+1;
- while (isspace(UCHAR(*p))) {
+ p = string + 1;
+ while (isspace(*p)) {
p++;
}
count = strtol(p, &end, 0);
@@ -1169,7 +2694,7 @@ ForwBack(
return NULL;
}
p = end;
- while (isspace(UCHAR(*p))) {
+ while (isspace(*p)) {
p++;
}
@@ -1183,19 +2708,17 @@ ForwBack(
*/
units = p;
- while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) {
+ while (*p != '\0' && !isspace(*p) && *p != '+' && *p != '-') {
p++;
}
length = p - units;
- if ((*units == 'd') &&
- (strncmp(units, "display", (length > 7 ? 7 : length)) == 0)) {
+ if (*units == 'd' && strncmp(units, "display", MIN(length, 7)) == 0) {
modifier = TKINDEX_DISPLAY;
if (length > 7) {
p -= (length - 7);
}
- } else if ((*units == 'a') &&
- (strncmp(units, "any", (length > 3 ? 3 : length)) == 0)) {
- modifier = TKINDEX_ANY;
+ } else if (*units == 'a' && strncmp(units, "any", MIN(length, 3)) == 0) {
+ modifier = TKINDEX_CHAR;
if (length > 3) {
p -= (length - 3);
}
@@ -1209,11 +2732,11 @@ ForwBack(
*/
if (modifier != TKINDEX_NONE) {
- while (isspace(UCHAR(*p))) {
+ while (isspace(*p)) {
p++;
}
units = p;
- while (*p!='\0' && !isspace(UCHAR(*p)) && *p!='+' && *p!='-') {
+ while (*p != '\0' && !isspace(*p) && *p != '+' && *p != '-') {
p++;
}
length = p - units;
@@ -1223,15 +2746,32 @@ ForwBack(
* Finally parse the units.
*/
- if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) {
+ if (*units == 'c' && strncmp(units, "chars", length) == 0) {
TkTextCountType type;
- if (modifier == TKINDEX_NONE) {
- type = COUNT_INDICES;
- } else if (modifier == TKINDEX_ANY) {
- type = COUNT_CHARS;
- } else {
+ if (modifier == TKINDEX_DISPLAY) {
type = COUNT_DISPLAY_CHARS;
+ } else { /* if (modifier == TKINDEX_NONE) */
+ assert(modifier == TKINDEX_NONE || modifier == TKINDEX_CHAR);
+
+ /*
+ * The following is incompatible to 8.4 (and prior versions), but I think that
+ * now it's the time to eliminate this known issue:
+ *
+ * Before Tk 8.5, the widget used the string “chars” to refer to index positions
+ * (which included characters, embedded windows and embedded images). As of Tk 8.5
+ * the text widget deals separately and correctly with “chars” and “indices”. For
+ * backwards compatibility, however, the index modifiers “+N chars” and “-N chars”
+ * continue to refer to indices. One must use any of the full forms “+N any chars”
+ * or “-N any chars” etc. to refer to actual character indices. This confusion may
+ * be fixed in a future release by making the widget correctly interpret “+N chars”
+ * as a synonym for “+N any chars”.
+ *
+ * This confusion is fixed now, we will interpret "+N chars" as a synonym for
+ * “+N any chars”.
+ */
+
+ type = COUNT_CHARS;
}
if (*string == '+') {
@@ -1239,7 +2779,7 @@ ForwBack(
} else {
TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type);
}
- } else if ((*units == 'i') && (strncmp(units, "indices", length) == 0)) {
+ } else if (*units == 'i' && strncmp(units, "indices", length) == 0) {
TkTextCountType type;
if (modifier == TKINDEX_DISPLAY) {
@@ -1253,7 +2793,7 @@ ForwBack(
} else {
TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type);
}
- } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) {
+ } else if (*units == 'l' && strncmp(units, "lines", length) == 0) {
if (modifier == TKINDEX_DISPLAY) {
/*
* Find the appropriate pixel offset of the current position
@@ -1266,15 +2806,19 @@ ForwBack(
* offset.
*/
- int xOffset, forward;
+ int xOffset;
+ bool forward;
- if (TkTextIsElided(textPtr, indexPtr, NULL)) {
- /*
- * Go forward to the first non-elided index.
- */
+ /*
+ * Go forward to the first non-elided index.
+ */
- TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr,
- COUNT_DISPLAY_INDICES);
+ if (TkTextIsElided(indexPtr)) {
+ TkTextSkipElidedRegion(indexPtr);
+ }
+
+ if (count == 0) {
+ return p;
}
/*
@@ -1284,43 +2828,13 @@ ForwBack(
* the case where we have "+ -3 displaylines", for example.
*/
- if ((count < 0) ^ (*string == '-')) {
- forward = 0;
- } else {
- forward = 1;
- }
-
+ forward = (count < 0) == (*string == '-');
count = abs(count);
- if (count == 0) {
- return p;
+ if (!forward) {
+ count = -count;
}
- if (forward) {
- TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, &xOffset);
- while (count-- > 0) {
- /*
- * Go to the end of the line, then forward one char/byte
- * to get to the beginning of the next line.
- */
-
- TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL);
- TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr,
- COUNT_DISPLAY_INDICES);
- }
- } else {
- TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, &xOffset);
- while (count-- > 0) {
- /*
- * Go to the beginning of the line, then backward one
- * char/byte to get to the end of the previous line.
- */
-
- TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
- TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr,
- COUNT_DISPLAY_INDICES);
- }
- TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
- }
+ TkTextFindDisplayIndex(textPtr, indexPtr, count, &xOffset);
/*
* This call assumes indexPtr is the beginning of a display line
@@ -1329,8 +2843,16 @@ ForwBack(
*/
TkTextIndexOfX(textPtr, xOffset, indexPtr);
+
+ /*
+ * We must skip any elided range.
+ */
+
+ if (TkTextIsElided(indexPtr)) {
+ TkTextSkipElidedRegion(indexPtr);
+ }
} else {
- lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
+ lineIndex = TkBTreeLinesTo(indexPtr->tree, textPtr, indexPtr->priv.linePtr, NULL);
if (*string == '+') {
lineIndex += count;
} else {
@@ -1358,8 +2880,10 @@ ForwBack(
* then pick the character at the same x-position in the new line.
*/
- TkTextMakeByteIndex(indexPtr->tree, textPtr, lineIndex,
- indexPtr->byteIndex, indexPtr);
+ if (textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
+ indexPtr->priv.byteIndex += TkTextSegToIndex(textPtr->startMarker);
+ }
+ TkTextMakeByteIndex(indexPtr->tree, textPtr, lineIndex, indexPtr->priv.byteIndex, indexPtr);
}
} else {
return NULL;
@@ -1373,11 +2897,11 @@ ForwBack(
* TkTextIndexForwBytes --
*
* Given an index for a text widget, this function creates a new index
- * that points "count" bytes ahead of the source index.
+ * that points "byteCount" bytes ahead of the source index.
*
* Results:
* *dstPtr is modified to refer to the character "count" bytes after
- * srcPtr, or to the last character in the TkText if there aren't "count"
+ * srcPtr, or to the last character in the TkText if there aren't "byteCount"
* bytes left.
*
* In this latter case, the function returns '1' to indicate that not all
@@ -1391,50 +2915,69 @@ ForwBack(
int
TkTextIndexForwBytes(
- const TkText *textPtr,
+ const TkText *textPtr, /* Overall information about text widget, can be NULL. */
const TkTextIndex *srcPtr, /* Source index. */
- int byteCount, /* How many bytes forward to move. May be
- * negative. */
+ int byteCount, /* How many bytes forward to move. May be negative. */
TkTextIndex *dstPtr) /* Destination index: gets modified. */
{
TkTextLine *linePtr;
- TkTextSegment *segPtr;
- int lineLength;
+ int byteIndex;
+
+ if (byteCount == 0) {
+ if (dstPtr != srcPtr) {
+ *dstPtr = *srcPtr;
+ }
+ return 0;
+ }
if (byteCount < 0) {
TkTextIndexBackBytes(textPtr, srcPtr, -byteCount, dstPtr);
return 0;
}
- *dstPtr = *srcPtr;
- dstPtr->byteIndex += byteCount;
- while (1) {
- /*
- * Compute the length of the current line.
- */
+ if (dstPtr != srcPtr) {
+ *dstPtr = *srcPtr;
+ }
+
+ TkTextIndexToByteIndex(dstPtr);
+ linePtr = TkTextIndexGetLine(dstPtr);
- lineLength = 0;
- for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- lineLength += segPtr->size;
+ if (textPtr) {
+ if (linePtr == TkBTreeGetLastLine(textPtr)) {
+ return 1;
}
+ if (textPtr->endMarker->sectionPtr->linePtr == linePtr) {
+ /*
+ * Special case: line contains end marker.
+ */
- /*
- * If the new index is in the same line then we're done. Otherwise go
- * on to the next line.
- */
+ int lineLength = SegToIndex(linePtr, textPtr->endMarker);
- if (dstPtr->byteIndex < lineLength) {
- return 0;
- }
- dstPtr->byteIndex -= lineLength;
- linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr);
- if (linePtr == NULL) {
- dstPtr->byteIndex = lineLength - 1;
- return 1;
+ if ((byteIndex = (dstPtr->priv.byteIndex += byteCount)) > lineLength) {
+ assert(linePtr->nextPtr);
+ TkTextIndexSetByteIndex2(dstPtr, linePtr->nextPtr, 0);
+ }
+ return byteIndex <= lineLength ? 0 : 1;
}
- dstPtr->linePtr = linePtr;
+ } else if (!linePtr->nextPtr) {
+ return 1;
+ }
+
+ if ((byteIndex = dstPtr->priv.byteIndex + byteCount) > linePtr->size) {
+ DEBUG(TkTextIndex index = *srcPtr);
+ bool rc = TkBTreeMoveForward(dstPtr, byteCount);
+ assert(!rc || TkTextIndexCountBytes(&index, dstPtr) == byteCount);
+ return rc ? 0 : 1;
}
+
+ if (byteIndex == linePtr->size) {
+ assert(linePtr->nextPtr);
+ TkTextIndexSetByteIndex2(dstPtr, linePtr->nextPtr, 0);
+ } else {
+ TkTextIndexSetByteIndex(dstPtr, byteIndex);
+ }
+
+ return 0;
}
/*
@@ -1455,143 +2998,129 @@ TkTextIndexForwBytes(
* srcPtr, or to the last character in the TkText if there aren't
* sufficient items left in the widget.
*
+ * This function returns whether the resulting index is different from
+ * source index.
+ *
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
-void
+bool
TkTextIndexForwChars(
- const TkText *textPtr, /* Overall information about text widget. */
+ const TkText *textPtr, /* Overall information about text widget, can be NULL. */
const TkTextIndex *srcPtr, /* Source index. */
- int charCount, /* How many characters forward to move. May
- * be negative. */
+ int charCount, /* How many characters forward to move. May be negative. */
TkTextIndex *dstPtr, /* Destination index: gets modified. */
TkTextCountType type) /* The type of item to count */
{
TkTextLine *linePtr;
TkTextSegment *segPtr;
- TkTextElideInfo *infoPtr = NULL;
+ TkTextSegment *endPtr;
+ TkSharedText *sharedTextPtr;
int byteOffset;
- char *start, *end, *p;
- Tcl_UniChar ch;
- int elide = 0;
- int checkElided = (type & COUNT_DISPLAY);
+ bool checkElided;
+ bool trimmed;
+ bool skipSpaces;
if (charCount < 0) {
- TkTextIndexBackChars(textPtr, srcPtr, -charCount, dstPtr, type);
- return;
+ return TkTextIndexBackChars(textPtr, srcPtr, -charCount, dstPtr, type);
}
- if (checkElided) {
- infoPtr = ckalloc(sizeof(TkTextElideInfo));
- elide = TkTextIsElided(textPtr, srcPtr, infoPtr);
- }
-
- *dstPtr = *srcPtr;
- /*
- * Find seg that contains src byteIndex. Move forward specified number of
- * chars.
- */
-
- if (checkElided) {
- /*
- * In this case we have already calculated the information we need, so
- * no need to use TkTextIndexToSeg()
- */
+ if (dstPtr != srcPtr) {
+ *dstPtr = *srcPtr;
+ }
- segPtr = infoPtr->segPtr;
- byteOffset = dstPtr->byteIndex - infoPtr->segOffset;
- } else {
- segPtr = TkTextIndexToSeg(dstPtr, &byteOffset);
+ if (TkTextIndexIsEndOfText(dstPtr)) {
+ return false;
}
- while (1) {
- /*
- * Go through each segment in line looking for specified character
- * index.
- */
+ sharedTextPtr = TkTextIndexGetShared(srcPtr);
+ checkElided = !!(type & COUNT_DISPLAY) && TkBTreeHaveElidedSegments(sharedTextPtr);
- for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
- /*
- * If we do need to pay attention to the visibility of
- * characters/indices, check that first. If the current segment
- * isn't visible, then we simply continue the loop.
- */
+ if (checkElided && TkTextIsElided(dstPtr) && !TkTextSkipElidedRegion(dstPtr)) {
+ return false;
+ }
- if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType)
- || (segPtr->typePtr == &tkTextToggleOnType))) {
- TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
+ if (charCount == 0) {
+ return false;
+ }
- /*
- * The elide state only changes if this tag is either the
- * current highest priority tag (and is therefore being
- * toggled off), or it's a new tag with higher priority.
- */
+ assert(dstPtr->priv.byteIndex <= FindEndByteIndex(dstPtr));
- if (tagPtr->elideString != NULL) {
- infoPtr->tagCnts[tagPtr->priority]++;
- if (infoPtr->tagCnts[tagPtr->priority] & 1) {
- infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
- }
+ /*
+ * Find seg that contains src byteIndex. Move forward specified number of chars.
+ */
- if (tagPtr->priority >= infoPtr->elidePriority) {
- if (segPtr->typePtr == &tkTextToggleOffType) {
- /*
- * If it is being toggled off, and it has an elide
- * string, it must actually be the current highest
- * priority tag, so this check is redundant:
- */
+ segPtr = TkTextIndexGetFirstSegment(dstPtr, &byteOffset);
+ endPtr = textPtr ? textPtr->endMarker : sharedTextPtr->endMarker;
+ TkTextIndexToByteIndex(dstPtr);
+ trimmed = textPtr && textPtr->spaceMode == TEXT_SPACEMODE_TRIM && !!(type & COUNT_DISPLAY);
+ skipSpaces = false;
- if (tagPtr->priority != infoPtr->elidePriority) {
- Tcl_Panic("Bad tag priority being toggled off");
- }
+ while (true) {
+ /*
+ * Go through each segment in line looking for specified character index.
+ */
- /*
- * Find previous elide tag, if any (if not then
- * elide will be zero, of course).
- */
-
- elide = 0;
- while (--infoPtr->elidePriority > 0) {
- if (infoPtr->tagCnts[infoPtr->elidePriority]
- & 1) {
- elide = infoPtr->tagPtrs
- [infoPtr->elidePriority]->elide;
+ for ( ; segPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->tagInfoPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ const char *start = segPtr->body.chars + byteOffset;
+ const char *end = segPtr->body.chars + segPtr->size;
+ const char *p = start;
+ int ch, n;
+
+ for (p = start; p < end; p += n) {
+ if (charCount <= 0) {
+ if (skipSpaces) {
+ while (*p == ' ') {
+ ++p;
+ }
+ if (p == end) {
break;
}
}
- } else {
- elide = tagPtr->elide;
- infoPtr->elidePriority = tagPtr->priority;
- }
- }
- }
- }
-
- if (!elide) {
- if (segPtr->typePtr == &tkTextCharType) {
- start = segPtr->body.chars + byteOffset;
- end = segPtr->body.chars + segPtr->size;
- for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) {
- if (charCount == 0) {
- dstPtr->byteIndex += (p - start);
+ dstPtr->priv.byteIndex += (p - start);
goto forwardCharDone;
}
- charCount--;
+ n = TkUtfToUniChar(p, &ch);
+ if (ch == ' ') {
+ if (!skipSpaces) {
+ skipSpaces = trimmed;
+ charCount -= 1;
+ }
+ } else {
+ skipSpaces = false;
+ charCount -= (type & COUNT_INDICES) ? n : 1;
+ }
}
} else if (type & COUNT_INDICES) {
- if (charCount < segPtr->size - byteOffset) {
- dstPtr->byteIndex += charCount;
+ assert(byteOffset == 0);
+ assert(segPtr->size <= 1);
+ if (charCount < segPtr->size) {
+ dstPtr->priv.byteIndex += charCount;
+ dstPtr->priv.segPtr = segPtr;
+ dstPtr->priv.isCharSegment = false;
goto forwardCharDone;
}
- charCount -= segPtr->size - byteOffset;
+ charCount -= segPtr->size;
}
+ dstPtr->priv.byteIndex += segPtr->size - byteOffset;
+ byteOffset = 0;
+ } else if (checkElided && segPtr->typePtr == &tkTextBranchType) {
+ TkTextIndexSetSegment(dstPtr, segPtr = segPtr->body.branch.nextPtr);
+ if (TkTextIndexRestrictToEndRange(dstPtr) >= 0) {
+ goto forwardCharDone;
+ }
+ TkTextIndexToByteIndex(dstPtr);
+ } else if (segPtr == endPtr) {
+ if (charCount > 0) {
+ TkTextIndexSetupToEndOfText(dstPtr, (TkText *) textPtr, srcPtr->tree);
+ }
+ goto forwardCharDone;
}
-
- dstPtr->byteIndex += segPtr->size - byteOffset;
- byteOffset = 0;
}
/*
@@ -1599,34 +3128,36 @@ TkTextIndexForwChars(
* one byte (for the terminal '\n' character) and return that index.
*/
- linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr);
- if (linePtr == NULL) {
- dstPtr->byteIndex -= sizeof(char);
+ if (!(linePtr = TkBTreeNextLine(textPtr, dstPtr->priv.linePtr))) {
+ TkTextIndexSetToLastChar(dstPtr);
goto forwardCharDone;
}
- dstPtr->linePtr = linePtr;
- dstPtr->byteIndex = 0;
- segPtr = dstPtr->linePtr->segPtr;
+ dstPtr->priv.linePtr = linePtr;
+ dstPtr->priv.byteIndex = 0;
+ dstPtr->priv.lineNo = -1;
+ dstPtr->priv.lineNoRel = -1;
+ segPtr = linePtr->segPtr;
}
forwardCharDone:
- if (infoPtr != NULL) {
- TkTextFreeElideInfo(infoPtr);
- ckfree(infoPtr);
- }
+ dstPtr->stateEpoch = TkBTreeEpoch(dstPtr->tree);
+ return true;
}
/*
*---------------------------------------------------------------------------
*
- * TkTextIndexCountBytes --
+ * TkTextSkipElidedRegion --
*
- * Given a pair of indices in a text widget, this function counts how
- * many bytes are between the two indices. The two indices do not need
- * to be ordered.
+ * Given an index for a text widget, this function returns an index with
+ * the position of first un-elided character, or end of text, if the first
+ * un-elided character is beyond of this text widget. This functions assumes
+ * that the text position specified with incoming index is elided.
*
* Results:
- * The number of bytes in the given range.
+ * *indexPtr is modified to refer to the first un-elided character. This
+ * functions returns 'false' iff we reach the end of the text (belonging to
+ * this widget).
*
* Side effects:
* None.
@@ -1634,39 +3165,52 @@ TkTextIndexForwChars(
*---------------------------------------------------------------------------
*/
-int
-TkTextIndexCountBytes(
- const TkText *textPtr,
- const TkTextIndex *indexPtr1, /* Index describing one location. */
- const TkTextIndex *indexPtr2) /* Index describing second location. */
+bool
+TkTextSkipElidedRegion(
+ TkTextIndex *indexPtr)
{
- int compare = TkTextIndexCmp(indexPtr1, indexPtr2);
+ TkTextSegment *segPtr;
- if (compare == 0) {
- return 0;
- } else if (compare > 0) {
- return IndexCountBytesOrdered(textPtr, indexPtr2, indexPtr1);
- } else {
- return IndexCountBytesOrdered(textPtr, indexPtr1, indexPtr2);
- }
+ assert(indexPtr->textPtr);
+ assert(TkTextIsElided(indexPtr));
+
+ segPtr = TkBTreeFindEndOfElidedRange(indexPtr->textPtr->sharedTextPtr,
+ indexPtr->textPtr, TkTextIndexGetContentSegment(indexPtr, NULL));
+ TkTextIndexSetSegment(indexPtr, segPtr);
+ return !TkTextIndexIsEndOfText(indexPtr);
}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TkTextIndexCountBytes --
+ *
+ * Given a pair of indices in a text widget, this function counts how
+ * many bytes are between the two indices. The two indices must be
+ * ordered.
+ *
+ * Results:
+ * The number of bytes in the given range.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
-static int
-IndexCountBytesOrdered(
- const TkText *textPtr,
- const TkTextIndex *indexPtr1,
- /* Index describing location of character from
- * which to count. */
- const TkTextIndex *indexPtr2)
- /* Index describing location of last character
- * at which to stop the count. */
+unsigned
+TkTextIndexCountBytes(
+ const TkTextIndex *indexPtr1, /* Index describing location of character from which to count. */
+ const TkTextIndex *indexPtr2) /* Index describing location of last character at which to
+ * stop the count. */
{
- int byteCount, offset;
- TkTextSegment *segPtr, *segPtr1;
+ int byteCount;
TkTextLine *linePtr;
- if (indexPtr1->linePtr == indexPtr2->linePtr) {
- return indexPtr2->byteIndex - indexPtr1->byteIndex;
+ assert(TkTextIndexCompare(indexPtr1, indexPtr2) <= 0);
+
+ if (indexPtr1->priv.linePtr == indexPtr2->priv.linePtr) {
+ return TkTextIndexGetByteIndex(indexPtr2) - TkTextIndexGetByteIndex(indexPtr1);
}
/*
@@ -1677,27 +3221,40 @@ IndexCountBytesOrdered(
* bytes between start of the indexPtr2 line and indexPtr2
*/
- segPtr1 = TkTextIndexToSeg(indexPtr1, &offset);
- byteCount = -offset;
- for (segPtr = segPtr1; segPtr != NULL; segPtr = segPtr->nextPtr) {
- byteCount += segPtr->size;
- }
-
- linePtr = TkBTreeNextLine(textPtr, indexPtr1->linePtr);
- while (linePtr != indexPtr2->linePtr) {
- for (segPtr = linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- byteCount += segPtr->size;
- }
- linePtr = TkBTreeNextLine(textPtr, linePtr);
- if (linePtr == NULL) {
- Tcl_Panic("TextIndexCountBytesOrdered ran out of lines");
- }
- }
+ linePtr = indexPtr1->priv.linePtr;
+ byteCount = linePtr->size - TkTextIndexGetByteIndex(indexPtr1);
+ byteCount += TkTextIndexGetByteIndex(indexPtr2);
+ byteCount += TkBTreeCountSize(indexPtr1->tree, NULL, linePtr->nextPtr, indexPtr2->priv.linePtr);
+ return byteCount;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TkTextIndexGetChar --
+ *
+ * Return the character at given index.
+ *
+ * Results:
+ * The character at given index.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
- byteCount += indexPtr2->byteIndex;
+Tcl_UniChar
+TkTextIndexGetChar(
+ const TkTextIndex *indexPtr)/* Index describing location of character. */
+{
+ TkTextSegment *segPtr;
+ int byteOffset;
+ int ch;
- return byteCount;
+ segPtr = TkTextIndexGetContentSegment(indexPtr, &byteOffset);
+ TkUtfToUniChar(segPtr->body.chars + byteOffset, &ch);
+ return ch;
}
/*
@@ -1714,6 +3271,8 @@ IndexCountBytesOrdered(
* attributes, i.e. if type is COUNT_DISPLAY_INDICES or
* COUNT_DISPLAY_CHARS.
*
+ * NOTE: here COUNT_INDICES is also counting chars in text segments.
+ *
* Results:
* The number of characters in the given range, which meet the
* appropriate 'type' attributes.
@@ -1724,177 +3283,122 @@ IndexCountBytesOrdered(
*---------------------------------------------------------------------------
*/
-int
+unsigned
TkTextIndexCount(
const TkText *textPtr, /* Overall information about text widget. */
const TkTextIndex *indexPtr1,
- /* Index describing location of character from
- * which to count. */
+ /* Index describing location of character from which to count. */
const TkTextIndex *indexPtr2,
- /* Index describing location of last character
- * at which to stop the count. */
+ /* Index describing location of last character at which to stop
+ * the count. */
TkTextCountType type) /* The kind of indices to count. */
{
- TkTextLine *linePtr1;
- TkTextSegment *segPtr, *seg2Ptr = NULL;
- TkTextElideInfo *infoPtr = NULL;
- int byteOffset, maxBytes, count = 0, elide = 0;
- int checkElided = (type & COUNT_DISPLAY);
+ TkTextLine *linePtr;
+ TkTextIndex index;
+ TkTextSegment *segPtr, *lastPtr;
+ int byteOffset, maxBytes;
+ unsigned count;
+ bool checkElided;
+
+ assert(textPtr);
+ assert(TkTextIndexCompare(indexPtr1, indexPtr2) <= 0);
+
+ checkElided = !!(type & COUNT_DISPLAY) && TkBTreeHaveElidedSegments(textPtr->sharedTextPtr);
+ index = *indexPtr1;
+
+ if (checkElided
+ && TkTextIsElided(&index)
+ && (!TkTextSkipElidedRegion(&index) || TkTextIndexCompare(&index, indexPtr2) >= 0)) {
+ return 0;
+ }
/*
- * Find seg that contains src index, and remember how many bytes not to
- * count in the given segment.
+ * Find seg that contains src index, and remember how many bytes not to count in the given segment.
*/
- segPtr = TkTextIndexToSeg(indexPtr1, &byteOffset);
- linePtr1 = indexPtr1->linePtr;
+ segPtr = TkTextIndexGetContentSegment(&index, &byteOffset);
+ lastPtr = TkTextIndexGetContentSegment(indexPtr2, &maxBytes);
+ linePtr = index.priv.linePtr;
+ count = 0;
- seg2Ptr = TkTextIndexToSeg(indexPtr2, &maxBytes);
+ if (byteOffset > 0) {
+ if (segPtr->tagInfoPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ if (type & (COUNT_INDICES|COUNT_TEXT)) {
+ count += Tcl_NumUtfChars(segPtr->body.chars + byteOffset,
+ (segPtr == lastPtr ? maxBytes : segPtr->size) - byteOffset);
+ }
+ } else if (segPtr->typePtr == &tkTextHyphenType) {
+ if (type & (COUNT_HYPHENS|COUNT_INDICES)) {
+ count += 1;
+ }
+ } else if (type & COUNT_INDICES) {
+ assert(segPtr->size == 1);
+ count += 1;
+ }
+ }
+ if (segPtr == lastPtr) {
+ return count;
+ }
+ segPtr = segPtr->nextPtr;
+ }
- if (checkElided) {
- infoPtr = ckalloc(sizeof(TkTextElideInfo));
- elide = TkTextIsElided(textPtr, indexPtr1, infoPtr);
+ if (maxBytes > 0 && (!checkElided || !TkTextSegmentIsElided(textPtr, lastPtr))) {
+ if (lastPtr->typePtr == &tkTextCharType) {
+ if (type & (COUNT_TEXT|COUNT_INDICES)) {
+ count += Tcl_NumUtfChars(lastPtr->body.chars, maxBytes);
+ }
+ } else if (lastPtr->typePtr == &tkTextHyphenType) {
+ if (type & (COUNT_HYPHENS|COUNT_INDICES)) {
+ count += 1;
+ }
+ } else if (type & COUNT_INDICES) {
+ assert(segPtr->size <= 1);
+ count += 1;
+ }
}
- while (1) {
+ while (true) {
/*
* Go through each segment in line adding up the number of characters.
*/
- for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
- /*
- * If we do need to pay attention to the visibility of
- * characters/indices, check that first. If the current segment
- * isn't visible, then we simply continue the loop.
- */
-
- if (checkElided) {
- if ((segPtr->typePtr == &tkTextToggleOffType)
- || (segPtr->typePtr == &tkTextToggleOnType)) {
- TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
-
- /*
- * The elide state only changes if this tag is either the
- * current highest priority tag (and is therefore being
- * toggled off), or it's a new tag with higher priority.
- */
-
- if (tagPtr->elideString != NULL) {
- infoPtr->tagCnts[tagPtr->priority]++;
- if (infoPtr->tagCnts[tagPtr->priority] & 1) {
- infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
- }
- if (tagPtr->priority >= infoPtr->elidePriority) {
- if (segPtr->typePtr == &tkTextToggleOffType) {
- /*
- * If it is being toggled off, and it has an
- * elide string, it must actually be the
- * current highest priority tag, so this check
- * is redundant:
- */
-
- if (tagPtr->priority!=infoPtr->elidePriority) {
- Tcl_Panic("Bad tag priority being toggled off");
- }
-
- /*
- * Find previous elide tag, if any (if not
- * then elide will be zero, of course).
- */
-
- elide = 0;
- while (--infoPtr->elidePriority > 0) {
- if (infoPtr->tagCnts[
- infoPtr->elidePriority] & 1) {
- elide = infoPtr->tagPtrs[
- infoPtr->elidePriority]->elide;
- break;
- }
- }
- } else {
- elide = tagPtr->elide;
- infoPtr->elidePriority = tagPtr->priority;
- }
- }
- }
- }
- if (elide) {
- if (segPtr == seg2Ptr) {
- goto countDone;
- }
- byteOffset = 0;
- continue;
- }
+ for ( ; segPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr == lastPtr) {
+ return count;
}
-
- if (segPtr->typePtr == &tkTextCharType) {
- int byteLen = segPtr->size - byteOffset;
- register unsigned char *str = (unsigned char *)
- segPtr->body.chars + byteOffset;
- register int i;
-
- if (segPtr == seg2Ptr) {
- if (byteLen > (maxBytes - byteOffset)) {
- byteLen = maxBytes - byteOffset;
+ if (segPtr->tagInfoPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ if (type & (COUNT_TEXT|COUNT_INDICES)) {
+ count += CountCharsInSeg(segPtr);
}
- }
- i = byteLen;
-
- /*
- * This is a speed sensitive function, so run specially over
- * the string to count continuous ascii characters before
- * resorting to the Tcl_NumUtfChars call. This is a long form
- * of:
- *
- * stringPtr->numChars =
- * Tcl_NumUtfChars(objPtr->bytes, objPtr->length);
- */
-
- while (i && (*str < 0xC0)) {
- i--;
- str++;
- }
- count += byteLen - i;
- if (i) {
- count += Tcl_NumUtfChars(segPtr->body.chars + byteOffset
- + (byteLen - i), i);
- }
- } else {
- if (type & COUNT_INDICES) {
- int byteLen = segPtr->size - byteOffset;
-
- if (segPtr == seg2Ptr) {
- if (byteLen > (maxBytes - byteOffset)) {
- byteLen = maxBytes - byteOffset;
- }
+ } else if (segPtr->typePtr == &tkTextHyphenType) {
+ if (type & (COUNT_HYPHENS|COUNT_INDICES)) {
+ count += CountCharsInSeg(segPtr);
}
- count += byteLen;
+ } else if (type & COUNT_INDICES) {
+ assert(segPtr->size == 1);
+ count += 1;
}
+ } else if (checkElided && segPtr->typePtr == &tkTextBranchType) {
+ TkTextIndexSetSegment(&index, segPtr = segPtr->body.branch.nextPtr);
+ if (TkTextIndexCompare(&index, indexPtr2) >= 0) {
+ return count;
+ }
+ linePtr = TkTextIndexGetLine(&index);
}
- if (segPtr == seg2Ptr) {
- goto countDone;
- }
- byteOffset = 0;
}
/*
- * Go to the next line. If we are at the end of the text item, back up
- * one byte (for the terminal '\n' character) and return that index.
+ * Go to the next line.
*/
- linePtr1 = TkBTreeNextLine(textPtr, linePtr1);
- if (linePtr1 == NULL) {
- Tcl_Panic("Reached end of text widget when counting characters");
- }
- segPtr = linePtr1->segPtr;
+ linePtr = TkBTreeNextLine(textPtr, linePtr);
+ assert(linePtr);
+ segPtr = linePtr->segPtr;
}
- countDone:
- if (infoPtr != NULL) {
- TkTextFreeElideInfo(infoPtr);
- ckfree(infoPtr);
- }
- return count;
+ return 0; /* never reached */
}
/*
@@ -1919,49 +3423,77 @@ TkTextIndexCount(
*---------------------------------------------------------------------------
*/
+/*
+ * NOTE: This function has external linkage, so we cannot change the return
+ * type to 'bool'.
+ */
+
int
TkTextIndexBackBytes(
- const TkText *textPtr,
+ const TkText *textPtr, /* Overall information about text widget, can be NULL. */
const TkTextIndex *srcPtr, /* Source index. */
- int byteCount, /* How many bytes backward to move. May be
- * negative. */
+ int byteCount, /* How many bytes backward to move. May be negative. */
TkTextIndex *dstPtr) /* Destination index: gets modified. */
{
- TkTextSegment *segPtr;
- int lineIndex;
+ TkTextLine *linePtr;
+ int byteIndex;
+
+ if (byteCount == 0) {
+ if (dstPtr != srcPtr) {
+ *dstPtr = *srcPtr;
+ }
+ return 0;
+ }
if (byteCount < 0) {
return TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr);
}
- *dstPtr = *srcPtr;
- dstPtr->byteIndex -= byteCount;
- lineIndex = -1;
- while (dstPtr->byteIndex < 0) {
+ if (dstPtr != srcPtr) {
+ *dstPtr = *srcPtr;
+ }
+ byteIndex = TkTextIndexGetByteIndex(dstPtr);
+ linePtr = TkTextIndexGetLine(dstPtr);
+
+ if (textPtr
+ && linePtr == textPtr->startMarker->sectionPtr->linePtr
+ && textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
/*
- * Move back one line in the text. If we run off the beginning of the
- * file then just return the first character in the text.
+ * Special case: this is the first line, and we have to consider the start marker.
*/
-
- if (lineIndex < 0) {
- lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr);
- }
- if (lineIndex == 0) {
- dstPtr->byteIndex = 0;
+
+ if ((byteIndex -= byteCount) < SegToIndex(linePtr, textPtr->startMarker)) {
+ TkTextIndexSetupToStartOfText(dstPtr, (TkText *) textPtr, textPtr->sharedTextPtr->tree);
return 1;
}
- lineIndex--;
- dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex);
- /*
- * Compute the length of the line and add that to dstPtr->charIndex.
- */
+ TkTextIndexSetByteIndex(dstPtr, byteIndex);
+ return 0;
+ }
- for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- dstPtr->byteIndex += segPtr->size;
- }
+ if (byteCount > byteIndex + 1) {
+ DEBUG(TkTextIndex index = *srcPtr);
+ bool rc = TkBTreeMoveBackward(dstPtr, byteCount);
+ assert(!rc || TkTextIndexCountBytes(dstPtr, &index) == byteCount);
+ return rc ? 0 : 1;
+ }
+
+ if ((byteIndex -= byteCount) >= 0) {
+ TkTextIndexSetByteIndex(dstPtr, byteIndex);
+ return 0;
+ }
+
+ /*
+ * Move back one line in the text. If we run off the beginning
+ * then just return the first character in the text widget.
+ */
+
+ if (!(linePtr = TkBTreePrevLine(textPtr, linePtr))) {
+ TkTextIndexSetToStartOfLine(dstPtr);
+ return 1;
}
+
+ TkTextIndexSetToLastChar2(dstPtr, linePtr);
return 0;
}
@@ -1983,201 +3515,189 @@ TkTextIndexBackBytes(
* srcPtr, or to the first index in the window if there aren't sufficient
* items earlier than srcPtr.
*
+ * This function returns whether the resulting index is different from
+ * source index.
+ *
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
-void
+bool
TkTextIndexBackChars(
- const TkText *textPtr, /* Overall information about text widget. */
+ const TkText *textPtr, /* Overall information about text widget, can be NULL. */
const TkTextIndex *srcPtr, /* Source index. */
- int charCount, /* How many characters backward to move. May
- * be negative. */
+ int charCount, /* How many characters backward to move. May be negative. */
TkTextIndex *dstPtr, /* Destination index: gets modified. */
TkTextCountType type) /* The type of item to count */
{
- TkTextSegment *segPtr, *oldPtr;
- TkTextElideInfo *infoPtr = NULL;
- int lineIndex, segSize;
- const char *p, *start, *end;
- int elide = 0;
- int checkElided = (type & COUNT_DISPLAY);
+ TkSharedText *sharedTextPtr;
+ TkTextSegment *segPtr;
+ TkTextSegment *startPtr;
+ TkTextLine *linePtr;
+ int segSize;
+ bool checkElided;
+ bool trimmed;
+ bool skipSpaces;
+ int byteIndex;
+
+ assert(textPtr || !(type & COUNT_DISPLAY));
if (charCount < 0) {
- TkTextIndexForwChars(textPtr, srcPtr, -charCount, dstPtr, type);
- return;
+ return TkTextIndexForwChars(textPtr, srcPtr, -charCount, dstPtr, type);
}
- if (checkElided) {
- infoPtr = ckalloc(sizeof(TkTextElideInfo));
- elide = TkTextIsElided(textPtr, srcPtr, infoPtr);
+
+ if (dstPtr != srcPtr) {
+ *dstPtr = *srcPtr;
}
- *dstPtr = *srcPtr;
+ if (charCount == 0) {
+ return false;
+ }
- /*
- * Find offset within seg that contains byteIndex. Move backward specified
- * number of chars.
- */
+ sharedTextPtr = TkTextIndexGetShared(srcPtr);
+ checkElided = !!(type & COUNT_DISPLAY) && TkBTreeHaveElidedSegments(textPtr->sharedTextPtr);
- lineIndex = -1;
+ if (checkElided && TkTextIsElided(dstPtr) && !TkTextSkipElidedRegion(dstPtr)) {
+ return false;
+ }
- segSize = dstPtr->byteIndex;
+ if (TkTextIndexIsStartOfLine(dstPtr) && !TkBTreePrevLine(textPtr, dstPtr->priv.linePtr)) {
+ return false;
+ }
- if (checkElided) {
- segPtr = infoPtr->segPtr;
- segSize -= infoPtr->segOffset;
- } else {
- TkTextLine *linePtr = dstPtr->linePtr;
- for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
- if (segPtr == NULL) {
- /*
- * Two logical lines merged into one display line through
- * eliding of a newline.
- */
+ if (textPtr && textPtr->endMarker != sharedTextPtr->endMarker) {
+ linePtr = TkBTreeGetLastLine(textPtr);
- linePtr = TkBTreeNextLine(NULL, linePtr);
- segPtr = linePtr->segPtr;
- }
- if (segSize <= segPtr->size) {
- break;
+ if (dstPtr->priv.linePtr == linePtr && linePtr != textPtr->endMarker->sectionPtr->linePtr) {
+ /*
+ * Special case: we are at start of last line, but this is not the end line.
+ */
+
+ if (--charCount == 0) {
+ byteIndex = TkTextSegToIndex(textPtr->endMarker);
+ dstPtr->priv.linePtr = textPtr->endMarker->sectionPtr->linePtr;
+ dstPtr->priv.segPtr = NULL;
+ dstPtr->priv.lineNo = dstPtr->priv.lineNoRel = -1;
+ goto backwardCharDone;
}
- segSize -= segPtr->size;
+ TkTextIndexSetSegment(dstPtr, textPtr->endMarker);
}
}
/*
+ * Move backward specified number of chars.
+ */
+
+ segPtr = TkTextIndexGetFirstSegment(dstPtr, &segSize);
+ startPtr = textPtr ? textPtr->startMarker : sharedTextPtr->startMarker;
+ byteIndex = TkTextIndexGetByteIndex(dstPtr);
+ dstPtr->priv.segPtr = NULL;
+
+ /*
* Now segPtr points to the segment containing the starting index.
*/
- while (1) {
+ trimmed = textPtr && textPtr->spaceMode == TEXT_SPACEMODE_TRIM && !!(type & COUNT_DISPLAY);
+ skipSpaces = false;
+
+ while (true) {
/*
* If we do need to pay attention to the visibility of
* characters/indices, check that first. If the current segment isn't
* visible, then we simply continue the loop.
*/
- if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType)
- || (segPtr->typePtr == &tkTextToggleOnType))) {
- TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
-
- /*
- * The elide state only changes if this tag is either the current
- * highest priority tag (and is therefore being toggled off), or
- * it's a new tag with higher priority.
- */
-
- if (tagPtr->elideString != NULL) {
- infoPtr->tagCnts[tagPtr->priority]++;
- if (infoPtr->tagCnts[tagPtr->priority] & 1) {
- infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
- }
- if (tagPtr->priority >= infoPtr->elidePriority) {
- if (segPtr->typePtr == &tkTextToggleOnType) {
- /*
- * If it is being toggled on, and it has an elide
- * string, it must actually be the current highest
- * priority tag, so this check is redundant:
- */
-
- if (tagPtr->priority != infoPtr->elidePriority) {
- Tcl_Panic("Bad tag priority being toggled on");
- }
+ if (segPtr->tagInfoPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ const char *start = segPtr->body.chars;
+ const char *end = segPtr->body.chars + segSize;
+ const char *p = end;
- /*
- * Find previous elide tag, if any (if not then elide
- * will be zero, of course).
- */
+ while (true) {
+ const char *q;
- elide = 0;
- while (--infoPtr->elidePriority > 0) {
- if (infoPtr->tagCnts[infoPtr->elidePriority] & 1) {
- elide = infoPtr->tagPtrs[
- infoPtr->elidePriority]->elide;
+ if (charCount <= 0) {
+ if (skipSpaces) {
+ while (p > start && p[-1] == ' ') {
+ --p;
+ }
+ if (p == start) {
break;
}
}
- } else {
- elide = tagPtr->elide;
- infoPtr->elidePriority = tagPtr->priority;
- }
- }
- }
- }
-
- if (!elide) {
- if (segPtr->typePtr == &tkTextCharType) {
- start = segPtr->body.chars;
- end = segPtr->body.chars + segSize;
- for (p = end; ; p = Tcl_UtfPrev(p, start)) {
- if (charCount == 0) {
- dstPtr->byteIndex -= (end - p);
+ byteIndex -= (end - p);
goto backwardCharDone;
}
if (p == start) {
break;
}
- charCount--;
- }
- } else {
- if (type & COUNT_INDICES) {
- if (charCount <= segSize) {
- dstPtr->byteIndex -= charCount;
- goto backwardCharDone;
+ p = Tcl_UtfPrev(q = p, start);
+ if (*p == ' ') {
+ if (!skipSpaces) {
+ skipSpaces = trimmed;
+ charCount -= 1;
+ }
+ } else {
+ skipSpaces = false;
+ charCount -= (type & COUNT_INDICES) ? q - p : 1;
}
- charCount -= segSize;
}
+ } else if (type & COUNT_INDICES) {
+ assert(segPtr->size <= 1);
+ if (charCount <= segSize) {
+ byteIndex -= charCount;
+ dstPtr->priv.segPtr = segPtr;
+ dstPtr->priv.isCharSegment = false;
+ goto backwardCharDone;
+ }
+ charCount -= segSize;
+ }
+ } else if (checkElided && segPtr->typePtr == &tkTextLinkType) {
+ TkTextIndexSetSegment(dstPtr, segPtr = segPtr->body.link.prevPtr);
+ dstPtr->priv.segPtr = segPtr;
+ dstPtr->priv.isCharSegment = false;
+ if (TkTextIndexRestrictToStartRange(dstPtr) <= 0) {
+ dstPtr->stateEpoch = TkBTreeEpoch(dstPtr->tree);
+ return true;
}
+ TkTextIndexToByteIndex(dstPtr);
+ byteIndex = TkTextIndexGetByteIndex(dstPtr);
+ } else if (segPtr == startPtr) {
+ TkTextIndexSetSegment(dstPtr, segPtr = startPtr);
+ byteIndex = TkTextIndexGetByteIndex(dstPtr);
+ goto backwardCharDone;
}
- dstPtr->byteIndex -= segSize;
/*
* Move back into previous segment.
*/
- oldPtr = segPtr;
- segPtr = dstPtr->linePtr->segPtr;
- if (segPtr != oldPtr) {
- for ( ; segPtr->nextPtr != oldPtr; segPtr = segPtr->nextPtr) {
- /* Empty body. */
- }
- segSize = segPtr->size;
- continue;
- }
+ if (!(segPtr = segPtr->prevPtr)) {
+ /*
+ * Move back to previous line
+ */
- /*
- * Move back to previous line.
- */
+ linePtr = TkBTreePrevLine(textPtr, dstPtr->priv.linePtr);
+ assert(linePtr);
- if (lineIndex < 0) {
- lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr);
- }
- if (lineIndex == 0) {
- dstPtr->byteIndex = 0;
- goto backwardCharDone;
+ dstPtr->priv.linePtr = linePtr;
+ dstPtr->priv.lineNo = -1;
+ dstPtr->priv.lineNoRel = -1;
+ byteIndex = linePtr->size;
+ segPtr = linePtr->lastPtr;
+ } else {
+ byteIndex -= segSize;
}
- lineIndex--;
- dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex);
- /*
- * Compute the length of the line and add that to dstPtr->byteIndex.
- */
-
- oldPtr = dstPtr->linePtr->segPtr;
- for (segPtr = oldPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
- dstPtr->byteIndex += segPtr->size;
- oldPtr = segPtr;
- }
- segPtr = oldPtr;
segSize = segPtr->size;
}
backwardCharDone:
- if (infoPtr != NULL) {
- TkTextFreeElideInfo(infoPtr);
- ckfree(infoPtr);
- }
+ dstPtr->stateEpoch = TkBTreeEpoch(dstPtr->tree);
+ dstPtr->priv.byteIndex = byteIndex;
+ return true;
}
/*
@@ -2203,83 +3723,72 @@ TkTextIndexBackChars(
static const char *
StartEnd(
TkText *textPtr, /* Information about text widget. */
- const char *string, /* String to parse for additional info about
- * modifier (count and units). Points to first
- * character of modifier word. */
+ const char *string, /* String to parse for additional info about modifier (count and units).
+ * Points to first character of modifier word. */
TkTextIndex *indexPtr) /* Index to modify based on string. */
{
const char *p;
size_t length;
- register TkTextSegment *segPtr;
+ TkTextSegment *segPtr;
int modifier;
+ int mode;
+
+ assert(textPtr);
/*
* Find the end of the modifier word.
*/
- for (p = string; isalnum(UCHAR(*p)); p++) {
+ for (p = string; isalnum(*p); ++p) {
/* Empty loop body. */
}
+ length = p - string;
- length = p-string;
- if ((*string == 'd') &&
- (strncmp(string, "display", (length > 7 ? 7 : length)) == 0)) {
+ if (*string == 'd' && strncmp(string, "display", MIN(length, 7)) == 0) {
modifier = TKINDEX_DISPLAY;
+ mode = COUNT_DISPLAY_INDICES;
if (length > 7) {
p -= (length - 7);
}
- } else if ((*string == 'a') &&
- (strncmp(string, "any", (length > 3 ? 3 : length)) == 0)) {
- modifier = TKINDEX_ANY;
+ } else if (*string == 'a' && strncmp(string, "any", MIN(length, 3)) == 0) {
+ modifier = TKINDEX_CHAR;
+ mode = COUNT_CHARS;
if (length > 3) {
p -= (length - 3);
}
} else {
modifier = TKINDEX_NONE;
+ mode = COUNT_INDICES;
}
/*
- * If we had a modifier, which we interpreted ok, so now forward to the
- * actual units.
+ * If we had a modifier, which we interpreted ok, so now forward to the actual units.
*/
if (modifier != TKINDEX_NONE) {
- while (isspace(UCHAR(*p))) {
- p++;
+ while (isspace(*p)) {
+ ++p;
}
string = p;
- while ((*p!='\0') && !isspace(UCHAR(*p)) && (*p!='+') && (*p!='-')) {
- p++;
+ while (*p && !isspace(*p) && *p != '+' && *p != '-') {
+ ++p;
}
length = p - string;
}
- if ((*string == 'l') && (strncmp(string, "lineend", length) == 0)
- && (length >= 5)) {
+ if (*string == 'l' && strncmp(string, "lineend", length) == 0 && length >= 5) {
if (modifier == TKINDEX_DISPLAY) {
- TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL);
+ TkTextFindDisplayLineStartEnd(textPtr, indexPtr, DISP_LINE_END);
} else {
- indexPtr->byteIndex = 0;
- for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- indexPtr->byteIndex += segPtr->size;
- }
-
- /*
- * We know '\n' is encoded with a single byte index.
- */
-
- indexPtr->byteIndex -= sizeof(char);
+ TkTextIndexSetToLastChar(indexPtr);
}
- } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0)
- && (length >= 5)) {
+ } else if (*string == 'l' && strncmp(string, "linestart", length) == 0 && length >= 5) {
if (modifier == TKINDEX_DISPLAY) {
- TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
+ TkTextFindDisplayLineStartEnd(textPtr, indexPtr, DISP_LINE_START);
} else {
- indexPtr->byteIndex = 0;
+ TkTextIndexSetToStartOfLine(indexPtr);
}
- } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0)
- && (length >= 5)) {
+ } else if (*string == 'w' && strncmp(string, "wordend", length) == 0 && length >= 5) {
int firstChar = 1;
int offset;
@@ -2290,11 +3799,10 @@ StartEnd(
*/
if (modifier == TKINDEX_DISPLAY) {
- TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr,
- COUNT_DISPLAY_INDICES);
+ TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES);
}
- segPtr = TkTextIndexToSeg(indexPtr, &offset);
- while (1) {
+ segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
+ while (true) {
int chSize = 1;
if (segPtr->typePtr == &tkTextCharType) {
@@ -2307,28 +3815,26 @@ StartEnd(
firstChar = 0;
}
offset += chSize;
- indexPtr->byteIndex += chSize;
+ indexPtr->priv.byteIndex += chSize;
if (offset >= segPtr->size) {
- segPtr = TkTextIndexToSeg(indexPtr, &offset);
+ do {
+ segPtr = segPtr->nextPtr;
+ } while (segPtr->size == 0);
+ offset = 0;
}
}
if (firstChar) {
- if (modifier == TKINDEX_DISPLAY) {
- TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr,
- COUNT_DISPLAY_INDICES);
- } else {
- TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr,
- COUNT_INDICES);
- }
+ TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, mode);
}
- } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0)
- && (length >= 5)) {
+ } else if (*string == 'w' && strncmp(string, "wordstart", length) == 0 && length >= 5) {
int firstChar = 1;
int offset;
if (modifier == TKINDEX_DISPLAY) {
- TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr,
- COUNT_DISPLAY_INDICES);
+ /*
+ * Skip elided region.
+ */
+ TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES);
}
/*
@@ -2338,58 +3844,49 @@ StartEnd(
* position.
*/
- segPtr = TkTextIndexToSeg(indexPtr, &offset);
- while (1) {
+ segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
+ while (true) {
int chSize = 1;
if (segPtr->typePtr == &tkTextCharType) {
-
int ch;
+
TkUtfToUniChar(segPtr->body.chars + offset, &ch);
if (!Tcl_UniCharIsWordChar(ch)) {
break;
}
if (offset > 0) {
- chSize = (segPtr->body.chars + offset
- - Tcl_UtfPrev(segPtr->body.chars + offset,
- segPtr->body.chars));
+ const char *prevPtr = Tcl_UtfPrev(segPtr->body.chars + offset, segPtr->body.chars);
+ chSize = segPtr->body.chars + offset - prevPtr;
}
firstChar = 0;
}
if (offset == 0) {
- if (modifier == TKINDEX_DISPLAY) {
- TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr,
- COUNT_DISPLAY_INDICES);
- } else {
- TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr,
- COUNT_INDICES);
- }
- } else {
- indexPtr->byteIndex -= chSize;
- }
- offset -= chSize;
- if (offset < 0) {
- if (indexPtr->byteIndex == 0) {
- goto done;
+ TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr, mode);
+ segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
+
+ if (offset < chSize && indexPtr->priv.byteIndex == 0) {
+ return p;
}
- segPtr = TkTextIndexToSeg(indexPtr, &offset);
+ } else if ((indexPtr->priv.byteIndex -= chSize) == 0) {
+ return p;
+ } else if ((offset -= chSize) < 0) {
+ assert(indexPtr->priv.byteIndex > 0);
+ do {
+ segPtr = segPtr->prevPtr;
+ assert(segPtr);
+ } while (segPtr->size == 0);
+ offset = 0;
}
}
if (!firstChar) {
- if (modifier == TKINDEX_DISPLAY) {
- TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr,
- COUNT_DISPLAY_INDICES);
- } else {
- TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr,
- COUNT_INDICES);
- }
+ TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, mode);
}
} else {
- return NULL;
+ p = NULL;
}
- done:
return p;
}
@@ -2399,4 +3896,5 @@ StartEnd(
* c-basic-offset: 4
* fill-column: 78
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/generic/tkTextLineBreak.c b/generic/tkTextLineBreak.c
new file mode 100644
index 0000000..aae2f8e
--- /dev/null
+++ b/generic/tkTextLineBreak.c
@@ -0,0 +1,988 @@
+/*
+ * tkTextLineBreak.c --
+ *
+ * This module provides line break computation for line wrapping.
+ * It uses the library "libunibreak" (from Wu Yongwei) for the
+ * computation, but only if available (currently only UNIX), and if
+ * the language support is enabled, otherwise our own line break
+ * algorithm is used (it's a simplified version of the recommendation
+ * at http://www.unicode.org/reports/tr14/tr14-26.html).
+ *
+ * The alternative is the use of ICU library (http://site.icu-project.org/),
+ * instead of libunibreak, but this would require to support a very
+ * complex interface of a dynamic load library, with other words, we
+ * would need dozens of functions pointers. This is not really a drawback,
+ * and probably the ICU library is the better choice, but I think that a
+ * change to the ICU library is reasonable only if the Tcl/Tk developer team
+ * is deciding to use this library also for complete Unicode support (character
+ * conversion, for instance).
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkText.h"
+
+#include <ctype.h>
+#include <assert.h>
+
+#ifndef MAX
+# define MAX(a,b) (((int) a) < ((int) b) ? b : a)
+#endif
+
+
+typedef void (*ComputeBreakLocationsFunc)(
+ const unsigned char *text, size_t len, const char *lang, char *brks);
+
+static void ComputeBreakLocations(
+ const unsigned char *text, size_t len, const char *lang, char *brks);
+
+static ComputeBreakLocationsFunc libLinebreakFunc = ComputeBreakLocations;
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GetLineBreakFunc --
+ *
+ * Return the appropriate line break function. If argument 'lang'
+ * is NULL, then our own line break alorithm will be used (fast,
+ * but a bit simple). If 'lang' is not NULL, then this function
+ * tries to load the library "libunibreak" (currently only UNIX).
+ * If the load succeeds, then set_linebreaks_utf8 will be returned,
+ * otherwise ComputeBreakLocations will be returned.
+ *
+ * Note that "libunibreak" has language specific support, but
+ * currently only for zh, ja, and ko. Nethertheless any non-NULL
+ * value for 'lang' tries to use this library.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The "libunibreak" library may be loaded, if available.
+ *
+ *----------------------------------------------------------------------
+ */
+
+#ifdef __UNIX__
+
+static int
+LoadFile(
+ Tcl_Interp *interp,
+ Tcl_Obj *pathPtr,
+ Tcl_LoadHandle *handle,
+ char const **symbols,
+ void **funcs)
+{
+ /* Keep backward compatibility to 8.5 */
+# if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5
+ return Tcl_FSLoadFile(interp, pathPtr, symbols[0], symbols[1],
+ (void *) &funcs[0], (void *) &funcs[1], handle, NULL);
+# else
+ return Tcl_LoadFile(interp, pathPtr, symbols, TCL_LOAD_GLOBAL, funcs, handle);
+# endif
+}
+
+static void
+LoadLibUnibreak(
+ Tcl_Interp *interp)
+{
+ typedef void *VoidP;
+ typedef void (*InitFunc)();
+
+ static char const *Symbols[3] = {
+ "init_linebreak",
+ "set_linebreaks_utf8",
+ NULL
+ };
+
+ VoidP Funcs[sizeof(Symbols)/sizeof(Symbols[0])];
+ Tcl_LoadHandle handle;
+ Tcl_Obj *pathPtr = Tcl_NewStringObj("libunibreak.so.1", -1);
+ bool rc;
+
+ Tcl_IncrRefCount(pathPtr);
+ rc = LoadFile(interp, pathPtr, &handle, Symbols, Funcs);
+ if (rc != TCL_OK) {
+ /*
+ * We couldn't find "libunibreak.so.1", so try the predecessor "liblinebreak.so.2".
+ */
+
+ Tcl_ResetResult(interp);
+ Tcl_DecrRefCount(pathPtr);
+ pathPtr = Tcl_NewStringObj("liblinebreak.so.2", -1);
+ rc = LoadFile(interp, pathPtr, &handle, Symbols, Funcs);
+ }
+ if (rc == TCL_OK) {
+ ((InitFunc) Funcs[0])();
+ libLinebreakFunc = Funcs[1];
+ } else {
+ Tcl_ResetResult(interp);
+ }
+ Tcl_DecrRefCount(pathPtr);
+}
+
+#endif /* __UNIX__ */
+
+static ComputeBreakLocationsFunc
+GetLineBreakFunc(
+ Tcl_Interp *interp,
+ char const *lang)
+{
+#ifdef __UNIX__
+ if (lang) {
+ static bool loaded = false;
+
+ if (!loaded) {
+ LoadLibUnibreak(interp);
+ }
+ }
+#endif
+ return libLinebreakFunc;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextComputeBreakLocations --
+ *
+ * Compute break locations in UTF-8 text. This function expects
+ * a nul-terminated string (this mean that the character at position
+ * 'len' must be NUL). Thus it is also required that the break buffer
+ * 'brks' has at least size 'len+1'. If 'lang' is not NULL, then the
+ * external library linunibreak will be used for the line break
+ * computation, but only if this library is loadable, otherwise the
+ * internal algorithm will be used.
+ *
+ * Results:
+ * The computed break locations. This function returns 'true' if
+ * the external linebreak library has been used for the computation,
+ * otherwise 'false' will be returned.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextComputeBreakLocations(
+ Tcl_Interp *interp,
+ const char *text, /* must be nul-terminated */
+ unsigned len, /* without trailing nul byte */
+ const char *lang, /* can be NULL */
+ char *brks)
+{
+ ComputeBreakLocationsFunc func;
+ int lastBreakablePos = -1;
+ unsigned i;
+
+ assert(text);
+ assert(brks);
+ assert(text[len] == '\0');
+ assert(!lang || (isalpha(lang[0]) && isalpha(lang[1]) && !lang[2]));
+
+ func = GetLineBreakFunc(interp, lang);
+
+ /*
+ * The algorithm don't give us a break value for the last character if we do
+ * not include the final nul char into the computation.
+ */
+
+ len += 1;
+ (*func)((const unsigned char *) text, len, lang, brks);
+ len -= 1;
+
+ for (i = 0; i < len; ++i) {
+ switch (brks[i]) {
+ case LINEBREAK_MUSTBREAK:
+ break;
+ case LINEBREAK_ALLOWBREAK:
+ if (text[i] == '-') {
+ if (brks[i] == LINEBREAK_ALLOWBREAK) {
+ /*
+ * Fix the problem with the contextual hyphen-minus sign, the implementation of
+ * libunibreak has (possibly) forgotten this case.
+ *
+ * The hyphen-minus (U+002D) needs special context treatment. For simplicity we
+ * will only check whether we have two preceding, and two succeeding letters.
+ * TODO: Is there a better method for the decision?
+ */
+
+ const char *r = text + i;
+ const char *p, *q, *s;
+ Tcl_UniChar uc;
+ bool allow = false;
+
+ q = Tcl_UtfPrev(r, text);
+ if (q != r) {
+ Tcl_UtfToUniChar(q, &uc);
+ if (Tcl_UniCharIsAlpha(uc)) {
+ p = Tcl_UtfPrev(q, text);
+ if (p != q) {
+ Tcl_UtfToUniChar(p, &uc);
+ if (Tcl_UniCharIsAlpha(uc)) {
+ s = r + 1;
+ s += Tcl_UtfToUniChar(s, &uc);
+ if (Tcl_UniCharIsAlpha(uc)) {
+ Tcl_UtfToUniChar(s, &uc);
+ if (Tcl_UniCharIsAlpha(uc)) {
+ allow = true;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (!allow) {
+ brks[i] = LINEBREAK_NOBREAK;
+ }
+ }
+ } else if (text[i] == '/' && i > 8) {
+ /*
+ * Ignore the breaking chance if there is a chance immediately before:
+ * no break inside "c/o", and no break after "http://" in a long line
+ * (a suggestion from Wu Yongwei).
+ */
+
+ if (lastBreakablePos >= i - 2
+ || (i > 40 && lastBreakablePos >= i - 7 && text[i - 1] == '/')) {
+ continue;
+ }
+
+ /*
+ * Special rule to treat Unix paths more nicely (a suggestion from Wu Yongwei).
+ */
+
+ if (i < len - 1 && text[i + 1] != ' ' && text[i - 1] == ' ') {
+ lastBreakablePos = i - 1;
+ continue;
+ }
+ }
+ lastBreakablePos = i;
+ break;
+ case LINEBREAK_INSIDEACHAR:
+ break;
+ }
+ }
+
+ return func != ComputeBreakLocations;
+}
+
+/*
+ * The following is implementing the recommendations at
+ * http://www.unicode.org/reports/tr14/tr14-26.html, but simplified -
+ * no language specific support, not all the rules (especially no
+ * combining marks), and mostly restricted to Latin-1 and relevant
+ * letters not belonging to specific languages. For a more sophisticated
+ * line break algorithm the library "libunibreak" (from Wu Yongwei)
+ * should be used.
+ */
+
+typedef enum {
+ /* Note that CR, LF, and NL will be interpreted as BK, so only BK is used. */
+ AI, AL, B2, BA, BB, BK, CL, CP, EX, GL, HY, IN, IS, NS, NU, OP, PO, PR, QU, SP, SY, WJ, ZW
+} LBClass;
+
+#define __ AI
+
+/*
+ * Changes in table below (different from Unicode recommendation):
+ *
+ * 0a: CB -> BK (LINE FEED)
+ * 0d: CR -> BK (CARRIAGE RETURN)
+ * 0e: XX -> BK (SHIFT OUT)
+ * 23: AL -> IN (NUMBER SIGN)
+ * 26: AL -> BB (AMPERSAND)
+ * 3d: AL -> GL (EQUALS SIGN)
+ * 60: CM -> AL (GRAVE ACCENT)
+ */
+
+static const char Table_0000[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, BA, BK, BK, BK, BK, BK, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ SP, EX, QU, IN, PR, PO, BB, QU, OP, CP, AL, PR, IS, HY, IS, SY, /* 20 - 2f */
+/* 3 */ NU, NU, NU, NU, NU, NU, NU, NU, NU, NU, IS, IS, AL, GL, AL, EX, /* 30 - 3f */
+/* 4 */ AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, /* 40 - 4f */
+/* 5 */ AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, OP, PR, CP, AL, AL, /* 50 - 5f */
+/* 6 */ AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, /* 60 - 6f */
+/* 7 */ AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, OP, BA, CL, AL, __, /* 70 - 7f */
+/* 8 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 80 - 8f */
+/* 9 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 90 - 9f */
+/* a */ GL, OP, PO, PR, PR, PR, AL, AL, AL, AL, __, QU, __, __, AL, AL, /* a0 - af */
+/* b */ PO, PR, AL, AL, BB, __, AL, AL, AL, AL, __, __, AL, AL, AL, OP, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+/*
+ * Changes in table below (different from Unicode recommendation):
+ *
+ * e2 80 89: BA -> WJ (THIN SPACE)
+ * e2 80 0a: BA -> WJ (HAIR SPACE)
+ */
+
+static const char Table_E280[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ BA, BA, BA, BA, BA, BA, BA, GL, BA, __, __, ZW, __, __, __, __, /* 80 - 8f */
+/* 9 */ BA, AL, BA, BA, B2, AL, AL, AL, QU, QU, OP, QU, QU, QU, OP, QU, /* 90 - 9f */
+/* a */ AL, AL, AL, AL, IN, IN, IN, BA, BK, BK, __, __, __, __, __, GL, /* a0 - af */
+/* b */ PO, PO, PO, PO, PO, PO, PO, PO, AL, QU, QU, AL, NS, NS, AL, AL, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E281[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ AL, AL, AL, AL, IS, OP, CL, NS, NS, NS, AL, AL, AL, AL, AL, AL, /* 80 - 8f */
+/* 9 */ AL, AL, __, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, __, /* 90 - 9f */
+/* a */ WJ, AL, AL, AL, AL, __, __, __, __, __, __, __, __, __, __, __, /* a0 - af */
+/* b */ __, __, __, __, __, __, __, __, __, __, __, __, __, OP, CL, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E282[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, __, __, __, __, __, __, __, __, __, __, __, __, CL, CL, __, /* 80 - 8f */
+/* 9 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 90 - 9f */
+/* a */ PR, PR, PR, PR, PR, PR, PR, PO, PR, PR, PR, PR, PR, PR, PR, PR, /* a0 - af */
+/* b */ PR, PR, PR, PR, PR, PR, PR, PR, PR, PR, PR, PR, PR, PR, PR, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E28C[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, __, __, __, __, __, __, __, OP, CL, OP, CL, __, __, __, __, /* 80 - 8f */
+/* 9 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 90 - 9f */
+/* a */ __, __, __, __, __, __, __, __, __, OP, CL, __, __, __, __, __, /* a0 - af */
+/* b */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E29D[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 80 - 8f */
+/* 9 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 90 - 9f */
+/* a */ __, __, __, __, __, __, __, __, OP, CL, OP, CL, OP, CL, OP, CL, /* a0 - af */
+/* b */ OP, CL, OP, CL, OP, CL, __, __, __, __, __, __, __, __, __, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E29F[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, __, __, __, __, OP, CL, __, __, __, __, __, __, __, __, __, /* 80 - 8f */
+/* 9 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 90 - 9f */
+/* a */ __, __, __, __, __, __, OP, CL, OP, CL, OP, CL, OP, CL, OP, CL, /* a0 - af */
+/* b */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E2A6[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, __, __, OP, CL, OP, CL, OP, CL, OP, CL, OP, CL, OP, CL, OP, /* 80 - 8f */
+/* 9 */ CL, OP, CL, OP, CL, OP, CL, OP, CL, __, __, __, __, __, __, __, /* 90 - 9f */
+/* a */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* a0 - af */
+/* b */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E2A7[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 80 - 8f */
+/* 9 */ __, __, __, __, __, __, __, __, OP, CL, OP, CL, __, __, __, __, /* 90 - 9f */
+/* a */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* a0 - af */
+/* b */ __, __, __, __, __, __, __, __, __, __, __, __, OP, CL, __, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E2B8[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ AL, AL, QU, QU, QU, QU, AL, AL, AL, QU, QU, AL, QU, QU, AL, AL, /* 80 - 8f */
+/* 9 */ AL, AL, AL, AL, AL, AL, AL, AL, OP, AL, AL, AL, QU, QU, AL, AL, /* 90 - 9f */
+/* a */ QU, QU, OP, CL, OP, CL, OP, CL, OP, CL, AL, AL, AL, AL, AL, __, /* a0 - af */
+/* b */ AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, B2, B2, AL, AL, AL, AL, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_E380[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, CL, CL, AL, __, NS, __, __, OP, CL, OP, CL, OP, CL, OP, CL, /* 80 - 8f */
+/* 9 */ OP, CL, __, __, OP, CL, OP, CL, OP, CL, OP, CL, NS, OP, CL, CL, /* 90 - 9f */
+/* a */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* a0 - af */
+/* b */ AL, __, __, __, __, __, __, __, __, __, __, NS, NS, AL, __, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_EFB8[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 80 - 8f */
+/* 9 */ IS, CL, CL, IS, IS, AL, AL, OP, CL, IN, __, __, __, __, __, __, /* 90 - 9f */
+/* a */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* a0 - af */
+/* b */ AL, AL, AL, AL, AL, OP, CL, OP, CL, OP, CL, OP, CL, OP, CL, OP, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_EFB9[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ CL, OP, CL, OP, CL, AL, AL, OP, CL, AL, AL, AL, AL, AL, AL, AL, /* 80 - 8f */
+/* 9 */ CL, CL, CL, __, NS, NS, AL, AL, B2, OP, CL, OP, CL, OP, CL, AL, /* 90 - 9f */
+/* a */ AL, AL, __, B2, __, __, __, __, AL, PR, PO, AL, __, __, __, __, /* a0 - af */
+/* b */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_EFBC[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, AL, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, EX, AL, AL, PR, PO, AL, AL, OP, CL, AL, __, CL, B2, CL, AL, /* 80 - 8f */
+/* 9 */ NU, NU, NU, NU, NU, NU, NU, NU, NU, NU, NS, NS, __, __, __, EX, /* 90 - 9f */
+/* a */ AL, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* a0 - af */
+/* b */ __, __, __, __, __, __, __, __, __, __, __, OP, AL, CL, __, __, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+static const char Table_EFBD[256] = {
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+/* 0 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 00 - 0f */
+/* 1 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 10 - 1f */
+/* 2 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 20 - 2f */
+/* 3 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 30 - 3f */
+/* 4 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 40 - 4f */
+/* 5 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 50 - 5f */
+/* 6 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 60 - 6f */
+/* 7 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 70 - 7f */
+/* 8 */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* 80 - 8f */
+/* 9 */ __, __, __, __, __, __, __, __, __, __, __, OP, __, CL, __, OP, /* 90 - 9f */
+/* a */ CL, CL, OP, CL, CL, AL, __, __, __, __, __, __, __, __, __, __, /* a0 - af */
+/* b */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, WJ, /* b0 - bf */
+/* c */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* c0 - cf */
+/* d */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* d0 - df */
+/* e */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* e0 - ef */
+/* f */ __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, /* f0 - ff */
+/* 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f */
+};
+
+#undef __
+
+#define PROHIBITED LINEBREAK_NOBREAK
+#define DIRECT LINEBREAK_ALLOWBREAK
+#define INDIRECT ((char) (~LINEBREAK_NOBREAK & ~LINEBREAK_ALLOWBREAK & 0x7f))
+
+#define X PROHIBITED /* B ^ A === B SP* × A */
+#define i INDIRECT /* B % A === B × A and B SP+ ÷ A */
+#define _ DIRECT /* B ÷ A */
+
+/* Note that BK, SP will no be used for lookup. */
+static const char BrkPairTable[23][23] = {
+/* AI AL B2 BA BB BK CL CP EX GL HY IN IS NS NU OP PO PR QU SP SY WJ ZW */
+/* AI */ { X, X, _, i, _, _, X, X, X, i, i, i, X, i, i, i, _, _, i, _, X, X, X }, /* AI */
+/* AL */ { i, i, _, i, _, _, X, X, X, i, i, i, X, i, i, i, _, _, i, _, X, X, X }, /* AL */
+/* B2 */ { _, _, _, i, _, _, X, X, X, i, i, _, X, i, _, _, _, _, i, _, X, X, X }, /* B2 */
+/* BA */ { _, _, _, i, _, _, X, X, X, i, i, _, X, i, _, _, _, _, i, _, X, X, X }, /* BA */
+/* BB */ { i, i, i, i, i, _, X, X, X, _, i, i, X, i, i, i, i, i, i, _, X, X, X }, /* BB */
+/* BK */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ }, /* BK */
+/* CL */ { _, _, _, i, _, _, X, X, X, i, i, _, X, X, _, _, i, i, i, _, X, X, X }, /* CL */
+/* CP */ { i, i, _, i, _, _, X, X, X, i, i, _, X, X, i, _, i, i, i, _, X, X, X }, /* CP */
+/* EX */ { _, _, _, i, _, _, X, X, X, i, i, _, X, i, _, _, _, _, i, _, X, X, X }, /* EX */
+/* GL */ { i, i, i, i, i, _, X, X, X, i, i, i, X, i, i, i, i, i, i, _, X, X, X }, /* GL */
+/* HY */ { _, _, _, i, _, _, X, X, X, _, i, _, X, i, i, _, _, _, i, _, X, X, X }, /* HY */
+/* IN */ { _, _, _, i, _, _, X, X, X, i, i, i, X, i, _, _, _, _, i, _, X, X, X }, /* IN */
+/* IS */ { i, i, _, i, _, _, X, X, X, i, i, _, X, i, i, _, _, _, i, _, X, X, X }, /* IS */
+/* NS */ { _, _, _, i, _, _, X, X, X, i, i, _, X, i, _, _, _, _, i, _, X, X, X }, /* NS */
+/* NU */ { i, i, _, i, _, _, X, X, X, i, i, i, X, i, i, i, i, i, i, _, X, X, X }, /* NU */
+/* OP */ { X, X, X, X, X, _, X, X, X, X, X, X, X, X, X, X, X, X, X, _, X, X, X }, /* OP */
+/* PO */ { i, i, _, i, _, _, X, X, X, i, i, _, X, i, i, i, _, _, i, _, X, X, X }, /* PO */
+/* PR */ { _, i, _, i, _, _, X, X, X, i, i, _, X, i, i, i, _, _, i, _, X, X, X }, /* PR */
+/* QU */ { i, i, i, i, i, _, X, X, X, i, i, i, X, i, i, X, i, i, i, _, X, X, X }, /* QU */
+/* SP */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _ }, /* SP */
+/* SY */ { _, _, _, i, _, _, X, X, X, i, i, _, X, i, i, _, _, _, i, _, X, X, X }, /* SY */
+/* WJ */ { i, i, i, i, i, _, X, X, X, i, i, i, X, i, i, i, i, i, i, _, X, X, X }, /* WJ */
+/* ZW */ { _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, X }, /* ZW */
+/* AI AL B2 BA BB BK CL CP EX GL HY IN IS NS NU OP PO PR QU SP SY WJ ZW */
+};
+
+#undef _
+#undef i
+#undef X
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ComputeBreakLocations --
+ *
+ * Compute break locations in UTF-8 text. This function is doing
+ * the same as set_linebreaks_utf8 (from "libunibreak"), but this
+ * function is using a simplified line break algorithm, although
+ * it is following the recommendations at
+ * http://www.unicode.org/reports/tr14/tr14-26.html.
+ *
+ * Note that this functions expects that the whole line will be
+ * parsed at once. This interface corresponds to the interface
+ * of the linebreak library. Of course, such a design is a bit
+ * unluckily.
+ *
+ * Results:
+ * The computed break locations, in 'brks'. This array must be as
+ * large as the input length (specified by 'len').
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ComputeBreakLocations(
+ const unsigned char *text,
+ size_t len,
+ const char *lang,
+ char *brks)
+{
+ size_t i;
+ size_t nbytes;
+ size_t nletters;
+ size_t brkIndex;
+ LBClass cls;
+ LBClass prevCls;
+
+ if (len == 0) {
+ return;
+ }
+
+ i = 0;
+ nletters = 0;
+ brkIndex = 0;
+ cls = BK;
+ prevCls = WJ;
+ brks[len - 1] = LINEBREAK_MUSTBREAK;
+
+ while (i < len) {
+ unsigned char ch;
+ LBClass pcls;
+
+ ch = text[i];
+
+ if (ch < 0x80) {
+ pcls = Table_0000[ch];
+ nbytes = 1;
+ } else if ((ch & 0xe0) == 0xc0) {
+ pcls = AI;
+ switch (ch) {
+ case 0xc2:
+ switch (UCHAR(text[i + 1])) {
+ case 0x85: pcls = BK; break; /* NL */
+ case 0xac: pcls = AL; break;
+ case 0xad: pcls = BA; break;
+ case 0xb1: pcls = AL; break;
+ case 0xbb: pcls = QU; break;
+ }
+ break;
+ case 0xc3:
+ case 0xc4:
+ case 0xc5:
+ case 0xc6:
+ case 0xc7:
+ case 0xc8:
+ case 0xc9:
+ ch = text[i + 1];
+ if (0x80 <= ch && ch <= 0xbf) {
+ pcls = AL;
+ }
+ break;
+ case 0xca:
+ ch = text[i + 1];
+ if (0x80 <= ch && ch <= 0xaf) {
+ pcls = AL;
+ }
+ break;
+ case 0xcb:
+ switch (UCHAR(text[i + 1])) {
+ case 0x88: /* fallthru */
+ case 0x8c: /* fallthru */
+ case 0x9f: pcls = BB; break;
+ }
+ break;
+ case 0xcd:
+ if (UCHAR(text[i + 1]) == 0x8f) {
+ pcls = GL;
+ }
+ break;
+ case 0xd7:
+ if (UCHAR(text[i + 1]) == 0x86) {
+ pcls = EX;
+ }
+ break;
+ case 0xdf:
+ if (UCHAR(text[i + 1]) == 0xb8) {
+ pcls = IS;
+ }
+ break;
+ }
+ nbytes = 2;
+ brks[i] = LINEBREAK_INSIDEACHAR;
+ } else if ((ch & 0xf0) == 0xe0) {
+ pcls = AI;
+ switch (ch) {
+ case 0xe2:
+ switch (UCHAR(text[i + 1])) {
+ case 0x80: pcls = Table_E280[UCHAR(text[i + 2])]; break;
+ case 0x81: pcls = Table_E281[UCHAR(text[i + 2])]; break;
+ case 0x82: pcls = Table_E282[UCHAR(text[i + 2])]; break;
+ case 0x8c: pcls = Table_E28C[UCHAR(text[i + 2])]; break;
+ case 0x9d: pcls = Table_E29D[UCHAR(text[i + 2])]; break;
+ case 0x9f: pcls = Table_E29F[UCHAR(text[i + 2])]; break;
+ case 0xa6: pcls = Table_E2A6[UCHAR(text[i + 2])]; break;
+ case 0xa7: pcls = Table_E2A7[UCHAR(text[i + 2])]; break;
+ case 0xb8: pcls = Table_E2B8[UCHAR(text[i + 2])]; break;
+ case 0x84:
+ switch (UCHAR(text[i + 2])) {
+ case 0x83: /* fallthru */
+ case 0x89: pcls = PO; break;
+ case 0x96: pcls = PR; break;
+ }
+ break;
+ case 0x88:
+ switch (UCHAR(text[i + 2])) {
+ case 0x92: /* fallthru */
+ case 0x93: pcls = PR; break;
+ }
+ break;
+ case 0xb9:
+ switch (UCHAR(text[i + 2])) {
+ case 0x80: pcls = B2; break;
+ case 0x81: pcls = AL; break;
+ case 0x82: pcls = OP; break;
+ }
+ break;
+ }
+ break;
+ case 0xe3:
+ if (UCHAR(text[i + 1]) == 0x80) {
+ pcls = Table_E380[UCHAR(text[i + 2])];
+ }
+ break;
+ case 0xef:
+ switch (UCHAR(text[i + 1])) {
+ case 0xb8: pcls = Table_EFB8[UCHAR(text[i + 2])]; break;
+ case 0xb9: pcls = Table_EFB9[UCHAR(text[i + 2])]; break;
+ case 0xbc: pcls = Table_EFBC[UCHAR(text[i + 2])]; break;
+ case 0xbd: pcls = Table_EFBD[UCHAR(text[i + 2])]; break;
+ case 0xb4:
+ switch (UCHAR(text[i + 2])) {
+ case 0xbe: pcls = CL; break;
+ case 0xbf: pcls = OP; break;
+ }
+ break;
+ case 0xbb:
+ if (UCHAR(text[i + 2]) == 0xbf) {
+ pcls = WJ; /* ZWNBSP (deprecated word joiner) */
+ }
+ break;
+ case 0xbf:
+ switch (UCHAR(text[i + 2])) {
+ case 0xa0: pcls = PO; break;
+ case 0xa1: /* fallthru */
+ case 0xa5: /* fallthru */
+ case 0xa6: pcls = PR; break;
+ }
+ break;
+ }
+ break;
+ }
+ nbytes = 3;
+ brks[i + 0] = LINEBREAK_INSIDEACHAR;
+ brks[i + 1] = LINEBREAK_INSIDEACHAR;
+ } else if ((ch & 0xf8) == 0xf0) {
+ pcls = AI;
+ nbytes = 4;
+ brks[i + 0] = LINEBREAK_INSIDEACHAR;
+ brks[i + 1] = LINEBREAK_INSIDEACHAR;
+ brks[i + 2] = LINEBREAK_INSIDEACHAR;
+#if TCL_UTF_MAX > 4
+ /*
+ * NOTE: For any reason newer TCL versions will allow > 4 bytes. I cannot
+ * understand this decision, this is not conform to UTF-8 standard.
+ * Moreover this decision is introducing severe compatibility problems.
+ */
+ } else if ((ch & 0xf8) == 0xf8) {
+ pcls = AI;
+ nbytes = 5;
+ brks[i + 0] = LINEBREAK_INSIDEACHAR;
+ brks[i + 1] = LINEBREAK_INSIDEACHAR;
+ brks[i + 2] = LINEBREAK_INSIDEACHAR;
+ brks[i + 3] = LINEBREAK_INSIDEACHAR;
+# if TCL_UTF_MAX > 5
+ } else if ((ch & 0xf8) == 0xfe) {
+ pcls = AI;
+ nbytes = 6;
+ brks[i + 0] = LINEBREAK_INSIDEACHAR;
+ brks[i + 1] = LINEBREAK_INSIDEACHAR;
+ brks[i + 2] = LINEBREAK_INSIDEACHAR;
+ brks[i + 3] = LINEBREAK_INSIDEACHAR;
+ brks[i + 4] = LINEBREAK_INSIDEACHAR;
+# endif /* TCL_UTF_MAX > 5 */
+#endif /* TCL_UTF_MAX > 4 */
+ } else {
+ /*
+ * This fallback is required, because ths current character conversion
+ * algorithm in Tcl library is producing overlong sequences (a violation
+ * of the UTF-8 standard). This observation has been reported to the
+ * Tcl/Tk team, but the response was ignorance.
+ */
+
+ int k;
+ const char *p = (const char *) text + i;
+
+ pcls = AI;
+ nbytes = Tcl_UtfNext(p) - p;
+ for (k = 0; k < nbytes; ++k) {
+ brks[i + k] = LINEBREAK_INSIDEACHAR;
+ }
+ }
+
+ if (i == 0) {
+ if ((cls = pcls) == SP) {
+ /* treat SP at start of input as if it followed a WJ */
+ prevCls = cls = WJ;
+ }
+ } else {
+ switch (pcls) {
+ case BK:
+ brks[i - 1] = LINEBREAK_NOBREAK;
+ brks[i] = LINEBREAK_MUSTBREAK;
+ prevCls = WJ;
+ return;
+ case SP:
+ /* handle spaces explicitly; do not update cls */
+ if (i > 0) {
+ brks[i - 1] = LINEBREAK_NOBREAK;
+ prevCls = SP;
+ } else {
+ prevCls = WJ;
+ }
+ nletters = 0;
+ break;
+ case HY: {
+ char brk = BrkPairTable[cls][HY];
+
+ /*
+ * The hyphen-minus (U+002D) needs special context treatment. For simplicity we
+ * will only check whether we have two preceding, and two succeeding letters.
+ * TODO: Is there a better method for the decision?
+ */
+
+ brks[i - 1] = LINEBREAK_NOBREAK;
+ cls = pcls;
+
+ if (brk == INDIRECT) {
+ prevCls = pcls;
+ } else {
+ prevCls = WJ;
+
+ if (brk == LINEBREAK_ALLOWBREAK && nletters >= 2) {
+ brkIndex = i - 1;
+ }
+ }
+ nletters = 0;
+ break;
+ }
+ default: {
+ char brk = BrkPairTable[cls][pcls];
+
+ if (brk == INDIRECT) {
+ brk = (prevCls == SP) ? LINEBREAK_ALLOWBREAK : LINEBREAK_NOBREAK;
+ prevCls = pcls;
+ } else {
+ prevCls = WJ;
+ }
+ brks[i - 1] = brk;
+ cls = pcls;
+
+ if (pcls == AL) {
+ nletters += 1;
+
+ if (brkIndex && nletters >= 2) {
+ brks[brkIndex] = LINEBREAK_ALLOWBREAK;
+ brkIndex = 0;
+ }
+ } else {
+ nletters = 0;
+ }
+ break;
+ }
+ }
+ }
+
+ i += nbytes;
+ }
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 105
+ * End:
+ * vi:set ts=8 sw=4:
+ */
diff --git a/generic/tkTextMark.c b/generic/tkTextMark.c
index 6a41c77..29847b4 100644
--- a/generic/tkTextMark.c
+++ b/generic/tkTextMark.c
@@ -6,6 +6,7 @@
*
* Copyright (c) 1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -14,36 +15,165 @@
#include "tkInt.h"
#include "tkText.h"
#include "tk3d.h"
+#include <inttypes.h>
+#include <assert.h>
+
+#ifndef MAX
+# define MAX(a,b) ((a) < (b) ? b : a)
+#endif
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? a : b)
+#endif
+
+#if NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
/*
- * Macro that determines the size of a mark segment:
+ * Forward references for functions defined in this file:
*/
-#define MSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \
- + sizeof(TkTextMark)))
-
+static void InsertUndisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr);
+static bool MarkDeleteProc(TkTextBTree tree, TkTextSegment *segPtr, int flags);
+static Tcl_Obj * MarkInspectProc(const TkSharedText *textPtr, const TkTextSegment *segPtr);
+static void MarkRestoreProc(TkTextSegment *segPtr);
+static void MarkCheckProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+static int MarkLayoutProc(const TkTextIndex *indexPtr,
+ TkTextSegment *segPtr, int offset, int maxX,
+ int maxChars, bool noCharsYet, TkWrapMode wrapMode,
+ TkTextSpaceMode spaceMode, TkTextDispChunk *chunkPtr);
+static int MarkFindNext(Tcl_Interp *interp, TkText *textPtr, const char *markName,
+ bool forward);
+static void ChangeGravity(TkSharedText *sharedTextPtr, TkText *textPtr,
+ TkTextSegment *markPtr, const Tk_SegType *newTypePtr,
+ TkTextUndoInfo *redoInfo);
+static struct TkTextSegment *SetMark(struct TkText *textPtr, const char *name,
+ const Tk_SegType *typePtr, struct TkTextIndex *indexPtr);
+static void UnsetMark(TkSharedText *sharedTextPtr, TkTextSegment *markPtr,
+ TkTextUndoInfo *redoInfo);
+static void ReactivateMark(TkSharedText *sharedTextPtr, TkTextSegment *markPtr);
+
+static const TkTextDispChunkProcs layoutInsertProcs = {
+ TEXT_DISP_CURSOR, /* type */
+ TkTextInsertDisplayProc, /* displayProc */
+ InsertUndisplayProc, /* undisplayProc */
+ NULL, /* measureProc */
+ NULL, /* bboxProc */
+};
/*
- * Forward references for functions defined in this file:
+ * We need some private undo/redo stuff.
*/
-static Tcl_Obj * GetMarkName(TkText *textPtr, TkTextSegment *segPtr);
-static void InsertUndisplayProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr);
-static int MarkDeleteProc(TkTextSegment *segPtr,
- TkTextLine *linePtr, int treeGone);
-static TkTextSegment * MarkCleanupProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static void MarkCheckProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static int MarkLayoutProc(TkText *textPtr, TkTextIndex *indexPtr,
- TkTextSegment *segPtr, int offset, int maxX,
- int maxChars, int noCharsYet, TkWrapMode wrapMode,
- TkTextDispChunk *chunkPtr);
-static int MarkFindNext(Tcl_Interp *interp,
- TkText *textPtr, Tcl_Obj *markName);
-static int MarkFindPrev(Tcl_Interp *interp,
- TkText *textPtr, Tcl_Obj *markName);
+static void UndoToggleGravityPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void UndoSetMarkPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void RedoSetMarkPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void UndoMoveMarkPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void UndoToggleGravityDestroy(TkSharedText *, TkTextUndoToken *, bool);
+static void UndoSetMarkDestroy(TkSharedText *, TkTextUndoToken *, bool);
+static void RedoSetMarkDestroy(TkSharedText *, TkTextUndoToken *, bool);
+static void UndoMoveMarkDestroy(TkSharedText *, TkTextUndoToken *, bool);
+static void UndoMarkGetRange(const TkSharedText *, const TkTextUndoToken *,
+ TkTextIndex *, TkTextIndex *);
+static void RedoSetMarkGetRange(const TkSharedText *, const TkTextUndoToken *,
+ TkTextIndex *, TkTextIndex *);
+static void RedoMoveMarkGetRange(const TkSharedText *, const TkTextUndoToken *,
+ TkTextIndex *, TkTextIndex *);
+static Tcl_Obj *UndoToggleGravityGetCommand(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *UndoSetMarkGetCommand(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *UndoToggleGravityInspect(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *UndoSetMarkInspect(const TkSharedText *, const TkTextUndoToken *);
+
+static const Tk_UndoType undoTokenToggleGravityType = {
+ TK_TEXT_UNDO_MARK_GRAVITY, /* action */
+ UndoToggleGravityGetCommand,/* commandProc */
+ UndoToggleGravityPerform, /* undoProc */
+ UndoToggleGravityDestroy, /* destroyProc */
+ UndoMarkGetRange, /* rangeProc */
+ UndoToggleGravityInspect /* inspectProc */
+};
+static const Tk_UndoType redoTokenToggleGravityType = {
+ TK_TEXT_REDO_MARK_GRAVITY, /* action */
+ UndoToggleGravityGetCommand,/* commandProc */
+ UndoToggleGravityPerform, /* undoProc */
+ UndoToggleGravityDestroy, /* destroyProc */
+ UndoMarkGetRange, /* rangeProc */
+ UndoToggleGravityInspect /* inspectProc */
+};
+
+static const Tk_UndoType undoTokenSetMarkType = {
+ TK_TEXT_UNDO_MARK_SET, /* action */
+ UndoSetMarkGetCommand, /* commandProc */
+ UndoSetMarkPerform, /* undoProc */
+ UndoSetMarkDestroy, /* destroyProc */
+ UndoMarkGetRange, /* rangeProc */
+ UndoSetMarkInspect /* inspectProc */
+};
+
+static const Tk_UndoType redoTokenSetMarkType = {
+ TK_TEXT_REDO_MARK_SET, /* action */
+ UndoSetMarkGetCommand, /* commandProc */
+ RedoSetMarkPerform, /* undoProc */
+ RedoSetMarkDestroy, /* destroyProc */
+ RedoSetMarkGetRange, /* rangeProc */
+ UndoSetMarkInspect /* inspectProc */
+};
+
+static const Tk_UndoType undoTokenMoveMarkType = {
+ TK_TEXT_UNDO_MARK_MOVE, /* action */
+ UndoSetMarkGetCommand, /* commandProc */
+ UndoMoveMarkPerform, /* undoProc */
+ UndoMoveMarkDestroy, /* destroyProc */
+ RedoMoveMarkGetRange, /* rangeProc */
+ UndoSetMarkInspect /* inspectProc */
+};
+
+static const Tk_UndoType redoTokenMoveMarkType = {
+ TK_TEXT_REDO_MARK_MOVE, /* action */
+ UndoSetMarkGetCommand, /* commandProc */
+ UndoMoveMarkPerform, /* undoProc */
+ UndoMoveMarkDestroy, /* destroyProc */
+ RedoMoveMarkGetRange, /* rangeProc */
+ UndoSetMarkInspect /* inspectProc */
+};
+
+typedef struct UndoTokenToggleMark {
+ const Tk_UndoType *undoType;
+ TkTextSegment *markPtr;
+} UndoTokenToggleMark;
+
+typedef struct UndoTokenToggleIndex {
+ const Tk_UndoType *undoType;
+ TkTextSegment *markPtr;
+} UndoTokenToggleIndex;
+
+/* derivation of UndoTokenToggleMark */
+typedef struct UndoTokenToggleGravity {
+ const Tk_UndoType *undoType;
+ TkTextSegment *markPtr;
+} UndoTokenToggleGravity;
+
+/* derivation of UndoTokenToggleMark */
+typedef struct UndoTokenSetMark {
+ const Tk_UndoType *undoType;
+ TkTextSegment *markPtr;
+} UndoTokenSetMark;
+
+/* derivation of UndoTokenSetMark */
+typedef struct RedoTokenSetMark {
+ const Tk_UndoType *undoType;
+ TkTextSegment *markPtr;
+ TkTextUndoIndex index;
+} RedoTokenSetMark;
+
+/* derivation of UndoTokenSetMark */
+typedef struct UndoTokenMoveMark {
+ const Tk_UndoType *undoType;
+ TkTextSegment *markPtr;
+ TkTextUndoIndex index;
+} UndoTokenMoveMark;
/*
* The following structures declare the "mark" segment types. There are
@@ -52,28 +182,337 @@ static int MarkFindPrev(Tcl_Interp *interp,
*/
const Tk_SegType tkTextRightMarkType = {
- "mark", /* name */
- 0, /* leftGravity */
- NULL, /* splitProc */
- MarkDeleteProc, /* deleteProc */
- MarkCleanupProc, /* cleanupProc */
- NULL, /* lineChangeProc */
- MarkLayoutProc, /* layoutProc */
- MarkCheckProc /* checkProc */
+ "mark", /* name */
+ SEG_GROUP_MARK, /* group */
+ GRAVITY_RIGHT, /* gravity */
+ MarkDeleteProc, /* deleteProc */
+ MarkRestoreProc, /* restoreProc */
+ MarkLayoutProc, /* layoutProc */
+ MarkCheckProc, /* checkProc */
+ MarkInspectProc /* inspectProc */
};
const Tk_SegType tkTextLeftMarkType = {
- "mark", /* name */
- 1, /* leftGravity */
- NULL, /* splitProc */
- MarkDeleteProc, /* deleteProc */
- MarkCleanupProc, /* cleanupProc */
- NULL, /* lineChangeProc */
- MarkLayoutProc, /* layoutProc */
- MarkCheckProc /* checkProc */
+ "mark", /* name */
+ SEG_GROUP_MARK, /* group */
+ GRAVITY_LEFT, /* gravity */
+ MarkDeleteProc, /* deleteProc */
+ MarkRestoreProc, /* restoreProc */
+ MarkLayoutProc, /* layoutProc */
+ MarkCheckProc, /* checkProc */
+ MarkInspectProc /* inspectProc */
};
/*
+ * Pointer to int, for some portable pointer hacks - it's guaranteed that
+ * 'uintptr_t' and 'void *' are convertible in both directions (C99 7.18.1.4).
+ */
+
+typedef union {
+ void *ptr;
+ uintptr_t flag;
+} __ptr_to_int;
+
+#define MARK_POINTER(ptr) (((__ptr_to_int *) &ptr)->flag |= (uintptr_t) 1)
+#define UNMARK_POINTER(ptr) (((__ptr_to_int *) &ptr)->flag &= ~(uintptr_t) 1)
+#define POINTER_IS_MARKED(ptr) (((__ptr_to_int *) &ptr)->flag & (uintptr_t) 1)
+
+#define IS_PRESERVED(seg) POINTER_IS_MARKED(seg->body.mark.ptr)
+#define MAKE_PRESERVED(seg) MARK_POINTER(seg->body.mark.ptr)
+
+#define GET_POINTER(ptr) ((void *) (((__ptr_to_int *) &ptr)->flag & ~(uintptr_t) 1))
+
+#define GET_NAME(seg) ((char *) GET_POINTER(seg->body.mark.ptr))
+#define GET_HPTR(seg) ((Tcl_HashEntry *) seg->body.mark.ptr)
+#define PTR_TO_INT(ptr) ((uintptr_t) ptr)
+
+#if !NDEBUG
+
+# undef GET_HPTR
+# undef GET_NAME
+
+static Tcl_HashEntry *GET_HPTR(const TkTextSegment *markPtr)
+{ assert(!IS_PRESERVED(markPtr)); return (Tcl_HashEntry *) markPtr->body.mark.ptr; }
+
+static char *GET_NAME(const TkTextSegment *markPtr)
+{ assert(IS_PRESERVED(markPtr)); return (char *) GET_POINTER(markPtr->body.mark.ptr); }
+
+#endif /* !NDEBUG */
+
+DEBUG_ALLOC(extern unsigned tkTextCountNewSegment);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroySegment);
+DEBUG_ALLOC(extern unsigned tkTextCountNewUndoToken);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroyUndoToken);
+
+/*
+ * Some functions for the undo/redo mechanism.
+ */
+
+static Tcl_Obj *
+AppendName(
+ Tcl_Obj *objPtr,
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *markPtr)
+{
+ const char *name;
+
+ if (IS_PRESERVED(markPtr)) {
+ name = GET_NAME(markPtr);
+ } else {
+ name = TkTextMarkName(sharedTextPtr, NULL, markPtr);
+ }
+ assert(name);
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(name, -1));
+ return objPtr;
+}
+
+static Tcl_Obj *
+UndoToggleGravityGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("mark", -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("gravity", -1));
+ return objPtr;
+}
+
+static Tcl_Obj *
+UndoToggleGravityInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenToggleGravity *token = (const UndoTokenToggleGravity *) item;
+ return AppendName(UndoToggleGravityGetCommand(sharedTextPtr, item), sharedTextPtr, token->markPtr);
+}
+
+static void
+UndoToggleGravityPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ UndoTokenToggleGravity *token = (UndoTokenToggleGravity *) undoInfo->token;
+ const Tk_SegType *newTypePtr;
+ const Tk_SegType *oldTypePtr;
+
+ assert(!token->markPtr->body.mark.changePtr);
+
+ oldTypePtr = token->markPtr->typePtr;
+ newTypePtr = (oldTypePtr == &tkTextRightMarkType) ? &tkTextLeftMarkType : &tkTextRightMarkType;
+ ChangeGravity(sharedTextPtr, NULL, token->markPtr, newTypePtr, NULL);
+
+ if (redoInfo) {
+ redoInfo->token = undoInfo->token;
+ redoInfo->token->undoType = isRedo ? &undoTokenToggleGravityType : &redoTokenToggleGravityType;
+ }
+}
+
+static void
+UndoToggleGravityDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *item,
+ bool reused)
+{
+ assert(!((UndoTokenToggleGravity *) item)->markPtr->body.mark.changePtr);
+
+ if (!reused) {
+ UndoTokenToggleGravity *token = (UndoTokenToggleGravity *) item;
+ MarkDeleteProc(sharedTextPtr->tree, token->markPtr, DELETE_MARKS);
+ }
+}
+
+static void
+UndoMoveMarkPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ UndoTokenMoveMark *token = (UndoTokenMoveMark *) undoInfo->token;
+ TkTextUndoIndex index = token->index;
+
+ assert(!token->markPtr->body.mark.changePtr);
+
+ if (redoInfo) {
+ TkBTreeMakeUndoIndex(sharedTextPtr, token->markPtr, &index);
+ token->index = index;
+ redoInfo->token = undoInfo->token;
+ redoInfo->token->undoType = isRedo ? &undoTokenMoveMarkType : &redoTokenMoveMarkType;
+ }
+
+ TkBTreeUnlinkSegment(sharedTextPtr, token->markPtr);
+ TkBTreeReInsertSegment(sharedTextPtr, &index, token->markPtr);
+}
+
+static void
+UndoMoveMarkDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *item,
+ bool reused)
+{
+ assert(!((UndoTokenMoveMark *) item)->markPtr->body.mark.changePtr);
+
+ if (!reused) {
+ UndoTokenMoveMark *token = (UndoTokenMoveMark *) item;
+ MarkDeleteProc(sharedTextPtr->tree, token->markPtr, DELETE_MARKS);
+ }
+}
+
+static Tcl_Obj *
+UndoSetMarkGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenSetMark *token = (const UndoTokenSetMark *) item;
+ const char *operation = POINTER_IS_MARKED(token->markPtr) ? "unset" : "set";
+ Tcl_Obj *objPtr = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("mark", -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(operation, -1));
+ return objPtr;
+}
+
+static Tcl_Obj *
+UndoSetMarkInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenSetMark *token = (const UndoTokenSetMark *) item;
+ const TkTextSegment *markPtr = GET_POINTER(token->markPtr);
+ Tcl_Obj *objPtr = UndoSetMarkGetCommand(sharedTextPtr, item);
+
+ objPtr = AppendName(objPtr, sharedTextPtr, markPtr);
+
+ if (!POINTER_IS_MARKED(token->markPtr)) {
+ const char *gravity = (markPtr->typePtr == &tkTextLeftMarkType) ? "left" : "right";
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(gravity, -1));
+ }
+
+ return objPtr;
+}
+
+static void
+UndoSetMarkPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ const UndoTokenSetMark *token = (const UndoTokenSetMark *) undoInfo->token;
+ TkTextSegment *markPtr = GET_POINTER(token->markPtr);
+
+ assert(!markPtr->body.mark.changePtr);
+ UnsetMark(sharedTextPtr, markPtr, redoInfo);
+ if (redoInfo && !isRedo) {
+ UNMARK_POINTER(((RedoTokenSetMark *) redoInfo->token)->markPtr);
+ }
+}
+
+static void
+UndoSetMarkDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *item,
+ bool reused)
+{
+ UndoTokenSetMark *token = (UndoTokenSetMark *) item;
+ TkTextSegment *markPtr = GET_POINTER(token->markPtr);
+
+ assert(!reused);
+ assert(!markPtr->body.mark.changePtr);
+
+ MarkDeleteProc(sharedTextPtr->tree, markPtr, DELETE_PRESERVE);
+}
+
+static void
+RedoSetMarkPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ RedoTokenSetMark *token = (RedoTokenSetMark *) undoInfo->token;
+ TkTextSegment *markPtr = GET_POINTER(token->markPtr);
+
+ assert(!markPtr->body.mark.changePtr);
+ assert(TkTextIsNormalMark(markPtr));
+
+ if (IS_PRESERVED(markPtr)) {
+ ReactivateMark(sharedTextPtr, markPtr);
+ }
+
+ TkBTreeReInsertSegment(sharedTextPtr, &token->index, markPtr);
+ markPtr->refCount += 1;
+
+ if (redoInfo) {
+ UndoTokenSetMark *redoToken;
+
+ redoToken = malloc(sizeof(UndoTokenSetMark));
+ redoToken->markPtr = token->markPtr;
+ redoToken->undoType = &undoTokenSetMarkType;
+ redoInfo->token = (TkTextUndoToken *) redoToken;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ markPtr->refCount += 1;
+ }
+}
+
+static void
+RedoSetMarkDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *item,
+ bool reused)
+{
+ RedoTokenSetMark *token = (RedoTokenSetMark *) item;
+ TkTextSegment *markPtr = GET_POINTER(token->markPtr);
+
+ assert(!reused);
+ assert(!markPtr->body.mark.changePtr);
+ MarkDeleteProc(sharedTextPtr->tree, markPtr, DELETE_MARKS);
+}
+
+static void
+UndoMarkGetRange(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item,
+ TkTextIndex *startIndex,
+ TkTextIndex *endIndex)
+{
+ const UndoTokenToggleMark *token = (UndoTokenToggleMark *) item;
+
+ TkTextIndexClear2(startIndex, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(startIndex, GET_POINTER(token->markPtr));
+ *endIndex = *startIndex;
+}
+
+static void
+RedoSetMarkGetRange(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item,
+ TkTextIndex *startIndex,
+ TkTextIndex *endIndex)
+{
+ RedoTokenSetMark *token = (RedoTokenSetMark *) item;
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->index, startIndex);
+ *endIndex = *startIndex;
+}
+
+static void
+RedoMoveMarkGetRange(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item,
+ TkTextIndex *startIndex,
+ TkTextIndex *endIndex)
+{
+ UndoTokenMoveMark *token = (UndoTokenMoveMark *) item;
+ TkTextSegment *markPtr = GET_POINTER(token->markPtr);
+
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->index, startIndex);
+ TkTextIndexClear2(endIndex, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(endIndex, markPtr);
+}
+
+/*
*--------------------------------------------------------------
*
* TkTextMarkCmd --
@@ -91,14 +530,20 @@ const Tk_SegType tkTextLeftMarkType = {
*--------------------------------------------------------------
*/
+static int
+SetResultNoMarkNamed(Tcl_Interp *interp, const char *name) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("there is no mark named \"%s\"", name));
+ Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_MARK", name, NULL);
+ return TCL_ERROR;
+}
+
int
TkTextMarkCmd(
- register TkText *textPtr, /* Information about text widget. */
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "mark". */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "mark". */
{
Tcl_HashEntry *hPtr;
TkTextSegment *markPtr;
@@ -107,25 +552,108 @@ TkTextMarkCmd(
const Tk_SegType *newTypePtr;
int optionIndex;
static const char *const markOptionStrings[] = {
- "gravity", "names", "next", "previous", "set", "unset", NULL
+ "compare", "exists", "generate", "gravity", "names", "next", "previous",
+ "set", "unset", NULL
};
enum markOptions {
- MARK_GRAVITY, MARK_NAMES, MARK_NEXT, MARK_PREVIOUS, MARK_SET,
- MARK_UNSET
+ MARK_COMPARE, MARK_EXISTS, MARK_GENERATE, MARK_GRAVITY, MARK_NAMES, MARK_NEXT, MARK_PREVIOUS,
+ MARK_SET, MARK_UNSET
};
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
+ Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?");
return TCL_ERROR;
}
+
if (Tcl_GetIndexFromObjStruct(interp, objv[2], markOptionStrings,
sizeof(char *), "mark option", 0, &optionIndex) != TCL_OK) {
return TCL_ERROR;
}
switch ((enum markOptions) optionIndex) {
+ case MARK_COMPARE: {
+ TkTextSegment *markPtr1, *markPtr2;
+ int relation, value;
+
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "markName1 op markName2");
+ return TCL_ERROR;
+ }
+ if (!(markPtr1 = TkTextFindMark(textPtr, Tcl_GetString(objv[2])))) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad comparison operand \"%s\": "
+ "must be an existing mark", Tcl_GetString(objv[2])));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_COMPARISON", NULL);
+ return TCL_ERROR;
+ }
+ if (!(markPtr2 = TkTextFindMark(textPtr, Tcl_GetString(objv[4])))) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad comparison operand \"%s\": "
+ "must be an existing mark", Tcl_GetString(objv[4])));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_COMPARISON", NULL);
+ return TCL_ERROR;
+ }
+
+ if (markPtr1 == markPtr2) {
+ relation = 0;
+ } else {
+ TkTextIndex index1, index2;
+
+ TkTextIndexClear(&index1, textPtr);
+ TkTextIndexClear(&index2, textPtr);
+ TkTextIndexSetSegment(&index1, markPtr1);
+ TkTextIndexSetSegment(&index2, markPtr2);
+ relation = TkTextIndexCompare(&index1, &index2);
+
+ if (relation == 0) {
+ TkTextSegment *segPtr = markPtr1->nextPtr;
+
+ while (segPtr && segPtr != markPtr2 && segPtr->size == 0) {
+ segPtr = segPtr->nextPtr;
+ }
+ relation = (segPtr == markPtr2) ? -1 : +1;
+ }
+ }
+
+ value = TkTextTestRelation(interp, relation, Tcl_GetString(objv[3]));
+ if (value == -1) {
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
+ break;
+ }
+ case MARK_EXISTS: {
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "markName");
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(!!TkTextFindMark(textPtr, Tcl_GetString(objv[3]))));
+ break;
+ }
+ case MARK_GENERATE: {
+ TkTextSegment *markPtr;
+ TkTextIndex index;
+ char uniqName[100];
+
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetSegment(&index, textPtr->startMarker);
+ /* ensure fixed length (depending on pointer size) */
+ snprintf(uniqName, sizeof(uniqName),
+#ifdef TCL_WIDE_INT_IS_LONG
+ "##ID##0x%016"PRIx64"##0x%016"PRIx64"##%08u##", /* we're on a real 64-bit system */
+ (uint64_t) textPtr, (uint64_t) textPtr->sharedTextPtr, ++textPtr->uniqueIdCounter);
+#else /* ifndef TCL_WIDE_INT_IS_LONG */
+ "##ID##0x%08"PRIx32"##0x%08"PRIx32"##%08u##", /* we're on a 32-bit system */
+ (uint32_t) textPtr, (uint32_t) textPtr->sharedTextPtr, ++textPtr->uniqueIdCounter);
+#endif /* TCL_WIDE_INT_IS_LONG */
+ assert(!TkTextFindMark(textPtr, uniqName));
+ markPtr = TkTextMakeMark(textPtr, uniqName);
+ markPtr->privateMarkFlag = true;
+ textPtr->sharedTextPtr->numMarks -= 1; /* take back counting */
+ textPtr->sharedTextPtr->numPrivateMarks += 1;
+ TkBTreeLinkSegment(textPtr->sharedTextPtr, markPtr, &index);
+ Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(uniqName, -1));
+ break;
+ }
case MARK_GRAVITY: {
- char c;
int length;
const char *str;
@@ -134,38 +662,26 @@ TkTextMarkCmd(
return TCL_ERROR;
}
str = Tcl_GetStringFromObj(objv[3], &length);
- if (length == 6 && !strcmp(str, "insert")) {
+ if (strcmp(str, "insert") == 0) {
markPtr = textPtr->insertMarkPtr;
- } else if (length == 7 && !strcmp(str, "current")) {
+ } else if (strcmp(str, "current") == 0) {
markPtr = textPtr->currentMarkPtr;
} else {
- hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, str);
- if (hPtr == NULL) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "there is no mark named \"%s\"", str));
- Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_MARK", str,
- NULL);
- return TCL_ERROR;
+ if (!(hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, str))) {
+ return SetResultNoMarkNamed(interp, Tcl_GetString(objv[3]));
}
markPtr = Tcl_GetHashValue(hPtr);
}
if (objc == 4) {
const char *typeStr;
-
- if (markPtr->typePtr == &tkTextRightMarkType) {
- typeStr = "right";
- } else {
- typeStr = "left";
- }
+ typeStr = markPtr->typePtr == &tkTextRightMarkType ? "right" : "left";
Tcl_SetObjResult(interp, Tcl_NewStringObj(typeStr, -1));
return TCL_OK;
}
str = Tcl_GetStringFromObj(objv[4],&length);
- c = str[0];
- if ((c == 'l') && (strncmp(str, "left", (unsigned) length) == 0)) {
+ if (strncmp(str, "left", length) == 0) {
newTypePtr = &tkTextLeftMarkType;
- } else if ((c == 'r') &&
- (strncmp(str, "right", (unsigned) length) == 0)) {
+ } else if (strncmp(str, "right", length) == 0) {
newTypePtr = &tkTextRightMarkType;
} else {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
@@ -173,10 +689,21 @@ TkTextMarkCmd(
Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_GRAVITY", NULL);
return TCL_ERROR;
}
- TkTextMarkSegToIndex(textPtr, markPtr, &index);
- TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
- markPtr->typePtr = newTypePtr;
- TkBTreeLinkSegment(markPtr, &index);
+ /*
+ * We have to force the re-insertion of the mark when steadyMarks is not enabled.
+ */
+
+ if (markPtr->typePtr != newTypePtr || !textPtr->sharedTextPtr->steadyMarks) {
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr = NULL;
+
+ if (textPtr->sharedTextPtr->steadyMarks
+ && TkTextIsNormalMark(markPtr)
+ && !TkTextUndoUndoStackIsFull(textPtr->sharedTextPtr->undoStack)) {
+ undoInfoPtr = &undoInfo;
+ }
+ ChangeGravity(textPtr->sharedTextPtr, textPtr, markPtr, newTypePtr, undoInfoPtr);
+ }
break;
}
case MARK_NAMES: {
@@ -186,17 +713,22 @@ TkTextMarkCmd(
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
+
resultObj = Tcl_NewObj();
- Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
- "insert", -1));
- Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
- "current", -1));
- for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->markTable,
- &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
- Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr),
- -1));
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj("insert", -1));
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj("current", -1));
+
+ for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->markTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
+ TkTextSegment *markPtr = Tcl_GetHashValue(hPtr);
+
+ if (!markPtr->privateMarkFlag && !markPtr->startEndMarkFlag) {
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
+ Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr), -1));
+ }
}
+
Tcl_SetObjResult(interp, resultObj);
break;
}
@@ -205,43 +737,102 @@ TkTextMarkCmd(
Tcl_WrongNumArgs(interp, 3, objv, "index");
return TCL_ERROR;
}
- return MarkFindNext(interp, textPtr, objv[3]);
+ return MarkFindNext(interp, textPtr, Tcl_GetString(objv[3]), true);
case MARK_PREVIOUS:
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "index");
return TCL_ERROR;
}
- return MarkFindPrev(interp, textPtr, objv[3]);
- case MARK_SET:
- if (objc != 5) {
- Tcl_WrongNumArgs(interp, 3, objv, "markName index");
+ return MarkFindNext(interp, textPtr, Tcl_GetString(objv[3]), false);
+ case MARK_SET: {
+ const Tk_SegType *typePtr = NULL;
+ const char *name;
+
+ if (objc != 5 && objc != 6) {
+ Tcl_WrongNumArgs(interp, 3, objv, "markName index ?direction?");
return TCL_ERROR;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[4], &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[4], &index)) {
return TCL_ERROR;
}
- TkTextSetMark(textPtr, Tcl_GetString(objv[3]), &index);
- return TCL_OK;
+ if (objc == 6) {
+ const char *direction = Tcl_GetString(objv[5]);
+
+ if (strcmp(direction, "left") == 0) {
+ typePtr = &tkTextLeftMarkType;
+ } else if (strcmp(direction, "right") == 0) {
+ typePtr = &tkTextRightMarkType;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad mark gravity \"%s\": must be left or right", direction));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_GRAVITY", NULL);
+ return TCL_ERROR;
+ }
+ }
+
+ name = Tcl_GetString(objv[3]);
+
+#if BEGIN_DOES_NOT_BELONG_TO_BASE
+
+ if (*name == 'b' && strcmp(name, "begin") == 0) {
+ static bool printWarning = true;
+
+ if (printWarning) {
+ fprintf(stderr, "\"begin\" is a reserved index identifier and shouldn't "
+ "be used for mark names anymore.\n");
+ printWarning = false;
+ }
+ }
+
+#else /* if !BEGIN_DOES_NOT_BELONG_TO_BASE */
+
+ /*
+ * TODO:
+ * Probably we should print a warning if the mark name is matching any of the
+ * following forms:
+ * - "begin"|"end"
+ * - <integer> "." (<integer>|"begin"|"end")
+ * - "@" (<integer>|"first"|"last") "," (<integer>|"first"|"last")
+ * - "##ID##" .*
+ */
+
+#endif /* BEGIN_DOES_NOT_BELONG_TO_BASE */
+
+ if (!SetMark(textPtr, name, typePtr, &index)) {
+ Tcl_Obj *msgPtr;
+
+ if (strcmp(name, "insert") == 0) {
+ return TCL_OK; /* the "watch" command did destroy the widget */
+ }
+ msgPtr = Tcl_ObjPrintf("\"%s\" is an expired generated mark", name);
+ Tcl_SetObjResult(interp, msgPtr);
+ Tcl_SetErrorCode(interp, "TK", "SET", "TEXT_MARK", name, NULL);
+ return TCL_ERROR;
+ }
+ break;
+ }
case MARK_UNSET: {
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr = NULL;
int i;
- for (i = 3; i < objc; i++) {
- hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable,
- Tcl_GetString(objv[i]));
- if (hPtr != NULL) {
- markPtr = Tcl_GetHashValue(hPtr);
-
- /*
- * Special case not needed with peer widgets.
- */
+ if (textPtr->sharedTextPtr->steadyMarks
+ && !TkTextUndoUndoStackIsFull(textPtr->sharedTextPtr->undoStack)) {
+ undoInfoPtr = &undoInfo;
+ }
- if ((markPtr == textPtr->insertMarkPtr)
- || (markPtr == textPtr->currentMarkPtr)) {
- continue;
+ for (i = 3; i < objc; i++) {
+ if ((hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, Tcl_GetString(objv[i])))) {
+ TkTextSegment *markPtr = Tcl_GetHashValue(hPtr);
+
+ if (TkTextIsPrivateMark(markPtr)) {
+ UnsetMark(textPtr->sharedTextPtr, markPtr, NULL);
+ } else if (!TkTextIsSpecialMark(markPtr)) {
+ UnsetMark(textPtr->sharedTextPtr, markPtr, undoInfoPtr);
+ if (undoInfoPtr && undoInfo.token) {
+ TkTextPushUndoToken(textPtr->sharedTextPtr, undoInfo.token, 0);
+ }
}
- TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
- Tcl_DeleteHashEntry(hPtr);
- ckfree(markPtr);
}
}
break;
@@ -253,11 +844,731 @@ TkTextMarkCmd(
/*
*----------------------------------------------------------------------
*
- * TkTextSetMark --
+ * TkTextFindMark --
+ *
+ * Return mark segment of given name, if exisiting.
+ *
+ * Results:
+ * The mark with this name, or NULL if not existing.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+TkTextSegment *
+TkTextFindMark(
+ const TkText *textPtr,
+ const char *name)
+{
+ Tcl_HashEntry *hPtr;
+
+ assert(textPtr);
+ assert(name);
+
+ switch (name[0]) {
+ case 'i':
+ if (strcmp(name, "insert") == 0) {
+ return textPtr->insertMarkPtr;
+ }
+ break;
+ case 'c':
+ if (strcmp(name, "current") == 0) {
+ return textPtr->currentMarkPtr;
+ }
+ break;
+ }
+ hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name);
+ return hPtr ? Tcl_GetHashValue(hPtr) : NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ReactivateMark --
+ *
+ * Reactivate a preserved mark.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Allocates some memory for hash table entry, and release memory
+ * of preserved name.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ReactivateMark(
+ TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ TkTextSegment *markPtr) /* Reactivate this mark. */
+{
+ Tcl_HashEntry *hPtr;
+ char *name;
+ bool isNew;
+
+ assert(IS_PRESERVED(markPtr));
+
+ name = GET_NAME(markPtr);
+ hPtr = Tcl_CreateHashEntry(&sharedTextPtr->markTable, name, (int *) &isNew);
+ assert(isNew);
+ free(name);
+ Tcl_SetHashValue(hPtr, markPtr);
+ markPtr->body.mark.ptr = PTR_TO_INT(hPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextFreeMarks --
+ *
+ * Free all used marks, also the hash table for marks will be
+ * destroyed. But do not free private marks if 'retainPrivateMarks'
+ * is true, in this case a new hash table will be built, only
+ * with the remaining private marks.
+ *
+ * Results:
+ * If 'retainPrivateMarks' is false, then return NULL. Otherwise
+ * the chain of retained private marks will be returned.
+ *
+ * Side effects:
+ * Free some memory, the old hash table for marks will be destroyed,
+ * and a new one will be created if 'retainPrivateMarks' is true.
+ *
+ *----------------------------------------------------------------------
+ */
+
+TkTextSegment *
+TkTextFreeMarks(
+ TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ bool retainPrivateMarks) /* Priate marks will be retained? */
+{
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&sharedTextPtr->markTable, &search);
+ TkTextSegment *chainPtr = NULL;
+
+ for ( ; hPtr; hPtr = Tcl_NextHashEntry(&search)) {
+ TkTextSegment *markPtr = Tcl_GetHashValue(hPtr);
+
+ if (!retainPrivateMarks || !TkTextIsPrivateMark(markPtr)) {
+ MarkDeleteProc(sharedTextPtr->tree, markPtr, DELETE_CLEANUP);
+ } else {
+ const char *name = Tcl_GetHashKey(&sharedTextPtr->markTable, hPtr);
+ char *dup;
+
+ MarkDeleteProc(sharedTextPtr->tree, markPtr, 0);
+ markPtr->nextPtr = NULL;
+ markPtr->prevPtr = NULL;
+ markPtr->sectionPtr = NULL;
+ markPtr->nextPtr = chainPtr;
+ dup = malloc(strlen(name) + 1);
+ markPtr->body.mark.ptr = PTR_TO_INT(strcpy(dup, name));
+ MAKE_PRESERVED(markPtr);
+ chainPtr = markPtr;
+ }
+ }
+
+ Tcl_DeleteHashTable(&sharedTextPtr->markTable);
+ sharedTextPtr->numMarks = 0;
+
+ if (retainPrivateMarks) {
+ TkTextSegment *markPtr;
+
+ Tcl_InitHashTable(&sharedTextPtr->markTable, TCL_STRING_KEYS);
+
+ for (markPtr = chainPtr; markPtr; markPtr = markPtr->nextPtr) {
+ ReactivateMark(sharedTextPtr, markPtr);
+ markPtr->refCount += 1;
+ }
+ } else {
+ sharedTextPtr->numPrivateMarks = 0;
+ }
+
+ return chainPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUpdateCurrentMark --
+ *
+ * If a position change of the "current" mark has been postponed
+ * we will do now the update.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The "current" mark will change the position.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextUpdateCurrentMark(
+ TkSharedText *sharedTextPtr) /* Shared text resource. */
+{
+ TkText *tPtr;
+
+ assert(sharedTextPtr->haveToSetCurrentMark);
+
+ sharedTextPtr->haveToSetCurrentMark = false;
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr->haveToSetCurrentMark) {
+ tPtr->haveToSetCurrentMark = false;
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->currentMarkPtr);
+ TkBTreeLinkSegment(sharedTextPtr, tPtr->currentMarkPtr, &tPtr->currentMarkIndex);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextMakeStartEndMark --
+ *
+ * Make (allocate) a new start/end mark.
+ *
+ * Results:
+ * The return value is a pointer to the new mark.
+ *
+ * Side effects:
+ * A new mark is created.
+ *
+ *----------------------------------------------------------------------
+ */
+
+TkTextSegment *
+TkTextMakeStartEndMark(
+ TkText *textPtr, /* can be NULL */
+ Tk_SegType const *typePtr)
+{
+ TkTextSegment *markPtr = TkTextMakeMark(NULL, NULL);
+
+ assert(typePtr == &tkTextLeftMarkType || typePtr == &tkTextRightMarkType);
+
+ markPtr->typePtr = typePtr;
+ markPtr->startEndMarkFlag = true;
+ markPtr->privateMarkFlag = true;
+ markPtr->body.mark.textPtr = textPtr;
+ return markPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextMakeMark --
+ *
+ * Make (allocate) a new mark, the gravity default to right.
+ *
+ * Results:
+ * The return value is a pointer to the new mark.
+ *
+ * Side effects:
+ * A new mark is created.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextSegment *
+MakeMark(
+ TkText *textPtr) /* Text widget in which to create mark. */
+{
+ TkTextSegment *markPtr;
+
+ markPtr = memset(malloc(SEG_SIZE(TkTextMark)), 0, SEG_SIZE(TkTextMark));
+ markPtr->typePtr = &tkTextRightMarkType;
+ markPtr->refCount = 1;
+ markPtr->body.mark.textPtr = textPtr;
+ DEBUG_ALLOC(tkTextCountNewSegment++);
+ return markPtr;
+}
+
+TkTextSegment *
+TkTextMakeMark(
+ TkText *textPtr, /* Text widget in which to create mark, can be NULL. */
+ const char *name) /* Name of this mark, can be NULL. */
+{
+ TkTextSegment *markPtr;
+ Tcl_HashEntry *hPtr;
+ bool isNew;
+
+ assert(!name || textPtr);
+ assert(!name || strcmp(name, "insert") != 0);
+ assert(!name || strcmp(name, "current") != 0);
+
+ if (!name) {
+ return MakeMark(textPtr);
+ }
+
+ hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->markTable, name, (int *) &isNew);
+
+ if (isNew) {
+ markPtr = MakeMark(textPtr);
+ markPtr->body.mark.ptr = PTR_TO_INT(hPtr);
+ Tcl_SetHashValue(hPtr, markPtr);
+ textPtr->sharedTextPtr->numMarks += 1;
+ } else {
+ markPtr = Tcl_GetHashValue(hPtr);
+ }
+
+ return markPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextMakeNewMark --
+ *
+ * Make (allocate) a new mark, the gravity default to right. This
+ * function will return NULL if the mark name already exists.
+ *
+ * Results:
+ * The return value is a pointer to the new mark, and will be NULL
+ * if the mark already exists.
+ *
+ * Side effects:
+ * A new mark is created.
+ *
+ *----------------------------------------------------------------------
+ */
+
+TkTextSegment *
+TkTextMakeNewMark(
+ TkText *textPtr, /* Text widget in which to create mark. */
+ const char *name) /* Name of this mark. */
+{
+ TkTextSegment *markPtr;
+ Tcl_HashEntry *hPtr;
+ bool isNew;
+
+ assert(textPtr);
+ assert(name);
+
+ hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->markTable, name, (int *) &isNew);
+
+ if (!isNew) {
+ return NULL;
+ }
+
+ markPtr = MakeMark(textPtr);
+ markPtr->body.mark.ptr = PTR_TO_INT(hPtr);
+ Tcl_SetHashValue(hPtr, markPtr);
+ textPtr->sharedTextPtr->numMarks += 1;
+
+ return markPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ChangeGravity --
+ *
+ * Change the gravity of a given mark.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Reset the type pointer of the mark, and set the undo information.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static TkTextMarkChange *
+MakeChangeItem(
+ TkSharedText *sharedTextPtr,
+ TkTextSegment *markPtr)
+{
+ TkTextMarkChange *changePtr = markPtr->body.mark.changePtr;
+
+ assert(TkTextIsNormalMark(markPtr));
+
+ if (!changePtr) {
+ if (sharedTextPtr->undoMarkListCount == sharedTextPtr->undoMarkListSize) {
+ sharedTextPtr->undoMarkListSize = MAX(20, 2*sharedTextPtr->undoMarkListSize);
+ sharedTextPtr->undoMarkList = realloc(sharedTextPtr->undoMarkList,
+ sharedTextPtr->undoMarkListSize * sizeof(sharedTextPtr->undoMarkList[0]));
+ }
+ changePtr = &sharedTextPtr->undoMarkList[sharedTextPtr->undoMarkListCount++];
+ memset(changePtr, 0, sizeof(*changePtr));
+ markPtr->body.mark.changePtr = changePtr;
+ (changePtr->markPtr = markPtr)->refCount += 1;
+ }
+
+ return changePtr;
+}
+
+static TkTextUndoToken *
+MakeUndoToggleGravity(
+ TkSharedText *sharedTextPtr,
+ TkTextSegment *markPtr,
+ const Tk_SegType *oldTypePtr)
+{
+ assert(TkTextIsNormalMark(markPtr));
+
+ sharedTextPtr->undoStackEvent = true;
+
+ if (!markPtr->body.mark.changePtr
+ || (!markPtr->body.mark.changePtr->setMark
+ && !markPtr->body.mark.changePtr->toggleGravity)) {
+ TkTextMarkChange *changePtr = MakeChangeItem(sharedTextPtr, markPtr);
+ UndoTokenToggleGravity *token;
+
+ token = memset(malloc(sizeof(UndoTokenToggleGravity)), 0, sizeof(UndoTokenToggleGravity));
+ token->undoType = &undoTokenToggleGravityType;
+ (token->markPtr = markPtr)->refCount += 1;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ changePtr->toggleGravity = (TkTextUndoToken *) token;
+ changePtr->savedMarkType = oldTypePtr;
+ sharedTextPtr->lastUndoTokenType = -1;
+ return (TkTextUndoToken *) token;
+ }
+
+ return NULL;
+}
+
+static void
+ChangeGravity(
+ TkSharedText *sharedTextPtr, /* Shared text resource. */
+ TkText *textPtr, /* The text widget, can be NULL. */
+ TkTextSegment *markPtr, /* Change toggle of this mark. */
+ const Tk_SegType *newTypePtr, /* This is the new toggle type. */
+ TkTextUndoInfo *undoInfo) /* Undo information, can be NULL */
+{
+ const Tk_SegType *oldTypePtr;
+ bool isNormalMark;
+
+ assert(markPtr);
+ assert(markPtr->typePtr->group == SEG_GROUP_MARK);
+ assert(sharedTextPtr);
+ assert(!undoInfo || TkTextIsNormalMark(markPtr));
+
+ oldTypePtr = markPtr->typePtr;
+ markPtr->typePtr = newTypePtr;
+ isNormalMark = TkTextIsNormalMark(markPtr);
+
+ if (!sharedTextPtr->steadyMarks) {
+ if (!textPtr || markPtr != textPtr->insertMarkPtr) {
+ /*
+ * We must re-insert the mark, the old rules of gravity may force
+ * a shuffle of the existing marks.
+ */
+
+ TkTextIndex index;
+
+ if (textPtr) {
+ TkTextIndexClear(&index, textPtr);
+ } else {
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ }
+ TkTextIndexSetSegment(&index, markPtr);
+ TkTextIndexToByteIndex(&index);
+ TkBTreeUnlinkSegment(sharedTextPtr, markPtr);
+ TkBTreeLinkSegment(sharedTextPtr, markPtr, &index);
+ }
+
+ if (isNormalMark) {
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ }
+ }
+
+ if (undoInfo && isNormalMark) {
+ undoInfo->token = MakeUndoToggleGravity(sharedTextPtr, markPtr, oldTypePtr);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * UnsetMark --
+ *
+ * Unset given mark.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Free some memory, and add a token to the undo/redo stack.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+UnsetMark(
+ TkSharedText *sharedTextPtr,
+ TkTextSegment *markPtr,
+ TkTextUndoInfo *redoInfo)
+{
+ int flags = DELETE_CLEANUP;
+
+ assert(markPtr);
+ assert(markPtr->typePtr->group == SEG_GROUP_MARK);
+ assert(!TkTextIsSpecialMark(markPtr));
+ assert(!TkTextIsPrivateMark(markPtr) || !redoInfo);
+
+ if (redoInfo) {
+ RedoTokenSetMark *token;
+ TkTextMarkChange *changePtr;
+
+ if ((changePtr = markPtr->body.mark.changePtr)) {
+ if (changePtr->toggleGravity) {
+ TkTextUndoPushItem(sharedTextPtr->undoStack, changePtr->toggleGravity, 0);
+ changePtr->toggleGravity = NULL;
+ }
+ if (changePtr->moveMark) {
+ free(changePtr->moveMark);
+ changePtr->moveMark = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ assert(markPtr->refCount > 1);
+ markPtr->refCount -= 1;
+ }
+ if (changePtr->setMark) {
+ free(changePtr->setMark);
+ changePtr->setMark = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ assert(markPtr->refCount > 1);
+ markPtr->refCount -= 1;
+ }
+ }
+
+ memset(redoInfo, 0, sizeof(*redoInfo));
+ token = malloc(sizeof(RedoTokenSetMark));
+ token->undoType = &redoTokenSetMarkType;
+ markPtr->refCount += 1;
+ token->markPtr = markPtr;
+ MARK_POINTER(token->markPtr);
+ TkBTreeMakeUndoIndex(sharedTextPtr, markPtr, &token->index);
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ redoInfo->token = (TkTextUndoToken *) token;
+ redoInfo->byteSize = 0;
+ flags = DELETE_PRESERVE;
+ }
+
+ sharedTextPtr->undoStackEvent = true;
+ sharedTextPtr->lastUndoTokenType = -1;
+ TkBTreeUnlinkSegment(sharedTextPtr, markPtr);
+ MarkDeleteProc(sharedTextPtr->tree, markPtr, flags);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TriggerWatchCursor --
+ *
+ * Trigger the watch command for movements of the insert cursor.
+ *
+ * Results:
+ * Returns 'false' if the referenced widget has been destroyed, otherwise
+ * 'true' will be returned.
+ *
+ * Side effects:
+ * It might happen that the receiver of the "watch" command is destroying the widget.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static bool
+TriggerWatchCursor(
+ TkText *textPtr,
+ const TkTextIndex *oldCursorIndexPtr,
+ const TkTextIndex *newCursorIndexPtr) /* NULL is allowed. */
+{
+ TkTextIndex index, newIndex;
+ char idx[2][TK_POS_CHARS];
+ TkTextTag *tagPtr;
+ TkTextTag *tagArrayBuffer[32];
+ TkTextTag **tagArrayPtr;
+ unsigned tagArraySize;
+ unsigned numTags, i;
+ Tcl_DString buf;
+ bool rc;
+
+ assert(oldCursorIndexPtr);
+ assert(!TkTextIndexIsEmpty(oldCursorIndexPtr));
+ assert(!newCursorIndexPtr || !TkTextIndexIsEmpty(newCursorIndexPtr));
+
+ if (!newCursorIndexPtr) {
+ TkTextIndexClear(&newIndex, textPtr);
+ TkTextIndexSetSegment(&newIndex, textPtr->insertMarkPtr);
+ newCursorIndexPtr = &newIndex;
+ }
+
+ if (TkTextIndexIsEqual(oldCursorIndexPtr, newCursorIndexPtr)) {
+ return true;
+ }
+
+ Tcl_DStringInit(&buf);
+ if (TkTextIndexIsEmpty(oldCursorIndexPtr)) {
+ idx[0][0] = '\0';
+ } else {
+ TkTextPrintIndex(textPtr, oldCursorIndexPtr, idx[0]);
+ }
+ TkTextPrintIndex(textPtr, newCursorIndexPtr, idx[1]);
+ if (textPtr->insertMarkPtr->typePtr == &tkTextLeftMarkType) {
+ index = *newCursorIndexPtr;
+ } else {
+ TkTextIndexBackChars(textPtr, newCursorIndexPtr, 1, &index, COUNT_INDICES);
+ }
+
+ numTags = 0;
+ tagArrayPtr = tagArrayBuffer;
+ tagArraySize = sizeof(tagArrayBuffer)/sizeof(tagArrayBuffer[0]);
+ tagPtr = TkBTreeGetTags(&index);
+ for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
+ if (numTags == tagArraySize) {
+ tagArraySize *= 2;
+ tagArrayPtr = realloc(tagArrayPtr == tagArrayBuffer ? NULL : tagArrayPtr, tagArraySize);
+ }
+ tagArrayPtr[numTags++] = tagPtr;
+ }
+ TkTextSortTags(numTags, tagArrayPtr);
+ for (i = 0; i < numTags; ++i) {
+ Tcl_DStringAppendElement(&buf, tagArrayPtr[i]->name);
+ }
+ if (tagArrayPtr != tagArrayBuffer) {
+ free(tagArrayPtr);
+ }
+
+ rc = TkTextTriggerWatchCmd(textPtr, "cursor", idx[0], idx[1], Tcl_DStringValue(&buf), false);
+ Tcl_DStringFree(&buf);
+ return rc;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextReleaseUndoMarkTokens --
+ *
+ * Release retained undo tokens for mark operations.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Free some memory.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextReleaseUndoMarkTokens(
+ TkSharedText *sharedTextPtr,
+ TkTextMarkChange *changePtr)
+{
+ assert(sharedTextPtr);
+ assert(changePtr);
+
+ if (!changePtr->markPtr) {
+ return; /* already released */
+ }
+
+ assert(changePtr->markPtr->body.mark.changePtr);
+
+ if (changePtr->toggleGravity) {
+ free(changePtr->toggleGravity);
+ changePtr->toggleGravity = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ assert(changePtr->markPtr->refCount > 1);
+ changePtr->markPtr->refCount -= 1;
+ }
+ if (changePtr->moveMark) {
+ free(changePtr->moveMark);
+ changePtr->moveMark = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ assert(changePtr->markPtr->refCount > 1);
+ changePtr->markPtr->refCount -= 1;
+ }
+ if (changePtr->setMark) {
+ free(changePtr->setMark);
+ changePtr->setMark = NULL;
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ assert(changePtr->markPtr->refCount > 1);
+ changePtr->markPtr->refCount -= 1;
+ }
+
+ assert(changePtr->markPtr->refCount > 1);
+ changePtr->markPtr->refCount -= 1;
+ changePtr->markPtr->body.mark.changePtr = NULL;
+ changePtr->markPtr = NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextPushUndoMarkTokens --
+ *
+ * Push retained undo tokens for mark operations onto the undo stack.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Same as TkTextUndoPushItem.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextPushUndoMarkTokens(
+ TkSharedText *sharedTextPtr,
+ TkTextMarkChange *changePtr)
+{
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->undoStack);
+ assert(changePtr);
+ assert(changePtr->markPtr);
+ assert(changePtr->markPtr->body.mark.changePtr == changePtr);
+
+ if (changePtr->toggleGravity) {
+ UndoTokenToggleGravity *token = (UndoTokenToggleGravity *) changePtr->toggleGravity;
+
+ if (changePtr->savedMarkType != token->markPtr->typePtr) {
+ TkTextUndoPushItem(sharedTextPtr->undoStack, (TkTextUndoToken *) token, 0);
+ } else {
+ free(token);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ assert(changePtr->markPtr->refCount > 1);
+ changePtr->markPtr->refCount -= 1;
+ }
+ changePtr->toggleGravity = NULL;
+ }
+ if (changePtr->moveMark) {
+ TkTextUndoPushItem(sharedTextPtr->undoStack, changePtr->moveMark, 0);
+ changePtr->moveMark = NULL;
+ }
+ if (changePtr->setMark) {
+ TkTextUndoPushItem(sharedTextPtr->undoStack, changePtr->setMark, 0);
+ changePtr->setMark = NULL;
+ }
+
+ assert(changePtr->markPtr->refCount > 1);
+ changePtr->markPtr->refCount -= 1;
+ changePtr->markPtr->body.mark.changePtr = NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextSetMark -- SetMark --
*
* Set a mark to a particular position, creating a new mark if one
* doesn't already exist.
*
+ * Take care when setting the "insert" mark. In this case it might
+ * happen that the receiver of the "watch" command is destroying the
+ * widget. In this case this function will return NULL (otherwise
+ * this function will always return non-NULL in case of setting the
+ * "insert" mark).
+ *
+ * The footprint of this function is public, so we cannot extend it
+ * without changing tkIntDecls.h, and I will not touch this file. So
+ * I gave parameter 'name' an additional flag: if this parameter is
+ * marked, then it's a private mark.
+ *
* Results:
* The return value is a pointer to the mark that was just set.
*
@@ -267,32 +1578,118 @@ TkTextMarkCmd(
*----------------------------------------------------------------------
*/
-TkTextSegment *
-TkTextSetMark(
+static TkTextSegment *
+SetMark(
TkText *textPtr, /* Text widget in which to create mark. */
const char *name, /* Name of mark to set. */
+ const Tk_SegType *typePtr, /* Sepcifies the gravity, either tkTextLeftMarkType,
+ * tkTextRightMarkType, or NULL. */
TkTextIndex *indexPtr) /* Where to set mark. */
{
Tcl_HashEntry *hPtr = NULL;
+ TkSharedText *sharedTextPtr;
TkTextSegment *markPtr;
- TkTextIndex insertIndex;
- int isNew, widgetSpecific;
-
- if (!strcmp(name, "insert")) {
- widgetSpecific = 1;
- markPtr = textPtr->insertMarkPtr;
- isNew = (markPtr == NULL ? 1 : 0);
- } else if (!strcmp(name, "current")) {
- widgetSpecific = 2;
- markPtr = textPtr->currentMarkPtr;
- isNew = (markPtr == NULL ? 1 : 0);
- } else {
- widgetSpecific = 0;
- hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->markTable, name,
- &isNew);
+ TkTextIndex oldIndex;
+ TkTextUndoIndex undoIndex;
+ bool pushUndoToken;
+ bool widgetSpecific;
+ const Tk_SegType *oldTypePtr = NULL;
+
+ assert(textPtr);
+ assert(indexPtr->textPtr == textPtr);
+
+ widgetSpecific = false;
+ markPtr = NULL;
+
+ switch (*name) {
+ case 'i':
+ if (strcmp(name, "insert") == 0) {
+ widgetSpecific = true;
+ markPtr = textPtr->insertMarkPtr;
+ if (TkTextIsElided(indexPtr)) {
+ TkTextSkipElidedRegion(indexPtr);
+ }
+ }
+ break;
+ case 'c':
+ if (strcmp(name, "current") == 0) {
+ widgetSpecific = true;
+ markPtr = textPtr->currentMarkPtr;
+ }
+ break;
+ }
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextIndexClear(&oldIndex, textPtr);
+
+ if (!widgetSpecific) {
+ int dummy;
+ hPtr = Tcl_CreateHashEntry(&sharedTextPtr->markTable, name, &dummy);
markPtr = Tcl_GetHashValue(hPtr);
}
- if (!isNew) {
+
+ if (!markPtr) {
+ if (name[0] == '#' && name[1] == '#' && name[2] == 'I') {
+#ifdef TCL_WIDE_INT_IS_LONG
+ static const int length = 32 + 2*sizeof(uint64_t);
+#else /* ifndef TCL_WIDE_INT_IS_LONG */
+ static const int length = 32 + 2*sizeof(uint32_t);
+#endif /* TCL_WIDE_INT_IS_LONG */
+
+ void *sPtr, *tPtr;
+ unsigned num;
+
+ if (strlen(name) == length && sscanf(name, "##ID##%p##%p##%u##", &sPtr, &tPtr, &num) == 3) {
+ assert(hPtr);
+ Tcl_DeleteHashEntry(hPtr);
+ return NULL; /* this is an expired generated mark */
+ }
+ }
+
+ markPtr = MakeMark(textPtr);
+
+ if (widgetSpecific) {
+ /*
+ * This is a special mark.
+ */
+ if (*name == 'i') { /* "insert" */
+ textPtr->insertMarkPtr = markPtr;
+ markPtr->insertMarkFlag = true;
+ } else { /* "current" */
+ textPtr->currentMarkPtr = markPtr;
+ markPtr->currentMarkFlag = true;
+ }
+ pushUndoToken = false;
+ } else {
+ markPtr->body.mark.ptr = PTR_TO_INT(hPtr);
+ markPtr->normalMarkFlag = true;
+ Tcl_SetHashValue(hPtr, markPtr);
+ pushUndoToken = sharedTextPtr->steadyMarks && sharedTextPtr->undoStack;
+ textPtr->sharedTextPtr->numMarks += 1;
+ }
+ } else {
+ const TkTextSegment *segPtr;
+
+ TkTextMarkSegToIndex(textPtr, markPtr, &oldIndex);
+
+ if (markPtr == textPtr->insertMarkPtr && TkTextIndexIsEndOfText(indexPtr)) {
+ /*
+ * The index is outside of visible text, so backup one char.
+ */
+ TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr, COUNT_INDICES);
+ }
+
+ if (!sharedTextPtr->steadyMarks
+ && (!typePtr || typePtr == markPtr->typePtr)
+ && TkTextIndexIsEqual(&oldIndex, indexPtr)) {
+ return markPtr; /* this mark did not change the position */
+ }
+
+ TkTextIndexToByteIndex(&oldIndex);
+ pushUndoToken = sharedTextPtr->steadyMarks
+ && sharedTextPtr->undoStack
+ && TkTextIsNormalMark(markPtr);
+
/*
* If this is the insertion point that's being moved, be sure to force
* a display update at the old position. Also, don't let the insertion
@@ -300,71 +1697,237 @@ TkTextSetMark(
*/
if (markPtr == textPtr->insertMarkPtr) {
- TkTextIndex index, index2;
- int nblines;
+ TkTextIndex index2;
- TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
+ TkTextIndexToByteIndex(indexPtr);
- /*
- * While we wish to redisplay, no heights have changed, so no need
- * to call TkTextInvalidateLineMetrics.
- */
-
- TkTextChanged(NULL, textPtr, &index, &index2);
-
- /*
- * The number of lines in the widget is zero if and only if it is
- * a partial peer with -startline == -endline, i.e. an empty
- * peer. In this case the mark shall be set exactly at the given
- * index, and not one character backwards (bug 3487407).
- */
-
- nblines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
- if ((TkBTreeLinesTo(textPtr, indexPtr->linePtr) == nblines)
- && (nblines > 0)) {
- TkTextIndexBackChars(NULL,indexPtr, 1, &insertIndex,
- COUNT_INDICES);
- indexPtr = &insertIndex;
+ if (textPtr->state == TK_TEXT_STATE_NORMAL) {
+ /*
+ * Test whether cursor is inside the actual range.
+ */
+ if (TkTextIndexRestrictToStartRange(&oldIndex) >= 0
+ && TkTextIndexRestrictToEndRange(&oldIndex) <= 0
+ && TkTextIndexForwChars(textPtr, &oldIndex, 1, &index2, COUNT_INDICES)) {
+ /*
+ * While we wish to redisplay, no heights have changed, so no need
+ * to call TkTextInvalidateLineMetrics.
+ */
+
+ TkTextChanged(NULL, textPtr, &oldIndex, &index2);
+ }
}
+ } else if (markPtr == textPtr->currentMarkPtr) {
+ textPtr->haveToSetCurrentMark = false;
+ } else if (pushUndoToken) {
+ TkBTreeMakeUndoIndex(sharedTextPtr, markPtr, &undoIndex);
}
- TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr);
- } else {
- markPtr = ckalloc(MSEG_SIZE);
- markPtr->typePtr = &tkTextRightMarkType;
- markPtr->size = 0;
- markPtr->body.mark.textPtr = textPtr;
- markPtr->body.mark.linePtr = indexPtr->linePtr;
- markPtr->body.mark.hPtr = hPtr;
- if (widgetSpecific == 0) {
- Tcl_SetHashValue(hPtr, markPtr);
- } else if (widgetSpecific == 1) {
- textPtr->insertMarkPtr = markPtr;
- } else {
- textPtr->currentMarkPtr = markPtr;
+
+ if ((segPtr = TkTextIndexGetSegment(indexPtr)) == markPtr) {
+ return markPtr;
+ }
+
+ if (segPtr && segPtr->size > 1) {
+ /* because TkBTreeUnlinkSegment may invalidate this index */
+ TkTextIndexToByteIndex(indexPtr);
}
+
+ TkBTreeUnlinkSegment(sharedTextPtr, markPtr);
}
- TkBTreeLinkSegment(markPtr, indexPtr);
- /*
- * If the mark is the insertion cursor, then update the screen at the
- * mark's new location.
- */
+ if (typePtr && typePtr != markPtr->typePtr) {
+ oldTypePtr = markPtr->typePtr;
+ markPtr->typePtr = typePtr;
+ }
+
+ /* this function will also update 'sectionPtr' */
+ TkBTreeLinkSegment(sharedTextPtr, markPtr, indexPtr);
- if (markPtr == textPtr->insertMarkPtr) {
- TkTextIndex index2;
+ if (pushUndoToken) {
+ TkTextMarkChange *changePtr;
- TkTextIndexForwChars(NULL, indexPtr, 1, &index2, COUNT_INDICES);
+ changePtr = MakeChangeItem(sharedTextPtr, markPtr);
+
+ if (!changePtr->setMark && !changePtr->moveMark) {
+ if (TkTextIndexIsEmpty(&oldIndex)) {
+ UndoTokenSetMark *token;
+
+ token = malloc(sizeof(UndoTokenSetMark));
+ token->undoType = &undoTokenSetMarkType;
+ (token->markPtr = markPtr)->refCount += 1;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ changePtr->setMark = (TkTextUndoToken *) token;
+ sharedTextPtr->undoStackEvent = true;
+ sharedTextPtr->lastUndoTokenType = -1;
+ oldTypePtr = NULL;
+ } else {
+ UndoTokenMoveMark *token;
+
+ token = malloc(sizeof(UndoTokenMoveMark));
+ token->undoType = &undoTokenMoveMarkType;
+ (token->markPtr = markPtr)->refCount += 1;
+ token->index = undoIndex;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ changePtr->moveMark = (TkTextUndoToken *) token;
+ sharedTextPtr->undoStackEvent = true;
+ sharedTextPtr->lastUndoTokenType = -1;
+ }
+ }
+
+ if (oldTypePtr) {
+ MakeUndoToggleGravity(sharedTextPtr, markPtr, oldTypePtr);
+ }
+ }
+ if (sharedTextPtr->steadyMarks && TkTextIsNormalMark(markPtr)) {
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ }
+
+ if (textPtr->state == TK_TEXT_STATE_NORMAL) {
/*
- * While we wish to redisplay, no heights have changed, so no need to
- * call TkTextInvalidateLineMetrics
+ * If the mark is the insertion cursor, then update the screen at the mark's new location.
*/
- TkTextChanged(NULL, textPtr, indexPtr, &index2);
+ if (markPtr == textPtr->insertMarkPtr) {
+ TkTextIndex index2;
+
+ TkTextIndexForwChars(textPtr, indexPtr, 1, &index2, COUNT_INDICES);
+
+ /*
+ * While we wish to redisplay, no heights have changed, so no need to
+ * call TkTextInvalidateLineMetrics.
+ */
+
+ /* TODO: this is very inefficient, it would be more appopriate to trigger
+ * a special cursor redraw function (see DisplayDLine in tkTextDisp).
+ * Instead of inserting a cursor chunk (not needed) we want to overlay
+ * with a cursor. This would speed up cursor movement.
+ */
+ TkTextChanged(NULL, textPtr, indexPtr, &index2);
+
+ /*
+ * Finally trigger the "watch" command for the "insert" cursor,
+ * this must be the last action.
+ */
+
+ if (textPtr->watchCmd && !TriggerWatchCursor(textPtr, &oldIndex, indexPtr)) {
+ return NULL; /* the receiver did destroy the widget */
+ }
+ }
}
+
return markPtr;
}
+
+TkTextSegment *
+TkTextSetMark(
+ TkText *textPtr, /* Text widget in which to create mark. */
+ const char *name, /* Name of mark to set. */
+ TkTextIndex *indexPtr) /* Where to set mark. */
+{
+ return SetMark(textPtr, name, NULL, indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUnsetMark --
+ *
+ * Unset (delete) given mark.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * A mark will be deleted.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextUnsetMark(
+ TkText *textPtr, /* Text widget in which to create mark. */
+ TkTextSegment *markPtr) /* Delete this mark. */
+{
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr = NULL;
+ bool isNormalMark = TkTextIsNormalMark(markPtr);
+
+ assert(TkTextIsNormalMark(markPtr));
+
+ if (isNormalMark
+ && textPtr->sharedTextPtr->steadyMarks
+ && !TkTextUndoUndoStackIsFull(textPtr->sharedTextPtr->undoStack)) {
+ undoInfoPtr = &undoInfo;
+ }
+ UnsetMark(textPtr->sharedTextPtr, markPtr, undoInfoPtr);
+ if (isNormalMark) {
+ if (undoInfoPtr && undoInfo.token) {
+ TkTextPushUndoToken(textPtr->sharedTextPtr, undoInfo.token, 0);
+ }
+ if (textPtr->sharedTextPtr->steadyMarks) {
+ TkTextUpdateAlteredFlag(textPtr->sharedTextPtr);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextSaveCursorIndex --
+ *
+ * Save the current position of the insert cursor, but only if
+ * it is not yet saved. Use this function only if a trigger of
+ * the "watch" command is wanted in case of cursor movement.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextSaveCursorIndex(
+ TkText *textPtr)
+{
+ if (TkTextIndexIsEmpty(&textPtr->insertIndex)) {
+ TkTextIndexSetSegment(&textPtr->insertIndex, textPtr->insertMarkPtr);
+ TkTextIndexSave(&textPtr->insertIndex);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextTriggerWatchCursor --
+ *
+ * Trigger the watch command for movements of the insert cursor.
+ *
+ * Results:
+ * Returns 'false' if the referenced widget has been destroyed, otherwise
+ * 'true' will be returned.
+ *
+ * Side effects:
+ * It might happen that the receiver of the "watch" command is destroying the widget.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextTriggerWatchCursor(
+ TkText *textPtr)
+{
+ assert(textPtr->watchCmd);
+
+ if (TkTextIndexIsEmpty(&textPtr->insertIndex)) {
+ return true;
+ }
+
+ TkTextIndexRebuild(&textPtr->insertIndex);
+ return TriggerWatchCursor(textPtr, &textPtr->insertIndex, NULL);
+}
/*
*--------------------------------------------------------------
@@ -386,19 +1949,18 @@ TkTextSetMark(
void
TkTextMarkSegToIndex(
- TkText *textPtr, /* Text widget containing mark. */
+ TkText *textPtr, /* Text widget containing mark, can be NULL. */
TkTextSegment *markPtr, /* Mark segment. */
TkTextIndex *indexPtr) /* Index information gets stored here. */
{
- TkTextSegment *segPtr;
-
- indexPtr->tree = textPtr->sharedTextPtr->tree;
- indexPtr->linePtr = markPtr->body.mark.linePtr;
- indexPtr->byteIndex = 0;
- for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr;
- segPtr = segPtr->nextPtr) {
- indexPtr->byteIndex += segPtr->size;
- }
+ assert(textPtr);
+ assert(markPtr);
+ assert(markPtr->sectionPtr); /* otherwise not linked */
+
+ TkTextIndexClear(indexPtr, textPtr);
+ /* disable range checks, because here it's is allowed that the index is out of range. */
+ DEBUG(indexPtr->discardConsistencyCheck = true);
+ TkTextIndexSetSegment(indexPtr, markPtr);
}
/*
@@ -410,16 +1972,16 @@ TkTextMarkSegToIndex(
* name.
*
* Results:
- * The return value is TCL_OK if "name" exists as a mark in the text
- * widget and is located within its -starline/-endline range. In this
+ * The return value is 'true' if "name" exists as a mark in the text
+ * widget and is located within its -start/-end range. In this
* case *indexPtr is filled in with the next segment who is after the
- * mark whose size is non-zero. TCL_ERROR is returned if the mark
- * doesn't exist in the text widget, or if it is out of its -starline/
- * -endline range. In this latter case *indexPtr still contains valid
+ * mark whose size is non-zero. 'false' is returned if the mark
+ * doesn't exist in the text widget, or if it is out of its -start/
+ * -end range. In this latter case *indexPtr still contains valid
* information, in particular TkTextMarkNameToIndex called with the
* "insert" or "current" mark name may return TCL_ERROR, but *indexPtr
- * contains the correct index of this mark before -startline or after
- * -endline.
+ * contains the correct index of this mark before -start or after
+ * -end.
*
* Side effects:
* None.
@@ -427,58 +1989,148 @@ TkTextMarkSegToIndex(
*--------------------------------------------------------------
*/
-int
+static bool
+MarkToIndex(
+ TkText *textPtr, /* Text widget containing mark. */
+ TkTextSegment *markPtr, /* Pointer to mark segment. */
+ TkTextIndex *indexPtr) /* Index information gets stored here. */
+{
+ TkTextIndex index;
+
+ assert(textPtr);
+ TkTextMarkSegToIndex(textPtr, markPtr, indexPtr);
+ indexPtr->textPtr = textPtr;
+
+ /*
+ * If indexPtr refers to somewhere outside the -start/-end range
+ * limits of the widget, error out since the mark indeed is not
+ * reachable from this text widget (it may be reachable from a peer)
+ * (bug 1630271).
+ */
+
+ if (textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetSegment(&index, textPtr->startMarker);
+ if (TkTextIndexCompare(indexPtr, &index) < 0) {
+ return false;
+ }
+ }
+ if (textPtr->endMarker != textPtr->sharedTextPtr->endMarker) {
+ if (TkTextIndexGetLine(indexPtr) == textPtr->endMarker->sectionPtr->linePtr) {
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetSegment(&index, textPtr->endMarker);
+ } else {
+ TkTextIndexSetupToEndOfText(&index, textPtr, indexPtr->tree);
+ }
+ if (TkTextIndexCompare(indexPtr, &index) > 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
TkTextMarkNameToIndex(
TkText *textPtr, /* Text widget containing mark. */
const char *name, /* Name of mark. */
TkTextIndex *indexPtr) /* Index information gets stored here. */
{
TkTextSegment *segPtr;
- TkTextIndex index;
- int start, end;
- if (textPtr == NULL) {
- return TCL_ERROR;
- }
+ assert(textPtr);
- if (!strcmp(name, "insert")) {
+ if (strcmp(name, "insert") == 0) {
segPtr = textPtr->insertMarkPtr;
- } else if (!strcmp(name, "current")) {
+ } else if (strcmp(name, "current") == 0) {
segPtr = textPtr->currentMarkPtr;
} else {
- Tcl_HashEntry *hPtr =
- Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name);
+ Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name);
if (hPtr == NULL) {
- return TCL_ERROR;
+ return false;
}
segPtr = Tcl_GetHashValue(hPtr);
}
- TkTextMarkSegToIndex(textPtr, segPtr, indexPtr);
- /* If indexPtr refers to somewhere outside the -startline/-endline
- * range limits of the widget, error out since the mark indeed is not
- * reachable from this text widget (it may be reachable from a peer)
- * (bug 1630271).
- */
+ return MarkToIndex(textPtr, segPtr, indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextInspectUndoMarkItem --
+ *
+ * Inspect retained undo token.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory is allocated for the result.
+ *
+ *----------------------------------------------------------------------
+ */
- if (textPtr->start != NULL) {
- start = TkBTreeLinesTo(NULL, textPtr->start);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, start, 0,
- &index);
- if (TkTextIndexCmp(indexPtr, &index) < 0) {
- return TCL_ERROR;
- }
+void
+TkTextInspectUndoMarkItem(
+ const TkSharedText *sharedTextPtr,
+ const TkTextMarkChange *changePtr,
+ Tcl_Obj* objPtr)
+{
+ assert(changePtr);
+
+ if (changePtr->setMark) {
+ Tcl_ListObjAppendElement(NULL, objPtr,
+ changePtr->setMark->undoType->inspectProc(sharedTextPtr, changePtr->setMark));
}
- if (textPtr->end != NULL) {
- end = TkBTreeLinesTo(NULL, textPtr->end);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, end, 0,
- &index);
- if (TkTextIndexCmp(indexPtr, &index) > 0) {
- return TCL_ERROR;
- }
+ if (changePtr->moveMark) {
+ Tcl_ListObjAppendElement(NULL, objPtr,
+ changePtr->moveMark->undoType->inspectProc(sharedTextPtr, changePtr->moveMark));
}
- return TCL_OK;
+ if (changePtr->toggleGravity) {
+ Tcl_ListObjAppendElement(NULL, objPtr,
+ changePtr->toggleGravity->undoType->inspectProc(sharedTextPtr,
+ changePtr->toggleGravity));
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * MarkInspectProc --
+ *
+ * This function is invoked to build the information for
+ * "inspect".
+ *
+ * Results:
+ * Return a TCL object containing the information for
+ * "inspect".
+ *
+ * Side effects:
+ * Storage is allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+MarkInspectProc(
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ const char *gravity = (segPtr->typePtr == &tkTextLeftMarkType) ? "left" : "right";
+ const char *name;
+
+ assert(!TkTextIsPrivateMark(segPtr));
+ assert(!IS_PRESERVED(segPtr));
+
+ name = TkTextMarkName(sharedTextPtr, NULL, segPtr);
+ assert(name);
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(gravity, -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(name, -1));
+ return objPtr;
}
/*
@@ -487,54 +2139,225 @@ TkTextMarkNameToIndex(
* MarkDeleteProc --
*
* This function is invoked by the text B-tree code whenever a mark lies
- * in a range of characters being deleted.
+ * in a range being deleted.
*
* Results:
- * Returns 1 to indicate that deletion has been rejected.
+ * Returns false to indicate that deletion has been rejected. Otherwise, if
+ * deletion has been done (virtually) because DELETE_MARKS is set, true
+ * will be returned. If the reference count of this segment is not going
+ * to zero then this segment will be preserved for undo.
*
* Side effects:
- * None (even if the whole tree is being deleted we don't free up the
- * mark; it will be done elsewhere).
+ * None when this functions returns false (even if the whole tree is being
+ * deleted we don't free up the mark; it will be done elsewhere). But
+ * if a deletion has been done the hash entry of this mark will be
+ * removed.
*
*--------------------------------------------------------------
*/
- /* ARGSUSED */
-static int
+static bool
MarkDeleteProc(
+ TkTextBTree tree,
TkTextSegment *segPtr, /* Segment being deleted. */
- TkTextLine *linePtr, /* Line containing segment. */
- int treeGone) /* Non-zero means the entire tree is being
- * deleted, so everything must get cleaned
- * up. */
+ int flags) /* The deletion flags. */
{
- return 1;
+ TkSharedText *sharedTextPtr;
+
+ assert(segPtr->refCount > 0);
+
+ if (TkTextIsSpecialMark(segPtr)) {
+ return false;
+ }
+
+ assert(segPtr->refCount > 0);
+
+ if (TkTextIsPrivateMark(segPtr)) {
+ if (!(flags & (DELETE_CLEANUP))) {
+ return false;
+ }
+ if (--segPtr->refCount == 0) {
+ if (segPtr->body.mark.ptr) {
+ Tcl_DeleteHashEntry(GET_HPTR(segPtr));
+ segPtr->body.mark.textPtr->sharedTextPtr->numPrivateMarks -= 1;
+ }
+ FREE_SEGMENT(segPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ return true;
+ }
+
+ if (!(flags & (DELETE_MARKS|DELETE_PRESERVE|DELETE_CLEANUP|TREE_GONE))) {
+ return false;
+ }
+
+ assert(segPtr->body.mark.ptr);
+ assert(segPtr->body.mark.textPtr);
+
+ sharedTextPtr = segPtr->body.mark.textPtr->sharedTextPtr;
+
+ if (segPtr->body.mark.changePtr) {
+ unsigned index;
+
+ index = segPtr->body.mark.changePtr - sharedTextPtr->undoMarkList;
+ TkTextReleaseUndoMarkTokens(sharedTextPtr, segPtr->body.mark.changePtr);
+ memmove(sharedTextPtr->undoMarkList + index, sharedTextPtr->undoMarkList + index + 1,
+ --sharedTextPtr->undoMarkListCount - index);
+ }
+
+ if (--segPtr->refCount == 0) {
+ if (IS_PRESERVED(segPtr)) {
+ free(GET_NAME(segPtr));
+ } else {
+ sharedTextPtr->numMarks -= 1;
+ Tcl_DeleteHashEntry(GET_HPTR(segPtr));
+ }
+ FREE_SEGMENT(segPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ } else if ((flags & DELETE_PRESERVE) && !IS_PRESERVED(segPtr)) {
+ Tcl_HashEntry *hPtr;
+ const char *name;
+ unsigned size;
+
+ name = Tcl_GetHashKey(&sharedTextPtr->markTable, hPtr = GET_HPTR(segPtr));
+ size = strlen(name) + 1;
+ segPtr->body.mark.ptr = PTR_TO_INT(memcpy(malloc(size), name, size));
+ MAKE_PRESERVED(segPtr);
+ Tcl_DeleteHashEntry(hPtr);
+ sharedTextPtr->numMarks -= 1;
+ }
+
+ return true;
}
/*
*--------------------------------------------------------------
*
- * MarkCleanupProc --
+ * MarkRestoreProc --
*
- * This function is invoked by the B-tree code whenever a mark segment is
- * moved from one line to another.
+ * This function is called when a mark segment will be resused
+ * from the undo chain.
*
* Results:
* None.
*
* Side effects:
- * The linePtr field of the segment gets updated.
+ * The name of the mark will be freed, and the mark will be
+ * re-entered into the hash table.
*
*--------------------------------------------------------------
*/
-static TkTextSegment *
-MarkCleanupProc(
- TkTextSegment *markPtr, /* Mark segment that's being moved. */
- TkTextLine *linePtr) /* Line that now contains segment. */
+static void
+MarkRestoreProc(
+ TkTextSegment *segPtr) /* Segment to restore. */
{
- markPtr->body.mark.linePtr = linePtr;
- return markPtr;
+ assert(TkTextIsNormalMark(segPtr));
+
+ if (IS_PRESERVED(segPtr)) {
+ TkSharedText *sharedTextPtr = segPtr->body.mark.textPtr->sharedTextPtr;
+ Tcl_HashEntry *hPtr;
+ int isNew;
+
+ hPtr = Tcl_CreateHashEntry(&sharedTextPtr->markTable, GET_NAME(segPtr), &isNew);
+ assert(isNew);
+ Tcl_SetHashValue(hPtr, segPtr);
+ free(GET_NAME(segPtr));
+ segPtr->body.mark.ptr = PTR_TO_INT(hPtr);
+ sharedTextPtr->numMarks += 1;
+ }
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * MarkCheckProc --
+ *
+ * This function is invoked by the B-tree code to perform consistency
+ * checks on mark segments.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The function panics if it detects anything wrong with
+ * the mark.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+MarkCheckProc(
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *markPtr) /* Segment to check. */
+{
+ if (!markPtr->nextPtr) {
+ Tcl_Panic("MarkCheckProc: mark is last segment in line");
+ }
+ if (markPtr->size != 0) {
+ Tcl_Panic("MarkCheckProc: mark has size %d", markPtr->size);
+ }
+ if (!markPtr->insertMarkFlag
+ && !markPtr->currentMarkFlag
+ && !markPtr->privateMarkFlag
+ && !markPtr->normalMarkFlag) {
+ Tcl_Panic("MarkCheckProc: mark is not specialized");
+ }
+ if (markPtr->insertMarkFlag +
+ markPtr->currentMarkFlag +
+ markPtr->privateMarkFlag +
+ markPtr->normalMarkFlag > 1) {
+ Tcl_Panic("MarkCheckProc: mark has more than one specialization");
+ }
+ if (markPtr->startEndMarkFlag && !markPtr->privateMarkFlag) {
+ Tcl_Panic("MarkCheckProc: start/end marks have to be private");
+ }
+
+ if (markPtr->body.mark.changePtr) {
+ /*
+ * Private marks and special marks will not have undo/redo data.
+ */
+
+ if (TkTextIsSpecialOrPrivateMark(markPtr)) {
+ Tcl_Panic("MarkCheckProc: private/special marks should not have undo/redo data");
+ }
+ }
+
+ /*
+ * The special marks ("insert", "current") are not in the hash table,
+ * the same with the start/end markers.
+ */
+
+ if (markPtr->body.mark.ptr) {
+ void *hPtr;
+
+ if (IS_PRESERVED(markPtr)) {
+ Tcl_Panic("MarkCheckProc: detected preserved mark outside of the undo chain");
+ }
+
+ hPtr = Tcl_GetHashKey(&sharedTextPtr->markTable, (Tcl_HashEntry *) markPtr->body.mark.ptr);
+ if (!hPtr) {
+ Tcl_Panic("MarkCheckProc: couldn't find hash table entry for mark");
+ }
+ }
+
+ if (markPtr->startEndMarkFlag) {
+ if (markPtr->typePtr == &tkTextLeftMarkType) {
+ if (markPtr->prevPtr
+ && markPtr->prevPtr->typePtr->group == SEG_GROUP_MARK
+ && (!markPtr->prevPtr->startEndMarkFlag
+ || markPtr->prevPtr->typePtr != &tkTextLeftMarkType)) {
+ Tcl_Panic("MarkCheckProc: start marker must be leftmost mark");
+ }
+ } else {
+ if (markPtr->nextPtr
+ && markPtr->nextPtr->typePtr->group == SEG_GROUP_MARK
+ && (!markPtr->nextPtr->startEndMarkFlag
+ || markPtr->nextPtr->typePtr != &tkTextRightMarkType)) {
+ Tcl_Panic("MarkCheckProc: end marker must be rightmost mark");
+ }
+ }
+ }
}
/*
@@ -558,31 +2381,26 @@ MarkCleanupProc(
static int
MarkLayoutProc(
- TkText *textPtr, /* Text widget being layed out. */
- TkTextIndex *indexPtr, /* Identifies first character in chunk. */
+ const TkTextIndex *indexPtr,/* Identifies first character in chunk. */
TkTextSegment *segPtr, /* Segment corresponding to indexPtr. */
- int offset, /* Offset within segPtr corresponding to
- * indexPtr (always 0). */
- int maxX, /* Chunk must not occupy pixels at this
- * position or higher. */
- int maxChars, /* Chunk must not include more than this many
- * characters. */
- int noCharsYet, /* Non-zero means no characters have been
- * assigned to this line yet. */
+ int offset, /* Offset within segPtr corresponding to indexPtr (always 0). */
+ int maxX, /* Chunk must not occupy pixels at this position or higher. */
+ int maxChars, /* Chunk must not include more than this many characters. */
+ bool noCharsYet, /* 'true' means no characters have been assigned to this line yet. */
TkWrapMode wrapMode, /* Not used. */
- register TkTextDispChunk *chunkPtr)
- /* Structure to fill in with information about
- * this chunk. The x field has already been
- * set by the caller. */
+ TkTextSpaceMode spaceMode, /* Not used. */
+ TkTextDispChunk *chunkPtr) /* Structure to fill in with information about this chunk. The x
+ * field has already been set by the caller. */
{
+ TkText *textPtr = indexPtr->textPtr;
+
+ assert(indexPtr->textPtr);
+
if (segPtr != textPtr->insertMarkPtr) {
return -1;
}
- chunkPtr->displayProc = TkTextInsertDisplayProc;
- chunkPtr->undisplayProc = InsertUndisplayProc;
- chunkPtr->measureProc = NULL;
- chunkPtr->bboxProc = NULL;
+ chunkPtr->layoutProcs = &layoutInsertProcs;
chunkPtr->numBytes = 0;
chunkPtr->minAscent = 0;
chunkPtr->minDescent = 0;
@@ -602,6 +2420,88 @@ MarkLayoutProc(
/*
*--------------------------------------------------------------
*
+ * TkTextDrawBlockCursor --
+ *
+ * This function returns whether a block will be drawn, which covers
+ * characters.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+bool
+TkTextDrawBlockCursor(
+ TkText *textPtr) /* The current text widget. */
+{
+ if (textPtr->blockCursorType) {
+ if (textPtr->flags & HAVE_FOCUS) {
+ if ((textPtr->flags & INSERT_ON) || textPtr->selBorder == textPtr->insertBorder) {
+ return true;
+ }
+ } else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_SOLID) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextGetCursorWidth --
+ *
+ * This function computes the cursor dimensions.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+unsigned
+TkTextGetCursorWidth(
+ TkText *textPtr, /* The current text widget. */
+ int *x, /* Shift x coordinate, can be NULL. */
+ int *offs) /* Offset in x-direction, can be NULL. */
+{
+ TkTextIndex index;
+ int ix = 0, iy = 0, iw = 0, ih = 0;
+ int charWidth = 0;
+
+ if (offs) {
+ *offs = -(textPtr->insertWidth/2);
+ }
+
+ if (textPtr->blockCursorType) {
+ TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
+ TkTextIndexBbox(textPtr, &index, &ix, &iy, &iw, &ih, &charWidth);
+
+ /*
+ * Don't draw the full lengh of a tab, in this case we are drawing
+ * a cursor at the right boundary with a standard with.
+ */
+
+ if (TkTextIndexGetChar(&index) == '\t') {
+ if (x) { *x += charWidth; }
+ charWidth = MIN(textPtr->charWidth, charWidth);
+ if (x) { *x -= charWidth; }
+ }
+ }
+
+ return charWidth + textPtr->insertWidth;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
* TkTextInsertDisplayProc --
*
* This function is called to display the insertion cursor.
@@ -615,52 +2515,45 @@ MarkLayoutProc(
*--------------------------------------------------------------
*/
- /* ARGSUSED */
void
TkTextInsertDisplayProc(
TkText *textPtr, /* The current text widget. */
TkTextDispChunk *chunkPtr, /* Chunk that is to be drawn. */
- int x, /* X-position in dst at which to draw this
- * chunk (may differ from the x-position in
- * the chunk because of scrolling). */
- int y, /* Y-position at which to draw this chunk in
- * dst (x-position is in the chunk itself). */
+ int x, /* X-position in dst at which to draw this chunk (may differ
+ * from the x-position in the chunk because of scrolling). */
+ int y, /* Y-position at which to draw this chunk in dst (x-position
+ * is in the chunk itself). */
int height, /* Total height of line. */
int baseline, /* Offset of baseline from y. */
Display *display, /* Display to use for drawing. */
Drawable dst, /* Pixmap or window in which to draw chunk. */
- int screenY) /* Y-coordinate in text window that
- * corresponds to y. */
+ int screenY) /* Y-coordinate in text window that corresponds to y. */
{
- /*
- * We have no need for the clientData.
- */
-
- /* TkText *textPtr = chunkPtr->clientData; */
- TkTextIndex index;
int halfWidth = textPtr->insertWidth/2;
- int rightSideWidth;
- int ix = 0, iy = 0, iw = 0, ih = 0, charWidth = 0;
-
- if (textPtr->insertCursorType) {
- TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- TkTextIndexBbox(textPtr, &index, &ix, &iy, &iw, &ih, &charWidth);
- rightSideWidth = charWidth + halfWidth;
- } else {
- rightSideWidth = halfWidth;
- }
+ int width = TkTextGetCursorWidth(textPtr, &x, NULL);
+ int rightSideWidth = width + halfWidth - textPtr->insertWidth;
if ((x + rightSideWidth) < 0) {
/*
- * The insertion cursor is off-screen. Indicate caret at 0,0 and
- * return.
+ * The insertion cursor is off-screen. Indicate caret at 0,0 and return.
*/
Tk_SetCaretPos(textPtr->tkwin, 0, 0, height);
return;
}
- Tk_SetCaretPos(textPtr->tkwin, x - halfWidth, screenY, height);
+ x -= halfWidth;
+
+ Tk_SetCaretPos(textPtr->tkwin, x, screenY, height);
+
+ if (POINTER_IS_MARKED(chunkPtr)) {
+ /*
+ * HACK: We are drawing into a tailored pixmap, because Tk has no clipping;
+ * see DisplayDLine().
+ */
+
+ x = y = 0;
+ }
/*
* As a special hack to keep the cursor visible on mono displays (or
@@ -670,37 +2563,31 @@ TkTextInsertDisplayProc(
* the cursor.
*/
- if (textPtr->flags & GOT_FOCUS) {
+ if (textPtr->flags & HAVE_FOCUS) {
if (textPtr->flags & INSERT_ON) {
- Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
- x - halfWidth, y, charWidth + textPtr->insertWidth,
- height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
+ Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, x, y,
+ width, height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
} else if (textPtr->selBorder == textPtr->insertBorder) {
- Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border,
- x - halfWidth, y, charWidth + textPtr->insertWidth,
- height, 0, TK_RELIEF_FLAT);
+ Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border, x, y,
+ width, height, 0, TK_RELIEF_FLAT);
}
} else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_HOLLOW) {
if (textPtr->insertBorderWidth < 1) {
/*
- * Hack to work around the fact that a "solid" border always
- * paints in black.
+ * Hack to work around the fact that a "solid" border always paints in black.
*/
TkBorder *borderPtr = (TkBorder *) textPtr->insertBorder;
- XDrawRectangle(Tk_Display(textPtr->tkwin), dst, borderPtr->bgGC,
- x - halfWidth, y, charWidth + textPtr->insertWidth - 1,
- height - 1);
+ XDrawRectangle(Tk_Display(textPtr->tkwin), dst, borderPtr->bgGC, x, y,
+ width - 1, height - 1);
} else {
- Tk_Draw3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
- x - halfWidth, y, charWidth + textPtr->insertWidth,
- height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
+ Tk_Draw3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, x, y,
+ width, height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
}
} else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_SOLID) {
- Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
- x - halfWidth, y, charWidth + textPtr->insertWidth, height,
- textPtr->insertBorderWidth, TK_RELIEF_RAISED);
+ Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, x, y,
+ width, height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
}
}
@@ -721,73 +2608,18 @@ TkTextInsertDisplayProc(
*--------------------------------------------------------------
*/
- /* ARGSUSED */
static void
InsertUndisplayProc(
TkText *textPtr, /* Overall information about text widget. */
TkTextDispChunk *chunkPtr) /* Chunk that is about to be freed. */
{
+ chunkPtr->clientData = NULL;
return;
}
/*
*--------------------------------------------------------------
*
- * MarkCheckProc --
- *
- * This function is invoked by the B-tree code to perform consistency
- * checks on mark segments.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The function panics if it detects anything wrong with
- * the mark.
- *
- *--------------------------------------------------------------
- */
-
-static void
-MarkCheckProc(
- TkTextSegment *markPtr, /* Segment to check. */
- TkTextLine *linePtr) /* Line containing segment. */
-{
- Tcl_HashSearch search;
- Tcl_HashEntry *hPtr;
-
- if (markPtr->body.mark.linePtr != linePtr) {
- Tcl_Panic("MarkCheckProc: markPtr->body.mark.linePtr bogus");
- }
-
- /*
- * These two marks are not in the hash table
- */
-
- if (markPtr->body.mark.textPtr->insertMarkPtr == markPtr) {
- return;
- }
- if (markPtr->body.mark.textPtr->currentMarkPtr == markPtr) {
- return;
- }
-
- /*
- * Make sure that the mark is still present in the text's mark hash table.
- */
-
- for (hPtr = Tcl_FirstHashEntry(
- &markPtr->body.mark.textPtr->sharedTextPtr->markTable,
- &search); hPtr != markPtr->body.mark.hPtr;
- hPtr = Tcl_NextHashEntry(&search)) {
- if (hPtr == NULL) {
- Tcl_Panic("MarkCheckProc couldn't find hash table entry for mark");
- }
- }
-}
-
-/*
- *--------------------------------------------------------------
- *
* MarkFindNext --
*
* This function searches forward for the next mark.
@@ -805,223 +2637,190 @@ static int
MarkFindNext(
Tcl_Interp *interp, /* For error reporting */
TkText *textPtr, /* The widget */
- Tcl_Obj *obj) /* The starting index or mark name */
+ const char *string, /* The starting index or mark name */
+ bool forward) /* Search forward. */
{
TkTextIndex index;
Tcl_HashEntry *hPtr;
- register TkTextSegment *segPtr;
- int offset;
- const char *string = Tcl_GetString(obj);
+ TkTextSegment *segPtr;
+ TkTextLine *linePtr;
- if (!strcmp(string, "insert")) {
+ assert(textPtr);
+
+ if (TkTextIsDeadPeer(textPtr)) {
+ return TCL_OK;
+ }
+
+ if (strcmp(string, "insert") == 0) {
segPtr = textPtr->insertMarkPtr;
- TkTextMarkSegToIndex(textPtr, segPtr, &index);
- segPtr = segPtr->nextPtr;
- } else if (!strcmp(string, "current")) {
+ linePtr = segPtr->sectionPtr->linePtr;
+ segPtr = forward ? segPtr->nextPtr : segPtr->prevPtr;
+ } else if (strcmp(string, "current") == 0) {
segPtr = textPtr->currentMarkPtr;
- TkTextMarkSegToIndex(textPtr, segPtr, &index);
- segPtr = segPtr->nextPtr;
+ linePtr = segPtr->sectionPtr->linePtr;
+ segPtr = forward ? segPtr->nextPtr : segPtr->prevPtr;
} else {
- hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string);
- if (hPtr != NULL) {
+ if ((hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string))) {
/*
- * If given a mark name, return the next mark in the list of
- * segments, even if it happens to be at the same character
- * position.
+ * If given a mark name, return the next/prev mark in the list of segments, even
+ * if it happens to be at the same character position.
*/
segPtr = Tcl_GetHashValue(hPtr);
- TkTextMarkSegToIndex(textPtr, segPtr, &index);
- segPtr = segPtr->nextPtr;
+ if (!MarkToIndex(textPtr, segPtr, &index)) {
+ return SetResultNoMarkNamed(interp, string);
+ }
+ linePtr = segPtr->sectionPtr->linePtr;
+ segPtr = forward ? segPtr->nextPtr : segPtr->prevPtr;
} else {
/*
* For non-mark name indices we want to return any marks that are
- * right at the index.
+ * right at the index, when searching forward, otherwise we do not
+ * return any marks that are right at the index.
*/
- if (TkTextGetObjIndex(interp, textPtr, obj, &index) != TCL_OK) {
+ if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) {
return TCL_ERROR;
}
- for (offset = 0, segPtr = index.linePtr->segPtr;
- segPtr != NULL && offset < index.byteIndex;
- offset += segPtr->size, segPtr = segPtr->nextPtr) {
- /* Empty loop body */ ;
+ segPtr = TkTextIndexGetFirstSegment(&index, NULL);
+ linePtr = segPtr->sectionPtr->linePtr;
+
+ if (!forward) {
+ while (segPtr && segPtr->size == 0) {
+ segPtr = segPtr->prevPtr;
+ }
}
}
}
- while (1) {
+ if (forward) {
+ TkTextSegment *lastPtr = textPtr->endMarker;
+
/*
- * segPtr points at the first possible candidate, or NULL if we ran
- * off the end of the line.
+ * Ensure that we can reach 'lastPtr'.
*/
- for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) {
- if (segPtr->typePtr == &tkTextRightMarkType ||
- segPtr->typePtr == &tkTextLeftMarkType) {
- Tcl_Obj *markName = GetMarkName(textPtr, segPtr);
-
- if (markName != NULL) {
- Tcl_SetObjResult(interp, markName);
- return TCL_OK;
- }
- }
+ while (lastPtr->size == 0) {
+ lastPtr = lastPtr->nextPtr;
}
- index.linePtr = TkBTreeNextLine(textPtr, index.linePtr);
- if (index.linePtr == NULL) {
- return TCL_OK;
- }
- index.byteIndex = 0;
- segPtr = index.linePtr->segPtr;
- }
-}
-
-/*
- *--------------------------------------------------------------
- *
- * MarkFindPrev --
- *
- * This function searches backwards for the previous mark.
- *
- * Results:
- * A standard Tcl result, which is a mark name or an empty string.
- *
- * Side effects:
- * None.
- *
- *--------------------------------------------------------------
- */
-static int
-MarkFindPrev(
- Tcl_Interp *interp, /* For error reporting */
- TkText *textPtr, /* The widget */
- Tcl_Obj *obj) /* The starting index or mark name */
-{
- TkTextIndex index;
- Tcl_HashEntry *hPtr;
- register TkTextSegment *segPtr, *seg2Ptr, *prevPtr;
- int offset;
- const char *string = Tcl_GetString(obj);
-
- if (!strcmp(string, "insert")) {
- segPtr = textPtr->insertMarkPtr;
- TkTextMarkSegToIndex(textPtr, segPtr, &index);
- } else if (!strcmp(string, "current")) {
- segPtr = textPtr->currentMarkPtr;
- TkTextMarkSegToIndex(textPtr, segPtr, &index);
- } else {
- hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string);
- if (hPtr != NULL) {
+ while (true) {
/*
- * If given a mark name, return the previous mark in the list of
- * segments, even if it happens to be at the same character
- * position.
+ * 'segPtr' points at the first possible candidate, or NULL if we ran
+ * off the end of the line.
*/
- segPtr = Tcl_GetHashValue(hPtr);
- TkTextMarkSegToIndex(textPtr, segPtr, &index);
- } else {
- /*
- * For non-mark name indices we do not return any marks that are
- * right at the index.
- */
+ for ( ; segPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr == lastPtr) {
+ return TCL_OK;
+ }
+ if (TkTextIsNormalOrSpecialMark(segPtr)) {
+ const char *name = TkTextMarkName(textPtr->sharedTextPtr, textPtr, segPtr);
- if (TkTextGetObjIndex(interp, textPtr, obj, &index) != TCL_OK) {
- return TCL_ERROR;
+ if (name) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(name, -1));
+ return TCL_OK;
+ }
+
+ /*
+ * It's a special mark not belonging to this widget, so ignore it.
+ */
+ }
}
- for (offset = 0, segPtr = index.linePtr->segPtr;
- segPtr != NULL && offset < index.byteIndex;
- offset += segPtr->size, segPtr = segPtr->nextPtr) {
- /* Empty loop body */
+
+ if (!(linePtr = linePtr->nextPtr)) {
+ return TCL_OK;
}
+ segPtr = linePtr->segPtr;
}
- }
+ } else {
+ TkTextSegment *firstPtr = textPtr->startMarker;
- while (1) {
/*
- * segPtr points just past the first possible candidate, or at the
- * beginning of the line.
+ * Ensure that we can reach 'firstPtr'.
*/
- for (prevPtr = NULL, seg2Ptr = index.linePtr->segPtr;
- seg2Ptr != NULL && seg2Ptr != segPtr;
- seg2Ptr = seg2Ptr->nextPtr) {
- if (seg2Ptr->typePtr == &tkTextRightMarkType ||
- seg2Ptr->typePtr == &tkTextLeftMarkType) {
- if (seg2Ptr->body.mark.hPtr == NULL) {
- if (seg2Ptr != textPtr->currentMarkPtr &&
- seg2Ptr != textPtr->insertMarkPtr) {
- /*
- * This is an insert or current mark from a
- * peer of textPtr.
- */
- continue;
- }
- }
- prevPtr = seg2Ptr;
- }
+ while (firstPtr->prevPtr && firstPtr->prevPtr->size == 0) {
+ firstPtr = firstPtr->prevPtr;
}
- if (prevPtr != NULL) {
- Tcl_Obj *markName = GetMarkName(textPtr, prevPtr);
- if (markName != NULL) {
- Tcl_SetObjResult(interp, markName);
+ while (true) {
+ /*
+ * 'segPtr' points at the first possible candidate, or NULL if we ran
+ * off the start of the line.
+ */
+
+ for ( ; segPtr; segPtr = segPtr->prevPtr) {
+ if (segPtr == firstPtr) {
+ return TCL_OK;
+ }
+ if (TkTextIsNormalOrSpecialMark(segPtr)) {
+ const char *name = TkTextMarkName(textPtr->sharedTextPtr, textPtr, segPtr);
+
+ if (name) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(name, -1));
+ return TCL_OK;
+ }
+
+ /*
+ * It's a special mark not belonging to this widget, so ignore it.
+ */
+ }
+ }
+
+ if (!(linePtr = linePtr->prevPtr)) {
return TCL_OK;
}
+ segPtr = linePtr->lastPtr;
}
- index.linePtr = TkBTreePreviousLine(textPtr, index.linePtr);
- if (index.linePtr == NULL) {
- return TCL_OK;
- }
- segPtr = NULL;
}
+
+ return TCL_OK; /* never reached */
}
/*
* ------------------------------------------------------------------------
*
- * GetMarkName --
+ * TkTextMarkName --
+ *
* Returns the name of the mark that is the given text segment, or NULL
* if it is unnamed (i.e., a widget-specific mark that isn't "current" or
* "insert").
*
+ * Results:
+ * The name of the mark, or NULL.
+ *
+ * Side effects:
+ * None.
+ *
* ------------------------------------------------------------------------
*/
-static Tcl_Obj *
-GetMarkName(
- TkText *textPtr,
- TkTextSegment *segPtr)
+const char *
+TkTextMarkName(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ const TkTextSegment *markPtr)
{
- const char *markName;
-
- if (segPtr == textPtr->currentMarkPtr) {
- markName = "current";
- } else if (segPtr == textPtr->insertMarkPtr) {
- markName = "insert";
- } else if (segPtr->body.mark.hPtr == NULL) {
- /*
- * Ignore widget-specific marks for the other widgets. This is either
- * an insert or a current mark (markPtr->body.mark.hPtr actually
- * receives NULL for these marks in TkTextSetMark). The insert and
- * current marks for textPtr having already been tested above, the
- * current segment is an insert or current mark from a peer of
- * textPtr, which we don't want to return.
- */
+ assert(!IS_PRESERVED(markPtr));
+ if (markPtr->insertMarkFlag) {
+ return !textPtr || textPtr == markPtr->body.mark.textPtr ? "insert" : NULL;
+ }
+ if (markPtr->currentMarkFlag) {
+ return !textPtr || textPtr == markPtr->body.mark.textPtr ? "current" : NULL;
+ }
+ if (!markPtr->body.mark.ptr) {
return NULL;
- } else {
- markName = Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable,
- segPtr->body.mark.hPtr);
}
- return Tcl_NewStringObj(markName, -1);
+ return Tcl_GetHashKey(&sharedTextPtr->markTable, (Tcl_HashEntry *) markPtr->body.mark.ptr);
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
- * fill-column: 78
+ * fill-column: 105
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/generic/tkTextPriv.h b/generic/tkTextPriv.h
new file mode 100644
index 0000000..79fd678
--- /dev/null
+++ b/generic/tkTextPriv.h
@@ -0,0 +1,976 @@
+/*
+ * tkTextPriv.h --
+ *
+ * Private implementation for text widget.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKTEXT
+# error "do not include this private header file"
+#endif
+
+
+#ifndef _TKTEXTPRIV
+#define _TKTEXTPRIV
+
+/*
+ * The following struct is private for TkTextBTree.c, but we want fast access to
+ * the internal content.
+ *
+ * The data structure below defines an entire B-tree. Since text widgets are
+ * the only current B-tree clients, 'clients' and 'numPixelReferences' are
+ * identical.
+ */
+
+struct TkBTreeNodePixelInfo;
+
+struct TkTextMyBTree {
+ struct Node *rootPtr;
+ /* Pointer to root of B-tree. */
+ unsigned clients; /* Number of clients of this B-tree. */
+ unsigned numPixelReferences;
+ /* Number of clients of this B-tree which care about pixel heights. */
+ struct TkBTreeNodePixelInfo *pixelInfoBuffer;
+ /* Buffer of size numPixelReferences used for recomputation of pixel
+ * information. */
+ unsigned stateEpoch; /* Updated each time any aspect of the B-tree changes. */
+ TkSharedText *sharedTextPtr;/* Used to find tagTable in consistency checking code, and to access
+ * list of all B-tree clients. */
+};
+
+#endif /* _TKTEXTPRIV */
+
+#ifdef _TK_NEED_IMPLEMENTATION
+
+#include <assert.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsSpecialMark --
+ *
+ * Test whether this is a special mark: "insert", or "current".
+ *
+ * Results:
+ * Whether this is a special mark.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIsSpecialMark(
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr);
+ return !!(segPtr->insertMarkFlag | segPtr->currentMarkFlag);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsPrivateMark --
+ *
+ * Test whether this is a private mark, not visible with "inspect"
+ * or "dump". These kind of marks will be used in library/text.tcl.
+ * Furthemore in practice it is guaranteed that this mark has a
+ * unique name.
+ *
+ * Results:
+ * Whether this is a private mark.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIsPrivateMark(
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr);
+ return segPtr->privateMarkFlag;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsNormalMark --
+ *
+ * Test whether this is a mark, and it is neither special, nor
+ * private, nor a start/end marker.
+ *
+ * Results:
+ * Whether this is a normal mark.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIsNormalMark(
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr);
+ return segPtr->normalMarkFlag;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsStartEndMarker --
+ *
+ * Test whether this is a start/end marker. This must not be a mark,
+ * it can also be a break segment.
+ *
+ * Results:
+ * Whether this is a start/end marker.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIsStartEndMarker(
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr);
+ return segPtr->startEndMarkFlag;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsStableMark --
+ *
+ * Test whether this is a mark, and it is neither special, nor
+ * private. Note that also a break segment is interpreted as
+ * a stable mark.
+ *
+ * Results:
+ * Whether this is a stable mark.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIsStableMark(
+ const TkTextSegment *segPtr)
+{
+ return TkTextIsStartEndMarker(segPtr) || TkTextIsNormalMark(segPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsSpecialOrPrivateMark --
+ *
+ * Test whether this is a special mark, or a private mark.
+ *
+ * Results:
+ * Whether this is a special or private mark.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIsSpecialOrPrivateMark(
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr);
+ return !!(segPtr->privateMarkFlag | segPtr->insertMarkFlag | segPtr->currentMarkFlag);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsNormalOrSpecialMark --
+ *
+ * Test whether this is a normal mark, or a special mark.
+ *
+ * Results:
+ * Whether this is a normal or special mark.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIsNormalOrSpecialMark(
+ const TkTextSegment *segPtr)
+{
+ return TkTextIsNormalMark(segPtr) || TkTextIsSpecialMark(segPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIsDeadPeer --
+ *
+ * Test whether given widget is dead, this means that the start
+ * index is on last line. If it is dead, then this peer will not
+ * have an insert mark.
+ *
+ * Results:
+ * Returns whether given peer is dead.
+ *
+ * Side effects:
+ * None
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIsDeadPeer(
+ const TkText *textPtr)
+{
+ return !textPtr->startMarker->sectionPtr->linePtr->nextPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeLinePixelInfo --
+ *
+ * Return widget pixel information for specified line.
+ *
+ * Results:
+ * The pixel information of this line for specified widget.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextPixelInfo *
+TkBTreeLinePixelInfo(
+ const TkText *textPtr,
+ TkTextLine *linePtr)
+{
+ assert(textPtr);
+ assert(textPtr->pixelReference >= 0);
+ assert(linePtr);
+
+ return linePtr->pixelInfo + textPtr->pixelReference;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeGetStartLine --
+ *
+ * This function returns the first line for this text widget.
+ *
+ * Results:
+ * The first line in this widget.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextLine *
+TkBTreeGetStartLine(
+ const TkText *textPtr)
+{
+ assert(textPtr);
+ return textPtr->startMarker->sectionPtr->linePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeGetLastLine --
+ *
+ * This function returns the last line for this text widget.
+ *
+ * Results:
+ * The last line in this widget.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextLine *
+TkBTreeGetLastLine(
+ const TkText *textPtr)
+{
+ TkTextLine *endLine;
+
+ assert(textPtr);
+ endLine = textPtr->endMarker->sectionPtr->linePtr;
+ return endLine->nextPtr ? endLine->nextPtr : endLine;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeGetShared --
+ *
+ * Get the shared resource for given tree.
+ *
+ * Results:
+ * The shared resource.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkSharedText *
+TkBTreeGetShared(
+ TkTextBTree tree) /* Return shared resource of this tree. */
+{
+ return ((struct TkTextMyBTree *) tree)->sharedTextPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeIncrEpoch --
+ *
+ * Increment the epoch of the tree.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Increment the epoch number.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+unsigned
+TkBTreeIncrEpoch(
+ TkTextBTree tree) /* Tree to increment epoch. */
+{
+ return ((struct TkTextMyBTree *) tree)->stateEpoch += 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeEpoch --
+ *
+ * Return the epoch for the B-tree. This number is incremented any time
+ * anything changes in the tree.
+ *
+ * Results:
+ * The epoch number.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+unsigned
+TkBTreeEpoch(
+ TkTextBTree tree) /* Tree to get epoch for. */
+{
+ return ((struct TkTextMyBTree *) tree)->stateEpoch;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeGetRoot --
+ *
+ * Return the root node of the B-Tree.
+ *
+ * Results:
+ * The root node of the B-Tree.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+struct Node *
+TkBTreeGetRoot(
+ TkTextBTree tree) /* Tree to get root node for. */
+{
+ return ((struct TkTextMyBTree *) tree)->rootPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeNextLine --
+ *
+ * Given an existing line in a B-tree, this function locates the next
+ * line in the B-tree, regarding the end line of this widget.
+ * B-tree.
+ *
+ * Results:
+ * The return value is a pointer to the line that immediately follows
+ * linePtr, or NULL if there is no such line.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextLine *
+TkBTreeNextLine(
+ const TkText *textPtr, /* Next line in the context of this client, can be NULL. */
+ TkTextLine *linePtr) /* Pointer to existing line in B-tree. */
+{
+ return textPtr && linePtr == TkBTreeGetLastLine(textPtr) ? NULL : linePtr->nextPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreePrevLine --
+ *
+ * Given an existing line in a B-tree, this function locates the previous
+ * line in the B-tree, regarding the start line of this widget.
+ *
+ * Results:
+ * The return value is a pointer to the line that immediately preceeds
+ * linePtr, or NULL if there is no such line.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextLine *
+TkBTreePrevLine(
+ const TkText *textPtr, /* Relative to this client of the B-tree, can be NULL. */
+ TkTextLine *linePtr) /* Pointer to existing line in B-tree. */
+{
+ return textPtr && linePtr == TkBTreeGetStartLine(textPtr) ? NULL : linePtr->prevPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreePrevLogicalLine --
+ *
+ * Given a line, this function is searching for the previous logical line,
+ * which don't has a predecessing line with elided newline. If the search
+ * reaches the start of the text, then the first line will be returned,
+ * even if it's not a logical line (the latter can only happen in peers
+ * with restricted ranges).
+ *
+ * Results:
+ * The return value is the previous logical line, in most cases this
+ * will be simply the previous line.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextLine *
+TkBTreePrevLogicalLine(
+ const TkSharedText* sharedTextPtr,
+ const TkText *textPtr, /* can be NULL */
+ TkTextLine *linePtr)
+{
+ assert(linePtr);
+ assert(linePtr != (textPtr ?
+ TkBTreeGetStartLine(textPtr) : sharedTextPtr->startMarker->sectionPtr->linePtr));
+
+ return TkBTreeGetLogicalLine(sharedTextPtr, textPtr, linePtr->prevPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeCountLines --
+ *
+ * This function counts the number of lines inside a given range.
+ *
+ * Results:
+ * The return value is the number of lines inside a given range.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+unsigned
+TkBTreeCountLines(
+ const TkTextBTree tree,
+ const TkTextLine *linePtr1, /* Start counting at this line. */
+ const TkTextLine *linePtr2) /* Stop counting at this line (don't count this line). */
+{
+ assert(TkBTreeLinesTo(tree, NULL, linePtr1, NULL) <= TkBTreeLinesTo(tree, NULL, linePtr2, NULL));
+
+ if (linePtr1 == linePtr2) {
+ return 0; /* this is catching a frequent case */
+ }
+ if (linePtr1->nextPtr == linePtr2) {
+ return 1; /* this is catching a frequent case */
+ }
+
+ return TkBTreeLinesTo(tree, NULL, linePtr2, NULL) - TkBTreeLinesTo(tree, NULL, linePtr1, NULL);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetPeer --
+ *
+ * Set this index to the start of the line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+void
+TkTextIndexSetPeer(
+ TkTextIndex *indexPtr,
+ TkText *textPtr)
+{
+ assert(indexPtr->tree);
+
+ indexPtr->textPtr = textPtr;
+ indexPtr->priv.lineNoRel = -1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexGetShared --
+ *
+ * Get the shared resource of this index.
+ *
+ * Results:
+ * The shared resource.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkSharedText *
+TkTextIndexGetShared(
+ const TkTextIndex *indexPtr)
+{
+ assert(indexPtr);
+ assert(indexPtr->tree);
+ return TkBTreeGetShared(indexPtr->tree);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeGetTags --
+ *
+ * Return information about all of the tags that are associated with a
+ * particular character in a B-tree of text.
+ *
+ * Results:
+ * The return value is the root of the tag chain, containing all tags
+ * associated with the character at the position given by linePtr and ch.
+ * If there are no tags at the given character then a NULL pointer is
+ * returned.
+ *
+ * Side effects:
+ * The attribute nextPtr of TkTextTag will be modified for any tag.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextTag *
+TkBTreeGetTags(
+ const TkTextIndex *indexPtr)/* Indicates a particular position in the B-tree. */
+{
+ const TkTextSegment *segPtr = TkTextIndexGetContentSegment(indexPtr, NULL);
+ return TkBTreeGetSegmentTags(TkTextIndexGetShared(indexPtr), segPtr, indexPtr->textPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexGetLine --
+ *
+ * Get the line pointer of this index.
+ *
+ * Results:
+ * The line pointer.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextLine *
+TkTextIndexGetLine(
+ const TkTextIndex *indexPtr)/* Indicates a particular position in the B-tree. */
+{
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+
+ return indexPtr->priv.linePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetToLastChar2 --
+ *
+ * Set the new line pointer, and set this index to one before the
+ * end of the line.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+void
+TkTextIndexSetToLastChar2(
+ TkTextIndex *indexPtr, /* Pointer to index. */
+ TkTextLine *linePtr) /* Pointer to line. */
+{
+ assert(indexPtr->tree);
+ assert(linePtr);
+ assert(linePtr->parentPtr); /* expired? */
+
+ indexPtr->priv.linePtr = linePtr;
+ indexPtr->priv.lineNo = -1;
+ indexPtr->priv.lineNoRel = -1;
+ TkTextIndexSetToLastChar(indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexGetSegment --
+ *
+ * Get the pointer to stored segment.
+ *
+ * Results:
+ * Pointer to the stored segment, this can be NULL.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+TkTextSegment *
+TkTextIndexGetSegment(
+ const TkTextIndex *indexPtr)/* Pointer to index. */
+{
+ TkTextSegment *segPtr;
+
+ assert(indexPtr->tree);
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+
+ segPtr = indexPtr->priv.segPtr;
+
+ if (!segPtr
+ || (indexPtr->priv.isCharSegment
+ && TkBTreeEpoch(indexPtr->tree) != indexPtr->stateEpoch)) {
+ return NULL;
+ }
+
+ assert(!segPtr || segPtr->typePtr); /* expired? */
+ assert(!segPtr || segPtr->sectionPtr); /* linked? */
+ assert(!segPtr || segPtr->sectionPtr->linePtr == indexPtr->priv.linePtr);
+
+ return segPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSave --
+ *
+ * Makes the index robust, so that it can be rebuild after modifications.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+void
+TkTextIndexSave(
+ TkTextIndex *indexPtr)
+{
+ TkTextIndexGetLineNumber(indexPtr, indexPtr->textPtr);
+ TkTextIndexGetByteIndex(indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSameLines --
+ *
+ * Test whether both given indicies are referring the same line.
+ *
+ * Results:
+ * Return true if both indices are referring the same line, otherwise
+ * false will be returned.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+bool
+TkTextIndexSameLines(
+ const TkTextIndex *indexPtr1, /* Pointer to index. */
+ const TkTextIndex *indexPtr2) /* Pointer to index. */
+{
+ assert(indexPtr1->priv.linePtr);
+ assert(indexPtr2->priv.linePtr);
+ assert(indexPtr1->priv.linePtr->parentPtr); /* expired? */
+ assert(indexPtr2->priv.linePtr->parentPtr); /* expired? */
+
+ return indexPtr1->priv.linePtr == indexPtr2->priv.linePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetEpoch --
+ *
+ * Update epoch of given index, don't clear the segment pointer.
+ * Use this function with care, it must be ensured that the
+ * segment pointer is still valid.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+void
+TkTextIndexUpdateEpoch(
+ TkTextIndex *indexPtr,
+ unsigned epoch)
+{
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+
+ indexPtr->stateEpoch = epoch;
+ indexPtr->priv.lineNo = -1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexInvalidate --
+ *
+ * Clear position attributes: segPtr, and byteIndex.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The given index will be in an invalid state, the TkIndexGet*
+ * functions cannot be used.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+void
+TkTextIndexInvalidate(
+ TkTextIndex *indexPtr) /* Pointer to index. */
+{
+ indexPtr->priv.segPtr = NULL;
+ indexPtr->priv.byteIndex = -1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexSetEpoch --
+ *
+ * Set epoch of given index, and clear the segment pointer if
+ * the new epoch is different from last epoch.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+void
+TkTextIndexSetEpoch(
+ TkTextIndex *indexPtr,
+ unsigned epoch)
+{
+ assert(indexPtr->priv.linePtr);
+ assert(indexPtr->priv.linePtr->parentPtr); /* expired? */
+
+ if (indexPtr->stateEpoch != epoch) {
+ indexPtr->stateEpoch = epoch;
+ indexPtr->priv.segPtr = NULL;
+ indexPtr->priv.lineNo = -1;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeGetNumberOfDisplayLines --
+ *
+ * Return the current number of display lines. This is the number
+ * of lines known by the B-Tree (not the number of lines known
+ * by the display stuff).
+ *
+ * We are including the implementation into this private header file,
+ * because it uses some facts only known by the display stuff.
+ *
+ * Results:
+ * Returns the current number of display lines (known by B-Tree).
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+int
+TkBTreeGetNumberOfDisplayLines(
+ const TkTextPixelInfo *pixelInfo)
+{
+ const TkTextDispLineInfo *dispLineInfo;
+
+ if (pixelInfo->height == 0) {
+ return 0;
+ }
+ if (!(dispLineInfo = pixelInfo->dispLineInfo)) {
+ return 1;
+ }
+ if (pixelInfo->epoch & 0x80000000) {
+ /*
+ * This will return the old number of display lines, because the
+ * computation of the corresponding logical line is currently in
+ * progress, and unfinished.
+ */
+ return dispLineInfo->entry[dispLineInfo->numDispLines].pixels;
+ }
+ return dispLineInfo->numDispLines;
+}
+
+#if TCL_UTF_MAX <= 4 && TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION < 7
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkUtfToUniChar --
+ *
+ * Only needed for backporting, see source of version 8.7 about
+ * this function.
+ *
+ * IMO this function is only a bad hack, Tcl should provide the
+ * appropriate functionality.
+ *
+ *----------------------------------------------------------------------
+ */
+
+inline
+int
+TkUtfToUniChar(const char *src, int *chPtr)
+{
+ Tcl_UniChar ch;
+ int result = Tcl_UtfToUniChar(src, &ch);
+ *chPtr = ch;
+ return result;
+}
+
+#endif /* end of backport for 8.6/8.5 */
+
+#undef _TK_NEED_IMPLEMENTATION
+#endif /* _TK_NEED_IMPLEMENTATION */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkTextTag.c b/generic/tkTextTag.c
index d9329f5..9c0e5a0 100644
--- a/generic/tkTextTag.c
+++ b/generic/tkTextTag.c
@@ -7,6 +7,7 @@
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -15,6 +16,33 @@
#include "default.h"
#include "tkInt.h"
#include "tkText.h"
+#include "tkTextUndo.h"
+#include "tkTextTagSet.h"
+#include "tkBitField.h"
+#include <assert.h>
+#include <stdlib.h>
+
+#ifndef MAX
+# define MAX(a,b) (((int) a) < ((int) b) ? b : a)
+#endif
+
+#if NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
+
+/*
+ * Support of tk8.5.
+ */
+#ifdef CONST
+# undef CONST
+#endif
+#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5
+# define CONST
+#else
+# define CONST const
+#endif
/*
* The 'TkWrapMode' enum in tkText.h is used to define a type for the -wrap
@@ -23,18 +51,29 @@
* a whole is not.
*/
-static const char *const wrapStrings[] = {
+static const char *CONST wrapStrings[] = {
"char", "none", "word", "", NULL
};
/*
+ * The 'TkTextSpaceMode' enum in tkText.h is used to define a type for the -spacing
+ * option of tags in a Text widget. These values are used as indices into the
+ * string table below. Tags are allowed an empty wrap value, but the widget as
+ * a whole is not.
+ */
+
+static const char *const spaceModeStrings[] = {
+ "none", "exact", "trim", NULL
+};
+
+/*
* The 'TkTextTabStyle' enum in tkText.h is used to define a type for the
* -tabstyle option of the Text widget. These values are used as indices into
- * the string table below. Tags are allowed an empty tabstyle value, but the
+ * the string table below. Tags are allowed an empty wrap value, but the
* widget as a whole is not.
*/
-static const char *const tabStyleStrings[] = {
+static const char *CONST tabStyleStrings[] = {
"tabular", "wordprocessor", "", NULL
};
@@ -44,37 +83,51 @@ static const Tk_OptionSpec tagOptionSpecs[] = {
{TK_OPTION_BITMAP, "-bgstipple", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, bgStipple), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_PIXELS, "-borderwidth", NULL, NULL,
- NULL, Tk_Offset(TkTextTag, borderWidthPtr), Tk_Offset(TkTextTag, borderWidth),
+ "0", Tk_Offset(TkTextTag, borderWidthPtr), Tk_Offset(TkTextTag, borderWidth),
TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT, 0, 0},
{TK_OPTION_STRING, "-elide", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, elideString),
+ "0", -1, Tk_Offset(TkTextTag, elideString),
TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ {TK_OPTION_COLOR, "-eolcolor", NULL, NULL,
+ NULL, -1, Tk_Offset(TkTextTag, eolColor), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_BITMAP, "-fgstipple", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, fgStipple), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_FONT, "-font", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, tkfont), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_COLOR, "-foreground", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, fgColor), TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_COLOR, "-hyphencolor", NULL, NULL,
+ NULL, -1, Tk_Offset(TkTextTag, hyphenColor), TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_STRING, "-hyphenrules", NULL, NULL,
+ NULL, Tk_Offset(TkTextTag, hyphenRulesPtr), -1, TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_STRING, "-indentbackground", NULL, NULL,
+ "0", -1, Tk_Offset(TkTextTag, indentBgString),
+ TK_OPTION_DONT_SET_DEFAULT|TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-justify", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, justifyString), TK_OPTION_NULL_OK, 0,0},
+ NULL, -1, Tk_Offset(TkTextTag, justifyString), TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_STRING, "-lang", NULL, NULL,
+ NULL, Tk_Offset(TkTextTag, langPtr), -1, TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-lmargin1", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, lMargin1String), TK_OPTION_NULL_OK,0,0},
+ NULL, -1, Tk_Offset(TkTextTag, lMargin1String), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-lmargin2", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, lMargin2String), TK_OPTION_NULL_OK,0,0},
+ NULL, -1, Tk_Offset(TkTextTag, lMargin2String), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_BORDER, "-lmargincolor", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, lMarginColor), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-offset", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, offsetString), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-overstrike", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, overstrikeString),
- TK_OPTION_NULL_OK, 0, 0},
+ NULL, -1, Tk_Offset(TkTextTag, overstrikeString), TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_COLOR, "-overstrikecolor", NULL, NULL,
+ NULL, -1, Tk_Offset(TkTextTag, overstrikeColor), TK_OPTION_NULL_OK, 0, 0},
+#if SUPPORT_DEPRECATED_TAG_OPTIONS
{TK_OPTION_COLOR, "-overstrikefg", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, overstrikeColor),
- TK_OPTION_NULL_OK, 0, 0},
+ NULL, -1, Tk_Offset(TkTextTag, overstrikeColor), TK_OPTION_NULL_OK, 0,
+ TK_TEXT_DEPRECATED_OVERSTRIKE_FG},
+#endif /* SUPPORT_DEPRECATED_TAG_OPTIONS */
{TK_OPTION_STRING, "-relief", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, reliefString), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-rmargin", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, rMarginString), TK_OPTION_NULL_OK, 0,0},
+ NULL, -1, Tk_Offset(TkTextTag, rMarginString), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_BORDER, "-rmargincolor", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, rMarginColor), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_BORDER, "-selectbackground", NULL, NULL,
@@ -82,40 +135,130 @@ static const Tk_OptionSpec tagOptionSpecs[] = {
{TK_OPTION_COLOR, "-selectforeground", NULL, NULL,
NULL, -1, Tk_Offset(TkTextTag, selFgColor), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-spacing1", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, spacing1String), TK_OPTION_NULL_OK,0,0},
+ NULL, -1, Tk_Offset(TkTextTag, spacing1String), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-spacing2", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, spacing2String), TK_OPTION_NULL_OK,0,0},
+ NULL, -1, Tk_Offset(TkTextTag, spacing2String), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-spacing3", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, spacing3String), TK_OPTION_NULL_OK,0,0},
+ NULL, -1, Tk_Offset(TkTextTag, spacing3String), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING, "-tabs", NULL, NULL,
NULL, Tk_Offset(TkTextTag, tabStringPtr), -1, TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_STRING_TABLE, "-tabstyle", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, tabStyle),
- TK_OPTION_NULL_OK, tabStyleStrings, 0},
+ NULL, -1, Tk_Offset(TkTextTag, tabStyle), TK_OPTION_NULL_OK, tabStyleStrings, 0},
{TK_OPTION_STRING, "-underline", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, underlineString),
- TK_OPTION_NULL_OK, 0, 0},
+ NULL, -1, Tk_Offset(TkTextTag, underlineString), TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_COLOR, "-underlinecolor", NULL, NULL,
+ NULL, -1, Tk_Offset(TkTextTag, underlineColor), TK_OPTION_NULL_OK, 0, 0},
+#if SUPPORT_DEPRECATED_TAG_OPTIONS
{TK_OPTION_COLOR, "-underlinefg", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, underlineColor),
- TK_OPTION_NULL_OK, 0, 0},
+ NULL, -1, Tk_Offset(TkTextTag, underlineColor), TK_OPTION_NULL_OK, 0,
+ TK_TEXT_DEPRECATED_UNDERLINE_FG},
+#endif /* SUPPORT_DEPRECATED_TAG_OPTIONS */
+ {TK_OPTION_BOOLEAN, "-undo", NULL, NULL,
+ "1", -1, Tk_Offset(TkTextTag, undo), 0, 0, 0},
{TK_OPTION_STRING_TABLE, "-wrap", NULL, NULL,
- NULL, -1, Tk_Offset(TkTextTag, wrapMode),
- TK_OPTION_NULL_OK, wrapStrings, 0},
+ NULL, -1, Tk_Offset(TkTextTag, wrapMode), TK_OPTION_NULL_OK, wrapStrings, 0},
{TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0}
};
+DEBUG_ALLOC(extern unsigned tkTextCountNewTag);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroyTag);
+DEBUG_ALLOC(extern unsigned tkTextCountNewUndoToken);
+DEBUG_ALLOC(extern unsigned tkTextCountDestroyUndoToken);
+
/*
* Forward declarations for functions defined later in this file:
*/
-static void ChangeTagPriority(TkText *textPtr, TkTextTag *tagPtr,
- int prio);
-static TkTextTag * FindTag(Tcl_Interp *interp, TkText *textPtr,
- Tcl_Obj *tagName);
-static void SortTags(int numTags, TkTextTag **tagArrayPtr);
-static int TagSortProc(const void *first, const void *second);
+static bool ChangeTagPriority(TkSharedText *sharedTextPtr, TkTextTag *tagPtr,
+ unsigned newPriority, bool undo);
static void TagBindEvent(TkText *textPtr, XEvent *eventPtr,
- int numTags, TkTextTag **tagArrayPtr);
+ unsigned numTags, TkTextTag **tagArrayPtr);
+static bool TagAddRemove(TkText *textPtr, const TkTextIndex *index1Ptr,
+ const TkTextIndex *index2Ptr, TkTextTag *tagPtr, bool add);
+static void FindTags(Tcl_Interp *interp, TkText *textPtr, const TkTextSegment *segPtr,
+ bool discardSelection);
+static void AppendTags(Tcl_Interp *interp, unsigned numTags, TkTextTag **tagArray);
+static void GrabSelection(TkText *textPtr, const TkTextTag *tagPtr, bool add);
+static TkTextTag * FindTag(Tcl_Interp *interp, const TkText *textPtr, Tcl_Obj *tagName);
+static int EnumerateTags(Tcl_Interp *interp, TkText *textPtr, int objc,
+ Tcl_Obj *const *objv);
+
+/*
+ * We need some private undo/redo stuff.
+ */
+
+static void UndoChangeTagPriorityPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static Tcl_Obj *UndoChangeTagPriorityGetCommand(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *UndoChangeTagPriorityInspect(const TkSharedText *, const TkTextUndoToken *);
+
+static const Tk_UndoType undoTokenTagPriorityType = {
+ TK_TEXT_UNDO_TAG_PRIORITY, /* action */
+ UndoChangeTagPriorityGetCommand, /* commandProc */
+ UndoChangeTagPriorityPerform, /* undoProc */
+ NULL, /* destroyProc */
+ NULL, /* rangeProc */
+ UndoChangeTagPriorityInspect /* inspectProc */
+};
+
+static const Tk_UndoType redoTokenTagPriorityType = {
+ TK_TEXT_REDO_TAG_PRIORITY, /* action */
+ UndoChangeTagPriorityGetCommand, /* commandProc */
+ UndoChangeTagPriorityPerform, /* undoProc */
+ NULL, /* destroyProc */
+ NULL, /* rangeProc */
+ UndoChangeTagPriorityInspect /* inspectProc */
+};
+
+typedef struct UndoTokenTagPriority {
+ const Tk_UndoType *undoType;
+ TkTextTag *tagPtr;
+ uint32_t priority;
+} UndoTokenTagPriority;
+
+static Tcl_Obj *
+UndoChangeTagPriorityGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenTagPriority *token = (const UndoTokenTagPriority *) item;
+ Tcl_Obj *objPtr = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("tag", -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("priority", -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(token->tagPtr->name, -1));
+ return objPtr;
+}
+
+static Tcl_Obj *
+UndoChangeTagPriorityInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenTagPriority *token = (const UndoTokenTagPriority *) item;
+ Tcl_Obj *objPtr = UndoChangeTagPriorityGetCommand(sharedTextPtr, item);
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewIntObj(token->priority));
+ return objPtr;
+}
+
+static void
+UndoChangeTagPriorityPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ UndoTokenTagPriority *token = (UndoTokenTagPriority *) undoInfo->token;
+ unsigned oldPriority = token->tagPtr->priority;
+
+ ChangeTagPriority(sharedTextPtr, token->tagPtr, token->priority, true);
+
+ if (redoInfo) {
+ redoInfo->token = undoInfo->token;
+ redoInfo->token->undoType = &redoTokenTagPriorityType;
+ token->priority = oldPriority;
+ }
+}
/*
*--------------------------------------------------------------
@@ -137,200 +280,96 @@ static void TagBindEvent(TkText *textPtr, XEvent *eventPtr,
int
TkTextTagCmd(
- register TkText *textPtr, /* Information about text widget. */
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "tag". */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "tag". */
{
static const char *const tagOptionStrings[] = {
- "add", "bind", "cget", "configure", "delete", "lower", "names",
- "nextrange", "prevrange", "raise", "ranges", "remove", NULL
+ "add", "bind", "cget", "clear", "configure", "delete", "findnext", "findprev",
+ "getrange", "lower", "names", "nextrange", "prevrange", "raise", "ranges",
+ "remove", NULL
};
enum tagOptions {
- TAG_ADD, TAG_BIND, TAG_CGET, TAG_CONFIGURE, TAG_DELETE, TAG_LOWER,
- TAG_NAMES, TAG_NEXTRANGE, TAG_PREVRANGE, TAG_RAISE, TAG_RANGES,
+ TAG_ADD, TAG_BIND, TAG_CGET, TAG_CLEAR, TAG_CONFIGURE, TAG_DELETE, TAG_FINDNEXT, TAG_FINDPREV,
+ TAG_GETRANGE, TAG_LOWER, TAG_NAMES, TAG_NEXTRANGE, TAG_PREVRANGE, TAG_RAISE, TAG_RANGES,
TAG_REMOVE
};
int optionIndex, i;
- register TkTextTag *tagPtr;
+ TkTextTag *tagPtr;
TkTextIndex index1, index2;
+ TkSharedText *sharedTextPtr;
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
+ Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?");
return TCL_ERROR;
}
-
- if (Tcl_GetIndexFromObjStruct(interp, objv[2], tagOptionStrings,
- sizeof(char *), "tag option", 0, &optionIndex) != TCL_OK) {
+ if (Tcl_GetIndexFromObjStruct(interp, objv[2], tagOptionStrings, sizeof(char *),
+ "tag option", 0, &optionIndex) != TCL_OK) {
return TCL_ERROR;
}
+ sharedTextPtr = textPtr->sharedTextPtr;
+
switch ((enum tagOptions)optionIndex) {
case TAG_ADD:
case TAG_REMOVE: {
- int addTag;
+ bool addTag;
+ bool anyChanges = false;
- if (((enum tagOptions)optionIndex) == TAG_ADD) {
- addTag = 1;
- } else {
- addTag = 0;
- }
+ addTag = ((enum tagOptions) optionIndex) == TAG_ADD;
if (objc < 5) {
- Tcl_WrongNumArgs(interp, 3, objv,
- "tagName index1 ?index2 index1 index2 ...?");
+ Tcl_WrongNumArgs(interp, 3, objv, "tagName index1 ?index2 index1 index2 ...?");
return TCL_ERROR;
}
tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3]), NULL);
if (tagPtr->elide) {
- /*
- * Indices are potentially obsolete after adding or removing
- * elided character ranges, especially indices having "display"
- * or "any" submodifier, therefore increase the epoch.
- */
- textPtr->sharedTextPtr->stateEpoch++;
+ /*
+ * Indices are potentially obsolete after adding or removing
+ * elided character ranges, especially indices having "display"
+ * or "any" submodifier, therefore increase the epoch.
+ */
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
}
for (i = 4; i < objc; i += 2) {
- if (TkTextGetObjIndex(interp, textPtr, objv[i],
- &index1) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i], &index1)) {
return TCL_ERROR;
}
- if (objc > (i+1)) {
- if (TkTextGetObjIndex(interp, textPtr, objv[i+1],
- &index2) != TCL_OK) {
+ if (objc > i + 1) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i + 1], &index2)) {
return TCL_ERROR;
}
- if (TkTextIndexCmp(&index1, &index2) >= 0) {
- return TCL_OK;
+ if (TkTextIndexCompare(&index1, &index2) >= 0) {
+ continue;
}
} else {
- index2 = index1;
- TkTextIndexForwChars(NULL,&index2, 1, &index2, COUNT_INDICES);
+ TkTextIndexForwChars(textPtr, &index1, 1, &index2, COUNT_INDICES);
}
-
- if (tagPtr->affectsDisplay) {
- TkTextRedrawTag(textPtr->sharedTextPtr, NULL, &index1, &index2,
- tagPtr, !addTag);
- } else {
- /*
- * Still need to trigger enter/leave events on tags that have
- * changed.
- */
-
- TkTextEventuallyRepick(textPtr);
+ if (TagAddRemove(textPtr, &index1, &index2, tagPtr, addTag)) {
+ anyChanges = true;
}
- if (TkBTreeTag(&index1, &index2, tagPtr, addTag)) {
- /*
- * If the tag is "sel", and we actually adjusted something
- * then grab the selection if we're supposed to export it and
- * don't already have it.
- *
- * Also, invalidate partially-completed selection retrievals.
- * We only need to check whether the tag is "sel" for this
- * textPtr (not for other peer widget's "sel" tags) because we
- * cannot reach this code path with a different widget's "sel"
- * tag.
- */
-
- if (tagPtr == textPtr->selTagPtr) {
- /*
- * Send an event that the selection changed. This is
- * equivalent to:
- * event generate $textWidget <<Selection>>
- */
-
- TkTextSelectionEvent(textPtr);
-
- if (addTag && textPtr->exportSelection
- && !(textPtr->flags & GOT_SELECTION)) {
- Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY,
- TkTextLostSelection, textPtr);
- textPtr->flags |= GOT_SELECTION;
- }
- textPtr->abortSelections = 1;
- }
+ }
+ if (anyChanges) {
+ if (tagPtr == textPtr->selTagPtr) {
+ GrabSelection(textPtr, tagPtr, addTag);
+ }
+ if (tagPtr->undo) {
+ TkTextUpdateAlteredFlag(sharedTextPtr);
}
+ /* still need to trigger enter/leave events on tags that have changed */
+ TkTextEventuallyRepick(textPtr);
}
break;
}
case TAG_BIND:
- if ((objc < 4) || (objc > 6)) {
+ if (objc < 4 || objc > 6) {
Tcl_WrongNumArgs(interp, 3, objv, "tagName ?sequence? ?command?");
return TCL_ERROR;
}
tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3]), NULL);
-
- /*
- * Make a binding table if the widget doesn't already have one.
- */
-
- if (textPtr->sharedTextPtr->bindingTable == NULL) {
- textPtr->sharedTextPtr->bindingTable =
- Tk_CreateBindingTable(interp);
- }
-
- if (objc == 6) {
- int append = 0;
- unsigned long mask;
- const char *fifth = Tcl_GetString(objv[5]);
-
- if (fifth[0] == 0) {
- return Tk_DeleteBinding(interp,
- textPtr->sharedTextPtr->bindingTable,
- (ClientData) tagPtr->name, Tcl_GetString(objv[4]));
- }
- if (fifth[0] == '+') {
- fifth++;
- append = 1;
- }
- mask = Tk_CreateBinding(interp,
- textPtr->sharedTextPtr->bindingTable,
- (ClientData) tagPtr->name, Tcl_GetString(objv[4]), fifth,
- append);
- if (mask == 0) {
- return TCL_ERROR;
- }
- if (mask & (unsigned) ~(ButtonMotionMask|Button1MotionMask
- |Button2MotionMask|Button3MotionMask|Button4MotionMask
- |Button5MotionMask|ButtonPressMask|ButtonReleaseMask
- |EnterWindowMask|LeaveWindowMask|KeyPressMask
- |KeyReleaseMask|PointerMotionMask|VirtualEventMask)) {
- Tk_DeleteBinding(interp, textPtr->sharedTextPtr->bindingTable,
- (ClientData) tagPtr->name, Tcl_GetString(objv[4]));
- Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "requested illegal events; only key, button, motion,"
- " enter, leave, and virtual events may be used", -1));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "TAG_BIND_EVENT",NULL);
- return TCL_ERROR;
- }
- } else if (objc == 5) {
- const char *command;
-
- command = Tk_GetBinding(interp,
- textPtr->sharedTextPtr->bindingTable,
- (ClientData) tagPtr->name, Tcl_GetString(objv[4]));
- if (command == NULL) {
- const char *string = Tcl_GetString(Tcl_GetObjResult(interp));
-
- /*
- * Ignore missing binding errors. This is a special hack that
- * relies on the error message returned by FindSequence in
- * tkBind.c.
- */
-
- if (string[0] != '\0') {
- return TCL_ERROR;
- }
- Tcl_ResetResult(interp);
- } else {
- Tcl_SetObjResult(interp, Tcl_NewStringObj(command, -1));
- }
- } else {
- Tk_GetAllBindings(interp, textPtr->sharedTextPtr->bindingTable,
- (ClientData) tagPtr->name);
- }
- break;
+ return TkTextBindEvent(interp, objc - 4, objv + 4, sharedTextPtr,
+ &sharedTextPtr->tagBindingTable, tagPtr->name);
case TAG_CGET:
if (objc != 5) {
Tcl_WrongNumArgs(interp, 1, objv, "tag cget tagName option");
@@ -338,625 +377,1275 @@ TkTextTagCmd(
} else {
Tcl_Obj *objPtr;
- tagPtr = FindTag(interp, textPtr, objv[3]);
- if (tagPtr == NULL) {
+ if (!(tagPtr = FindTag(interp, textPtr, objv[3]))) {
return TCL_ERROR;
}
objPtr = Tk_GetOptionValue(interp, (char *) tagPtr,
tagPtr->optionTable, objv[4], textPtr->tkwin);
- if (objPtr == NULL) {
+ if (!objPtr) {
return TCL_ERROR;
}
Tcl_SetObjResult(interp, objPtr);
return TCL_OK;
}
break;
- case TAG_CONFIGURE: {
- int newTag;
+ case TAG_CLEAR: {
+ bool discardSelection;
+ unsigned epoch, countTags;
+ TkTextTag **arrayPtr;
+ bool anyChanges;
+ int arg;
if (objc < 4) {
- Tcl_WrongNumArgs(interp, 3, objv,
- "tagName ?-option? ?value? ?-option value ...?");
+ Tcl_WrongNumArgs(interp, 3, objv, "?-discardselection? index1 ?index2 index1 index2 ...?");
return TCL_ERROR;
}
- tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3]), &newTag);
- if (objc <= 5) {
- Tcl_Obj *objPtr = Tk_GetOptionInfo(interp, (char *) tagPtr,
- tagPtr->optionTable,
- (objc == 5) ? objv[4] : NULL, textPtr->tkwin);
- if (objPtr == NULL) {
- return TCL_ERROR;
- }
- Tcl_SetObjResult(interp, objPtr);
- return TCL_OK;
- } else {
- int result = TCL_OK;
+ arg = 3;
- if (Tk_SetOptions(interp, (char *) tagPtr, tagPtr->optionTable,
- objc-4, objv+4, textPtr->tkwin, NULL, NULL) != TCL_OK) {
+ if (objc > 4 && *Tcl_GetString(objv[arg]) == '-') {
+ if (strcmp(Tcl_GetString(objv[arg++]), "-discardselection") == 0) {
+ discardSelection = true;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -discardselection", Tcl_GetString(objv[3])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL);
return TCL_ERROR;
}
+ }
- /*
- * Some of the configuration options, like -underline and
- * -justify, require additional translation (this is needed
- * because we need to distinguish a particular value of an option
- * from "unspecified").
- */
+ discardSelection = false;
+ epoch = TkBTreeEpoch(sharedTextPtr->tree);
+ arrayPtr = malloc(sharedTextPtr->numEnabledTags * sizeof(TkTextTag *));
+ countTags = 0;
+ anyChanges = false;
- if (tagPtr->borderWidth < 0) {
- tagPtr->borderWidth = 0;
- }
- if (tagPtr->reliefString != NULL) {
- if (Tk_GetRelief(interp, tagPtr->reliefString,
- &tagPtr->relief) != TCL_OK) {
- return TCL_ERROR;
- }
- }
- if (tagPtr->justifyString != NULL) {
- if (Tk_GetJustify(interp, tagPtr->justifyString,
- &tagPtr->justify) != TCL_OK) {
- return TCL_ERROR;
- }
- }
- if (tagPtr->lMargin1String != NULL) {
- if (Tk_GetPixels(interp, textPtr->tkwin,
- tagPtr->lMargin1String, &tagPtr->lMargin1) != TCL_OK) {
- return TCL_ERROR;
- }
- }
- if (tagPtr->lMargin2String != NULL) {
- if (Tk_GetPixels(interp, textPtr->tkwin,
- tagPtr->lMargin2String, &tagPtr->lMargin2) != TCL_OK) {
- return TCL_ERROR;
- }
- }
- if (tagPtr->offsetString != NULL) {
- if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->offsetString,
- &tagPtr->offset) != TCL_OK) {
- return TCL_ERROR;
- }
- }
- if (tagPtr->overstrikeString != NULL) {
- if (Tcl_GetBoolean(interp, tagPtr->overstrikeString,
- &tagPtr->overstrike) != TCL_OK) {
- return TCL_ERROR;
- }
- }
- if (tagPtr->rMarginString != NULL) {
- if (Tk_GetPixels(interp, textPtr->tkwin,
- tagPtr->rMarginString, &tagPtr->rMargin) != TCL_OK) {
- return TCL_ERROR;
- }
- }
- if (tagPtr->spacing1String != NULL) {
- if (Tk_GetPixels(interp, textPtr->tkwin,
- tagPtr->spacing1String, &tagPtr->spacing1) != TCL_OK) {
- return TCL_ERROR;
- }
- if (tagPtr->spacing1 < 0) {
- tagPtr->spacing1 = 0;
- }
- }
- if (tagPtr->spacing2String != NULL) {
- if (Tk_GetPixels(interp, textPtr->tkwin,
- tagPtr->spacing2String, &tagPtr->spacing2) != TCL_OK) {
- return TCL_ERROR;
- }
- if (tagPtr->spacing2 < 0) {
- tagPtr->spacing2 = 0;
- }
- }
- if (tagPtr->spacing3String != NULL) {
- if (Tk_GetPixels(interp, textPtr->tkwin,
- tagPtr->spacing3String, &tagPtr->spacing3) != TCL_OK) {
- return TCL_ERROR;
- }
- if (tagPtr->spacing3 < 0) {
- tagPtr->spacing3 = 0;
- }
- }
- if (tagPtr->tabArrayPtr != NULL) {
- ckfree(tagPtr->tabArrayPtr);
- tagPtr->tabArrayPtr = NULL;
- }
- if (tagPtr->tabStringPtr != NULL) {
- tagPtr->tabArrayPtr =
- TkTextGetTabs(interp, textPtr, tagPtr->tabStringPtr);
- if (tagPtr->tabArrayPtr == NULL) {
- return TCL_ERROR;
- }
+ for (i = arg; i < objc; i += 2) {
+ TkTextIndex index1, index2;
+ TkTextTag *tagPtr;
+
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i], &index1)) {
+ return TCL_ERROR;
}
- if (tagPtr->underlineString != NULL) {
- if (Tcl_GetBoolean(interp, tagPtr->underlineString,
- &tagPtr->underline) != TCL_OK) {
+
+ if (objc > i + 1) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i + 1], &index2)) {
return TCL_ERROR;
}
- }
- if (tagPtr->elideString != NULL) {
- if (Tcl_GetBoolean(interp, tagPtr->elideString,
- &tagPtr->elide) != TCL_OK) {
- return TCL_ERROR;
+ if (TkTextIndexCompare(&index1, &index2) >= 0) {
+ continue;
}
-
- /*
- * Indices are potentially obsolete after changing -elide,
- * especially those computed with "display" or "any"
- * submodifier, therefore increase the epoch.
- */
-
- textPtr->sharedTextPtr->stateEpoch++;
+ } else {
+ TkTextIndexForwChars(textPtr, &index1, 1, &index2, COUNT_INDICES);
}
- /*
- * If the "sel" tag was changed, be sure to mirror information
- * from the tag back into the text widget record. NOTE: we don't
- * have to free up information in the widget record before
- * overwriting it, because it was mirrored in the tag and hence
- * freed when the tag field was overwritten.
- */
-
- if (tagPtr == textPtr->selTagPtr) {
- if (tagPtr->selBorder == NULL) {
- textPtr->selBorder = tagPtr->border;
- } else {
- textPtr->selBorder = tagPtr->selBorder;
- }
- textPtr->selBorderWidth = tagPtr->borderWidth;
- textPtr->selBorderWidthPtr = tagPtr->borderWidthPtr;
- if (tagPtr->selFgColor == NULL) {
- textPtr->selFgColorPtr = tagPtr->fgColor;
- } else {
- textPtr->selFgColorPtr = tagPtr->selFgColor;
- }
+ if (!discardSelection) {
+ TkTextClearSelection(sharedTextPtr, &index1, &index2);
}
- tagPtr->affectsDisplay = 0;
- tagPtr->affectsDisplayGeometry = 0;
- if ((tagPtr->elideString != NULL)
- || (tagPtr->tkfont != None)
- || (tagPtr->justifyString != NULL)
- || (tagPtr->lMargin1String != NULL)
- || (tagPtr->lMargin2String != NULL)
- || (tagPtr->offsetString != NULL)
- || (tagPtr->rMarginString != NULL)
- || (tagPtr->spacing1String != NULL)
- || (tagPtr->spacing2String != NULL)
- || (tagPtr->spacing3String != NULL)
- || (tagPtr->tabStringPtr != NULL)
- || (tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE)
- || (tagPtr->wrapMode != TEXT_WRAPMODE_NULL)) {
- tagPtr->affectsDisplay = 1;
- tagPtr->affectsDisplayGeometry = 1;
- }
- if ((tagPtr->border != NULL)
- || (tagPtr->selBorder != NULL)
- || (tagPtr->reliefString != NULL)
- || (tagPtr->bgStipple != None)
- || (tagPtr->fgColor != NULL)
- || (tagPtr->selFgColor != NULL)
- || (tagPtr->fgStipple != None)
- || (tagPtr->overstrikeString != NULL)
- || (tagPtr->overstrikeColor != NULL)
- || (tagPtr->underlineString != NULL)
- || (tagPtr->underlineColor != NULL)
- || (tagPtr->lMarginColor != NULL)
- || (tagPtr->rMarginColor != NULL)) {
- tagPtr->affectsDisplay = 1;
+ if ((tagPtr = TkTextClearTags(sharedTextPtr, textPtr, &index1, &index2, discardSelection))) {
+ for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
+ if (tagPtr->epoch != epoch) {
+ tagPtr->epoch = epoch;
+ arrayPtr[countTags++] = tagPtr;
+
+ if (tagPtr == textPtr->selTagPtr) {
+ GrabSelection(textPtr, tagPtr, false);
+ }
+ if (tagPtr->undo) {
+ anyChanges = true;
+ }
+ }
+ }
}
- if (!newTag) {
- /*
- * This line is not necessary if this is a new tag, since it
- * can't possibly have been applied to anything yet.
- */
-
- /*
- * VMD: If this is the 'sel' tag, then we don't need to call
- * this for all peers, unless we actually want to synchronize
- * sel-style changes across the peers.
- */
+ }
- TkTextRedrawTag(textPtr->sharedTextPtr, NULL,
- NULL, NULL, tagPtr, 1);
- }
- return result;
+ if (anyChanges) {
+ TkTextUpdateAlteredFlag(sharedTextPtr);
}
+ AppendTags(interp, countTags, arrayPtr);
+ free(arrayPtr);
break;
}
+ case TAG_CONFIGURE:
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "tagName ?option? ?value? ?option value ...?");
+ return TCL_ERROR;
+ }
+ return TkConfigureTag(interp, textPtr, Tcl_GetString(objv[3]), objc - 4, objv + 4);
case TAG_DELETE: {
Tcl_HashEntry *hPtr;
+ bool anyChanges = false;
if (objc < 4) {
Tcl_WrongNumArgs(interp, 3, objv, "tagName ?tagName ...?");
return TCL_ERROR;
}
for (i = 3; i < objc; i++) {
- hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->tagTable,
- Tcl_GetString(objv[i]));
- if (hPtr == NULL) {
+ if (!(hPtr = Tcl_FindHashEntry(&sharedTextPtr->tagTable, Tcl_GetString(objv[i])))) {
/*
- * Either this tag doesn't exist or it's the 'sel' tag (which
- * is not in the hash table). Either way we don't want to
- * delete it.
+ * Either this tag doesn't exist or it's the 'sel' tag (which is not in
+ * the hash table). Either way we don't want to delete it.
*/
continue;
}
tagPtr = Tcl_GetHashValue(hPtr);
- if (tagPtr == textPtr->selTagPtr) {
- continue;
- }
- if (tagPtr->affectsDisplay) {
- TkTextRedrawTag(textPtr->sharedTextPtr, NULL,
- NULL, NULL, tagPtr, 1);
+ assert(tagPtr != textPtr->selTagPtr);
+ if (TkTextDeleteTag(textPtr, tagPtr, hPtr) && tagPtr->undo) {
+ anyChanges = true;
}
- TkTextDeleteTag(textPtr, tagPtr);
- Tcl_DeleteHashEntry(hPtr);
+ }
+ if (anyChanges) {
+ TkTextUpdateAlteredFlag(sharedTextPtr);
}
break;
}
- case TAG_LOWER: {
- TkTextTag *tagPtr2;
- int prio;
+ case TAG_FINDNEXT: {
+ TkTextSegment *segPtr;
+ const TkBitField *selTags = NULL;
- if ((objc != 4) && (objc != 5)) {
- Tcl_WrongNumArgs(interp, 3, objv, "tagName ?belowThis?");
- return TCL_ERROR;
- }
- tagPtr = FindTag(interp, textPtr, objv[3]);
- if (tagPtr == NULL) {
+ if (objc != 4 && objc != 5) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?-discardselection? index");
return TCL_ERROR;
}
if (objc == 5) {
- tagPtr2 = FindTag(interp, textPtr, objv[4]);
- if (tagPtr2 == NULL) {
+ if (strcmp(Tcl_GetString(objv[3]), "-discardselection") == 0) {
+ selTags = sharedTextPtr->selectionTags;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -discardselection", Tcl_GetString(objv[3])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL);
return TCL_ERROR;
}
- if (tagPtr->priority < tagPtr2->priority) {
- prio = tagPtr2->priority - 1;
+ }
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[objc - 1], &index1)) {
+ return TCL_ERROR;
+ }
+ TkTextIndexSetupToEndOfText(&index2, textPtr, sharedTextPtr->tree);
+ if ((segPtr = TkBTreeFindNextTagged(&index1, &index2, selTags))) {
+ TkTextIndex index;
+ char buf[TK_POS_CHARS];
+
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetSegment(&index, segPtr);
+ TkTextPrintIndex(textPtr, &index, buf);
+ Tcl_AppendElement(interp, buf);
+ }
+ break;
+ }
+ case TAG_FINDPREV: {
+ bool discardSelection = false;
+ TkTextSegment *segPtr;
+
+ if (objc != 4 && objc != 5) {
+ Tcl_WrongNumArgs(interp, 3, objv, "-discardselection? index");
+ return TCL_ERROR;
+ }
+ if (objc == 5) {
+ if (strcmp(Tcl_GetString(objv[3]), "-discardselection") == 0) {
+ discardSelection = true;
} else {
- prio = tagPtr2->priority;
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -discardselection", Tcl_GetString(objv[3])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL);
+ return TCL_ERROR;
}
- } else {
- prio = 0;
}
- ChangeTagPriority(textPtr, tagPtr, prio);
-
- /*
- * If this is the 'sel' tag, then we don't actually need to call this
- * for all peers.
- */
-
- TkTextRedrawTag(textPtr->sharedTextPtr, NULL, NULL, NULL, tagPtr, 1);
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[objc - 1], &index1)) {
+ return TCL_ERROR;
+ }
+ TkTextIndexSetupToStartOfText(&index2, textPtr, sharedTextPtr->tree);
+ if ((segPtr = TkBTreeFindPrevTagged(&index1, &index2, discardSelection))) {
+ TkTextIndex index;
+ char buf[TK_POS_CHARS];
+
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetSegment(&index, segPtr);
+ TkTextPrintIndex(textPtr, &index, buf);
+ Tcl_AppendElement(interp, buf);
+ }
break;
}
- case TAG_NAMES: {
- TkTextTag **arrayPtr;
- int arraySize;
- Tcl_Obj *listObj;
+ case TAG_GETRANGE: {
+ TkTextIndex index1, index2;
- if ((objc != 3) && (objc != 4)) {
- Tcl_WrongNumArgs(interp, 3, objv, "?index?");
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 3, objv, "tagName index");
return TCL_ERROR;
}
- if (objc == 3) {
- Tcl_HashSearch search;
- Tcl_HashEntry *hPtr;
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[4], &index1)) {
+ return TCL_ERROR;
+ }
+ if (!(tagPtr = FindTag(interp, textPtr, objv[3]))) {
+ return TCL_ERROR;
+ }
+ if (tagPtr->rootPtr && TkBTreeCharTagged(&index1, tagPtr)) {
+ char buf[2][TK_POS_CHARS];
+ TkTextSearch tSearch;
- arrayPtr = ckalloc(textPtr->sharedTextPtr->numTags
- * sizeof(TkTextTag *));
- for (i=0, hPtr = Tcl_FirstHashEntry(
- &textPtr->sharedTextPtr->tagTable, &search);
- hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) {
- arrayPtr[i] = Tcl_GetHashValue(hPtr);
- }
+ TkTextIndexForwChars(textPtr, &index1, 1, &index1, COUNT_INDICES);
+ TkTextIndexSetupToEndOfText(&index2, textPtr, sharedTextPtr->tree);
+ TkBTreeStartSearch(&index1, &index2, tagPtr, &tSearch, SEARCH_EITHER_TAGON_TAGOFF);
+ TkBTreeNextTag(&tSearch);
+ assert(tSearch.segPtr); /* last search must not fail */
+ assert(!tSearch.tagon); /* must be tagoff */
+ TkTextPrintIndex(textPtr, &tSearch.curIndex, buf[1]);
- /*
- * The 'sel' tag is not in the hash table.
- */
+ TkTextIndexSetupToStartOfText(&index2, textPtr, sharedTextPtr->tree);
+ TkBTreeStartSearchBack(&index1, &index2, tagPtr, &tSearch, SEARCH_NEXT_TAGON);
+ TkBTreePrevTag(&tSearch);
+ assert(tSearch.segPtr); /* last search must not fail */
+ TkTextPrintIndex(textPtr, &tSearch.curIndex, buf[0]);
- arrayPtr[i] = textPtr->selTagPtr;
- arraySize = ++i;
- } else {
- if (TkTextGetObjIndex(interp, textPtr, objv[3],
- &index1) != TCL_OK) {
+ Tcl_AppendElement(interp, buf[0]);
+ Tcl_AppendElement(interp, buf[1]);
+ }
+ break;
+ }
+ case TAG_LOWER: {
+ TkTextTag *tagPtr2;
+ unsigned newPriority;
+
+ if (objc != 4 && objc != 5) {
+ Tcl_WrongNumArgs(interp, 3, objv, "tagName ?belowThis?");
+ return TCL_ERROR;
+ }
+ if (!(tagPtr = FindTag(interp, textPtr, objv[3]))) {
+ return TCL_ERROR;
+ }
+ if (objc == 5) {
+ if (!(tagPtr2 = FindTag(interp, textPtr, objv[4]))) {
return TCL_ERROR;
}
- arrayPtr = TkBTreeGetTags(&index1, textPtr, &arraySize);
- if (arrayPtr == NULL) {
- return TCL_OK;
+ newPriority = tagPtr2->priority;
+ if (tagPtr->priority < tagPtr2->priority) {
+ newPriority -= 1;
}
+ } else {
+ newPriority = 0;
}
+ if (ChangeTagPriority(sharedTextPtr, tagPtr, newPriority, true) && tagPtr->rootPtr) {
+ if (tagPtr->undo) {
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ }
- SortTags(arraySize, arrayPtr);
- listObj = Tcl_NewListObj(0, NULL);
+ /*
+ * If this is the 'sel' tag, then we don't actually need to call this for all peers.
+ *
+ * TODO: The current implementation is sloppy, we need only to refresh the ranges
+ * with actual changes, and not all the ranges of this tag.
+ */
- for (i = 0; i < arraySize; i++) {
- tagPtr = arrayPtr[i];
- Tcl_ListObjAppendElement(interp, listObj,
- Tcl_NewStringObj(tagPtr->name,-1));
+ TkTextRedrawTag(tagPtr == textPtr->selTagPtr ? NULL : sharedTextPtr,
+ textPtr, NULL, NULL, tagPtr, false);
}
- Tcl_SetObjResult(interp, listObj);
- ckfree(arrayPtr);
break;
}
+ case TAG_NAMES:
+ return EnumerateTags(interp, textPtr, objc, objv);
+ /* not reached */
case TAG_NEXTRANGE: {
- TkTextIndex last;
TkTextSearch tSearch;
char position[TK_POS_CHARS];
Tcl_Obj *resultObj;
- if ((objc != 5) && (objc != 6)) {
+ if (objc != 5 && objc != 6) {
Tcl_WrongNumArgs(interp, 3, objv, "tagName index1 ?index2?");
return TCL_ERROR;
}
- tagPtr = FindTag(NULL, textPtr, objv[3]);
- if (tagPtr == NULL) {
+ if (!(tagPtr = FindTag(NULL, textPtr, objv[3])) || !tagPtr->rootPtr) {
return TCL_OK;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[4], &index1) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[4], &index1)) {
return TCL_ERROR;
}
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr),
- 0, &last);
if (objc == 5) {
- index2 = last;
- } else if (TkTextGetObjIndex(interp, textPtr, objv[5],
- &index2) != TCL_OK) {
+ TkTextIndexSetupToEndOfText(&index2, textPtr, sharedTextPtr->tree);
+ } else if (!TkTextGetIndexFromObj(interp, textPtr, objv[5], &index2)) {
return TCL_ERROR;
}
- /*
- * The search below is a bit tricky. Rather than use the B-tree
- * facilities to stop the search at index2, let it search up until the
- * end of the file but check for a position past index2 ourselves.
- * The reason for doing it this way is that we only care whether the
- * *start* of the range is before index2; once we find the start, we
- * don't want TkBTreeNextTag to abort the search because the end of
- * the range is after index2.
- */
-
- TkBTreeStartSearch(&index1, &last, tagPtr, &tSearch);
- if (TkBTreeCharTagged(&index1, tagPtr)) {
- TkTextSegment *segPtr;
- int offset;
+ TkBTreeStartSearch(&index1, &index2, tagPtr, &tSearch, SEARCH_NEXT_TAGON);
+ if (TkBTreeNextTag(&tSearch)) {
+ assert(TkTextIndexCompare(&tSearch.curIndex, &index1) >= 0);
+ assert(TkTextIndexCompare(&tSearch.curIndex, &index2) < 0);
+ if (TkTextIndexIsEqual(&index1, &tSearch.curIndex)) {
+ TkTextIndex oneBack;
- /*
- * The first character is tagged. See if there is an on-toggle
- * just before the character. If not, then skip to the end of this
- * tagged range.
- */
+ /*
+ * The first character is tagged. See if there is an on-toggle just
+ * before the character. If not, then skip to the end of this tagged range.
+ */
- for (segPtr = index1.linePtr->segPtr, offset = index1.byteIndex;
- offset >= 0;
- offset -= segPtr->size, segPtr = segPtr->nextPtr) {
- if ((offset == 0) && (segPtr->typePtr == &tkTextToggleOnType)
- && (segPtr->body.toggle.tagPtr == tagPtr)) {
- goto gotStart;
+ if (TkTextIndexBackChars(textPtr, &index1, 1, &oneBack, COUNT_DISPLAY_INDICES)
+ && TkBTreeCharTagged(&oneBack, tagPtr)
+ && (!TkBTreeNextTag(&tSearch) || !TkBTreeNextTag(&tSearch))) {
+ return TCL_OK;
}
+ assert(TkTextIndexCompare(&tSearch.curIndex, &index2) < 0);
}
- if (!TkBTreeNextTag(&tSearch)) {
- return TCL_OK;
- }
- }
-
- /*
- * Find the start of the tagged range.
- */
-
- if (!TkBTreeNextTag(&tSearch)) {
- return TCL_OK;
+ resultObj = Tcl_NewObj();
+ TkTextPrintIndex(textPtr, &tSearch.curIndex, position);
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(position, -1));
+ TkBTreeLiftSearch(&tSearch); /* we need tagoff even if outside of the range */
+ TkBTreeNextTag(&tSearch); /* cannot fail */
+ assert(tSearch.segPtr); /* proof last assumption */
+ TkTextPrintIndex(textPtr, &tSearch.curIndex, position);
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(position, -1));
+ Tcl_SetObjResult(interp, resultObj);
}
-
- gotStart:
- if (TkTextIndexCmp(&tSearch.curIndex, &index2) >= 0) {
- return TCL_OK;
- }
- resultObj = Tcl_NewObj();
- TkTextPrintIndex(textPtr, &tSearch.curIndex, position);
- Tcl_ListObjAppendElement(NULL, resultObj,
- Tcl_NewStringObj(position, -1));
- TkBTreeNextTag(&tSearch);
- TkTextPrintIndex(textPtr, &tSearch.curIndex, position);
- Tcl_ListObjAppendElement(NULL, resultObj,
- Tcl_NewStringObj(position, -1));
- Tcl_SetObjResult(interp, resultObj);
break;
}
case TAG_PREVRANGE: {
- TkTextIndex last;
TkTextSearch tSearch;
char position1[TK_POS_CHARS];
char position2[TK_POS_CHARS];
Tcl_Obj *resultObj;
- if ((objc != 5) && (objc != 6)) {
+ if (objc != 5 && objc != 6) {
Tcl_WrongNumArgs(interp, 3, objv, "tagName index1 ?index2?");
return TCL_ERROR;
}
- tagPtr = FindTag(NULL, textPtr, objv[3]);
- if (tagPtr == NULL) {
+ if (!(tagPtr = FindTag(NULL, textPtr, objv[3])) || !tagPtr->rootPtr) {
return TCL_OK;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[4], &index1) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[4], &index1)) {
return TCL_ERROR;
}
if (objc == 5) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &index2);
- } else if (TkTextGetObjIndex(interp, textPtr, objv[5],
- &index2) != TCL_OK) {
+ TkTextIndexSetupToStartOfText(&index2, textPtr, sharedTextPtr->tree);
+ } else if (!TkTextGetIndexFromObj(interp, textPtr, objv[5], &index2)) {
return TCL_ERROR;
}
- /*
- * The search below is a bit weird. The previous toggle can be either
- * an on or off toggle. If it is an on toggle, then we need to turn
- * around and search forward for the end toggle. Otherwise we keep
- * searching backwards.
- */
-
- TkBTreeStartSearchBack(&index1, &index2, tagPtr, &tSearch);
+ TkBTreeStartSearchBack(&index1, &index2, tagPtr, &tSearch, SEARCH_EITHER_TAGON_TAGOFF);
- if (!TkBTreePrevTag(&tSearch)) {
- /*
- * Special case, there may be a tag off toggle at index1, and a
- * tag on toggle before the start of a partial peer widget. In
- * this case we missed it.
- */
+ if (TkBTreePrevTag(&tSearch)) {
+ assert(TkTextIndexCompare(&tSearch.curIndex, &index1) < 0);
+ assert(TkTextIndexCompare(&tSearch.curIndex, &index2) >= 0);
+ index1 = tSearch.curIndex;
+ if (tSearch.tagon) {
+ TkTextIndex end;
- if (textPtr->start != NULL && (textPtr->start == index2.linePtr)
- && (index2.byteIndex == 0)
- && TkBTreeCharTagged(&index2, tagPtr)
- && (TkTextIndexCmp(&index2, &index1) < 0)) {
/*
- * The first character is tagged, so just add the range from
- * the first char to the start of the range.
+ * We've found tagon. Now search forward for tagoff.
*/
- TkTextPrintIndex(textPtr, &index2, position1);
- TkTextPrintIndex(textPtr, &index1, position2);
- goto gotPrevIndexPair;
- }
- return TCL_OK;
- }
-
- if (tSearch.segPtr->typePtr == &tkTextToggleOnType) {
- TkTextPrintIndex(textPtr, &tSearch.curIndex, position1);
- if (textPtr->start != NULL) {
+ TkTextPrintIndex(textPtr, &index1, position1);
+ TkTextIndexSetupToEndOfText(&end, textPtr, sharedTextPtr->tree);
+ TkTextIndexForwChars(textPtr, &index1, 1, &index1, COUNT_INDICES);
+ TkBTreeStartSearch(&index1, &end, tagPtr, &tSearch, SEARCH_EITHER_TAGON_TAGOFF);
+ TkBTreeNextTag(&tSearch); /* cannot fail */
+ assert(tSearch.segPtr); /* proof last assumption */
+ assert(!tSearch.tagon); /* must be tagoff */
+ TkTextPrintIndex(textPtr, &tSearch.curIndex, position2);
+ } else {
/*
- * Make sure the first index is not before the first allowed
- * text index in this widget.
+ * We've found tagoff. Now search backwards for tagon.
*/
- TkTextIndex firstIndex;
-
- firstIndex.linePtr = textPtr->start;
- firstIndex.byteIndex = 0;
- firstIndex.textPtr = NULL;
- if (TkTextIndexCmp(&tSearch.curIndex, &firstIndex) < 0) {
- if (TkTextIndexCmp(&firstIndex, &index1) >= 0) {
- /*
- * But now the new first index is actually too far
- * along in the text, so nothing is returned.
- */
-
- return TCL_OK;
- }
- TkTextPrintIndex(textPtr, &firstIndex, position1);
- }
- }
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr),
- 0, &last);
- TkBTreeStartSearch(&tSearch.curIndex, &last, tagPtr, &tSearch);
- TkBTreeNextTag(&tSearch);
- TkTextPrintIndex(textPtr, &tSearch.curIndex, position2);
- } else {
- TkTextPrintIndex(textPtr, &tSearch.curIndex, position2);
- TkBTreePrevTag(&tSearch);
- TkTextPrintIndex(textPtr, &tSearch.curIndex, position1);
- if (TkTextIndexCmp(&tSearch.curIndex, &index2) < 0) {
- if (textPtr->start != NULL && index2.linePtr == textPtr->start
- && index2.byteIndex == 0) {
- /* It's ok */
- TkTextPrintIndex(textPtr, &index2, position1);
- } else {
+ if (!TkBTreePrevTag(&tSearch)) {
return TCL_OK;
}
+ assert(TkTextIndexCompare(&tSearch.curIndex, &index2) >= 0);
+ TkTextPrintIndex(textPtr, &tSearch.curIndex, position1);
+ TkTextPrintIndex(textPtr, &index1, position2);
}
+ resultObj = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(position1, -1));
+ Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(position2, -1));
+ Tcl_SetObjResult(interp, resultObj);
}
-
- gotPrevIndexPair:
- resultObj = Tcl_NewObj();
- Tcl_ListObjAppendElement(NULL, resultObj,
- Tcl_NewStringObj(position1, -1));
- Tcl_ListObjAppendElement(NULL, resultObj,
- Tcl_NewStringObj(position2, -1));
- Tcl_SetObjResult(interp, resultObj);
break;
}
case TAG_RAISE: {
TkTextTag *tagPtr2;
- int prio;
+ unsigned newPriority;
- if ((objc != 4) && (objc != 5)) {
+ if (objc != 4 && objc != 5) {
Tcl_WrongNumArgs(interp, 3, objv, "tagName ?aboveThis?");
return TCL_ERROR;
}
- tagPtr = FindTag(interp, textPtr, objv[3]);
- if (tagPtr == NULL) {
+ if (!(tagPtr = FindTag(interp, textPtr, objv[3]))) {
return TCL_ERROR;
}
if (objc == 5) {
- tagPtr2 = FindTag(interp, textPtr, objv[4]);
- if (tagPtr2 == NULL) {
+ if (!(tagPtr2 = FindTag(interp, textPtr, objv[4]))) {
return TCL_ERROR;
}
- if (tagPtr->priority <= tagPtr2->priority) {
- prio = tagPtr2->priority;
- } else {
- prio = tagPtr2->priority + 1;
+ newPriority = tagPtr2->priority;
+ if (tagPtr->priority > tagPtr2->priority) {
+ newPriority += 1;
}
} else {
- prio = textPtr->sharedTextPtr->numTags-1;
+ newPriority = sharedTextPtr->numEnabledTags - 1;
}
- ChangeTagPriority(textPtr, tagPtr, prio);
+ if (ChangeTagPriority(sharedTextPtr, tagPtr, newPriority, true) && tagPtr->rootPtr) {
+ if (tagPtr->undo) {
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ }
- /*
- * If this is the 'sel' tag, then we don't actually need to call this
- * for all peers.
- */
+ /*
+ * If this is the 'sel' tag, then we don't actually need to call this for all peers.
+ *
+ * TODO: The current implementation is sloppy, we need only to refresh the ranges
+ * with actual changes, and not all the ranges of this tag.
+ */
- TkTextRedrawTag(textPtr->sharedTextPtr, NULL, NULL, NULL, tagPtr, 1);
+ TkTextRedrawTag(tagPtr == textPtr->selTagPtr ? NULL : sharedTextPtr,
+ textPtr, NULL, NULL, tagPtr, false);
+ }
break;
}
case TAG_RANGES: {
TkTextIndex first, last;
TkTextSearch tSearch;
- Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
- int count = 0;
+ Tcl_Obj *listObj = Tcl_NewObj();
+ DEBUG(bool found = false);
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "tagName");
return TCL_ERROR;
}
- tagPtr = FindTag(NULL, textPtr, objv[3]);
- if (tagPtr == NULL) {
- return TCL_OK;
+ if ((tagPtr = FindTag(NULL, textPtr, objv[3])) && tagPtr->rootPtr) {
+ TkTextIndexSetupToStartOfText(&first, textPtr, sharedTextPtr->tree);
+ TkTextIndexSetupToEndOfText(&last, textPtr, sharedTextPtr->tree);
+ TkBTreeStartSearch(&first, &last, tagPtr, &tSearch, SEARCH_NEXT_TAGON);
+ while (TkBTreeNextTag(&tSearch)) {
+ Tcl_ListObjAppendElement(NULL, listObj, TkTextNewIndexObj(&tSearch.curIndex));
+ DEBUG(found = true);
+ }
+ assert(!found || !tSearch.tagon); /* search must find end of text */
+ Tcl_SetObjResult(interp, listObj);
+ }
+ break;
+ }
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextClearTags --
+ *
+ * Clear the selection in specified range.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * See TkBTreeTag and TkTextSelectionEvent for side effects.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextClearSelection(
+ TkSharedText *sharedTextPtr,
+ const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2)
+{
+ TkText *textPtr;
+
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
+ if (TkBTreeTag(sharedTextPtr, textPtr, indexPtr1, indexPtr2, textPtr->selTagPtr,
+ false, NULL, TkTextRedrawTag)) {
+ if (!textPtr->abortSelections) {
+ /*
+ * Send an event that the selection changed. This is equivalent to:
+ * event generate $textWidget <<Selection>>
+ */
+
+ TkTextSelectionEvent(textPtr); /* <<Selection>> will be received after deletion */
+ textPtr->abortSelections = true;
+ }
}
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &first);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr),
- 0, &last);
- TkBTreeStartSearch(&first, &last, tagPtr, &tSearch);
- if (TkBTreeCharTagged(&first, tagPtr)) {
- Tcl_ListObjAppendElement(NULL, listObj,
- TkTextNewIndexObj(textPtr, &first));
- count++;
- }
- while (TkBTreeNextTag(&tSearch)) {
- Tcl_ListObjAppendElement(NULL, listObj,
- TkTextNewIndexObj(textPtr, &tSearch.curIndex));
- count++;
- }
- if (count % 2 == 1) {
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextClearTags --
+ *
+ * Turn all tags off inside a given range.
+ *
+ * Results:
+ * Whether any tag has been removed.
+ *
+ * Side effects:
+ * See TkBTreeClearTags and TkTextPushUndoToken for side effects.
+ *
+ *----------------------------------------------------------------------
+ */
+
+TkTextTag *
+TkTextClearTags(
+ TkSharedText *sharedTextPtr,
+ TkText *textPtr,
+ const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2,
+ bool discardSelection)
+{
+ TkTextTag *tagPtr;
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr;
+
+ undoInfoPtr = TkTextUndoStackIsFull(sharedTextPtr->undoStack) ? NULL : &undoInfo;
+ tagPtr = TkBTreeClearTags(sharedTextPtr, textPtr, indexPtr1, indexPtr2, undoInfoPtr,
+ discardSelection, TkTextRedrawTag);
+ if (tagPtr && undoInfoPtr && undoInfo.token) {
+ TkTextPushUndoToken(sharedTextPtr, undoInfo.token, undoInfo.byteSize);
+ }
+ return tagPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUpdateTagDisplayFlags --
+ *
+ * Update the display flags 'affectsDisplay' and 'affectsDisplayGeometry',
+ * according to the current attributes of the given tag.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The flags 'affectsDisplay' and 'affectsDisplayGeometry' may change.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextUpdateTagDisplayFlags(
+ TkTextTag *tagPtr)
+{
+ tagPtr->affectsDisplay = false;
+ tagPtr->affectsDisplayGeometry = false;
+
+ if (tagPtr->elideString
+ || tagPtr->tkfont != None
+ || tagPtr->justifyString
+ || tagPtr->lMargin1String
+ || tagPtr->lMargin2String
+ || tagPtr->offsetString
+ || tagPtr->rMarginString
+ || tagPtr->spacing1String
+ || tagPtr->spacing2String
+ || tagPtr->spacing3String
+ || tagPtr->tabStringPtr
+ || tagPtr->tabStyle != TK_TEXT_TABSTYLE_NONE
+ || tagPtr->wrapMode != TEXT_WRAPMODE_NULL) {
+ tagPtr->affectsDisplay = true;
+ tagPtr->affectsDisplayGeometry = true;
+ } else if (tagPtr->border
+ || tagPtr->selBorder
+ || tagPtr->reliefString
+ || tagPtr->bgStipple != None
+ || tagPtr->indentBgString
+ || tagPtr->fgColor
+ || tagPtr->selFgColor
+ || tagPtr->fgStipple != None
+ || tagPtr->eolColor
+ || tagPtr->hyphenColor
+ || tagPtr->overstrikeString
+ || tagPtr->overstrikeColor
+ || tagPtr->underlineString
+ || tagPtr->underlineColor
+ || tagPtr->lMarginColor
+ || tagPtr->rMarginColor) {
+ tagPtr->affectsDisplay = true;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkConfigureTag --
+ *
+ * This function is called to process an objv/objc list, plus the Tk
+ * option database, in order to configure (or reconfigure) a text tag.
+ *
+ * Results:
+ * Any of the standard Tcl return values.
+ *
+ * Side effects:
+ * A new tag will be created if required, otherwise an existing tag
+ * will be modified.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkConfigureTag(
+ Tcl_Interp *interp, /* Current interpreter. */
+ TkText *textPtr, /* Info about overall widget. */
+ char const *tagName, /* Name of affected tag. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Remaining argument objects. */
+{
+ int mask = 0;
+ bool newTag;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextTag *tagPtr = TkTextCreateTag(textPtr, tagName, &newTag);
+ const char *elideString = tagPtr->elideString;
+ bool elide = tagPtr->elide;
+ bool undo = tagPtr->undo;
+ bool affectsDisplay = tagPtr->affectsDisplay;
+ bool affectsLineHeight = false;
+
+ if (objc <= 1) {
+ Tcl_Obj *objPtr = Tk_GetOptionInfo(interp, (char *) tagPtr, tagPtr->optionTable,
+ objc == 1 ? objv[0] : NULL, textPtr->tkwin);
+
+ if (!objPtr) {
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, objPtr);
+ return TCL_OK;
+ }
+
+ if (Tk_SetOptions(interp, (char *) tagPtr, tagPtr->optionTable,
+ objc, objv, textPtr->tkwin, NULL, &mask) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+#if SUPPORT_DEPRECATED_TAG_OPTIONS
+
+ if (mask & (TK_TEXT_DEPRECATED_OVERSTRIKE_FG|TK_TEXT_DEPRECATED_UNDERLINE_FG)) {
+ static bool warnAboutOverstrikeFg = true;
+ static bool warnAboutUnderlineFg = true;
+
+ if (mask & TK_TEXT_DEPRECATED_OVERSTRIKE_FG) {
+ if (warnAboutOverstrikeFg) {
+ fprintf(stderr, "Tag option \"-overstrikefg\" is deprecated, please use option "
+ "\"-overstrikecolor\"\n");
+ warnAboutOverstrikeFg = false;
+ }
+ }
+ if (mask & TK_TEXT_DEPRECATED_UNDERLINE_FG) {
+ if (warnAboutUnderlineFg) {
+ fprintf(stderr, "Tag option \"-underlinefg\" is deprecated, please use option "
+ "\"-underlinecolor\"\n");
+ warnAboutUnderlineFg = false;
+ }
+ }
+ }
+
+#endif /* SUPPORT_DEPRECATED_TAG_OPTIONS */
+
+ /*
+ Some of the configuration options, like -underline and -justify, require
+ * additional translation (this is needed because we need to distinguish a
+ * particular value of an option from "unspecified").
+ */
+
+ if (tagPtr->borderWidth < 0) {
+ tagPtr->borderWidth = 0;
+ }
+ if (tagPtr->langPtr) {
+ if (!TkTextTestLangCode(interp, tagPtr->langPtr)) {
+ return TCL_ERROR;
+ }
+ memcpy(tagPtr->lang, Tcl_GetString(tagPtr->langPtr), 3);
+ } else {
+ memset(tagPtr->lang, 0, 3);
+ }
+ if (tagPtr->indentBgString) {
+ if (Tcl_GetBoolean(interp, tagPtr->indentBgString, (int *) &tagPtr->indentBg) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->reliefString) {
+ if (Tk_GetRelief(interp, tagPtr->reliefString, &tagPtr->relief) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->justifyString) {
+ const char *identifier = NULL;
+ int j = -1;
+
+ /*
+ * Tk_Justify only knows "left", "right", and "center", so we have to parse by ourself.
+ */
+
+ switch (*tagPtr->justifyString) {
+ case 'l': identifier = "left"; j = TK_TEXT_JUSTIFY_LEFT; break;
+ case 'r': identifier = "right"; j = TK_TEXT_JUSTIFY_RIGHT; break;
+ case 'f': identifier = "full"; j = TK_TEXT_JUSTIFY_FULL; break;
+ case 'c': identifier = "center"; j = TK_TEXT_JUSTIFY_CENTER; break;
+ }
+ if (j == -1 || strcmp(tagPtr->justifyString, identifier) != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad justification \"%s\": must be left, right, full, or center",
+ tagPtr->justifyString));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "JUSTIFY", NULL);
+ return TCL_ERROR;
+ }
+ tagPtr->justify = j;
+ }
+ if (tagPtr->lMargin1String) {
+ if (Tk_GetPixels(interp, textPtr->tkwin,
+ tagPtr->lMargin1String, &tagPtr->lMargin1) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->lMargin2String) {
+ if (Tk_GetPixels(interp, textPtr->tkwin,
+ tagPtr->lMargin2String, &tagPtr->lMargin2) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->offsetString) {
+ if (Tk_GetPixels(interp, textPtr->tkwin, tagPtr->offsetString,
+ &tagPtr->offset) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->overstrikeString) {
+ if (Tcl_GetBoolean(interp, tagPtr->overstrikeString, (int *) &tagPtr->overstrike) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->rMarginString) {
+ if (Tk_GetPixels(interp, textPtr->tkwin,
+ tagPtr->rMarginString, &tagPtr->rMargin) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->spacing1String) {
+ if (Tk_GetPixels(interp, textPtr->tkwin,
+ tagPtr->spacing1String, &tagPtr->spacing1) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (tagPtr->spacing1 < 0) {
+ tagPtr->spacing1 = 0;
+ }
+ }
+ if (tagPtr->spacing2String) {
+ if (Tk_GetPixels(interp, textPtr->tkwin,
+ tagPtr->spacing2String, &tagPtr->spacing2) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (tagPtr->spacing2 < 0) {
+ tagPtr->spacing2 = 0;
+ }
+ }
+ if (tagPtr->spacing3String) {
+ if (Tk_GetPixels(interp, textPtr->tkwin,
+ tagPtr->spacing3String, &tagPtr->spacing3) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (tagPtr->spacing3 < 0) {
+ tagPtr->spacing3 = 0;
+ }
+ }
+ if (tagPtr->tabArrayPtr) {
+ free(tagPtr->tabArrayPtr);
+ tagPtr->tabArrayPtr = NULL;
+ }
+ if (tagPtr->tabStringPtr) {
+ if (!(tagPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr, tagPtr->tabStringPtr))) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->hyphenRulesPtr) {
+ int oldHyphenRules = tagPtr->hyphenRules;
+
+ if (TkTextParseHyphenRules(textPtr, tagPtr->hyphenRulesPtr, &tagPtr->hyphenRules) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (oldHyphenRules != tagPtr->hyphenRules && textPtr->hyphenate) {
+ affectsDisplay = true;
+ }
+ }
+ if (tagPtr->underlineString) {
+ if (Tcl_GetBoolean(interp, tagPtr->underlineString, (int *) &tagPtr->underline) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ }
+ if (tagPtr->elideString) {
+ if (!elideString) {
+ sharedTextPtr->numElisionTags += 1;
+ }
+
+ if (TkBitTest(sharedTextPtr->selectionTags, tagPtr->index)) {
/*
- * If a text widget uses '-end', it won't necessarily run to the
- * end of the B-tree, and therefore the tag range might not be
- * closed. In this case we add the end of the range.
+ * It's not allowed to set the elide attribute of the special selection tag
+ * to 'true' (this would cause errors, because this case is not implemented).
*/
- Tcl_ListObjAppendElement(NULL, listObj,
- TkTextNewIndexObj(textPtr, &last));
+ free(tagPtr->elideString);
+ tagPtr->elideString = NULL;
+ tagPtr->elide = false;
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "not allowed to set elide option of selection tag \"%s\"", tagPtr->name));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "ELIDE", NULL);
+ return TCL_ERROR;
}
- Tcl_SetObjResult(interp, listObj);
- break;
+
+ if (Tcl_GetBoolean(interp, tagPtr->elideString, (int *) &tagPtr->elide) != TCL_OK) {
+ return TCL_ERROR;
+ }
+
+ /*
+ * Indices are potentially obsolete after changing -elide,
+ * especially those computed with "display" or "any"
+ * submodifier, therefore increase the epoch.
+ */
+
+ TkBTreeIncrEpoch(sharedTextPtr->tree);
+ } else {
+ if (elideString) {
+ sharedTextPtr->numElisionTags -= 1;
+ }
+ tagPtr->elide = false;
+ }
+ if (tagPtr->undo != undo) {
+ TkBitPut(sharedTextPtr->dontUndoTags, tagPtr->index, !tagPtr->undo);
+ }
+
+ /*
+ * If the "sel" tag was changed, be sure to mirror information
+ * from the tag back into the text widget record. NOTE: we don't
+ * have to free up information in the widget record before
+ * overwriting it, because it was mirrored in the tag and hence
+ * freed when the tag field was overwritten.
+ */
+
+ if (tagPtr == textPtr->selTagPtr) {
+ textPtr->selBorder = tagPtr->selBorder ? tagPtr->selBorder : tagPtr->border;
+ textPtr->selBorderWidth = tagPtr->borderWidth;
+ textPtr->selBorderWidthPtr = tagPtr->borderWidthPtr;
+ textPtr->selFgColorPtr = tagPtr->selFgColor ? tagPtr->selFgColor : tagPtr->fgColor;
+ }
+
+ TkTextUpdateTagDisplayFlags(tagPtr);
+ if (tagPtr->affectsDisplay) {
+ affectsDisplay = true;
}
+ if (tagPtr->tkfont != None && tagPtr->tkfont != textPtr->tkfont) {
+ Tk_FontMetrics fm;
+
+ Tk_GetFontMetrics(tagPtr->tkfont, &fm);
+ if (MAX(1, fm.linespace) != textPtr->lineHeight) {
+ affectsLineHeight = true;
+ }
+ }
+
+ TkBitPut(sharedTextPtr->elisionTags, tagPtr->index, !!tagPtr->elideString);
+ TkBitPut(sharedTextPtr->affectDisplayTags, tagPtr->index, tagPtr->affectsDisplay);
+ TkBitPut(sharedTextPtr->notAffectDisplayTags, tagPtr->index, !tagPtr->affectsDisplay);
+ TkBitPut(sharedTextPtr->affectGeometryTags, tagPtr->index, tagPtr->affectsDisplayGeometry);
+ TkBitPut(sharedTextPtr->affectLineHeightTags, tagPtr->index, affectsLineHeight);
+
+ if (!TkBitTest(sharedTextPtr->selectionTags, tagPtr->index)) {
+ TkBitPut(sharedTextPtr->affectDisplayNonSelTags, tagPtr->index, tagPtr->affectsDisplay);
+ TkBitPut(sharedTextPtr->affectGeometryNonSelTags, tagPtr->index,
+ tagPtr->affectsDisplayGeometry);
}
+
+ if (!tagPtr->elideString != !elideString || (tagPtr->elideString && elide != tagPtr->elide)) {
+ /*
+ * Eventually we have to insert/remove branches and links according to
+ * the elide information of this tag.
+ */
+
+ TkBTreeUpdateElideInfo(textPtr, tagPtr);
+ }
+
+ if (!newTag && affectsDisplay) {
+ /*
+ * This line is not necessary if this is a new tag, since it can't possibly have
+ * been applied to anything yet.
+ *
+ * If this is the 'sel' tag, then we don't need to call this for all peers, unless
+ * we actually want to synchronize sel-style changes across the peers.
+ */
+
+ TkTextRedrawTag(sharedTextPtr, NULL, NULL, NULL, tagPtr, false);
+ }
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextFontHeightChanged --
+ *
+ * The font height of the text widget has changed, so we have to update
+ * textPtr->affectLineHeightTags accordingly.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * textPtr->affectLineHeightTags will be updated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextFontHeightChanged(
+ TkText *textPtr) /* Info about overall widget. */
+{
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr = NULL;
+ TkBitField *affectLineHeightTags = textPtr->sharedTextPtr->affectLineHeightTags;
+
+ TkBitClear(affectLineHeightTags);
+
+ for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->tagTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
+ const TkTextTag *tagPtr = Tcl_GetHashValue(hPtr);
+
+ if (tagPtr->tkfont != None && tagPtr->tkfont != textPtr->tkfont) {
+ Tk_FontMetrics fm;
+
+ Tk_GetFontMetrics(tagPtr->tkfont, &fm);
+ if (MAX(1, fm.linespace) != textPtr->lineHeight) {
+ TkBitSet(affectLineHeightTags, tagPtr->index);
+ }
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * AppendTags --
+ *
+ * This function is appending the given array of tags to the interpreter.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Results will be appended to the interpreter.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+AppendTags(
+ Tcl_Interp *interp, /* Current interpreter. */
+ unsigned numTags, /* Size of array. */
+ TkTextTag **tagArray) /* Array of tag pointer, some pointer may be NULL. */
+{
+ unsigned i;
+ Tcl_Obj *listObj;
+
+ if (numTags == 0) {
+ return;
+ }
+
+ TkTextSortTags(numTags, tagArray);
+ listObj = Tcl_NewObj();
+
+ for (i = 0; i < numTags; ++i) {
+ if (tagArray[i]) {
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewStringObj(tagArray[i]->name, -1));
+ }
+ }
+ Tcl_SetObjResult(interp, listObj);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FindTags --
+ *
+ * This function is appending the tags from given char segment to the
+ * interpreter.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Results will be appended to the interpreter.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FindTags(
+ Tcl_Interp *interp, /* Current interpreter. */
+ TkText *textPtr, /* Info about overall widget. */
+ const TkTextSegment *segPtr,/* Tags from this segment. */
+ bool discardSelection) /* "sel" tag will be discarded? */
+{
+ TkTextTag *tagPtr;
+ TkTextTag **tagArray;
+ unsigned count;
+
+ assert(segPtr);
+
+ tagArray = malloc(textPtr->sharedTextPtr->numEnabledTags * sizeof(TkTextTag *));
+ tagPtr = TkBTreeGetSegmentTags(textPtr->sharedTextPtr, segPtr, textPtr);
+
+ for (count = 0; tagPtr; tagPtr = tagPtr->nextPtr) {
+ if (!discardSelection || tagPtr != textPtr->selTagPtr) {
+ tagArray[count++] = tagPtr;
+ }
+ }
+
+ AppendTags(interp, count, tagArray);
+ free(tagArray);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextTagChangedUndoRedo --
+ *
+ * This function is called when any tag range has been changed during
+ * an undo/redo operation.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * See TkTextRedrawTag, and TkTextGrabSelection.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextTagChangedUndoRedo(
+ const TkSharedText *sharedTextPtr,
+ TkText *textPtr,
+ const TkTextIndex *indexPtr1,
+ const TkTextIndex *indexPtr2,
+ const TkTextTag *tagPtr,
+ bool affectsDisplayGeometry)
+{
+ if (!TkTextRedrawTag(sharedTextPtr, textPtr, indexPtr1, indexPtr2, tagPtr, affectsDisplayGeometry)) {
+ return false;
+ }
+ if (tagPtr && tagPtr->textPtr) {
+ assert(tagPtr == textPtr->selTagPtr);
+ GrabSelection(tagPtr->textPtr, tagPtr, TkTextTestTag(indexPtr1, tagPtr));
+ }
+ return true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GrabSelection --
+ * Grab the selection if we're supposed to export it and don't already
+ * have it.
+ *
+ * Also, invalidate partially-completed selection retrievals. We only
+ * need to check whether the tag is "sel" for this textPtr (not for
+ * other peer widget's "sel" tags) because we cannot reach this code
+ * path with a different widget's "sel" tag.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some text segments may be modified.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+GrabSelection(
+ TkText *textPtr, /* Info about overall widget. */
+ const TkTextTag *tagPtr, /* Tag which has been modified. */
+ bool add) /* 'true' means that we have added this tag;
+ * 'false' means we have removed this tag. */
+{
+ assert(tagPtr == textPtr->selTagPtr);
+
+ /*
+ * Send an event that the selection changed. This is
+ * equivalent to:
+ * event generate $textWidget <<Selection>>
+ */
+
+ TkTextSelectionEvent(textPtr);
+
+ if (add && textPtr->exportSelection && !(textPtr->flags & GOT_SELECTION)) {
+ Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, TkTextLostSelection, textPtr);
+ textPtr->flags |= GOT_SELECTION;
+ }
+ textPtr->abortSelections = true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TagAddRemove --
+ * This functions adds or removes a tag (or all tags) from the characters
+ * between given index range.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some text segments may be modified.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static bool
+UndoTagOperation(
+ const TkSharedText *sharedTextPtr,
+ const TkTextTag *tagPtr)
+{
+ return sharedTextPtr->undoStack && (!tagPtr || tagPtr->undo);
+}
+
+static bool
+TagAddRemove(
+ TkText *textPtr, /* Info about overall widget. */
+ const TkTextIndex *index1Ptr,
+ /* Indicates first character in range. */
+ const TkTextIndex *index2Ptr,
+ /* Indicates character just after the last one in range. */
+ TkTextTag *tagPtr, /* Tag to add or remove. */
+ bool add) /* 'true' means add tag to the given range of characters;
+ * 'false' means remove the tag from the range. */
+{
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextUndoInfo *undoInfoPtr;
+ TkTextUndoInfo undoInfo;
+
+ assert(!sharedTextPtr->undoStack || !TkTextUndoIsPerformingUndo(sharedTextPtr->undoStack));
+ assert(!sharedTextPtr->undoStack || !TkTextUndoIsPerformingRedo(sharedTextPtr->undoStack));
+
+ if (!add && !tagPtr->rootPtr) {
+ return false; /* no change possible */
+ }
+
+ undoInfoPtr = UndoTagOperation(sharedTextPtr, tagPtr) ? &undoInfo : NULL;
+
+ if (!TkBTreeTag(sharedTextPtr, textPtr, index1Ptr, index2Ptr, tagPtr, add,
+ undoInfoPtr, TkTextRedrawTag)) {
+ return false;
+ }
+
+ if (undoInfoPtr) {
+ if (undoInfo.token) {
+ tagPtr->refCount += 1;
+ TkTextUndoPushItem(sharedTextPtr->undoStack, undoInfo.token, undoInfo.byteSize);
+ }
+ sharedTextPtr->undoStackEvent = true;
+ }
+
+ return true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextBindEvent --
+ *
+ * Bind events to the specified resource name.
+ *
+ * Results:
+ * Any of the standard Tcl return values.
+ *
+ * Side effects:
+ * A new entry in the binding table will be inserted, or an exisiting
+ * entry will be deleted.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextBindEvent(
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[], /* Remaining argument objects. */
+ TkSharedText *sharedTextPtr,/* Shared text resource. */
+ Tk_BindingTable *bindingTablePtr,
+ /* Pointer to binding table. */
+ const char *name) /* Name of the resource (tag, or image) */
+{
+ static const unsigned motionMask = ButtonMotionMask|Button1MotionMask
+ |Button2MotionMask|Button3MotionMask|Button4MotionMask
+ |Button5MotionMask|PointerMotionMask;
+
+ /*
+ * Make a binding table if the widget doesn't already have one.
+ */
+
+ if (!*bindingTablePtr) {
+ *bindingTablePtr = Tk_CreateBindingTable(interp);
+ }
+
+ if (objc == 2) {
+ bool append = false;
+ unsigned mask;
+ const char *eventString = Tcl_GetString(objv[0]);
+ const char *fifth = Tcl_GetString(objv[1]);
+
+ if (fifth[0] == '\0') {
+ return Tk_DeleteBinding(interp, *bindingTablePtr, (ClientData) name, eventString);
+ }
+ if (fifth[0] == '+') {
+ fifth += 1;
+ append = true;
+ }
+ mask = Tk_CreateBinding(interp, *bindingTablePtr, (ClientData) name, eventString, fifth, append);
+ if (mask == 0) {
+ return TCL_ERROR;
+ }
+ if (mask & motionMask) {
+ /*
+ * TODO: It would be better to count tags with motion mask, but this silly
+ * binding protocol does not provide any function which helps to detect when
+ * bindings with motion masks will be deleted. So we cannot do more than
+ * to detect whether any motion mask has ever been set. This has an effect
+ * on TkTextPickCurrent, this function will be considerably faster if
+ * 'numMotionEventBindings' is zero, because in latter case only traversals
+ * between display chunks will be considered. We assume that the use of a
+ * motion mask is rather seldom, normally only the Enter/Leave events are
+ * of interest.
+ */
+ sharedTextPtr->numMotionEventBindings = 1;
+ }
+ if (mask & (unsigned) ~(motionMask|ButtonPressMask|ButtonReleaseMask|EnterWindowMask
+ |LeaveWindowMask|KeyPressMask|KeyReleaseMask|VirtualEventMask)) {
+ Tk_DeleteBinding(interp, *bindingTablePtr, (ClientData) name, eventString);
+ Tcl_ResetResult(interp);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "requested illegal events; only key, button, motion,"
+ " enter, leave, and virtual events may be used", -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "TAG_BIND_EVENT",NULL);
+ return TCL_ERROR;
+ }
+ } else if (objc == 1) {
+ const char *command;
+
+ command = Tk_GetBinding(interp, *bindingTablePtr, (ClientData) name, Tcl_GetString(objv[0]));
+ if (!command) {
+ const char *string = Tcl_GetString(Tcl_GetObjResult(interp));
+
+ /*
+ * Ignore missing binding errors. This is a special hack that relies on the
+ * error message returned by FindSequence in tkBind.c.
+ */
+
+ if (string[0] != '\0') {
+ return TCL_ERROR;
+ }
+ Tcl_ResetResult(interp);
+ } else {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(command, -1));
+ }
+ } else {
+ Tk_GetAllBindings(interp, *bindingTablePtr, (ClientData) name);
+ }
+
return TCL_OK;
}
@@ -978,39 +1667,80 @@ TkTextTagCmd(
*----------------------------------------------------------------------
*/
+static void
+MarkIndex(
+ TkSharedText *sharedTextPtr,
+ TkTextTag *tagPtr,
+ bool set)
+{
+ if (set && tagPtr->index >= TkBitSize(sharedTextPtr->usedTags)) {
+ sharedTextPtr->tagInfoSize = TkBitAdjustSize(tagPtr->index + 1);
+ }
+
+ TkBitPut(sharedTextPtr->usedTags, tagPtr->index, set);
+ assert((!sharedTextPtr->tagLookup[tagPtr->index]) == set);
+ sharedTextPtr->tagLookup[tagPtr->index] = set ? tagPtr : NULL;
+}
+
TkTextTag *
TkTextCreateTag(
TkText *textPtr, /* Widget in which tag is being used. */
const char *tagName, /* Name of desired tag. */
- int *newTag) /* If non-NULL, then return 1 if new, or 0 if
- * already exists. */
+ bool *newTag) /* If non-NULL, then return true if new, or false if already exists. */
{
- register TkTextTag *tagPtr;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextTag *tagPtr;
Tcl_HashEntry *hPtr = NULL;
- int isNew;
+ bool isNew, isSelTag;
const char *name;
+ unsigned index;
+
+ isSelTag = (strcmp(tagName, "sel") == 0);
- if (!strcmp(tagName, "sel")) {
- if (textPtr->selTagPtr != NULL) {
- if (newTag != NULL) {
- *newTag = 0;
+ if (isSelTag) {
+ if (textPtr->selTagPtr) {
+ if (newTag) {
+ *newTag = false;
}
return textPtr->selTagPtr;
}
- if (newTag != NULL) {
- *newTag = 1;
+ if (newTag) {
+ *newTag = true;
}
name = "sel";
} else {
- hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->tagTable,
- tagName, &isNew);
- if (newTag != NULL) {
+ hPtr = Tcl_CreateHashEntry(&sharedTextPtr->tagTable, tagName, (int *) &isNew);
+ if (newTag) {
*newTag = isNew;
}
if (!isNew) {
return Tcl_GetHashValue(hPtr);
}
- name = Tcl_GetHashKey(&textPtr->sharedTextPtr->tagTable, hPtr);
+ name = Tcl_GetHashKey(&sharedTextPtr->tagTable, hPtr);
+ }
+
+ if ((index = TkBitFindFirstNot(sharedTextPtr->usedTags)) == TK_BIT_NPOS) {
+ unsigned oldSize = TkBitSize(sharedTextPtr->usedTags);
+ unsigned newSize = TkBitAdjustSize((index = oldSize) + 1);
+
+ sharedTextPtr->usedTags = TkBitResize(sharedTextPtr->usedTags, newSize);
+ sharedTextPtr->elisionTags = TkBitResize(sharedTextPtr->elisionTags, newSize);
+ sharedTextPtr->selectionTags = TkBitResize(sharedTextPtr->selectionTags, newSize);
+ sharedTextPtr->dontUndoTags = TkBitResize(sharedTextPtr->dontUndoTags, newSize);
+ sharedTextPtr->affectDisplayTags = TkBitResize(sharedTextPtr->affectDisplayTags, newSize);
+ sharedTextPtr->notAffectDisplayTags = TkBitResize(sharedTextPtr->notAffectDisplayTags, newSize);
+ sharedTextPtr->affectDisplayNonSelTags = TkBitResize(
+ sharedTextPtr->affectDisplayNonSelTags, newSize);
+ sharedTextPtr->affectGeometryTags = TkBitResize( sharedTextPtr->affectGeometryTags, newSize);
+ sharedTextPtr->affectGeometryNonSelTags = TkBitResize(
+ sharedTextPtr->affectGeometryNonSelTags, newSize);
+ sharedTextPtr->affectLineHeightTags = TkBitResize(sharedTextPtr->affectLineHeightTags, newSize);
+ sharedTextPtr->tagLookup = realloc(sharedTextPtr->tagLookup, newSize * sizeof(TkTextTag *));
+ DEBUG(memset(sharedTextPtr->tagLookup + oldSize, 0, (newSize - oldSize) * sizeof(TkTextTag *)));
+ }
+
+ if (sharedTextPtr->tagInfoSize <= index) {
+ sharedTextPtr->tagInfoSize = TkBitAdjustSize(index + 1);
}
/*
@@ -1018,71 +1748,78 @@ TkTextCreateTag(
* to it to the hash table entry.
*/
- tagPtr = ckalloc(sizeof(TkTextTag));
+ tagPtr = memset(malloc(sizeof(TkTextTag)), 0, sizeof(TkTextTag));
tagPtr->name = name;
- tagPtr->textPtr = NULL;
- tagPtr->toggleCount = 0;
- tagPtr->tagRootPtr = NULL;
- tagPtr->priority = textPtr->sharedTextPtr->numTags;
- tagPtr->border = NULL;
- tagPtr->borderWidth = 0;
- tagPtr->borderWidthPtr = NULL;
- tagPtr->reliefString = NULL;
+ tagPtr->index = index;
+ tagPtr->priority = textPtr->sharedTextPtr->numEnabledTags;
tagPtr->relief = TK_RELIEF_FLAT;
tagPtr->bgStipple = None;
- tagPtr->fgColor = NULL;
- tagPtr->tkfont = NULL;
tagPtr->fgStipple = None;
- tagPtr->justifyString = NULL;
- tagPtr->justify = TK_JUSTIFY_LEFT;
- tagPtr->lMargin1String = NULL;
- tagPtr->lMargin1 = 0;
- tagPtr->lMargin2String = NULL;
- tagPtr->lMargin2 = 0;
- tagPtr->lMarginColor = NULL;
- tagPtr->offsetString = NULL;
- tagPtr->offset = 0;
- tagPtr->overstrikeString = NULL;
- tagPtr->overstrike = 0;
- tagPtr->overstrikeColor = NULL;
- tagPtr->rMarginString = NULL;
- tagPtr->rMargin = 0;
- tagPtr->rMarginColor = NULL;
- tagPtr->selBorder = NULL;
- tagPtr->selFgColor = NULL;
- tagPtr->spacing1String = NULL;
- tagPtr->spacing1 = 0;
- tagPtr->spacing2String = NULL;
- tagPtr->spacing2 = 0;
- tagPtr->spacing3String = NULL;
- tagPtr->spacing3 = 0;
- tagPtr->tabStringPtr = NULL;
- tagPtr->tabArrayPtr = NULL;
+ tagPtr->justify = TK_TEXT_JUSTIFY_LEFT;
tagPtr->tabStyle = TK_TEXT_TABSTYLE_NONE;
- tagPtr->underlineString = NULL;
- tagPtr->underline = 0;
- tagPtr->underlineColor = NULL;
- tagPtr->elideString = NULL;
- tagPtr->elide = 0;
tagPtr->wrapMode = TEXT_WRAPMODE_NULL;
- tagPtr->affectsDisplay = 0;
- tagPtr->affectsDisplayGeometry = 0;
- textPtr->sharedTextPtr->numTags++;
- if (!strcmp(tagName, "sel")) {
+ tagPtr->undo = !isSelTag;
+ tagPtr->sharedTextPtr = sharedTextPtr;
+ tagPtr->undoTagListIndex = -1;
+ tagPtr->refCount = 1;
+ DEBUG_ALLOC(tkTextCountNewTag++);
+
+ textPtr->sharedTextPtr->numTags += 1;
+ textPtr->sharedTextPtr->numEnabledTags += 1;
+ if (isSelTag) {
tagPtr->textPtr = textPtr;
- textPtr->refCount++;
+ textPtr->refCount += 1;
+ TkBitSet(sharedTextPtr->selectionTags, index);
+ TkBitSet(sharedTextPtr->dontUndoTags, index);
} else {
CLANG_ASSERT(hPtr);
Tcl_SetHashValue(hPtr, tagPtr);
}
- tagPtr->optionTable =
- Tk_CreateOptionTable(textPtr->interp, tagOptionSpecs);
+ tagPtr->optionTable = Tk_CreateOptionTable(textPtr->interp, tagOptionSpecs);
+ MarkIndex(sharedTextPtr, tagPtr, true);
return tagPtr;
}
/*
*----------------------------------------------------------------------
*
+ * TkTextFindTag --
+ *
+ * See if tag is defined for a given widget.
+ *
+ * Results:
+ * If tagName is defined in textPtr, a pointer to its TkTextTag structure
+ * is returned. Otherwise NULL is returned.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+TkTextTag *
+TkTextFindTag(
+ const TkText *textPtr, /* Widget in which tag is being used. */
+ const char *tagName) /* Name of desired tag. */
+{
+ Tcl_HashEntry *hPtr;
+
+ assert(textPtr);
+ assert(tagName);
+
+ if (strcmp(tagName, "sel") == 0) {
+ return textPtr->selTagPtr;
+ }
+ hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->tagTable, tagName);
+ if (hPtr) {
+ return Tcl_GetHashValue(hPtr);
+ }
+ return NULL;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* FindTag --
*
* See if tag is defined for a given widget.
@@ -1100,44 +1837,142 @@ TkTextCreateTag(
static TkTextTag *
FindTag(
- Tcl_Interp *interp, /* Interpreter to use for error message; if
- * NULL, then don't record an error
- * message. */
- TkText *textPtr, /* Widget in which tag is being used. */
+ Tcl_Interp *interp, /* Interpreter to use for error message; if NULL, then don't record
+ * an error message. */
+ const TkText *textPtr, /* Widget in which tag is being used. */
Tcl_Obj *tagName) /* Name of desired tag. */
{
- Tcl_HashEntry *hPtr;
- int len;
- const char *str;
+ const char *name = Tcl_GetString(tagName);
+ TkTextTag *tagPtr = TkTextFindTag(textPtr, name);
- str = Tcl_GetStringFromObj(tagName, &len);
- if (len == 3 && !strcmp(str, "sel")) {
- return textPtr->selTagPtr;
- }
- hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->tagTable,
- Tcl_GetString(tagName));
- if (hPtr != NULL) {
- return Tcl_GetHashValue(hPtr);
- }
- if (interp != NULL) {
+ if (!tagPtr && interp) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "tag \"%s\" isn't defined in text widget",
- Tcl_GetString(tagName)));
- Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_TAG",
- Tcl_GetString(tagName), NULL);
+ "tag \"%s\" isn't defined in text widget", name));
+ Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_TAG", name, NULL);
+ }
+
+ return tagPtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextEnableTag --
+ *
+ * If this tag is disabled, then re-enable it.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextEnableTag(
+ TkSharedText *sharedTextPtr,/* Shared text resource. */
+ TkTextTag *tagPtr) /* Tag being deleted. */
+{
+ if (tagPtr->isDisabled) {
+ tagPtr->isDisabled = false;
+ MarkIndex(sharedTextPtr, tagPtr, true);
+ sharedTextPtr->numEnabledTags += 1;
+ ChangeTagPriority(sharedTextPtr, tagPtr, tagPtr->savedPriority, false);
}
- return NULL;
}
/*
*----------------------------------------------------------------------
*
+ * TkTextReleaseTag --
+ *
+ * Delete this tag if the reference counter is going to zero, in this
+ * case clean up the tag structure itself. This requires that the given
+ * tag is not in use.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory and other resources are freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextReleaseTag(
+ TkSharedText *sharedTextPtr,/* Shared text resource. */
+ TkTextTag *tagPtr, /* Tag being deleted. */
+ Tcl_HashEntry *hPtr) /* Pointer into hash table, can be NULL. */
+{
+ assert(tagPtr->refCount > 1 || !tagPtr->rootPtr);
+
+ if (--tagPtr->refCount > 0) {
+ return;
+ }
+
+ assert(!tagPtr->recentTagAddRemoveToken);
+ assert(!tagPtr->recentChangePriorityToken);
+
+ MarkIndex(sharedTextPtr, tagPtr, false);
+ sharedTextPtr->numTags -= 1;
+
+ if (!hPtr) {
+ hPtr = Tcl_FindHashEntry(&sharedTextPtr->tagTable, tagPtr->name);
+ }
+ if (hPtr) {
+ Tcl_DeleteHashEntry(hPtr);
+ } else {
+ assert(strcmp(tagPtr->name, "sel") == 0);
+ }
+
+ /*
+ * Let Tk do most of the hard work for us.
+ */
+
+ Tk_FreeConfigOptions((char *) tagPtr, tagPtr->optionTable, sharedTextPtr->peers->tkwin);
+
+ /*
+ * This associated information is managed by us.
+ */
+
+ if (tagPtr->tabArrayPtr) {
+ free(tagPtr->tabArrayPtr);
+ }
+
+ if (sharedTextPtr->tagBindingTable) {
+ Tk_DeleteAllBindings(sharedTextPtr->tagBindingTable, (ClientData) tagPtr->name);
+ }
+
+ /*
+ * If this tag is widget-specific (peer widgets) then clean up the
+ * refCount it holds.
+ */
+
+ if (tagPtr->textPtr) {
+ if (--((TkText *) tagPtr->textPtr)->refCount == 0) {
+ free(tagPtr->textPtr);
+ }
+ tagPtr->textPtr = NULL;
+ }
+
+ /*
+ * Finally free the tag's memory.
+ */
+
+ free(tagPtr);
+ DEBUG_ALLOC(tkTextCountDestroyTag++);
+}
+/*
+ *----------------------------------------------------------------------
+ *
* TkTextDeleteTag --
*
* This function is called to carry out most actions associated with the
* 'tag delete' sub-command. It will remove all evidence of the tag from
- * the B-tree, and then call TkTextFreeTag to clean up the tag structure
- * itself.
+ * the B-tree, and then clean up the tag structure itself.
*
* The only actions this doesn't carry out it to check if the deletion of
* the tag requires something to be re-displayed, and to remove the tag
@@ -1146,7 +1981,7 @@ FindTag(
* actions.
*
* Results:
- * None.
+ * Returns whether this tag was used in current text content.
*
* Side effects:
* Memory and other resources are freed, the B-tree is manipulated.
@@ -1154,54 +1989,116 @@ FindTag(
*----------------------------------------------------------------------
*/
-void
+bool
TkTextDeleteTag(
TkText *textPtr, /* Info about overall widget. */
- register TkTextTag *tagPtr) /* Tag being deleted. */
+ TkTextTag *tagPtr, /* Tag being deleted. */
+ Tcl_HashEntry *hPtr) /* Pointer into hash table, can be NULL (but only for "sel"). */
{
- TkTextIndex first, last;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ bool used;
+ unsigned i;
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, &first);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &last),
- TkBTreeTag(&first, &last, tagPtr, 0);
+ assert(!sharedTextPtr->undoStack || !TkTextUndoIsPerformingUndo(sharedTextPtr->undoStack));
+ assert(!sharedTextPtr->undoStack || !TkTextUndoIsPerformingRedo(sharedTextPtr->undoStack));
+ assert(hPtr || strcmp(tagPtr->name, "sel") == 0);
- if (tagPtr == textPtr->selTagPtr) {
+ used = !!tagPtr->rootPtr;
+
+ if (used) {
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr;
+ TkTextIndex startIndex;
+ TkTextIndex index[2];
+ TkTextSearch tSearch;
+ bool useUndo = !!(textPtr->flags & DESTROYED) && UndoTagOperation(sharedTextPtr, tagPtr);
+
+ undoInfoPtr = useUndo ? &undoInfo : NULL;
+
+ TkTextIndexSetupToStartOfText(&index[0], NULL, sharedTextPtr->tree);
+ TkTextIndexSetupToEndOfText(&index[1], NULL, sharedTextPtr->tree);
+
+ TkBTreeStartSearch(&index[0], &index[1], tagPtr, &tSearch, SEARCH_NEXT_TAGON);
+ TkBTreeNextTag(&tSearch);
+ assert(tSearch.segPtr); /* last search must not fail */
+ startIndex = tSearch.curIndex;
+
+ TkBTreeStartSearchBack(&index[1], &index[0], tagPtr, &tSearch, SEARCH_EITHER_TAGON_TAGOFF);
+ TkBTreePrevTag(&tSearch);
+ assert(tSearch.segPtr); /* last search must not fail */
+ assert(!tSearch.tagon); /* we must find tagoff */
+
+ TkBTreeTag(textPtr->sharedTextPtr, textPtr, &startIndex, &tSearch.curIndex,
+ tagPtr, false, undoInfoPtr, TkTextRedrawTag);
+
+ if (undoInfoPtr && undoInfoPtr->token) {
+ tagPtr->refCount += 1;
+ TkTextUndoPushItem(sharedTextPtr->undoStack, undoInfo.token, undoInfo.byteSize);
+ }
+ }
+
+ assert(!tagPtr->rootPtr);
+
+ if (!(textPtr->flags & DESTROYED) && tagPtr == textPtr->selTagPtr) {
/*
* Send an event that the selection changed. This is equivalent to:
* event generate $textWidget <<Selection>>
*/
TkTextSelectionEvent(textPtr);
- } else {
- /*
- * Since all peer widgets have an independent "sel" tag, we
- * don't want removal of one sel tag to remove bindings which
- * are still valid in other peer widgets.
- */
+ }
+
+ /*
+ * Update the tag priorities to reflect the deletion of this tag.
+ */
- if (textPtr->sharedTextPtr->bindingTable != NULL) {
- Tk_DeleteAllBindings(textPtr->sharedTextPtr->bindingTable,
- (ClientData) tagPtr->name);
+ tagPtr->savedPriority = tagPtr->priority;
+ ChangeTagPriority(sharedTextPtr, tagPtr, sharedTextPtr->numEnabledTags - 1, false);
+ sharedTextPtr->numEnabledTags -= 1;
+
+ /*
+ * Make sure this tag isn't referenced from the 'current' tag array.
+ */
+
+ for (i = 0; i < textPtr->numCurTags; ++i) {
+ if (textPtr->curTagArrayPtr[i] == tagPtr) {
+ memmove(textPtr->curTagArrayPtr + i,
+ textPtr->curTagArrayPtr + i + 1,
+ (textPtr->numCurTags - i - 1)*sizeof(textPtr->curTagArrayPtr[0]));
+ textPtr->numCurTags -= 1;
+ DEBUG(textPtr->curTagArrayPtr[textPtr->numCurTags] = NULL);
+ break;
}
}
/*
- * Update the tag priorities to reflect the deletion of this tag.
+ * Handle the retained undo tokens.
*/
- ChangeTagPriority(textPtr, tagPtr, textPtr->sharedTextPtr->numTags-1);
- textPtr->sharedTextPtr->numTags -= 1;
- TkTextFreeTag(textPtr, tagPtr);
+ if (tagPtr->undoTagListIndex >= 0) {
+ if (sharedTextPtr->undoStack) {
+ TkTextPushUndoTagTokens(sharedTextPtr, tagPtr);
+ } else {
+ TkTextReleaseUndoTagToken(sharedTextPtr, tagPtr);
+ }
+ }
+
+ tagPtr->isDisabled = true;
+ TkTextReleaseTag(sharedTextPtr, tagPtr, hPtr);
+ return used;
}
/*
*----------------------------------------------------------------------
*
- * TkTextFreeTag --
+ * TkTextFreeAllTags --
*
- * This function is called when a tag is deleted to free up the memory
- * and other resources associated with the tag.
+ * This function is called when all tags are deleted to free up the memory
+ * and other resources associated with tags.
+ *
+ * Note that this function is not freeing the indices
+ * ('sharedTextPtr->usedTags', 'sharedTextPtr->elisionTags'), but both
+ * sets will be cleared.
*
* Results:
* None.
@@ -1213,68 +2110,74 @@ TkTextDeleteTag(
*/
void
-TkTextFreeTag(
- TkText *textPtr, /* Info about overall widget. */
- register TkTextTag *tagPtr) /* Tag being deleted. */
+TkTextFreeAllTags(
+ TkText *textPtr) /* Info about overall widget. */
{
- int i;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr;
- /*
- * Let Tk do most of the hard work for us.
- */
+ for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
+ TkTextTag *tagPtr = Tcl_GetHashValue(hPtr);
- Tk_FreeConfigOptions((char *) tagPtr, tagPtr->optionTable,
- textPtr->tkwin);
+ assert(tagPtr->refCount == 1);
- /*
- * This associated information is managed by us.
- */
+ /*
+ * Let Tk do most of the hard work for us.
+ */
- if (tagPtr->tabArrayPtr != NULL) {
- ckfree(tagPtr->tabArrayPtr);
- }
+ Tk_FreeConfigOptions((char *) tagPtr, tagPtr->optionTable, textPtr->tkwin);
- /*
- * Make sure this tag isn't referenced from the 'current' tag array.
- */
+ /*
+ * This associated information is managed by us.
+ */
- for (i = 0; i < textPtr->numCurTags; i++) {
- if (textPtr->curTagArrayPtr[i] == tagPtr) {
- for (; i < textPtr->numCurTags-1; i++) {
- textPtr->curTagArrayPtr[i] = textPtr->curTagArrayPtr[i+1];
- }
- textPtr->curTagArrayPtr[textPtr->numCurTags-1] = NULL;
- textPtr->numCurTags--;
- break;
+ if (tagPtr->tabArrayPtr) {
+ free(tagPtr->tabArrayPtr);
}
- }
-
- /*
- * If this tag is widget-specific (peer widgets) then clean up the
- * refCount it holds.
- */
- if (tagPtr->textPtr != NULL) {
- if (textPtr != tagPtr->textPtr) {
- Tcl_Panic("Tag being deleted from wrong widget");
+ if (tagPtr->undoTagListIndex >= 0) {
+ TkTextReleaseUndoTagToken(sharedTextPtr, tagPtr);
}
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
+
+ /*
+ * If this tag is widget-specific (peer widgets) then clean up the
+ * refCount it holds.
+ */
+
+ if (tagPtr->textPtr) {
+ assert(textPtr == tagPtr->textPtr);
+ if (--textPtr->refCount == 0) {
+ free(textPtr);
+ }
+ tagPtr->textPtr = NULL;
}
- tagPtr->textPtr = NULL;
- }
- /*
- * Finally free the tag's memory.
- */
+ /*
+ * Finally free the tag's memory.
+ */
- ckfree(tagPtr);
+ free(tagPtr);
+ DEBUG_ALLOC(tkTextCountDestroyTag++);
+ }
+
+ textPtr->numCurTags = 0;
+ TkBitClear(sharedTextPtr->usedTags);
+ TkBitClear(sharedTextPtr->elisionTags);
+ TkBitClear(sharedTextPtr->affectDisplayTags);
+ TkBitClear(sharedTextPtr->notAffectDisplayTags);
+ TkBitClear(sharedTextPtr->affectDisplayNonSelTags);
+ TkBitClear(sharedTextPtr->affectGeometryTags);
+ TkBitClear(sharedTextPtr->affectGeometryNonSelTags);
+ TkBitClear(sharedTextPtr->affectLineHeightTags);
}
/*
*----------------------------------------------------------------------
*
- * SortTags --
+ * TkTextSortTags --
*
* This function sorts an array of tag pointers in increasing order of
* priority, optimizing for the common case where the array is small.
@@ -1288,23 +2191,32 @@ TkTextFreeTag(
*----------------------------------------------------------------------
*/
-static void
-SortTags(
- int numTags, /* Number of tag pointers at *tagArrayPtr. */
+static int
+TagSortProc(
+ const void *first,
+ const void *second) /* Elements to be compared. */
+{
+ return (*(TkTextTag **) first)->priority - (*(TkTextTag **) second)->priority;
+}
+
+void
+TkTextSortTags(
+ unsigned numTags, /* Number of tag pointers at *tagArrayPtr. */
TkTextTag **tagArrayPtr) /* Pointer to array of pointers. */
{
- int i, j, prio;
- register TkTextTag **tagPtrPtr;
- TkTextTag **maxPtrPtr, *tmp;
+ unsigned i, j, prio;
+ TkTextTag **tagPtrPtr;
+ TkTextTag **maxPtrPtr;
+ TkTextTag *tmp;
- if (numTags < 2) {
+ if (numTags <= 1) {
return;
}
- if (numTags < 20) {
- for (i = numTags-1; i > 0; i--, tagArrayPtr++) {
+ if (numTags <= 20) {
+ for (i = numTags - 1; i > 0; i--, tagArrayPtr++) {
maxPtrPtr = tagPtrPtr = tagArrayPtr;
prio = tagPtrPtr[0]->priority;
- for (j = i, tagPtrPtr++; j > 0; j--, tagPtrPtr++) {
+ for (j = i, tagPtrPtr += 1; j > 0; --j, ++tagPtrPtr) {
if (tagPtrPtr[0]->priority < prio) {
prio = tagPtrPtr[0]->priority;
maxPtrPtr = tagPtrPtr;
@@ -1315,40 +2227,256 @@ SortTags(
*tagArrayPtr = tmp;
}
} else {
- qsort(tagArrayPtr,(unsigned)numTags,sizeof(TkTextTag *),TagSortProc);
+ qsort(tagArrayPtr, numTags, sizeof(TkTextTag *), TagSortProc);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextReleaseUndoTagToken --
+ *
+ * Release retained undo tokens for tag operations.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Free some memory.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextReleaseUndoTagToken(
+ TkSharedText *sharedTextPtr,
+ TkTextTag *tagPtr)
+{
+ assert(sharedTextPtr);
+
+ if (!tagPtr) {
+ return;
}
+
+ assert(tagPtr->undoTagListIndex >= 0);
+ assert(tagPtr->undoTagListIndex < sharedTextPtr->undoTagListCount);
+
+ if (tagPtr->recentTagAddRemoveToken) {
+ free(tagPtr->recentTagAddRemoveToken);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ tagPtr->recentTagAddRemoveToken = NULL;
+ }
+ if (tagPtr->recentChangePriorityToken) {
+ free(tagPtr->recentChangePriorityToken);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ tagPtr->recentChangePriorityToken = NULL;
+ }
+
+ sharedTextPtr->undoTagList[tagPtr->undoTagListIndex] = NULL;
+ tagPtr->undoTagListIndex = -1;
+ assert(tagPtr->refCount > 1);
+ tagPtr->refCount -= 1;
}
/*
*----------------------------------------------------------------------
*
- * TagSortProc --
+ * TkTextInspectUndoTagItem --
*
- * This function is called by qsort() when sorting an array of tags in
- * priority order.
+ * Inspect retained undo token.
*
* Results:
- * The return value is -1 if the first argument should be before the
- * second element (i.e. it has lower priority), 0 if it's equivalent
- * (this should never happen!), and 1 if it should be after the second
- * element.
+ * None.
*
* Side effects:
+ * Memory is allocated for the result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextInspectUndoTagItem(
+ const TkSharedText *sharedTextPtr,
+ const TkTextTag *tagPtr,
+ Tcl_Obj* objPtr)
+{
+ if (tagPtr) {
+ if (tagPtr->recentTagAddRemoveToken && !tagPtr->recentTagAddRemoveTokenIsNull) {
+ Tcl_ListObjAppendElement(NULL, objPtr,
+ TkBTreeUndoTagInspect(sharedTextPtr, tagPtr->recentTagAddRemoveToken));
+ }
+ if (tagPtr->recentChangePriorityToken) {
+ Tcl_ListObjAppendElement(NULL, objPtr,
+ UndoChangeTagPriorityInspect(sharedTextPtr, tagPtr->recentChangePriorityToken));
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextPushUndoTagTokens --
+ *
+ * Push retained undo tokens for tag operations onto the undo stack.
+ *
+ * Results:
* None.
*
+ * Side effects:
+ * Same as TkTextUndoPushItem.
+ *
*----------------------------------------------------------------------
*/
-static int
-TagSortProc(
- const void *first,
- const void *second) /* Elements to be compared. */
+void
+TkTextPushUndoTagTokens(
+ TkSharedText *sharedTextPtr,
+ TkTextTag *tagPtr)
+{
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->undoStack);
+
+ if (!tagPtr) {
+ return;
+ }
+
+ assert(tagPtr->undoTagListIndex >= 0);
+ assert(tagPtr->undoTagListIndex < sharedTextPtr->undoTagListCount);
+
+ if (tagPtr->recentTagAddRemoveToken) {
+ if (tagPtr->recentTagAddRemoveTokenIsNull) {
+ free(tagPtr->recentTagAddRemoveToken);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ } else {
+ TkTextUndoPushItem(sharedTextPtr->undoStack, tagPtr->recentTagAddRemoveToken, 0);
+ tagPtr->refCount += 1;
+ }
+ tagPtr->recentTagAddRemoveToken = NULL;
+ }
+ if (tagPtr->recentChangePriorityToken) {
+ if (tagPtr->savedPriority != tagPtr->priority) {
+ TkTextUndoPushItem(sharedTextPtr->undoStack, tagPtr->recentChangePriorityToken, 0);
+ tagPtr->refCount += 1;
+ } else {
+ free(tagPtr->recentChangePriorityToken);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ }
+ tagPtr->recentChangePriorityToken = NULL;
+ }
+
+ sharedTextPtr->undoTagList[tagPtr->undoTagListIndex] = NULL;
+ tagPtr->undoTagListIndex = -1;
+ assert(tagPtr->refCount > 1);
+ tagPtr->refCount -= 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextTagAddRetainedUndo --
+ *
+ * Add given tag to undo list, because this tag has retained undo
+ * tokens.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The reference counter of the tag will be incremented.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextTagAddRetainedUndo(
+ TkSharedText *sharedTextPtr, /* Shared text resource. */
+ TkTextTag *tagPtr) /* Add this tag to undo list. */
{
- TkTextTag *tagPtr1, *tagPtr2;
+ assert(sharedTextPtr);
+ assert(tagPtr);
- tagPtr1 = * (TkTextTag **) first;
- tagPtr2 = * (TkTextTag **) second;
- return tagPtr1->priority - tagPtr2->priority;
+ if (tagPtr->undoTagListIndex >= 0) {
+ return;
+ }
+
+ if (sharedTextPtr->undoTagListCount == sharedTextPtr->undoTagListSize) {
+ sharedTextPtr->undoTagListSize = 2*sharedTextPtr->numEnabledTags;
+ sharedTextPtr->undoTagList = realloc(sharedTextPtr->undoTagList,
+ sharedTextPtr->undoTagListSize * sizeof(sharedTextPtr->undoTagList[0]));
+ }
+ sharedTextPtr->undoTagList[sharedTextPtr->undoTagListCount] = tagPtr;
+ sharedTextPtr->undoStackEvent = true;
+ sharedTextPtr->lastUndoTokenType = -1;
+ tagPtr->undoTagListIndex = sharedTextPtr->undoTagListCount++;
+ tagPtr->refCount += 1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextPushTagPriorityUndo --
+ *
+ * This function is pushing an undo item for setting the priority
+ * of a mark (raise/lower command).
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some memory will be allocated, and see TkTextPushUndoToken.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextPushTagPriorityUndo(
+ TkSharedText *sharedTextPtr,
+ TkTextTag *tagPtr,
+ unsigned priority)
+{
+ UndoTokenTagPriority *token;
+
+ token = malloc(sizeof(UndoTokenTagPriority));
+ token->undoType = &undoTokenTagPriorityType;
+ (token->tagPtr = tagPtr)->refCount += 1;
+ token->priority = priority;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+
+ TkTextPushUndoToken(sharedTextPtr, token, 0);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextPushTagPriorityRedo --
+ *
+ * This function is pushing a redo item for setting the priority
+ * of a mark (raise/lower command).
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some memory will be allocated, and see TkTextPushRedoToken.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextPushTagPriorityRedo(
+ TkSharedText *sharedTextPtr,
+ TkTextTag *tagPtr,
+ unsigned priority)
+{
+ UndoTokenTagPriority *token;
+
+ token = malloc(sizeof(UndoTokenTagPriority));
+ token->undoType = &redoTokenTagPriorityType;
+ (token->tagPtr = tagPtr)->refCount += 1;
+ token->priority = priority;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+
+ TkTextPushRedoToken(sharedTextPtr, token, 0);
}
/*
@@ -1365,39 +2493,62 @@ TagSortProc(
* Side effects:
* Priorities may be changed for some or all of the tags in textPtr. The
* tags will be arranged so that there is exactly one tag at each
- * priority level between 0 and textPtr->sharedTextPtr->numTags-1, with
- * tagPtr at priority "prio".
+ * priority level between 0 and textPtr->sharedTextPtr->numEnabledTags-1,
+ * with tagPtr at priority "newPriority".
*
*----------------------------------------------------------------------
*/
-static void
+static bool
ChangeTagPriority(
- TkText *textPtr, /* Information about text widget. */
+ TkSharedText *sharedTextPtr,/* Shared text resource. */
TkTextTag *tagPtr, /* Tag whose priority is to be changed. */
- int prio) /* New priority for tag. */
+ unsigned newPriority, /* New priority for tag. */
+ bool undo) /* Push undo item for this action? */
{
- int low, high, delta;
- register TkTextTag *tagPtr2;
+ int delta;
+ unsigned low, high;
+ TkTextTag *tagPtr2;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
+ TkText *peer;
- if (prio < 0) {
- prio = 0;
- }
- if (prio >= textPtr->sharedTextPtr->numTags) {
- prio = textPtr->sharedTextPtr->numTags-1;
+ assert(newPriority < sharedTextPtr->numEnabledTags);
+
+ if (newPriority == tagPtr->priority) {
+ return false;
}
- if (prio == tagPtr->priority) {
- return;
+
+ if (undo && tagPtr->undo && !TkTextUndoStackIsFull(sharedTextPtr->undoStack)) {
+ UndoTokenTagPriority *token = (UndoTokenTagPriority *) tagPtr->recentChangePriorityToken;
+
+ /*
+ * Don't push changes of tag priorities immediately onto the undo stack, this
+ * may blow up the stack. We save this undo token inside the tag, in this way
+ * only the relevant changes will be pushed as soon as a separator will be
+ * pushed.
+ */
+
+ if (!tagPtr->recentChangePriorityToken) {
+ tagPtr->savedPriority = tagPtr->priority;
+ token = malloc(sizeof(UndoTokenTagPriority));
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ tagPtr->recentChangePriorityToken = (TkTextUndoToken *) token;
+ TkTextTagAddRetainedUndo(sharedTextPtr, tagPtr);
+ }
+
+ token->undoType = &undoTokenTagPriorityType;
+ token->tagPtr = tagPtr;
+ token->priority = tagPtr->priority;
}
- if (prio < tagPtr->priority) {
- low = prio;
- high = tagPtr->priority-1;
+
+ if (newPriority < tagPtr->priority) {
+ low = newPriority;
+ high = tagPtr->priority - 1;
delta = 1;
} else {
- low = tagPtr->priority+1;
- high = prio;
+ low = tagPtr->priority + 1;
+ high = newPriority;
delta = -1;
}
@@ -1405,18 +2556,24 @@ ChangeTagPriority(
* Adjust first the 'sel' tag, then all others from the hash table
*/
- if ((textPtr->selTagPtr->priority >= low)
- && (textPtr->selTagPtr->priority <= high)) {
- textPtr->selTagPtr->priority += delta;
+ for (peer = sharedTextPtr->peers; peer; peer = peer->next) {
+ if (low <= peer->selTagPtr->priority && peer->selTagPtr->priority <= high) {
+ peer->selTagPtr->priority += delta;
+ }
}
- for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->tagTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
+
+ for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
tagPtr2 = Tcl_GetHashValue(hPtr);
- if ((tagPtr2->priority >= low) && (tagPtr2->priority <= high)) {
+ if (low <= tagPtr2->priority && tagPtr2->priority <= high) {
tagPtr2->priority += delta;
}
}
- tagPtr->priority = prio;
+
+ tagPtr->priority = newPriority;
+
+ return true;
}
/*
@@ -1442,13 +2599,12 @@ TkTextBindProc(
ClientData clientData, /* Pointer to canvas structure. */
XEvent *eventPtr) /* Pointer to X event that just happened. */
{
- TkText *textPtr = clientData;
- int repick = 0;
+ enum { AnyButtonMask = Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask };
-# define AnyButtonMask \
- (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)
+ TkText *textPtr = clientData;
+ bool repick = false;
- textPtr->refCount++;
+ textPtr->refCount += 1;
/*
* This code simulates grabs for mouse buttons by keeping track of whether
@@ -1459,34 +2615,20 @@ TkTextBindProc(
if (eventPtr->type == ButtonPress) {
textPtr->flags |= BUTTON_DOWN;
} else if (eventPtr->type == ButtonRelease) {
- int mask;
+ unsigned mask = 0;
switch (eventPtr->xbutton.button) {
- case Button1:
- mask = Button1Mask;
- break;
- case Button2:
- mask = Button2Mask;
- break;
- case Button3:
- mask = Button3Mask;
- break;
- case Button4:
- mask = Button4Mask;
- break;
- case Button5:
- mask = Button5Mask;
- break;
- default:
- mask = 0;
- break;
+ case Button1: mask = Button1Mask; break;
+ case Button2: mask = Button2Mask; break;
+ case Button3: mask = Button3Mask; break;
+ case Button4: mask = Button4Mask; break;
+ case Button5: mask = Button5Mask; break;
}
- if ((eventPtr->xbutton.state & AnyButtonMask) == (unsigned) mask) {
+ if ((eventPtr->xbutton.state & AnyButtonMask) == mask) {
textPtr->flags &= ~BUTTON_DOWN;
- repick = 1;
+ repick = true;
}
- } else if ((eventPtr->type == EnterNotify)
- || (eventPtr->type == LeaveNotify)) {
+ } else if (eventPtr->type == EnterNotify || eventPtr->type == LeaveNotify) {
if (eventPtr->xcrossing.state & AnyButtonMask) {
textPtr->flags |= BUTTON_DOWN;
} else {
@@ -1502,18 +2644,24 @@ TkTextBindProc(
}
TkTextPickCurrent(textPtr, eventPtr);
}
- if ((textPtr->numCurTags > 0)
- && (textPtr->sharedTextPtr->bindingTable != NULL)
- && (textPtr->tkwin != NULL) && !(textPtr->flags & DESTROYED)) {
- TagBindEvent(textPtr, eventPtr, textPtr->numCurTags,
- textPtr->curTagArrayPtr);
+ if (!(textPtr->flags & DESTROYED)) {
+ if (textPtr->numCurTags > 0 && textPtr->sharedTextPtr->tagBindingTable) {
+ TagBindEvent(textPtr, eventPtr, textPtr->numCurTags, textPtr->curTagArrayPtr);
+ }
+ if (textPtr->hoveredImageArrSize && textPtr->sharedTextPtr->imageBindingTable) {
+ unsigned i;
+
+ for (i = 0; i < textPtr->hoveredImageArrSize; ++i) {
+ Tk_BindEvent(textPtr->sharedTextPtr->imageBindingTable, eventPtr,
+ textPtr->tkwin, 1, (ClientData *) &textPtr->hoveredImageArr[i]->name);
+ }
+ }
}
if (repick) {
- unsigned int oldState;
+ unsigned oldState;
oldState = eventPtr->xbutton.state;
- eventPtr->xbutton.state &= ~(Button1Mask|Button2Mask
- |Button3Mask|Button4Mask|Button5Mask);
+ eventPtr->xbutton.state &= ~(Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask);
if (!(textPtr->flags & DESTROYED)) {
TkTextPickCurrent(textPtr, eventPtr);
}
@@ -1521,8 +2669,8 @@ TkTextBindProc(
}
done:
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
+ if (--textPtr->refCount == 0) {
+ free(textPtr);
}
}
@@ -1532,37 +2680,54 @@ TkTextBindProc(
* TkTextPickCurrent --
*
* Find the character containing the coordinates in an event and place
- * the "current" mark on that character. If the "current" mark has moved
- * then generate a fake leave event on the old current character and a
- * fake enter event on the new current character.
+ * the "current" mark on that character (but the real update of the
+ * segment will be postponed). If the "current" mark has moved then
+ * generate a fake leave event on the old current character and a fake
+ * enter event on the new current character.
*
* Results:
* None.
*
* Side effects:
- * The current mark for textPtr may change. If it does, then the commands
- * associated with character entry and leave could do just about
- * anything. For example, the text widget might be deleted. It is up to
- * the caller to protect itself by incrementing the refCount of the text
- * widget.
+ * The index of the current mark for textPtr may change. If it does,
+ * then the commands associated with character entry and leave could
+ * do just about anything. For example, the text widget might be deleted.
+ * It is up to the caller to protect itself by incrementing the refCount
+ * of the text widget.
*
*--------------------------------------------------------------
*/
+static bool
+ImageHitCallback(
+ TkQTreeUid uid,
+ const TkQTreeRect *rect,
+ TkQTreeState *state,
+ TkQTreeClientData arg)
+{
+ TkTextEmbImage **eiListPtr = (TkTextEmbImage **) arg;
+ TkTextEmbImage *eiPtr = (TkTextEmbImage *) uid;
+
+ eiPtr->nextPtr = *eiListPtr;
+ *eiListPtr = eiPtr;
+ return true;
+}
+
void
TkTextPickCurrent(
- register TkText *textPtr, /* Text widget in which to select current
- * character. */
+ TkText *textPtr, /* Text widget in which to select current character. */
XEvent *eventPtr) /* Event describing location of mouse cursor.
- * Must be EnterWindow, LeaveWindow,
- * ButtonRelease, or MotionNotify. */
+ * Must be EnterWindow, LeaveWindow, ButtonRelease, or MotionNotify. */
{
TkTextIndex index;
- TkTextTag **oldArrayPtr, **newArrayPtr;
- TkTextTag **copyArrayPtr = NULL;
- /* Initialization needed to prevent compiler
- * warning. */
- int numOldTags, numNewTags, i, j, size, nearby;
+ TkTextTag **oldArrayPtr;
+ TkTextTag **newArrayPtr;
+ TkTextTag **copyArrayPtr = NULL; /* prevent compiler warning */
+ TkTextTag *copyArrayBuffer[32];
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ unsigned numOldTags, numNewTags, i, size, epoch;
+ bool sameChunkWithUnchangedTags = false;
+ bool nearby = false;
XEvent event;
/*
@@ -1572,20 +2737,19 @@ TkTextPickCurrent(
*/
if (textPtr->flags & BUTTON_DOWN) {
- if (((eventPtr->type == EnterNotify)
- || (eventPtr->type == LeaveNotify))
- && ((eventPtr->xcrossing.mode == NotifyGrab)
- || (eventPtr->xcrossing.mode == NotifyUngrab))) {
- /*
- * Special case: the window is being entered or left because of a
- * grab or ungrab. In this case, repick after all. Furthermore,
- * clear BUTTON_DOWN to release the simulated grab.
- */
-
- textPtr->flags &= ~BUTTON_DOWN;
- } else {
+ if ((eventPtr->type != EnterNotify && eventPtr->type != LeaveNotify)
+ || (eventPtr->xcrossing.mode != NotifyGrab
+ && eventPtr->xcrossing.mode != NotifyUngrab)) {
return;
}
+
+ /*
+ * Special case: the window is being entered or left because of a
+ * grab or ungrab. In this case, repick after all. Furthermore,
+ * clear BUTTON_DOWN to release the simulated grab.
+ */
+
+ textPtr->flags &= ~BUTTON_DOWN;
}
/*
@@ -1598,12 +2762,10 @@ TkTextPickCurrent(
*/
if (eventPtr != &textPtr->pickEvent) {
- if ((eventPtr->type == MotionNotify)
- || (eventPtr->type == ButtonRelease)) {
+ if (eventPtr->type == MotionNotify || eventPtr->type == ButtonRelease) {
textPtr->pickEvent.xcrossing.type = EnterNotify;
textPtr->pickEvent.xcrossing.serial = eventPtr->xmotion.serial;
- textPtr->pickEvent.xcrossing.send_event
- = eventPtr->xmotion.send_event;
+ textPtr->pickEvent.xcrossing.send_event = eventPtr->xmotion.send_event;
textPtr->pickEvent.xcrossing.display = eventPtr->xmotion.display;
textPtr->pickEvent.xcrossing.window = eventPtr->xmotion.window;
textPtr->pickEvent.xcrossing.root = eventPtr->xmotion.root;
@@ -1615,8 +2777,7 @@ TkTextPickCurrent(
textPtr->pickEvent.xcrossing.y_root = eventPtr->xmotion.y_root;
textPtr->pickEvent.xcrossing.mode = NotifyNormal;
textPtr->pickEvent.xcrossing.detail = NotifyNonlinear;
- textPtr->pickEvent.xcrossing.same_screen
- = eventPtr->xmotion.same_screen;
+ textPtr->pickEvent.xcrossing.same_screen = eventPtr->xmotion.same_screen;
textPtr->pickEvent.xcrossing.focus = False;
textPtr->pickEvent.xcrossing.state = eventPtr->xmotion.state;
} else {
@@ -1624,79 +2785,117 @@ TkTextPickCurrent(
}
}
+ if (textPtr->dontRepick) {
+ return;
+ }
+
/*
- * Find the new current character, then find and sort all of the tags
- * associated with it.
+ * Find the new current character, then find and sort all of the tags associated with it.
*/
+ numNewTags = 0;
+ newArrayPtr = NULL;
+
if (textPtr->pickEvent.type != LeaveNotify) {
- TkTextPixelIndex(textPtr, textPtr->pickEvent.xcrossing.x,
- textPtr->pickEvent.xcrossing.y, &index, &nearby);
- if (nearby) {
- newArrayPtr = NULL;
- numNewTags = 0;
- } else {
- newArrayPtr = TkBTreeGetTags(&index, textPtr, &numNewTags);
- SortTags(numNewTags, newArrayPtr);
+ sameChunkWithUnchangedTags = TkTextPixelIndex(textPtr,
+ textPtr->pickEvent.xcrossing.x, textPtr->pickEvent.xcrossing.y, &index, &nearby);
+
+ if (textPtr->currNearbyFlag != nearby) {
+ sameChunkWithUnchangedTags = false;
+ textPtr->currNearbyFlag = nearby;
+ } else if (nearby) {
+ sameChunkWithUnchangedTags = true;
+ } else if (eventPtr->type != MotionNotify || sharedTextPtr->numMotionEventBindings > 0) {
+ sameChunkWithUnchangedTags = false;
}
- } else {
- newArrayPtr = NULL;
- numNewTags = 0;
- }
-
- /*
- * Resort the tags associated with the previous marked character (the
- * priorities might have changed), then make a copy of the new tags, and
- * compare the old tags to the copy, nullifying any tags that are present
- * in both groups (i.e. the tags that haven't changed).
- */
- SortTags(textPtr->numCurTags, textPtr->curTagArrayPtr);
- if (numNewTags > 0) {
- size = numNewTags * sizeof(TkTextTag *);
- copyArrayPtr = ckalloc(size);
- memcpy(copyArrayPtr, newArrayPtr, (size_t) size);
- for (i = 0; i < textPtr->numCurTags; i++) {
- for (j = 0; j < numNewTags; j++) {
- if (textPtr->curTagArrayPtr[i] == copyArrayPtr[j]) {
- textPtr->curTagArrayPtr[i] = NULL;
- copyArrayPtr[j] = NULL;
- break;
+ if (!nearby && !sameChunkWithUnchangedTags) {
+ TkTextTag *tagPtr = TkBTreeGetTags(&index);
+ if (tagPtr) {
+ epoch = ++sharedTextPtr->pickEpoch;
+ newArrayPtr = malloc(sharedTextPtr->numEnabledTags * sizeof(newArrayPtr[0]));
+ for (i = 0; i < textPtr->numCurTags; ++i) {
+ textPtr->curTagArrayPtr[i]->flag = false; /* mark as *not* common */
+ textPtr->curTagArrayPtr[i]->epoch = epoch;
+ }
+ for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
+ newArrayPtr[numNewTags++] = tagPtr;
+ tagPtr->flag = (tagPtr->epoch == epoch); /* is common? */
}
+ TkTextSortTags(numNewTags, newArrayPtr);
}
}
}
- /*
- * Invoke the binding system with a LeaveNotify event for all of the tags
- * that have gone away. We have to be careful here, because it's possible
- * that the binding could do something (like calling tkwait) that
- * eventually modifies textPtr->curTagArrayPtr. To avoid problems in
- * situations like this, update curTagArrayPtr to its new value before
- * invoking any bindings, and don't use it any more here.
- */
+ if (!sameChunkWithUnchangedTags) {
+ /*
+ * Resort the tags associated with the previous marked character (the
+ * priorities might have changed), then make a copy of the new tags, and
+ * compare the old tags to the copy, nullifying any tags that are present
+ * in both groups (i.e. the tags that haven't changed).
+ */
- numOldTags = textPtr->numCurTags;
- textPtr->numCurTags = numNewTags;
- oldArrayPtr = textPtr->curTagArrayPtr;
- textPtr->curTagArrayPtr = newArrayPtr;
- if (numOldTags != 0) {
- if ((textPtr->sharedTextPtr->bindingTable != NULL)
- && (textPtr->tkwin != NULL)
- && !(textPtr->flags & DESTROYED)) {
- event = textPtr->pickEvent;
- event.type = LeaveNotify;
+ TkTextSortTags(textPtr->numCurTags, textPtr->curTagArrayPtr);
+ if (numNewTags > 0) {
+ size = numNewTags * sizeof(copyArrayPtr[0]);
+ if (size < sizeof(copyArrayBuffer)/sizeof(copyArrayBuffer[0])) {
+ copyArrayPtr = copyArrayBuffer;
+ } else {
+ copyArrayPtr = memcpy(malloc(size), newArrayPtr, size);
+ }
+ memcpy(copyArrayPtr, newArrayPtr, size);
/*
- * Always use a detail of NotifyAncestor. Besides being
- * consistent, this avoids problems where the binding code will
- * discard NotifyInferior events.
+ * Omit common tags. Note that the complexity of this algorithm is linear,
+ * the complexity of old implementation (wish8.6) was quadratic.
*/
- event.xcrossing.detail = NotifyAncestor;
- TagBindEvent(textPtr, &event, numOldTags, oldArrayPtr);
+ for (i = 0; i < textPtr->numCurTags; ++i) {
+ if (textPtr->curTagArrayPtr[i]->flag) {
+ textPtr->curTagArrayPtr[i] = NULL;
+ }
+ }
+ for (i = 0; i < numNewTags; ++i) {
+ if (copyArrayPtr[i]->flag) {
+ copyArrayPtr[i] = NULL;
+ }
+ }
+ }
+
+ /*
+ * Invoke the binding system with a LeaveNotify event for all of the tags
+ * that have gone away. We have to be careful here, because it's possible
+ * that the binding could do something (like calling tkwait) that
+ * eventually modifies textPtr->curTagArrayPtr. To avoid problems in
+ * situations like this, update curTagArrayPtr to its new value before
+ * invoking any bindings, and don't use it any more here.
+ */
+
+ numOldTags = textPtr->numCurTags;
+ textPtr->numCurTags = numNewTags;
+ oldArrayPtr = textPtr->curTagArrayPtr;
+ textPtr->curTagArrayPtr = newArrayPtr;
+
+ if (numOldTags > 0) {
+ if (sharedTextPtr->tagBindingTable && !(textPtr->flags & DESTROYED)) {
+ event = textPtr->pickEvent;
+ event.type = LeaveNotify;
+
+ /*
+ * Always use a detail of NotifyAncestor. Besides being
+ * consistent, this avoids problems where the binding code will
+ * discard NotifyInferior events.
+ */
+
+ event.xcrossing.detail = NotifyAncestor;
+ TagBindEvent(textPtr, &event, numOldTags, oldArrayPtr);
+ }
+ free(oldArrayPtr);
}
- ckfree(oldArrayPtr);
+ }
+
+ if (textPtr->flags & DESTROYED) {
+ return;
}
/*
@@ -1706,19 +2905,92 @@ TkTextPickCurrent(
* appeared.
*/
- TkTextPixelIndex(textPtr, textPtr->pickEvent.xcrossing.x,
- textPtr->pickEvent.xcrossing.y, &index, &nearby);
- TkTextSetMark(textPtr, "current", &index);
- if (numNewTags != 0) {
- if ((textPtr->sharedTextPtr->bindingTable != NULL)
- && (textPtr->tkwin != NULL)
- && !(textPtr->flags & DESTROYED) && !nearby) {
+ TkTextPixelIndex(textPtr, textPtr->pickEvent.xcrossing.x, textPtr->pickEvent.xcrossing.y,
+ &index, &nearby);
+
+ if (numNewTags > 0) {
+ if (sharedTextPtr->tagBindingTable) {
+ assert(!nearby);
event = textPtr->pickEvent;
event.type = EnterNotify;
event.xcrossing.detail = NotifyAncestor;
TagBindEvent(textPtr, &event, numNewTags, copyArrayPtr);
}
- ckfree(copyArrayPtr);
+ if (copyArrayPtr != copyArrayBuffer) {
+ free(copyArrayPtr);
+ }
+ }
+
+ if (textPtr->flags & DESTROYED) {
+ return;
+ }
+
+ /*
+ * We want to avoid that a cursor movement is constantly splitting and
+ * joining char segments. So we postpone the insertion of the "current"
+ * mark until TextWidgetObjCmd will be executed.
+ */
+
+ textPtr->currentMarkIndex = index;
+ TkTextIndexToByteIndex(&textPtr->currentMarkIndex);
+ textPtr->haveToSetCurrentMark = true;
+ sharedTextPtr->haveToSetCurrentMark = true;
+
+ if (textPtr->imageBboxTree && sharedTextPtr->imageBindingTable) {
+ /*
+ * Trigger the Enter and Leave events for embedded images.
+ * It's quite unlikely, but we have to consider that some images are overlapping.
+ */
+
+ TkTextEmbImage *eiListPtr = NULL, *eiPtr;
+ unsigned countHoveredImages = 0;
+ int dx, dy;
+
+ event = textPtr->pickEvent;
+ switch (event.type) {
+ case EnterNotify: /* fallthru */
+ case LeaveNotify: event.xcrossing.detail = NotifyAncestor; break;
+ case FocusIn: /* fallthru */
+ case FocusOut: event.xfocus.detail = NotifyAncestor; break;
+ }
+ TkTextGetViewOffset(textPtr, &dx, &dy);
+ TkQTreeSearch(
+ textPtr->imageBboxTree,
+ eventPtr->xmotion.x + dx,
+ eventPtr->xmotion.y + dy,
+ ImageHitCallback,
+ (TkQTreeClientData) &eiListPtr);
+ for (i = 0; i < textPtr->hoveredImageArrSize; ++i) {
+ textPtr->hoveredImageArr[i]->hovered = false;
+ }
+ for (eiPtr = eiListPtr; eiPtr; eiPtr = eiPtr->nextPtr, ++countHoveredImages) {
+ eiPtr->hovered = true;
+ }
+ for (i = 0; i < textPtr->hoveredImageArrSize; ++i) {
+ eiPtr = textPtr->hoveredImageArr[i];
+ if (!eiPtr->hovered) {
+ assert(eiPtr->image);
+ event.type = LeaveNotify;
+ Tk_BindEvent(sharedTextPtr->imageBindingTable, &event,
+ textPtr->tkwin, 1, (ClientData *) &eiPtr->name);
+ }
+ }
+ textPtr->hoveredImageArrSize = 0;
+ if (countHoveredImages > textPtr->hoveredImageArrCapacity) {
+ textPtr->hoveredImageArrCapacity = MAX(4, 2*textPtr->hoveredImageArrCapacity);
+ textPtr->hoveredImageArr = realloc(textPtr->hoveredImageArr,
+ textPtr->hoveredImageArrCapacity * sizeof(textPtr->hoveredImageArr[0]));
+ }
+ for (eiPtr = eiListPtr; eiPtr; eiPtr = eiPtr->nextPtr) {
+ if (eiPtr->hovered) {
+ assert(eiPtr->image);
+ event.type = EnterNotify;
+ event.xcrossing.detail = NotifyAncestor;
+ Tk_BindEvent(sharedTextPtr->imageBindingTable, &event,
+ textPtr->tkwin, 1, (ClientData *) &eiPtr->name);
+ textPtr->hoveredImageArr[textPtr->hoveredImageArrSize++] = eiPtr;
+ }
+ }
}
}
@@ -1745,22 +3017,21 @@ static void
TagBindEvent(
TkText *textPtr, /* Text widget to fire bindings in. */
XEvent *eventPtr, /* What actually happened. */
- int numTags, /* Number of relevant tags. */
+ unsigned numTags, /* Number of relevant tags. */
TkTextTag **tagArrayPtr) /* Array of relevant tags. */
{
-# define NUM_BIND_TAGS 10
- const char *nameArray[NUM_BIND_TAGS];
+ const char *nameArrayBuf[10];
const char **nameArrPtr;
- int i;
+ unsigned i;
/*
* Try to avoid allocation unless there are lots of tags.
*/
- if (numTags > NUM_BIND_TAGS) {
- nameArrPtr = ckalloc(numTags * sizeof(const char *));
+ if (numTags > sizeof(nameArrayBuf) / sizeof(nameArrayBuf[0])) {
+ nameArrPtr = malloc(numTags * sizeof(nameArrPtr[0]));
} else {
- nameArrPtr = nameArray;
+ nameArrPtr = nameArrayBuf;
}
/*
@@ -1769,10 +3040,10 @@ TagBindEvent(
* peer widgets, despite the fact that each has its own tagPtr object.
*/
- for (i = 0; i < numTags; i++) {
+ for (i = 0; i < numTags; ++i) {
TkTextTag *tagPtr = tagArrayPtr[i];
- if (tagPtr != NULL) {
+ if (tagPtr) {
nameArrPtr[i] = tagPtr->name;
} else {
/*
@@ -1784,18 +3055,240 @@ TagBindEvent(
nameArrPtr[i] = NULL;
}
}
- Tk_BindEvent(textPtr->sharedTextPtr->bindingTable, eventPtr,
+ Tk_BindEvent(textPtr->sharedTextPtr->tagBindingTable, eventPtr,
textPtr->tkwin, numTags, (ClientData *) nameArrPtr);
- if (numTags > NUM_BIND_TAGS) {
- ckfree(nameArrPtr);
+ if (nameArrPtr != nameArrayBuf) {
+ free(nameArrPtr);
}
}
/*
+ *--------------------------------------------------------------
+ *
+ * EnumerateTags --
+ *
+ * Implements the "tag enumerate" command, see documentation.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * Memory is allocated for the result, if needed (standard Tcl result
+ * side effects).
+ *
+ *--------------------------------------------------------------
+ */
+
+static TkBitField *
+AddBits(
+ TkBitField *dst, /* can be NULL */
+ const TkBitField *src)
+{
+ if (!dst) {
+ dst = TkBitResize(NULL, TkBitSize(src));
+ }
+ TkBitJoin(dst, src);
+ return dst;
+}
+
+static TkBitField *
+AddComplementBits(
+ TkBitField *dst, /* can be NULL */
+ const TkBitField *src)
+{
+ if (!dst) {
+ dst = TkBitResize(NULL, TkBitSize(src));
+ }
+ TkBitComplementTo(dst, src);
+ return dst;
+}
+
+static TkBitField *
+AddSet(
+ const TkSharedText *sharedTextPtr,
+ TkBitField *dst, /* can be NULL */
+ const TkTextTagSet *src)
+{
+ TkBitField *compl = TkTextTagSetToBits(src, TkBitSize(sharedTextPtr->usedTags));
+
+ dst = AddBits(dst, compl);
+ TkBitDecrRefCount(compl);
+ return dst;
+}
+
+static TkBitField *
+AddComplementSet(
+ const TkSharedText *sharedTextPtr,
+ TkBitField *dst, /* can be NULL */
+ const TkTextTagSet *src)
+{
+ TkBitField *compl = TkTextTagSetToBits(src, TkBitSize(sharedTextPtr->usedTags));
+
+ dst = AddComplementBits(dst, compl);
+ TkBitDecrRefCount(compl);
+ return dst;
+}
+
+static int
+EnumerateTags(
+ Tcl_Interp *interp,
+ TkText *textPtr,
+ int objc,
+ Tcl_Obj *const *objv)
+{
+ static const char *const optStrings[] = {
+ "-all", "-discardselection", "-display", "-elide", "-geometry", "-lineheight",
+ "-nodisplay", "-noelide", "-nogeometry", "-nolineheight", "-noselection",
+ "-noundo", "-noused", "-selection", "-undo", "-unused", "-used", NULL
+ };
+ enum opts {
+ ENUM_ALL, ENUM_DISCARD_SELECTION, ENUM_DISPLAY, ENUM_ELIDE, ENUM_GEOEMTRY, ENUM_LINEHEIGHT,
+ ENUM_NO_DISPLAY, ENUM_NO_ELIDE, ENUM_NO_GEOMETRY, ENUM_NO_LINEHEIGHT, ENUM_NO_SELECTION,
+ ENUM_NO_UNDO, ENUM_NO_USED, ENUM_SELECTION, ENUM_UNDO, ENUM_UNUSED, ENUM_USED
+ };
+
+ const TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkBitField *includeBits = NULL;
+ TkBitField *discardBits = NULL;
+ bool discardSelection = false;
+ TkTextTag **arrayPtr;
+ int index, countTags, i;
+
+ for (i = 3; i < objc; ++i) {
+ const char *option = Tcl_GetString(objv[i]);
+
+ if (*option != '-') {
+ break;
+ }
+
+ if (Tcl_GetIndexFromObjStruct(interp, objv[i], optStrings, sizeof(char *),
+ "tag option", 0, &index) != TCL_OK) {
+ if (includeBits) { TkBitDecrRefCount(includeBits); }
+ if (discardBits) { TkBitDecrRefCount(discardBits); }
+ return TCL_ERROR;
+ }
+
+ switch ((enum opts) index) {
+ case ENUM_ALL:
+ case ENUM_DISCARD_SELECTION:
+ discardSelection = true;
+ break;
+ case ENUM_DISPLAY:
+ includeBits = AddBits(includeBits, sharedTextPtr->affectDisplayTags);
+ break;
+ case ENUM_ELIDE:
+ includeBits = AddBits(includeBits, sharedTextPtr->elisionTags);
+ break;
+ case ENUM_GEOEMTRY:
+ includeBits = AddBits(includeBits, sharedTextPtr->affectGeometryTags);
+ break;
+ case ENUM_LINEHEIGHT:
+ includeBits = AddBits(includeBits, sharedTextPtr->affectLineHeightTags);
+ break;
+ case ENUM_NO_DISPLAY:
+ discardBits = AddBits(discardBits, sharedTextPtr->affectDisplayTags);
+ break;
+ case ENUM_NO_ELIDE:
+ discardBits = AddBits(discardBits, sharedTextPtr->elisionTags);
+ break;
+ case ENUM_NO_GEOMETRY:
+ discardBits = AddBits(discardBits, sharedTextPtr->affectGeometryTags);
+ break;
+ case ENUM_NO_LINEHEIGHT:
+ discardBits = AddBits(discardBits, sharedTextPtr->affectLineHeightTags);
+ break;
+ case ENUM_NO_SELECTION:
+ discardSelection = true;
+ break;
+ case ENUM_NO_UNDO:
+ discardBits = AddComplementBits(discardBits, sharedTextPtr->dontUndoTags);
+ break;
+ case ENUM_NO_USED:
+ discardBits = AddComplementSet(sharedTextPtr, discardBits,
+ TkBTreeRootTagInfo(sharedTextPtr->tree));
+ break;
+ case ENUM_SELECTION:
+ includeBits = AddBits(includeBits, sharedTextPtr->selectionTags);
+ break;
+ case ENUM_UNDO:
+ includeBits = AddComplementBits(includeBits, sharedTextPtr->dontUndoTags);
+ break;
+ case ENUM_UNUSED:
+ includeBits = AddComplementSet(sharedTextPtr, includeBits,
+ TkBTreeRootTagInfo(sharedTextPtr->tree));
+ break;
+ case ENUM_USED:
+ includeBits = AddSet(sharedTextPtr, includeBits, TkBTreeRootTagInfo(sharedTextPtr->tree));
+ break;
+ }
+ }
+
+ if (objc == i + 1) {
+ TkTextIndex index;
+ TkTextSegment *segPtr;
+ TkTextTagSet *tagInfoPtr;
+
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i], &index)) {
+ return TCL_ERROR;
+ }
+
+ segPtr = TkTextIndexGetContentSegment(&index, NULL);
+
+ if (!includeBits && !discardBits) {
+ FindTags(interp, textPtr, segPtr, discardSelection);
+ return TCL_OK;
+ }
+
+ TkTextTagSetIncrRefCount(tagInfoPtr = segPtr->tagInfoPtr);
+ if (includeBits) {
+ tagInfoPtr = TkTextTagSetIntersectBits(tagInfoPtr, includeBits);
+ TkBitDecrRefCount(includeBits);
+ }
+ includeBits = TkTextTagSetToBits(tagInfoPtr, TkBitSize(sharedTextPtr->usedTags));
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ } else if (objc > i) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?options? ?index?");
+ return TCL_ERROR;
+ }
+
+ if (discardSelection) {
+ discardBits = AddBits(discardBits, sharedTextPtr->selectionTags);
+ }
+ if (!includeBits) {
+ if (discardBits) {
+ includeBits = TkBitCopy(sharedTextPtr->usedTags, -1);
+ } else {
+ TkBitIncrRefCount(includeBits = sharedTextPtr->usedTags);
+ }
+ }
+ if (discardBits) {
+ TkBitRemove(includeBits, discardBits);
+ }
+
+ arrayPtr = malloc(sharedTextPtr->numEnabledTags * sizeof(TkTextTag *));
+ countTags = 0;
+
+ for (i = TkBitFindFirst(includeBits); i != TK_BIT_NPOS; i = TkBitFindNext(includeBits, i)) {
+ arrayPtr[countTags++] = sharedTextPtr->tagLookup[i];
+ }
+
+ AppendTags(interp, countTags, arrayPtr);
+ free(arrayPtr);
+
+ TkBitDecrRefCount(includeBits);
+ if (discardBits) {
+ TkBitDecrRefCount(discardBits);
+ }
+
+ return TCL_OK;
+}
+
+/*
* Local Variables:
* mode: c
* c-basic-offset: 4
- * fill-column: 78
+ * fill-column: 105
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/generic/tkTextTagSet.c b/generic/tkTextTagSet.c
new file mode 100644
index 0000000..66c7462
--- /dev/null
+++ b/generic/tkTextTagSet.c
@@ -0,0 +1,1738 @@
+/*
+ * tkTextTagSet.c --
+ *
+ * This module implements a set for tagging information.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkTextTagSet.h"
+
+#if !(__STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900))
+# define _TK_NEED_IMPLEMENTATION
+# include "tkTextTagSetPriv.h"
+#endif
+
+#if !TK_TEXT_DONT_USE_BITFIELDS
+
+# include <assert.h>
+# include <string.h>
+
+# ifndef MAX
+# define MAX(a,b) (((int) a) < ((int) b) ? b : a)
+# endif
+
+/*
+ * Don't use expensive checks for speed improvements. But probably these "expensive"
+ * checks aren't so much expensive? This needs more testing for a final decision.
+ */
+#define USE_EXPENSIVE_CHECKS 0
+
+
+static bool IsPowerOf2(unsigned n) { return !(n & (n - 1)); }
+
+
+static unsigned
+NextPowerOf2(
+ unsigned n)
+{
+ --n;
+ n |= n >> 1;
+ n |= n >> 2;
+ n |= n >> 4;
+ n |= n >> 8;
+ n |= n >> 16;
+
+#if !(UINT_MAX <= 4294967295u)
+ /* unsigned is 64 bit wide, this is unusual, but possible */
+ n |= n >> 32;
+#endif
+
+ return ++n;
+}
+
+
+static TkTextTagSet *
+ConvertToBitField(
+ TkTextTagSet *ts,
+ unsigned newSize)
+{
+ TkBitField *bf;
+ assert(ts->base.isSetFlag);
+ assert(ts->base.refCount > 0);
+ bf = TkBitFromSet(&ts->set, newSize);
+ TkIntSetDecrRefCount(&ts->set);
+ return (TkTextTagSet *) bf;
+}
+
+
+static TkTextTagSet *
+ConvertToIntSet(
+ TkTextTagSet *ts)
+{
+ TkIntSet *set;
+ assert(!ts->base.isSetFlag);
+ assert(ts->base.refCount > 0);
+ set = TkIntSetFromBits(&ts->bf);
+ TkBitDecrRefCount(&ts->bf);
+ return (TkTextTagSet *) set;
+}
+
+
+static TkTextTagSet *
+ConvertToEmptySet(
+ TkTextTagSet *ts)
+{
+ if (TkTextTagSetIsEmpty(ts)) {
+ return ts;
+ }
+ if (ts->base.refCount > 1) {
+ ts->base.refCount -= 1;
+ return (TkTextTagSet *) TkBitResize(NULL, 0);
+ }
+ if (ts->base.isSetFlag) {
+ return (TkTextTagSet *) TkIntSetClear(&ts->set);
+ }
+ TkBitClear(&ts->bf);
+ return ts;
+}
+
+
+static TkTextTagSet *
+Convert(
+ TkTextTagSet *ts)
+{
+ if (ts->base.isSetFlag) {
+ TkIntSetType size;
+
+ if (TkIntSetIsEmpty(&ts->set)) {
+ return ts;
+ }
+
+ size = TkIntSetMax(&ts->set) + 1;
+
+ if (size <= TK_TEXT_SET_MAX_BIT_SIZE) {
+ if (!IsPowerOf2(size)) {
+ size = NextPowerOf2(size);
+ }
+ return ConvertToBitField(ts, size);
+ }
+ } else if (TkBitSize(&ts->bf) > TK_TEXT_SET_MAX_BIT_SIZE) {
+ return ConvertToIntSet(ts);
+ }
+ return ts;
+}
+
+
+static TkBitField *
+MakeBitCopy(
+ TkTextTagSet *ts)
+{
+ assert(ts->base.refCount > 1);
+ assert(!ts->base.isSetFlag);
+
+ ts->base.refCount -= 1;
+ return TkBitCopy(&ts->bf, -1);
+}
+
+
+static TkIntSet *
+MakeIntSetCopy(
+ TkTextTagSet *ts)
+{
+ assert(ts->base.refCount > 1);
+ assert(ts->base.isSetFlag);
+
+ ts->base.refCount -= 1;
+ return TkIntSetCopy(&ts->set);
+}
+
+
+static TkTextTagSet *
+MakeBitCopyIfNeeded(
+ TkTextTagSet *ts)
+{
+ assert(ts->base.refCount > 0);
+ assert(!ts->base.isSetFlag);
+
+ return (TkTextTagSet *) (ts->base.refCount == 1 ? &ts->bf : MakeBitCopy(ts));
+}
+
+
+static TkIntSet *
+MakeIntSetCopyIfNeeded(
+ TkTextTagSet *ts)
+{
+ assert(ts->base.refCount > 0);
+ assert(ts->base.isSetFlag);
+
+ return ts->base.refCount == 1 ? &ts->set : MakeIntSetCopy(ts);
+}
+
+
+static TkIntSet *
+ToIntSet(
+ const TkTextTagSet *set)
+{
+ if (set->base.isSetFlag) {
+ return (TkIntSet *) &set->set;
+ }
+ return (TkIntSet *) TkIntSetFromBits(&((TkTextTagSet *) set)->bf);
+}
+
+
+TkBitField *
+TkTextTagSetToBits(
+ const TkTextTagSet *src,
+ int size)
+{
+ assert(src);
+
+ if (src->base.isSetFlag) {
+ return TkBitFromSet(&src->set, size < 0 ? TkIntSetMax(&src->set) + 1 : size);
+ }
+
+ if (size < 0 || TkBitSize(&src->bf) == size) {
+ ((TkTextTagSet *) src)->base.refCount += 1;
+ return (TkBitField *) &src->bf;
+ }
+
+ return TkBitCopy(&src->bf, size);
+}
+
+
+void
+TkTextTagSetDestroy(
+ TkTextTagSet **tsPtr)
+{
+ assert(tsPtr);
+
+ if (*tsPtr) {
+ if ((*tsPtr)->base.isSetFlag) {
+ TkIntSetDestroy((TkIntSet **) tsPtr);
+ } else {
+ TkBitDestroy((TkBitField **) tsPtr);
+ }
+ }
+}
+
+
+TkTextTagSet *
+TkTextTagSetResize(
+ TkTextTagSet *ts,
+ unsigned newSize)
+{
+ assert(!ts || TkTextTagSetRefCount(ts) > 0);
+
+ if (!ts) {
+ ts = TkTextTagSetNew(newSize);
+ ts->base.refCount = 1;
+ return ts;
+ }
+ if (ts->base.isSetFlag) {
+ if (newSize <= TK_TEXT_SET_MAX_BIT_SIZE) {
+ ts = ConvertToBitField(ts, newSize);
+ }
+ } else {
+ if (newSize <= TK_TEXT_SET_MAX_BIT_SIZE) {
+ ts = (TkTextTagSet *) TkBitResize(&ts->bf, newSize);
+ } else {
+ ts = ConvertToIntSet(ts);
+ }
+ }
+
+ return ts;
+}
+
+
+bool
+TkTextTagSetIsEqual_(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(ts1);
+ assert(ts2);
+ assert(ts1->base.isSetFlag || ts2->base.isSetFlag);
+
+ if (ts1->base.isSetFlag) {
+ if (ts2->base.isSetFlag) {
+ return TkIntSetIsEqual(&ts1->set, &ts2->set);
+ }
+ return TkIntSetIsEqualBits(&ts1->set, &ts2->bf);
+ }
+ return TkIntSetIsEqualBits(&ts2->set, &ts1->bf);
+}
+
+
+bool
+TkTextTagSetContains_(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(ts1);
+ assert(ts2);
+ assert(ts1->base.isSetFlag || ts2->base.isSetFlag);
+
+ if (ts1->base.isSetFlag) {
+ if (ts2->base.isSetFlag) {
+ return TkIntSetContains(&ts1->set, &ts2->set);
+ }
+ return TkIntSetContainsBits(&ts1->set, &ts2->bf);
+ }
+ return TkIntSetIsContainedBits(&ts2->set, &ts1->bf);
+}
+
+
+bool
+TkTextTagSetDisjunctive_(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(ts1);
+ assert(ts2);
+ assert(ts1->base.isSetFlag || ts2->base.isSetFlag);
+
+ if (ts1->base.isSetFlag) {
+ if (ts2->base.isSetFlag) {
+ return TkIntSetDisjunctive(&ts1->set, &ts2->set);
+ }
+ return TkIntSetDisjunctiveBits(&ts1->set, &ts2->bf);
+ }
+ return TkIntSetDisjunctiveBits(&ts2->set, &ts1->bf);
+}
+
+
+bool
+TkTextTagSetIntersectionIsEqual_(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2,
+ const TkBitField *bf)
+{
+ assert(ts1);
+ assert(ts2);
+ assert(bf);
+ assert(ts1->base.isSetFlag || ts2->base.isSetFlag);
+
+ if (ts1->base.isSetFlag) {
+ if (ts2->base.isSetFlag) {
+ return TkIntSetIntersectionIsEqual(&ts1->set, &ts2->set, bf);
+ }
+ return TkIntSetIntersectionIsEqualBits(&ts1->set, &ts2->bf, bf);
+ }
+ return TkIntSetIntersectionIsEqualBits(&ts2->set, &ts1->bf, bf);
+}
+
+
+TkTextTagSet *
+TkTextTagSetJoin(
+ TkTextTagSet *dst,
+ const TkTextTagSet *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (src == dst || TkTextTagSetIsEmpty(src)) {
+ return dst;
+ }
+
+ if (TkTextTagSetIsEmpty(dst)) {
+ TkTextTagSetIncrRefCount((TkTextTagSet *) src); /* mutable by definition */
+ TkTextTagSetDecrRefCount(dst);
+ return Convert((TkTextTagSet *) src);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetContains(src, dst) || TkTextTagSetContains(dst, src)) {
+ return dst;
+ }
+#endif
+
+ if (src->base.isSetFlag | dst->base.isSetFlag) {
+ if (src->base.isSetFlag) {
+ if (dst->base.isSetFlag) {
+ return (TkTextTagSet *) TkIntSetJoin(&dst->set, &src->set);
+ }
+ return (TkTextTagSet *) TkIntSetJoin(&ConvertToIntSet(dst)->set, &src->set);
+ }
+ return (TkTextTagSet *) TkIntSetJoinBits(&dst->set, &src->bf);
+ }
+
+ if (TkBitSize(&dst->bf) < TkBitSize(&src->bf)) {
+ TkTextTagSet *set = dst;
+ dst = (TkTextTagSet *) TkBitCopy(&src->bf, -1);
+ TkBitJoin(&dst->bf, &set->bf);
+ TkBitDecrRefCount(&set->bf);
+ return dst;
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitJoin(&dst->bf, &src->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetJoin2(
+ TkTextTagSet *dst,
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(dst);
+ assert(ts1);
+ assert(ts2);
+
+ if (ts2 == dst || TkTextTagSetIsEmpty(ts2)) {
+ return TkTextTagSetJoin(dst, ts1);
+ }
+ if (ts1 == dst || ts1 == ts2 || TkTextTagSetIsEmpty(ts1)) {
+ return TkTextTagSetJoin(dst, ts2);
+ }
+ if (TkTextTagSetIsEmpty(dst)) {
+ TkTextTagSetIncrRefCount((TkTextTagSet *) ts1); /* mutable by definition */
+ TkTextTagSetDecrRefCount(dst);
+ return TkTextTagSetJoin((TkTextTagSet *) ts1, ts2);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetContains(ts1, ts2) || TkTextTagSetContains(dst, ts2)) {
+ return TkTextTagSetJoin(dst, ts1);
+ }
+ if (TkTextTagSetContains(ts2, ts1) || TkTextTagSetContains(dst, ts1)) {
+ return TkTextTagSetJoin(dst, ts2);
+ }
+ if (TkTextTagSetContains(ts1, dst)) {
+ TkTextTagSetDecrRefCount(dst);
+ return TkTextTagSetJoin(TkTextTagSetCopy(ts1), ts2);
+ }
+ if (TkTextTagSetContains(ts2, dst)) {
+ TkTextTagSetDecrRefCount(dst);
+ return TkTextTagSetJoin(TkTextTagSetCopy(ts2), ts1);
+ }
+#endif
+
+ if (ts1->base.isSetFlag | ts2->base.isSetFlag | dst->base.isSetFlag) {
+ return TkTextTagSetJoin(TkTextTagSetJoin(dst, ts1), ts2);
+ }
+
+ if (TkBitSize(&ts1->bf) < TkBitSize(&ts2->bf)) {
+ const TkTextTagSet *tmp = ts1;
+ ts1 = ts2;
+ ts2 = tmp;
+ }
+ if (TkBitSize(&dst->bf) < TkBitSize(&ts1->bf)) {
+ TkTextTagSet *set = dst;
+ dst = (TkTextTagSet *) TkBitCopy(&ts1->bf, -1);
+ TkBitJoin2(&dst->bf, &set->bf, &ts2->bf);
+ TkBitDecrRefCount(&set->bf);
+ return dst;
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitJoin2(&dst->bf, &ts1->bf, &ts2->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetIntersect(
+ TkTextTagSet *dst,
+ const TkTextTagSet *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (src == dst || TkTextTagSetIsEmpty(dst)) {
+ return dst;
+ }
+ if (TkTextTagSetIsEmpty(src)) {
+ TkTextTagSetIncrRefCount((TkTextTagSet *) src); /* mutable by definition */
+ TkTextTagSetDecrRefCount(dst);
+ return (TkTextTagSet *) src;
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetContains(dst, src)) {
+ return dst;
+ }
+ if (TkTextTagSetContains(src, dst)) {
+ TkTextTagSetIncrRefCount((TkTextTagSet *) src); /* mutable by definition */
+ TkTextTagSetDecrRefCount(dst);
+ return (TkTextTagSet *) src;
+ }
+#endif
+
+ if (src->base.isSetFlag | dst->base.isSetFlag) {
+ TkBitField *bf, *tmp;
+
+ assert(dst->base.refCount > 0);
+
+ if (src->base.isSetFlag) {
+ if (dst->base.isSetFlag) {
+ return (TkTextTagSet *) TkIntSetIntersect(&dst->set, &src->set);
+ }
+
+ tmp = TkBitFromSet(&src->set, TkBitSize(&dst->bf));
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitIntersect(&dst->bf, tmp);
+ TkBitDestroy(&tmp);
+ return dst;
+ }
+
+ bf = TkBitCopy(&src->bf, -1);
+ tmp = TkBitFromSet(&dst->set, TkBitSize(&src->bf));
+ TkBitIntersect(bf, tmp);
+ TkBitDestroy(&tmp);
+ TkIntSetDecrRefCount(&dst->set);
+ return (TkTextTagSet *) bf;
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitIntersect(&dst->bf, &src->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetIntersectBits(
+ TkTextTagSet *dst,
+ const TkBitField *src)
+{
+ assert(src);
+ assert(dst);
+
+ if ((const TkTextTagSet *) src == dst || TkTextTagSetIsEmpty(dst)) {
+ return dst;
+ }
+ if (TkBitNone(src)) {
+ TkTextTagSetIncrRefCount((TkTextTagSet *) src); /* mutable by definition */
+ TkTextTagSetDecrRefCount(dst);
+ return Convert((TkTextTagSet *) src);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetContainsBits(dst, src)) {
+ return dst;
+ }
+ if (TkTextTagIsContainedInBits(dst, src)) {
+ TkBitIncrRefCount((TkBitField *) src); /* mutable by definition */
+ TkTextTagSetDecrRefCount(dst);
+ return (TkTextTagSet *) src;
+ }
+#endif
+
+ if (dst->base.isSetFlag) {
+ return Convert((TkTextTagSet *) TkIntSetIntersectBits(&dst->set, src));
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitIntersect(&dst->bf, src);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetRemove(
+ TkTextTagSet *dst,
+ const TkTextTagSet *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (TkTextTagSetIsEmpty(src) || TkTextTagSetIsEmpty(dst)) {
+ return dst;
+ }
+ if (src == dst) {
+ return ConvertToEmptySet(dst);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetContains(src, dst)) {
+ return ConvertToEmptySet(dst);
+ }
+#endif
+
+ if (src->base.isSetFlag | dst->base.isSetFlag) {
+ TkBitField *bf;
+
+ assert(dst->base.refCount > 0);
+
+ if (dst->base.isSetFlag) {
+ if (src->base.isSetFlag) {
+ return Convert((TkTextTagSet *) TkIntSetRemove(&dst->set, &src->set));
+ }
+ return Convert((TkTextTagSet *) TkIntSetRemoveBits(&dst->set, &src->bf));
+ }
+
+ bf = TkBitFromSet(&src->set, TkBitSize(&dst->bf));
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitRemove(&dst->bf, bf);
+ TkBitDestroy(&bf);
+ return dst;
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitRemove(&dst->bf, &src->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetRemoveBits(
+ TkTextTagSet *dst,
+ const TkBitField *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (TkBitNone(src) || TkTextTagSetIsEmpty(dst)) {
+ return dst;
+ }
+ if ((const TkTextTagSet *) src == dst) {
+ return ConvertToEmptySet(dst);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetIsContainedBits(dst, src)) {
+ return ConvertToEmptySet(dst);
+ }
+#endif
+
+ if (dst->base.isSetFlag) {
+ return Convert((TkTextTagSet *) TkIntSetRemoveBits(&dst->set, src));
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitRemove(&dst->bf, src);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetComplementTo(
+ TkTextTagSet *dst,
+ const TkTextTagSet *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (src == dst) {
+ return ConvertToEmptySet(dst);
+ }
+ if (TkTextTagSetIsEmpty(src) || TkTextTagSetIsEmpty(dst)) {
+ TkTextTagSetIncrRefCount((TkTextTagSet *) src); /* mutable by definition */
+ TkTextTagSetDecrRefCount(dst);
+ return (TkTextTagSet *) src;
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetContains(dst, src)) {
+ return ConvertToEmptySet(dst);
+ }
+#endif
+
+ if (src->base.isSetFlag | dst->base.isSetFlag) {
+ TkIntSet *set;
+
+ if (dst->base.isSetFlag) {
+ if (src->base.isSetFlag) {
+ return Convert((TkTextTagSet *) TkIntSetComplementTo(&dst->set, &src->set));
+ }
+ return Convert((TkTextTagSet *) TkIntSetComplementToBits(&dst->set, &src->bf));
+ }
+
+ TkIntSetIncrRefCount((TkIntSet *) &src->set);
+ set = TkIntSetRemoveBits((TkIntSet *) &src->set, &dst->bf);
+ TkBitDecrRefCount(&dst->bf);
+ return Convert((TkTextTagSet *) set);
+ }
+
+ if (dst->base.refCount > 1 || TkBitSize(&dst->bf) < TkBitSize(&src->bf)) {
+ TkBitField *bf;
+
+ bf = TkBitCopy(&src->bf, -1);
+ TkBitRemove(bf, &dst->bf);
+ TkBitDecrRefCount(&dst->bf);
+ return (TkTextTagSet *) bf;
+ }
+ TkBitComplementTo(&dst->bf, &src->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetJoinComplementTo(
+ TkTextTagSet *dst,
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(dst);
+ assert(ts1);
+ assert(ts2);
+
+ if (dst == ts2 || TkTextTagSetIsEmpty(ts2)) {
+ return dst;
+ }
+ if (TkTextTagSetIsEmpty(ts1)) {
+ return TkTextTagSetJoin(dst, ts2);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetContains(dst, ts2) || TkTextTagSetContains(ts1, ts2)) {
+ return dst;
+ }
+ if (TkTextTagSetContains(dst, ts1)) {
+ return TkTextTagSetJoin(dst, ts2);
+ }
+ if (TkTextTagSetContains(ts2, dst)) {
+ TkTextTagSetDecrRefCount(dst);
+ return TkTextTagSetRemove(TkTextTagSetCopy(ts2), ts1);
+ }
+#endif
+
+ if (dst->base.isSetFlag | ts1->base.isSetFlag | ts2->base.isSetFlag) {
+ TkTextTagSet *tmp;
+
+ if (!(dst->base.isSetFlag | ts1->base.isSetFlag)) {
+ TkBitField *bf2 = TkBitFromSet(&ts2->set, TkBitSize(&ts1->bf));
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitJoinComplementTo(&dst->bf, &ts1->bf, bf2);
+ TkBitDestroy(&bf2);
+ return dst;
+ }
+
+ tmp = TkTextTagSetRemove(TkTextTagSetCopy(ts2), ts1);
+ dst = TkTextTagSetJoin(dst, tmp);
+ TkTextTagSetDecrRefCount(tmp);
+ return dst;
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitJoinComplementTo(&dst->bf, &ts1->bf, &ts2->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetJoinNonIntersection(
+ TkTextTagSet *dst,
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(dst);
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1 == ts2) {
+ return dst;
+ }
+ if (TkTextTagSetIsEmpty(ts1) && TkTextTagSetIsEmpty(ts2)) {
+ return dst;
+ }
+ if (dst == ts1 || TkTextTagSetIsEmpty(ts1)) {
+ return TkTextTagSetJoin(dst, ts2);
+ }
+ if (dst == ts2 || TkTextTagSetIsEmpty(ts2)) {
+ return TkTextTagSetJoin(dst, ts1);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetIsEqual(ts1, ts2)) {
+ return dst;
+ }
+ if (TkTextTagSetContains(dst, ts1)) {
+ return TkTextTagSetJoin(dst, ts2);
+ }
+ if (TkTextTagSetContains(dst, ts2)) {
+ return TkTextTagSetJoin(dst, ts1);
+ }
+#endif
+
+ if (dst->base.isSetFlag | ts1->base.isSetFlag | ts2->base.isSetFlag) {
+ TkIntSet *set1, *set2;
+
+ dst = dst->base.isSetFlag ? (TkTextTagSet *) MakeIntSetCopyIfNeeded(dst) : ConvertToIntSet(dst);
+ set1 = ToIntSet(ts1);
+ set2 = ToIntSet(ts2);
+ dst = (TkTextTagSet *) TkIntSetJoinNonIntersection(&dst->set, set1, set2);
+ if (&ts1->set != set1) { TkIntSetDestroy(&set1); }
+ if (&ts2->set != set2) { TkIntSetDestroy(&set2); }
+ return Convert(dst);
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitJoinNonIntersection(&dst->bf, &ts1->bf, &ts2->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetJoin2ComplementToIntersection(
+ TkTextTagSet *dst,
+ const TkTextTagSet *add,
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(dst);
+ assert(add);
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1 == ts2) {
+ return TkTextTagSetJoin(dst, add);
+ }
+ if (TkTextTagSetIsEmpty(ts1)) {
+ return TkTextTagSetJoin2(dst, add, ts2);
+ }
+ if (TkTextTagSetIsEmpty(ts2)) {
+ return TkTextTagSetJoin2(dst, add, ts1);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetIsEqual(ts1, ts2)) {
+ return TkTextTagSetJoin(dst, add);
+ }
+ if (TkTextTagSetContains(dst, ts1) && TkTextTagSetContains(dst, ts2)) {
+ return TkTextTagSetJoin(dst, add);
+ }
+#endif
+
+ if (dst->base.isSetFlag | add->base.isSetFlag | ts1->base.isSetFlag | ts2->base.isSetFlag) {
+ TkIntSet *set1, *set2, *set3;
+
+ dst = dst->base.isSetFlag ? (TkTextTagSet *) MakeIntSetCopyIfNeeded(dst) : ConvertToIntSet(dst);
+ set1 = ToIntSet(add);
+ set2 = ToIntSet(ts1);
+ set3 = ToIntSet(ts2);
+ dst = (TkTextTagSet *) TkIntSetJoin2ComplementToIntersection(&dst->set, set1, set2, set3);
+ if (&add->set != set1) { TkIntSetDestroy(&set1); }
+ if (&ts1->set != set2) { TkIntSetDestroy(&set2); }
+ if (&ts2->set != set3) { TkIntSetDestroy(&set3); }
+ return dst;
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitJoin2ComplementToIntersection(&dst->bf, &add->bf, &ts1->bf, &ts2->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetJoinOfDifferences(
+ TkTextTagSet *dst,
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(dst);
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1 == ts2) {
+ return TkTextTagSetRemove(dst, ts1);
+ }
+ if (dst == ts1) {
+ TkTextTagSetDecrRefCount(dst);
+ TkTextTagSetIncrRefCount((TkTextTagSet *) ts1); /* mutable due to concept */
+ return TkTextTagSetRemove((TkTextTagSet *) ts1, ts2);
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetIsEqual(ts1, ts2)) {
+ return TkTextTagSetRemove(dst, ts1);
+ }
+ if (TkTextTagSetContains(ts1, dst)) {
+ TkTextTagSetDecrRefCount(dst);
+ TkTextTagSetIncrRefCount(ts1);
+ return TkTextTagSetRemove(ts1, ts2);
+ }
+#endif
+
+ if (dst->base.isSetFlag | ts1->base.isSetFlag | ts2->base.isSetFlag) {
+ TkIntSet *set1, *set2;
+
+ dst = dst->base.isSetFlag ? (TkTextTagSet *) MakeIntSetCopyIfNeeded(dst) : ConvertToIntSet(dst);
+ set1 = ToIntSet(ts1);
+ set2 = ToIntSet(ts2);
+ dst = (TkTextTagSet *) TkIntSetJoinOfDifferences(&dst->set, set1, set2);
+ if (&ts1->set != set1) { TkIntSetDestroy(&set1); }
+ if (&ts2->set != set2) { TkIntSetDestroy(&set2); }
+ return Convert(dst);
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitJoinOfDifferences(&dst->bf, &ts1->bf, &ts2->bf);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetAdd(
+ TkTextTagSet *dst,
+ unsigned n)
+{
+ assert(dst);
+
+ if (dst->base.isSetFlag) {
+ return (TkTextTagSet *) TkIntSetAdd(MakeIntSetCopyIfNeeded(dst), n);
+ }
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitSet(&dst->bf, n);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetErase(
+ TkTextTagSet *dst,
+ unsigned n)
+{
+ assert(dst);
+
+ if (dst->base.isSetFlag) {
+ return (TkTextTagSet *) TkIntSetErase(MakeIntSetCopyIfNeeded(dst), n);
+ }
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitUnset(&dst->bf, n);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetTestAndSet(
+ TkTextTagSet *dst,
+ unsigned n)
+{
+ assert(dst);
+
+ if (dst->base.isSetFlag) {
+ if (dst->base.refCount <= 1) {
+ return (TkTextTagSet *) TkIntSetTestAndSet(&dst->set, n);
+ }
+ if (TkIntSetTest(&dst->set, n)) {
+ return NULL;
+ }
+ return (TkTextTagSet *) TkIntSetAdd(MakeIntSetCopy(dst), n);
+ }
+ if (dst->base.refCount <= 1) {
+ return TkBitTestAndSet(&dst->bf, n) ? dst : NULL;
+ }
+ if (TkBitTest(&dst->bf, n)) {
+ return NULL;
+ }
+ dst = (TkTextTagSet *) MakeBitCopy(dst);
+ TkBitSet(&dst->bf, n);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetTestAndUnset(
+ TkTextTagSet *dst,
+ unsigned n)
+{
+ assert(dst);
+
+ if (dst->base.isSetFlag) {
+ if (dst->base.refCount <= 1) {
+ return (TkTextTagSet *) TkIntSetTestAndUnset(&dst->set, n);
+ }
+ if (!TkIntSetTest(&dst->set, n)) {
+ return NULL;
+ }
+ return (TkTextTagSet *) TkIntSetErase(MakeIntSetCopy(dst), n);
+ }
+ if (dst->base.refCount <= 1) {
+ return TkBitTestAndUnset(&dst->bf, n) ? dst : NULL;
+ }
+ if (!TkBitTest(&dst->bf, n)) {
+ return NULL;
+ }
+ dst = (TkTextTagSet *) MakeBitCopy(dst);
+ TkBitUnset(&dst->bf, n);
+ return dst;
+}
+
+
+TkTextTagSet *
+TkTextTagSetClear(
+ TkTextTagSet *dst)
+{
+ assert(dst);
+
+ if (dst->base.isSetFlag) {
+ TkIntSetDecrRefCount(&dst->set);
+ return (TkTextTagSet *) TkBitResize(NULL, 0);
+ }
+ return ConvertToEmptySet(dst);
+}
+
+
+unsigned
+TkTextTagSetFindFirstInIntersection(
+ const TkTextTagSet *ts,
+ const TkBitField *bf)
+{
+ unsigned size, i;
+
+ assert(ts);
+ assert(bf);
+
+ if (!ts->base.isSetFlag) {
+ return TkBitFindFirstInIntersection(&ts->bf, bf);
+ }
+
+ if (!TkBitNone(bf)) {
+ size = TkIntSetSize(&ts->set);
+
+ for (i = 0; i < size; ++i) {
+ TkIntSetType value = TkIntSetAccess(&ts->set, i);
+
+ if (TkBitTest(bf, value)) {
+ return value;
+ }
+ }
+ }
+
+ return TK_TEXT_TAG_SET_NPOS;
+}
+
+# if !NDEBUG
+
+void
+TkTextTagSetPrint(
+ const TkTextTagSet *set)
+{
+ if (!set) {
+ printf("<null>\n");
+ } else if (TkTextTagSetIsEmpty(set)) {
+ printf("<empty>\n");
+ } else if (set->base.isSetFlag) {
+ TkIntSetPrint(&set->set);
+ } else {
+ TkBitPrint(&set->bf);
+ }
+}
+
+# endif /* !NDEBUG */
+# if 0
+
+/*
+ * These functions are not needed anymore, but shouldn't be removed, because sometimes
+ * any of these functions might be useful.
+ */
+
+static unsigned
+MaxSize3(
+ const TkTextTagSet *ts1, const TkTextTagSet *ts2, const TkTextTagSet *ts3)
+{
+ return TkBitAdjustSize(MAX(TkTextTagSetRangeSize(ts1),
+ MAX(TkTextTagSetRangeSize(ts2), TkTextTagSetRangeSize(ts3))));
+}
+
+
+static unsigned
+MaxSize4(
+ const TkTextTagSet *ts1, const TkTextTagSet *ts2,
+ const TkTextTagSet *ts3, const TkTextTagSet *ts4)
+{
+ return TkBitAdjustSize(MAX(TkTextTagSetRangeSize(ts1), MAX(TkTextTagSetRangeSize(ts2),
+ MAX(TkTextTagSetRangeSize(ts3), TkTextTagSetRangeSize(ts4)))));
+}
+
+
+static TkBitField *
+GetBitField(
+ const TkTextTagSet *ts,
+ int size)
+{
+ assert(size == -1 || size >= TkTextTagSetRangeSize(ts));
+ assert(size == -1 || size == TkBitAdjustSize(size));
+
+ if (ts->base.isSetFlag) {
+ return TkBitFromSet(&ts->set, size);
+ }
+
+ if (size >= 0 && TkBitSize(&ts->bf) != size) {
+ return TkBitCopy(&ts->bf, size);
+ }
+
+ /* mutable due to concept */
+ TkBitIncrRefCount((TkBitField *) &ts->bf);
+ return (TkBitField *) &ts->bf;
+}
+
+
+TkTextTagSet *
+TkTextTagSetInnerJoinDifference(
+ TkTextTagSet *dst,
+ const TkTextTagSet *add,
+ const TkTextTagSet *sub)
+{
+ assert(dst);
+ assert(add);
+ assert(sub);
+
+ /* dst := (dst & add) + (add - sub) */
+
+ if (add == dst) {
+ return dst;
+ }
+ if (TkTextTagSetIsEmpty(add)) {
+ return TkTextTagSetClear(dst);
+ }
+ if (TkTextTagSetIsEqual(add, dst)) {
+ return dst;
+ }
+
+#if USE_EXPENSIVE_CHECKS
+ if (TkTextTagSetContains(dst, add)) {
+ return dst;
+ }
+ if (TkTextTagSetContains(add, dst) || TkTextTagSetContains(dst, sub)) {
+ TkTextTagSetIncrRefCount((TkTextTagSet *) add); /* mutable by definition */
+ TkTextTagSetDecrRefCount(dst);
+ return (TkTextTagSet *) add;
+ }
+ if (TkTextTagSetContains(sub, add) || TkTextTagSetContains(add, sub)) {
+ return TkTextTagSetIntersect(dst, add);
+ }
+#endif
+
+ if (dst->base.isSetFlag | add->base.isSetFlag | sub->base.isSetFlag) {
+ TkIntSet *set1, *set2, *res;
+
+ res = dst->base.isSetFlag ? TkIntSetCopy(&dst->set) : ConvertToIntSet(dst);
+ set1 = ToIntSet(add);
+ set2 = ToIntSet(sub);
+ res = TkIntSetInnerJoinDifference(res, set1, set2);
+ if (&add->set != set1) { TkIntSetDestroy(&set1); }
+ if (&sub->set != set2) { TkIntSetDestroy(&set2); }
+ return (TkTextTagSet *) res;
+ }
+
+ dst = MakeBitCopyIfNeeded(dst);
+ TkBitInnerJoinDifference(&dst->bf, &add->bf, &sub->bf);
+ return dst;
+}
+
+
+bool
+TkTextTagSetInnerJoinDifferenceIsEmpty(
+ const TkTextTagSet *ts,
+ const TkTextTagSet *add,
+ const TkTextTagSet *sub)
+{
+ TkBitField *bf, *bfAdd, *bfSub;
+ bool isEmpty;
+ unsigned n, size;
+
+ assert(ts);
+ assert(add);
+ assert(sub);
+
+ if (ts == add) {
+ return TkTextTagSetIsEmpty(ts);
+ }
+ if ((n = ts->base.isSetFlag + add->base.isSetFlag + sub->base.isSetFlag) == 0) {
+ return TkBitInnerJoinDifferenceIsEmpty(&ts->bf, &add->bf, &sub->bf);
+ }
+ if (n == 3) {
+ return TkIntSetInnerJoinDifferenceIsEmpty(&ts->set, &add->set, &sub->set);
+ }
+ if (TkTextTagSetIsEmpty(add)) {
+ return true;
+ }
+ if (TkTextTagSetIsEqual(ts, add)) {
+ return TkTextTagSetIsEmpty(add);
+ }
+
+ size = MaxSize3(ts, add, sub);
+ bf = GetBitField(ts, size);
+ bfAdd = GetBitField(add, size);
+ bfSub = GetBitField(sub, size);
+ isEmpty = TkBitInnerJoinDifferenceIsEmpty(bf, bfAdd, bfSub);
+ TkBitDecrRefCount(bf);
+ TkBitDecrRefCount(bfAdd);
+ TkBitDecrRefCount(bfSub);
+
+ return isEmpty;
+}
+
+
+bool
+TkTextTagSetIsEqualToDifference(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2,
+ const TkTextTagSet *sub2)
+{
+ TkBitField *bf1, *bf2, *bfSub;
+ bool isEqual;
+ unsigned n, size;
+
+ assert(ts1);
+ assert(ts2);
+ assert(sub2);
+
+ if ((n = ts1->base.isSetFlag + ts2->base.isSetFlag + sub2->base.isSetFlag) == 0) {
+ return TkBitIsEqualToDifference(&ts1->bf, &ts2->bf, &sub2->bf);
+ }
+ if (n == 3) {
+ return TkIntSetIsEqualToDifference(&ts1->set, &ts2->set, &sub2->set);
+ }
+ if (TkTextTagSetIsEmpty(ts2)) {
+ return TkTextTagSetIsEmpty(ts1);
+ }
+ if (TkTextTagSetIsEmpty(ts1)) {
+ return TkTextTagSetContains(sub2, ts2);
+ }
+
+ size = MaxSize3(ts1, ts2, sub2);
+ bf1 = GetBitField(ts1, size);
+ bf2 = GetBitField(ts2, size);
+ bfSub = GetBitField(sub2, size);
+ isEqual = TkBitIsEqualToDifference(bf1, bf2, bfSub);
+ TkBitDecrRefCount(bf1);
+ TkBitDecrRefCount(bf2);
+ TkBitDecrRefCount(bfSub);
+
+ return isEqual;
+}
+
+
+bool
+TkTextTagSetIsEqualToInnerJoin(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2,
+ const TkTextTagSet *add2)
+{
+ TkBitField *bf1, *bf2, *bfAdd;
+ bool isEqual;
+ unsigned n, size;
+
+ assert(ts1);
+ assert(ts2);
+ assert(add2);
+
+ if (ts1 == ts2) {
+ return true;
+ }
+ if ((n = ts1->base.isSetFlag + ts2->base.isSetFlag + add2->base.isSetFlag) == 0) {
+ return TkBitIsEqualToInnerJoin(&ts1->bf, &ts2->bf, &add2->bf);
+ }
+ if (n == 3) {
+ return TkIntSetIsEqualToInnerJoin(&ts1->set, &ts2->set, &add2->set);
+ }
+ if (TkTextTagSetIsEqual(ts2, add2)) {
+ return TkTextTagSetIsEqual(ts1, ts2);
+ }
+ if (TkTextTagSetIsEmpty(ts2)) {
+ return TkTextTagSetIsEmpty(ts1);
+ }
+ if (TkTextTagSetIsEqual(ts1, ts2)) {
+ return true;
+ }
+
+ size = MaxSize3(ts1, ts2, add2);
+ bf1 = GetBitField(ts1, size);
+ bf2 = GetBitField(ts2, size);
+ bfAdd = GetBitField(add2, size);
+ isEqual = TkBitIsEqualToInnerJoin(bf1, bf2, bfAdd);
+ TkBitDecrRefCount(bf1);
+ TkBitDecrRefCount(bf2);
+ TkBitDecrRefCount(bfAdd);
+
+ return isEqual;
+}
+
+
+bool
+TkTextTagSetIsEqualToInnerJoinDifference(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2,
+ const TkTextTagSet *add2,
+ const TkTextTagSet *sub2)
+{
+ TkBitField *bf1, *bf2, *bfAdd, *bfSub;
+ bool isEqual;
+ unsigned n, size;
+
+ assert(ts1);
+ assert(ts2);
+ assert(add2);
+ assert(sub2);
+
+ n = ts1->base.isSetFlag + ts2->base.isSetFlag + add2->base.isSetFlag + sub2->base.isSetFlag;
+
+ if (n == 0) {
+ return TkBitIsEqualToInnerJoinDifference(&ts1->bf, &ts2->bf, &add2->bf, &sub2->bf);
+ }
+ if (n == 4) {
+ return TkIntSetIsEqualToInnerJoinDifference(&ts1->set, &ts2->set, &add2->set, &sub2->set);
+ }
+ if (TkTextTagSetIsEmpty(add2)) {
+ return TkTextTagSetIsEmpty(ts1);
+ }
+ if (TkTextTagSetIsEmpty(sub2)) {
+ return TkTextTagSetIsEqualToInnerJoin(ts1, add2, ts2);
+ }
+
+ size = MaxSize4(ts1, ts2, add2, sub2);
+ bf1 = GetBitField(ts1, size);
+ bf2 = GetBitField(ts2, size);
+ bfAdd = GetBitField(add2, size);
+ bfSub = GetBitField(sub2, size);
+ isEqual = TkBitIsEqualToInnerJoinDifference(bf1, bf2, bfAdd, bfSub);
+ TkBitDecrRefCount(bf1);
+ TkBitDecrRefCount(bf2);
+ TkBitDecrRefCount(bfAdd);
+ TkBitDecrRefCount(bfSub);
+
+ return isEqual;
+}
+
+
+bool
+TkTextTagSetInnerJoinDifferenceIsEqual(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2,
+ const TkTextTagSet *add,
+ const TkTextTagSet *sub)
+{
+ TkBitField *bf1, *bf2, *bfAdd, *bfSub;
+ bool isEqual;
+ unsigned n, size;
+
+ assert(ts1);
+ assert(ts2);
+ assert(add);
+ assert(sub);
+
+ n = ts1->base.isSetFlag + ts2->base.isSetFlag + add->base.isSetFlag + sub->base.isSetFlag;
+
+ if (n == 0) {
+ return TkBitInnerJoinDifferenceIsEqual(&ts1->bf, &ts2->bf, &add->bf, &sub->bf);
+ }
+ if (n == 4) {
+ return TkIntSetInnerJoinDifferenceIsEqual(&ts1->set, &ts2->set, &add->set, &sub->set);
+ }
+ if (TkTextTagSetIsEmpty(add)) {
+ return true;
+ }
+
+ size = MaxSize4(ts1, ts2, add, sub);
+ bf1 = GetBitField(ts1, size);
+ bf2 = GetBitField(ts2, size);
+ bfAdd = GetBitField(add, size);
+ bfSub = GetBitField(sub, size);
+ isEqual = TkBitInnerJoinDifferenceIsEqual(bf1, bf2, bfAdd, bfSub);
+ TkBitDecrRefCount(bf1);
+ TkBitDecrRefCount(bf2);
+ TkBitDecrRefCount(bfAdd);
+ TkBitDecrRefCount(bfSub);
+
+ return isEqual;
+}
+
+# endif /* 0 */
+#else /* integer set only implementation **************************************/
+
+static TkIntSet *
+ConvertToEmptySet(
+ TkIntSet *ts)
+{
+ if (TkIntSetIsEmpty(ts)) {
+ return ts;
+ }
+ if (ts->refCount == 1) {
+ return TkIntSetClear(ts);
+ }
+ ts->refCount -= 1;
+ (ts = TkIntSetNew())->refCount = 1;
+ return ts;
+}
+
+
+static TkIntSet *
+MakeCopyIfNeeded(
+ TkIntSet *ts)
+{
+ assert(ts->refCount > 0);
+
+ if (ts->refCount == 1) {
+ return ts;
+ }
+ ts->refCount -= 1;
+ return TkIntSetCopy(ts);
+}
+
+
+TkBitField *
+TkTextTagSetToBits(
+ const TkTextTagSet *src,
+ int size)
+{
+ assert(src);
+ return TkBitFromSet(&src->set, size < 0 ? TkIntSetMax(&src->set) + 1 : size);
+}
+
+
+TkIntSet *
+TkTextTagSetJoin(
+ TkIntSet *dst,
+ const TkIntSet *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (src == dst || TkIntSetIsEmpty(src)) {
+ return dst;
+ }
+ if (TkIntSetIsEmpty(dst)) {
+ TkIntSetIncrRefCount((TkIntSet *) src); /* mutable by definition */
+ TkIntSetDecrRefCount(dst);
+ return (TkIntSet *) src;
+ }
+ return TkIntSetJoin(MakeCopyIfNeeded(dst), src);
+}
+
+
+TkIntSet *
+TkTextTagSetIntersect(
+ TkIntSet *dst,
+ const TkIntSet *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (src == dst || TkIntSetIsEmpty(dst)) {
+ return dst;
+ }
+ if (TkIntSetIsEmpty(src)) {
+ TkIntSetIncrRefCount((TkIntSet *) src); /* mutable by definition */
+ TkIntSetDecrRefCount(dst);
+ return (TkIntSet *) src;
+ }
+ return TkIntSetIntersect(MakeCopyIfNeeded(dst), src);
+}
+
+
+TkIntSet *
+TkTextTagSetRemove(
+ TkIntSet *dst,
+ const TkIntSet *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (TkIntSetIsEmpty(src) || TkIntSetIsEmpty(dst)) {
+ return dst;
+ }
+ if (src == dst) {
+ return ConvertToEmptySet(dst);
+ }
+ return TkIntSetRemove(MakeCopyIfNeeded(dst), src);
+}
+
+
+TkIntSet *
+TkTextTagSetIntersectBits(
+ TkIntSet *dst,
+ const TkBitField *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (TkIntSetIsEmpty(dst)) {
+ return dst;
+ }
+ if (TkBitNone(src)) {
+ TkIntSetIncrRefCount((TkIntSet *) src); /* mutable by definition */
+ TkIntSetDecrRefCount(dst);
+ return (TkIntSet *) src;
+ }
+ return TkIntSetIntersectBits(MakeCopyIfNeeded(dst), src);
+}
+
+
+TkIntSet *
+TkTextTagSetRemoveBits(
+ TkIntSet *dst,
+ const TkBitField *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (TkBitNone(src) || TkIntSetIsEmpty(dst)) {
+ return dst;
+ }
+ return TkIntSetRemoveBits(MakeCopyIfNeeded(dst), src);
+}
+
+
+TkIntSet *
+TkTextTagSetJoin2(
+ TkIntSet *dst,
+ const TkIntSet *ts1,
+ const TkIntSet *ts2)
+{
+ assert(dst);
+ assert(ts1);
+ assert(ts2);
+
+ if (ts2 == dst || TkIntSetIsEmpty(ts2)) {
+ return TkIntSetJoin(dst, ts1);
+ }
+ if (ts1 == dst || ts1 == ts2 || TkIntSetIsEmpty(ts1)) {
+ return TkIntSetJoin(dst, ts2);
+ }
+ if (TkIntSetIsEmpty(dst)) {
+ TkIntSetIncrRefCount((TkIntSet *) ts1); /* mutable by definition */
+ TkIntSetDecrRefCount(dst);
+ return TkIntSetJoin((TkIntSet *) ts1, ts2);
+ }
+ return TkIntSetJoin2(MakeCopyIfNeeded(dst), ts1, ts2);
+}
+
+
+TkIntSet *
+TkTextTagSetComplementTo(
+ TkIntSet *dst,
+ const TkIntSet *src)
+{
+ assert(src);
+ assert(dst);
+
+ if (src == dst) {
+ return ConvertToEmptySet(dst);
+ }
+ if (TkIntSetIsEmpty(src) || TkIntSetIsEmpty(dst)) {
+ TkIntSetIncrRefCount((TkIntSet *) src); /* mutable by definition */
+ TkIntSetDecrRefCount(dst);
+ return (TkIntSet *) src;
+ }
+ return TkIntSetComplementTo(MakeCopyIfNeeded(dst), src);
+}
+
+
+TkIntSet *
+TkTextTagSetJoinComplementTo(
+ TkIntSet *dst,
+ const TkIntSet *ts1,
+ const TkIntSet *ts2)
+{
+ assert(dst);
+ assert(ts1);
+ assert(ts2);
+
+ if (dst == ts2 || TkIntSetIsEmpty(ts2)) {
+ return dst;
+ }
+ if (TkIntSetIsEmpty(ts1)) {
+ return TkIntSetJoin(dst, ts2);
+ }
+ return TkIntSetJoinComplementTo(MakeCopyIfNeeded(dst), ts1, ts2);
+}
+
+
+TkIntSet *
+TkTextTagSetJoinNonIntersection(
+ TkIntSet *dst,
+ const TkIntSet *ts1,
+ const TkIntSet *ts2)
+{
+ assert(dst);
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1 == ts2) {
+ return dst;
+ }
+ if (dst == ts1 || TkIntSetIsEmpty(ts1)) {
+ return TkIntSetJoin(dst, ts2);
+ }
+ if (dst == ts2 || TkIntSetIsEmpty(ts2)) {
+ return TkIntSetJoin(dst, ts1);
+ }
+ return TkIntSetJoinNonIntersection(MakeCopyIfNeeded(dst), ts1, ts2);
+}
+
+
+TkIntSet *
+TkTextTagSetJoin2ComplementToIntersection(
+ TkIntSet *dst,
+ const TkIntSet *add,
+ const TkIntSet *ts1,
+ const TkIntSet *ts2)
+{
+ assert(dst);
+ assert(add);
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1 == ts2) {
+ return TkIntSetJoin(dst, add);
+ }
+ if (TkIntSetIsEmpty(ts1)) {
+ return TkIntSetJoin2(dst, add, ts2);
+ }
+ if (TkIntSetIsEmpty(ts2)) {
+ return TkIntSetJoin2(dst, add, ts1);
+ }
+ return TkIntSetJoin2ComplementToIntersection(MakeCopyIfNeeded(dst), add, ts1, ts2);
+}
+
+
+TkIntSet *
+TkTextTagSetAdd(
+ TkIntSet *dst,
+ unsigned n)
+{
+ assert(dst);
+ return TkIntSetAdd(MakeCopyIfNeeded(dst), n);
+}
+
+
+TkIntSet *
+TkTextTagSetErase(
+ TkIntSet *dst,
+ unsigned n)
+{
+ assert(dst);
+ return TkIntSetErase(MakeCopyIfNeeded(dst), n);
+}
+
+
+TkIntSet *
+TkTextTagSetTestAndSet(
+ TkIntSet *dst,
+ unsigned n)
+{
+ assert(dst);
+
+ if (dst->refCount <= 1) {
+ return TkIntSetTestAndSet(dst, n);
+ }
+ if (TkIntSetTest(dst, n)) {
+ return NULL;
+ }
+ dst->refCount -= 1;
+ return TkIntSetAdd(TkIntSetCopy(dst), n);
+}
+
+
+TkIntSet *
+TkTextTagSetTestAndUnset(
+ TkIntSet *dst,
+ unsigned n)
+{
+ assert(dst);
+
+ if (dst->refCount <= 1) {
+ return TkIntSetTestAndUnset(dst, n);
+ }
+ if (!TkIntSetTest(dst, n)) {
+ return NULL;
+ }
+ dst->refCount -= 1;
+ return TkIntSetErase(TkIntSetCopy(dst), n);
+}
+
+
+TkIntSet *
+TkTextTagSetJoinOfDifferences(
+ TkIntSet *dst,
+ const TkIntSet *ts1,
+ const TkIntSet *ts2)
+{
+ assert(dst);
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1 == ts2) {
+ return TkIntSetRemove(dst, ts1);
+ }
+ if (dst == ts1) {
+ TkIntSetDecrRefCount(dst);
+ TkIntSetIncrRefCount((TkIntSet *) ts1); /* mutable due to concept */
+ return TkIntSetRemove((TkIntSet *) ts1, ts2);
+ }
+ return TkIntSetJoinOfDifferences(MakeCopyIfNeeded(dst), ts1, ts2);
+}
+
+#endif /* !TK_TEXT_USE_BITFIELDS */
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+/* Additionally we need stand-alone object code. */
+#define inline extern
+#if !TK_TEXT_DONT_USE_BITFIELDS
+inline TkTextTagSet *TkTextTagSetNew(unsigned size);
+inline unsigned TkTextTagSetRefCount(const TkTextTagSet *ts);
+inline void TkTextTagSetIncrRefCount(TkTextTagSet *ts);
+inline unsigned TkTextTagSetDecrRefCount(TkTextTagSet *ts);
+inline TkTextTagSet *TkTextTagSetCopy(const TkTextTagSet *src);
+inline bool TkTextTagSetIsEmpty(const TkTextTagSet *ts);
+inline bool TkTextTagSetIsBitField(const TkTextTagSet *ts);
+inline unsigned TkTextTagSetSize(const TkTextTagSet *ts);
+inline unsigned TkTextTagSetCount(const TkTextTagSet *ts);
+inline bool TkTextTagSetTest(const TkTextTagSet *ts, unsigned n);
+inline bool TkTextTagSetNone(const TkTextTagSet *ts);
+inline bool TkTextTagSetAny(const TkTextTagSet *ts);
+inline bool TkTextTagSetIsEqual(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+inline bool TkTextTagSetContains(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+inline bool TkTextTagSetDisjunctive(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+inline bool TkTextTagSetIntersects(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+inline bool TkTextTagSetIntersectionIsEqual(const TkTextTagSet *ts1, const TkTextTagSet *ts2,
+ const TkBitField *bf);
+inline bool TkTextTagBitContainsSet(const TkBitField *bf, const TkTextTagSet *ts);
+inline bool TkTextTagSetIsEqualBits(const TkTextTagSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetContainsBits(const TkTextTagSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetDisjunctiveBits(const TkTextTagSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetIntersectsBits(const TkTextTagSet *ts, const TkBitField *bf);
+inline unsigned TkTextTagSetFindFirst(const TkTextTagSet *ts);
+inline unsigned TkTextTagSetFindNext(const TkTextTagSet *ts, unsigned prev);
+inline TkTextTagSet *TkTextTagSetAddOrErase(TkTextTagSet *ts, unsigned n, bool value);
+inline unsigned TkTextTagSetRangeSize(const TkTextTagSet *ts);
+inline const unsigned char *TkTextTagSetData(const TkTextTagSet *ts);
+inline unsigned TkTextTagSetByteSize(const TkTextTagSet *ts);
+#else /* integer set only implementation **************************************/
+inline TkIntSet *TkTextTagSetNew(unsigned size);
+inline TkIntSet *TkTextTagSetResize(TkIntSet *ts, unsigned newSize);
+inline void TkTextTagSetDestroy(TkIntSet **tsPtr);
+inline unsigned TkTextTagSetRefCount(const TkIntSet *ts);
+inline void TkTextTagSetIncrRefCount(TkIntSet *ts);
+inline unsigned TkTextTagSetDecrRefCount(TkIntSet *ts);
+inline TkIntSet *TkTextTagSetCopy(const TkIntSet *src);
+inline bool TkTextTagSetIsEmpty(const TkIntSet *ts);
+inline bool TkTextTagSetIsBitField(const TkIntSet *ts);
+inline unsigned TkTextTagSetSize(const TkIntSet *ts);
+inline unsigned TkTextTagSetCount(const TkIntSet *ts);
+inline bool TkTextTagSetTest(const TkIntSet *ts, unsigned n);
+inline bool TkTextTagSetNone(const TkIntSet *ts);
+inline bool TkTextTagSetAny(const TkIntSet *ts);
+inline bool TkTextTagSetIsEqual(const TkIntSet *ts1, const TkIntSet *ts2);
+inline bool TkTextTagSetContains(const TkIntSet *ts1, const TkIntSet *ts2);
+inline bool TkTextTagSetDisjunctive(const TkIntSet *ts1, const TkIntSet *ts2);
+inline bool TkTextTagSetIntersects(const TkIntSet *ts1, const TkIntSet *ts2);
+inline bool TkTextTagSetIntersectionIsEqual(const TkIntSet *ts1, const TkIntSet *ts2,
+ const TkBitField *bf);
+inline bool TkTextTagBitContainsSet(const TkBitField *bf, const TkIntSet *ts);
+inline bool TkTextTagSetIsEqualBits(const TkIntSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetContainsBits(const TkIntSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetDisjunctiveBits(const TkIntSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetIntersectsBits(const TkIntSet *ts, const TkBitField *bf);
+inline unsigned TkTextTagSetFindFirst(const TkIntSet *ts);
+inline unsigned TkTextTagSetFindNext(const TkIntSet *ts, unsigned prev);
+inline unsigned TkTextTagSetFindFirstInIntersection(const TkIntSet *ts, const TkBitField *bf);
+inline TkIntSet *TkTextTagSetAddOrErase(TkIntSet *ts, unsigned n, bool value);
+inline TkIntSet *TkTextTagSetClear(TkIntSet *ts);
+inline unsigned TkTextTagSetRangeSize(const TkIntSet *ts);
+inline const unsigned char *TkTextTagSetData(const TkIntSet *ts);
+inline unsigned TkTextTagSetByteSize(const TkIntSet *ts);
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+#endif /* __STDC_VERSION__ >= 199901L */
+
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkTextTagSet.h b/generic/tkTextTagSet.h
new file mode 100644
index 0000000..8f0acf7
--- /dev/null
+++ b/generic/tkTextTagSet.h
@@ -0,0 +1,292 @@
+/*
+ * tkTextTagSet.h --
+ *
+ * This module implements a set for tagging information. The real type
+ * is either a bit field, or a set of integers, depending on the size
+ * of the tag set.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKTEXTTAGSET
+#define _TKTEXTTAGSET
+
+#include "tkBitField.h"
+#include "tkIntSet.h"
+
+#include <stdint.h>
+
+#if defined(__GNUC__) || defined(__clang__)
+# define __warn_unused__ __attribute__((warn_unused_result))
+#else
+# define __warn_unused__
+#endif
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+/*
+ * Currently our implementation is using a shared bitfield/integer set implementation.
+ * Bitfields will be used as long as the number of tags is below a certain limit
+ * (will be satisfied in most applications), but in some sophisticated applications
+ * this limit will be exceeded, and in this case the integer set comes into play,
+ * because a bitfield is too memory hungry with a large number of tags. Bitfields
+ * are very, very fast, and integer sets are moderate in speed. So a bitfield will be
+ * preferred. Nevertheless this implementation might be a bit over the top, probably
+ * an implementation only with integer sets is already satisfactory.
+ *
+ * NOTE: The bit field implementation shouldn't be removed, even if this implementation
+ * will not be used, because it is required for testing the integer set (TkIntSet).
+ *
+ * We will use the compiler constant TK_TEXT_DONT_USE_BITFIELDS for the choice (with or
+ * without bitfields).
+ */
+
+/* This is common to both implementations. */
+# define TK_TEXT_TAG_SET_NPOS TK_SET_NPOS
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS /* shared implementation ****************************/
+
+/*
+ * The struct below is using C inheritance, this is portable due to C99 section
+ * 6.7.2.1 bullet point 13:
+ *
+ * Within a structure object, the non-bit-field members and the units
+ * in which bit-fields reside have addresses that increase in the order
+ * in which they are declared. A pointer to a structure object, suitably
+ * converted, points to its initial member (or if that member is a
+ * bit-field, then to the unit in which it resides), and vice versa.
+ * There may be unnamed padding within a structure object, but not at
+ * beginning.
+ *
+ * This inheritance concept is also used in the portable GTK library.
+ */
+
+typedef struct TkTextTagSetBase {
+ uint32_t refCount:31;
+ uint32_t isSetFlag:1;
+} TkTextTagSetBase;
+
+typedef union TkTextTagSet {
+ TkTextTagSetBase base;
+ TkBitField bf;
+ TkIntSet set;
+} TkTextTagSet;
+
+
+inline TkTextTagSet *TkTextTagSetNew(unsigned size) __warn_unused__;
+TkTextTagSet *TkTextTagSetResize(TkTextTagSet *ts, unsigned newSize) __warn_unused__;
+void TkTextTagSetDestroy(TkTextTagSet **tsPtr);
+
+inline unsigned TkTextTagSetRefCount(const TkTextTagSet *ts);
+inline void TkTextTagSetIncrRefCount(TkTextTagSet *ts);
+inline unsigned TkTextTagSetDecrRefCount(TkTextTagSet *ts);
+
+inline TkTextTagSet *TkTextTagSetCopy(const TkTextTagSet *src) __warn_unused__;
+TkBitField *TkTextTagSetToBits(const TkTextTagSet *src, int size) __warn_unused__;
+
+TkTextTagSet *TkTextTagSetJoin(TkTextTagSet *dst, const TkTextTagSet *src) __warn_unused__;
+TkTextTagSet *TkTextTagSetIntersect(TkTextTagSet *dst, const TkTextTagSet *src) __warn_unused__;
+TkTextTagSet *TkTextTagSetRemove(TkTextTagSet *dst, const TkTextTagSet *src) __warn_unused__;
+
+TkTextTagSet *TkTextTagSetIntersectBits(TkTextTagSet *dst, const TkBitField *src) __warn_unused__;
+TkTextTagSet *TkTextTagSetRemoveBits(TkTextTagSet *dst, const TkBitField *src) __warn_unused__;
+
+/* dst := dst + ts1 + ts2 */
+TkTextTagSet *TkTextTagSetJoin2(TkTextTagSet *dst, const TkTextTagSet *ts1, const TkTextTagSet *ts2)
+ __warn_unused__;
+/* dst := src - dst */
+TkTextTagSet *TkTextTagSetComplementTo(TkTextTagSet *dst, const TkTextTagSet *src) __warn_unused__;
+/* dst := dst + (ts2 - ts1) */
+TkTextTagSet *TkTextTagSetJoinComplementTo(TkTextTagSet *dst,
+ const TkTextTagSet *ts1, const TkTextTagSet *ts2) __warn_unused__;
+/* dst := dst + (ts1 - ts2) + (ts2 - ts1) */
+TkTextTagSet *TkTextTagSetJoinNonIntersection(TkTextTagSet *dst,
+ const TkTextTagSet *ts1, const TkTextTagSet *ts2) __warn_unused__;
+/* dst := dst + add + ((ts1 + ts2) - (ts1 & ts2)) */
+TkTextTagSet *TkTextTagSetJoin2ComplementToIntersection(TkTextTagSet *dst,
+ const TkTextTagSet *add, const TkTextTagSet *ts1, const TkTextTagSet *ts2) __warn_unused__;
+/* dst := (dst - ts1) + (ts1 - ts2) */
+TkTextTagSet *TkTextTagSetJoinOfDifferences(TkTextTagSet *dst, const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2) __warn_unused__;
+
+inline bool TkTextTagSetIsEmpty(const TkTextTagSet *ts);
+inline bool TkTextTagSetIsBitField(const TkTextTagSet *ts);
+
+inline unsigned TkTextTagSetSize(const TkTextTagSet *ts);
+inline unsigned TkTextTagSetCount(const TkTextTagSet *ts);
+
+inline bool TkTextTagSetTest(const TkTextTagSet *ts, unsigned n);
+inline bool TkTextTagSetNone(const TkTextTagSet *ts);
+inline bool TkTextTagSetAny(const TkTextTagSet *ts);
+
+inline bool TkTextTagSetIsEqual(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+inline bool TkTextTagSetContains(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+inline bool TkTextTagSetDisjunctive(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+inline bool TkTextTagSetIntersects(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+/* (ts1 & bf) == (ts2 & bf) */
+inline bool TkTextTagSetIntersectionIsEqual(const TkTextTagSet *ts1, const TkTextTagSet *ts2,
+ const TkBitField *bf);
+inline bool TkTextTagBitContainsSet(const TkBitField *bf, const TkTextTagSet *ts);
+
+inline bool TkTextTagSetIsEqualBits(const TkTextTagSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetContainsBits(const TkTextTagSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetDisjunctiveBits(const TkTextTagSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetIntersectsBits(const TkTextTagSet *ts, const TkBitField *bf);
+
+inline unsigned TkTextTagSetFindFirst(const TkTextTagSet *ts);
+inline unsigned TkTextTagSetFindNext(const TkTextTagSet *ts, unsigned prev);
+unsigned TkTextTagSetFindFirstInIntersection(const TkTextTagSet *ts, const TkBitField *bf);
+
+TkTextTagSet *TkTextTagSetAdd(TkTextTagSet *ts, unsigned n) __warn_unused__;
+TkTextTagSet *TkTextTagSetErase(TkTextTagSet *ts, unsigned n) __warn_unused__;
+inline TkTextTagSet *TkTextTagSetAddOrErase(TkTextTagSet *ts, unsigned n, bool value)
+ __warn_unused__;
+TkTextTagSet *TkTextTagSetTestAndSet(TkTextTagSet *ts, unsigned n) __warn_unused__;
+TkTextTagSet *TkTextTagSetTestAndUnset(TkTextTagSet *ts, unsigned n) __warn_unused__;
+TkTextTagSet *TkTextTagSetClear(TkTextTagSet *ts) __warn_unused__;
+
+inline unsigned TkTextTagSetRangeSize(const TkTextTagSet *ts);
+
+inline const unsigned char *TkTextTagSetData(const TkTextTagSet *ts);
+inline unsigned TkTextTagSetByteSize(const TkTextTagSet *ts);
+
+# if !NDEBUG
+void TkTextTagSetPrint(const TkTextTagSet *set);
+# endif
+
+
+# if 0
+
+/*
+ * These functions are not needed anymore, but shouldn't be removed, because sometimes
+ * any of these functions might be useful.
+ */
+
+/* dst := (dst + (ts - sub)) & ts */
+TkTextTagSet *TkTextTagSetInnerJoinDifference(TkTextTagSet *dst,
+ const TkTextTagSet *ts, const TkTextTagSet *sub) __warn_unused__;
+/* ((ts + (add - sub)) & add) == nil */
+bool TkTextTagSetInnerJoinDifferenceIsEmpty(const TkTextTagSet *ts,
+ const TkTextTagSet *add, const TkTextTagSet *sub);
+/* ts1 == ts2 - sub2 */
+bool TkTextTagSetIsEqualToDifference(const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2, const TkTextTagSet *sub2);
+/* ts1 == ts2 + (add2 & ts2) */
+bool TkTextTagSetIsEqualToInnerJoin(const TkTextTagSet *ts1, const TkTextTagSet *ts2,
+ const TkTextTagSet *add2);
+/* ts1 == ((ts2 + (add2 - sub2)) & add2) */
+bool TkTextTagSetIsEqualToInnerJoinDifference(const TkTextTagSet *ts1, const TkTextTagSet *ts2,
+ const TkTextTagSet *add2, const TkTextTagSet *sub2);
+/* ((ts1 + (add - sub)) & add) == ((ts2 + (add - sub)) & add) */
+bool TkTextTagSetInnerJoinDifferenceIsEqual(const TkTextTagSet *ts1, const TkTextTagSet *ts2,
+ const TkTextTagSet *add, const TkTextTagSet *sub);
+
+# endif /* 0 */
+
+#else /* integer set only implementation **************************************/
+
+# define TkTextTagSet TkIntSet
+
+inline TkIntSet *TkTextTagSetNew(unsigned size) __warn_unused__;
+inline TkIntSet *TkTextTagSetResize(TkIntSet *ts, unsigned newSize) __warn_unused__;
+inline void TkTextTagSetDestroy(TkIntSet **tsPtr);
+
+inline unsigned TkTextTagSetRefCount(const TkIntSet *ts);
+inline void TkTextTagSetIncrRefCount(TkIntSet *ts);
+inline unsigned TkTextTagSetDecrRefCount(TkIntSet *ts);
+
+inline TkIntSet *TkTextTagSetCopy(const TkIntSet *src) __warn_unused__;
+TkBitField *TkTextTagSetToBits(const TkTextTagSet *src, int size) __warn_unused__;
+
+TkIntSet *TkTextTagSetJoin(TkIntSet *dst, const TkIntSet *src) __warn_unused__;
+TkIntSet *TkTextTagSetIntersect(TkIntSet *dst, const TkIntSet *src) __warn_unused__;
+TkIntSet *TkTextTagSetRemove(TkIntSet *dst, const TkIntSet *src) __warn_unused__;
+
+TkIntSet *TkTextTagSetIntersectBits(TkIntSet *dst, const TkBitField *src) __warn_unused__;
+TkIntSet *TkTextTagSetRemoveBits(TkIntSet *dst, const TkBitField *src) __warn_unused__;
+
+/* dst := dst + ts1 + ts2 */
+TkIntSet *TkTextTagSetJoin2(TkIntSet *dst, const TkIntSet *ts1, const TkIntSet *ts2) __warn_unused__;
+/* dst := src - dst */
+TkIntSet *TkTextTagSetComplementTo(TkIntSet *dst, const TkIntSet *src) __warn_unused__;
+/* dst := dst + (bf2 - bf1) */
+TkIntSet *TkTextTagSetJoinComplementTo(TkIntSet *dst, const TkIntSet *ts1, const TkIntSet *ts2)
+ __warn_unused__;
+/* dst := dst + (set1 - set2) + (set2 - set1) */
+TkIntSet *TkTextTagSetJoinNonIntersection(TkIntSet *dst, const TkIntSet *ts1, const TkIntSet *ts2)
+ __warn_unused__;
+/* dst := dst + add + ((ts1 + ts2) - (ts1 & ts2)) */
+TkIntSet *TkTextTagSetJoin2ComplementToIntersection(TkIntSet *dst, const TkIntSet *add,
+ const TkIntSet *ts1, const TkIntSet *ts2) __warn_unused__;
+/* dst := (dst - ts1) + (ts1 - ts2) */
+TkIntSet *TkTextTagSetJoinOfDifferences(TkIntSet *dst, const TkIntSet *ts1, const TkIntSet *ts2)
+ __warn_unused__;
+
+inline bool TkTextTagSetIsEmpty(const TkIntSet *ts);
+inline bool TkTextTagSetIsBitField(const TkIntSet *ts);
+
+inline unsigned TkTextTagSetSize(const TkIntSet *ts);
+inline unsigned TkTextTagSetCount(const TkIntSet *ts);
+
+inline bool TkTextTagSetTest(const TkIntSet *ts, unsigned n);
+inline bool TkTextTagSetNone(const TkIntSet *ts);
+inline bool TkTextTagSetAny(const TkIntSet *ts);
+
+inline bool TkTextTagSetIsEqual(const TkIntSet *ts1, const TkIntSet *ts2);
+inline bool TkTextTagSetContains(const TkIntSet *ts1, const TkIntSet *ts2);
+inline bool TkTextTagSetDisjunctive(const TkIntSet *ts1, const TkIntSet *ts2);
+inline bool TkTextTagSetIntersects(const TkIntSet *ts1, const TkIntSet *ts2);
+/* (ts1 & bf) == (ts2 & bf) */
+inline bool TkTextTagSetIntersectionIsEqual(const TkIntSet *ts1, const TkIntSet *ts2,
+ const TkBitField *bf);
+inline bool TkTextTagBitContainsSet(const TkBitField *bf, const TkIntSet *ts);
+
+inline bool TkTextTagSetIsEqualBits(const TkIntSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetContainsBits(const TkIntSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetDisjunctiveBits(const TkIntSet *ts, const TkBitField *bf);
+inline bool TkTextTagSetIntersectsBits(const TkIntSet *ts, const TkBitField *bf);
+
+inline unsigned TkTextTagSetFindFirst(const TkIntSet *ts);
+inline unsigned TkTextTagSetFindNext(const TkIntSet *ts, unsigned prev);
+inline unsigned TkTextTagSetFindFirstInIntersection(const TkIntSet *ts, const TkBitField *bf);
+
+TkIntSet *TkTextTagSetAdd(TkIntSet *ts, unsigned n) __warn_unused__;
+TkIntSet *TkTextTagSetErase(TkIntSet *ts, unsigned n) __warn_unused__;
+inline TkIntSet *TkTextTagSetAddOrErase(TkIntSet *ts, unsigned n, bool value) __warn_unused__;
+TkIntSet *TkTextTagSetTestAndSet(TkIntSet *ts, unsigned n) __warn_unused__;
+TkIntSet *TkTextTagSetTestAndUnset(TkIntSet *ts, unsigned n) __warn_unused__;
+inline TkIntSet *TkTextTagSetClear(TkIntSet *ts) __warn_unused__;
+
+inline unsigned TkTextTagSetRangeSize(const TkIntSet *ts);
+
+inline const unsigned char *TkTextTagSetData(const TkIntSet *ts);
+inline unsigned TkTextTagSetByteSize(const TkIntSet *ts);
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+
+
+#undef __warn_unused__
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+# define _TK_NEED_IMPLEMENTATION
+# include "tkTextTagSetPriv.h"
+# undef _TK_NEED_IMPLEMENTATION
+#else
+# undef inline
+#endif
+
+#endif /* _TKTEXTTAGSET */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkTextTagSetPriv.h b/generic/tkTextTagSetPriv.h
new file mode 100644
index 0000000..a97bebd
--- /dev/null
+++ b/generic/tkTextTagSetPriv.h
@@ -0,0 +1,505 @@
+/*
+ * tkTextTagSetPriv.h --
+ *
+ * Private implementation.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKTEXTTAGSET
+# error "do not include this private header file"
+#endif
+
+
+#ifndef _TKTEXTTAGSETPRIV
+#define _TKTEXTTAGSETPRIV
+
+#if !TK_TEXT_DONT_USE_BITFIELDS /* shared implementation ****************************/
+
+/*
+ * The constant TK_TEXT_SET_MAX_BIT_SIZE is defining the upper bound of
+ * the bit size in bit fields. This means that if more than TK_TEXT_SET_MAX_BIT_SIZE
+ * tags are in usage, the tag set is using integer sets instead of bit fields,
+ * because large bit fields are exploding the memory usage.
+ *
+ * The constant TK_TEXT_SET_MAX_BIT_SIZE must be a multiple of TK_BIT_NBITS.
+ */
+
+#ifdef TCL_WIDE_INT_IS_LONG
+
+/*
+ * On 64 bit systems this is the optimal size and it is not recommended to
+ * choose a lower size.
+ */
+# define TK_TEXT_SET_MAX_BIT_SIZE (((512 + TK_BIT_NBITS - 1)/TK_BIT_NBITS)*TK_BIT_NBITS)
+
+#else /* TCL_WIDE_INT_IS_LONG */
+
+/*
+ * On 32 bit systems the current size (512) might be too large. If so it should
+ * be reduced to 256, but it is not recommended to define a lower constant than
+ * 256.
+ */
+# define TK_TEXT_SET_MAX_BIT_SIZE (((512 + TK_BIT_NBITS - 1)/TK_BIT_NBITS)*TK_BIT_NBITS)
+
+#endif /* TCL_WIDE_INT_IS_LONG */
+
+
+MODULE_SCOPE bool TkTextTagSetIsEqual_(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+MODULE_SCOPE bool TkTextTagSetContains_(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+MODULE_SCOPE bool TkTextTagSetDisjunctive_(const TkTextTagSet *ts1, const TkTextTagSet *ts2);
+MODULE_SCOPE bool TkTextTagSetIntersectionIsEqual_(const TkTextTagSet *ts1, const TkTextTagSet *ts2,
+ const TkBitField *bf);
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+#endif /* _TKTEXTTAGSETPRIV */
+
+
+#ifdef _TK_NEED_IMPLEMENTATION
+
+#ifndef _TK
+#include "tk.h"
+#endif
+
+#include <assert.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+#if !TK_TEXT_DONT_USE_BITFIELDS /* shared implementation ****************************/
+
+inline
+TkTextTagSet *
+TkTextTagSetNew(
+ unsigned size)
+{
+ if (size <= TK_TEXT_SET_MAX_BIT_SIZE) {
+ return (TkTextTagSet *) TkBitNew(size);
+ }
+ return (TkTextTagSet *) TkIntSetNew();
+}
+
+
+inline
+unsigned
+TkTextTagSetRefCount(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.refCount;
+}
+
+
+inline
+void
+TkTextTagSetIncrRefCount(
+ TkTextTagSet *ts)
+{
+ assert(ts);
+ ts->base.refCount += 1;
+}
+
+
+inline
+unsigned
+TkTextTagSetDecrRefCount(
+ TkTextTagSet *ts)
+{
+ unsigned refCount;
+
+ assert(ts);
+ assert(TkTextTagSetRefCount(ts) > 0);
+
+ if ((refCount = --ts->base.refCount) == 0) {
+ TkTextTagSetDestroy(&ts);
+ }
+ return refCount;
+}
+
+
+inline
+bool
+TkTextTagSetIsEmpty(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TkIntSetIsEmpty(&ts->set) : TkBitNone(&ts->bf);
+}
+
+
+inline
+bool
+TkTextTagSetIsBitField(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return !ts->base.isSetFlag;
+}
+
+
+inline
+unsigned
+TkTextTagSetSize(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TK_TEXT_TAG_SET_NPOS - 1 : TkBitSize(&ts->bf);
+}
+
+
+inline
+unsigned
+TkTextTagSetRangeSize(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+
+ if (!ts->base.isSetFlag) {
+ return TkBitSize(&ts->bf);
+ }
+ return TkIntSetIsEmpty(&ts->set) ? 0 : TkIntSetMax(&ts->set) + 1;
+}
+
+
+inline
+unsigned
+TkTextTagSetCount(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TkIntSetSize(&ts->set) : TkBitCount(&ts->bf);
+}
+
+
+inline
+bool
+TkTextTagSetIsEqual(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1->base.isSetFlag || ts2->base.isSetFlag) {
+ return TkTextTagSetIsEqual_(ts1, ts2);
+ }
+ return TkBitIsEqual(&ts1->bf, &ts2->bf);
+}
+
+
+inline
+bool
+TkTextTagSetContains(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1->base.isSetFlag || ts2->base.isSetFlag) {
+ return TkTextTagSetContains_(ts1, ts2);
+ }
+ return TkBitContains(&ts1->bf, &ts2->bf);
+}
+
+
+inline
+bool
+TkTextTagSetDisjunctive(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1->base.isSetFlag || ts2->base.isSetFlag) {
+ return TkTextTagSetDisjunctive_(ts1, ts2);
+ }
+ return TkBitDisjunctive(&ts1->bf, &ts2->bf);
+}
+
+
+inline
+bool
+TkTextTagSetIntersects(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2)
+{
+ return !TkTextTagSetDisjunctive(ts1, ts2);
+}
+
+
+inline
+bool
+TkTextTagSetIntersectionIsEqual(
+ const TkTextTagSet *ts1,
+ const TkTextTagSet *ts2,
+ const TkBitField *bf)
+{
+ assert(ts1);
+ assert(ts2);
+
+ if (ts1->base.isSetFlag || ts2->base.isSetFlag) {
+ return TkTextTagSetIntersectionIsEqual_(ts1, ts2, bf);
+ }
+ return TkBitIntersectionIsEqual(&ts1->bf, &ts2->bf, bf);
+}
+
+
+inline
+bool
+TkTextTagBitContainsSet(
+ const TkBitField *bf,
+ const TkTextTagSet *ts)
+{
+ return ts->base.isSetFlag ? TkIntSetIsContainedBits(&ts->set, bf) : TkBitContains(bf, &ts->bf);
+}
+
+
+inline
+bool
+TkTextTagSetIsEqualBits(
+ const TkTextTagSet *ts,
+ const TkBitField *bf)
+{
+ assert(ts);
+ assert(bf);
+ return ts->base.isSetFlag ? TkIntSetIsEqualBits(&ts->set, bf) : TkBitIsEqual(&ts->bf, bf);
+}
+
+
+inline
+bool
+TkTextTagSetContainsBits(
+ const TkTextTagSet *ts,
+ const TkBitField *bf)
+{
+ assert(ts);
+ assert(bf);
+ return ts->base.isSetFlag ? TkIntSetContainsBits(&ts->set, bf) : TkBitContains(&ts->bf, bf);
+}
+
+
+inline
+bool
+TkTextTagSetDisjunctiveBits(
+ const TkTextTagSet *ts,
+ const TkBitField *bf)
+{
+ assert(ts);
+ assert(bf);
+ return ts->base.isSetFlag ? TkIntSetDisjunctiveBits(&ts->set, bf) : TkBitDisjunctive(&ts->bf, bf);
+}
+
+
+inline
+bool
+TkTextTagSetIntersectsBits(
+ const TkTextTagSet *ts,
+ const TkBitField *bf)
+{
+ return !TkTextTagSetDisjunctiveBits(ts, bf);
+}
+
+
+inline
+bool
+TkTextTagSetTest(
+ const TkTextTagSet *ts,
+ unsigned n)
+{
+ assert(ts);
+
+ if (ts->base.isSetFlag) {
+ return TkIntSetTest(&ts->set, n);
+ }
+ return n < TkBitSize(&ts->bf) && TkBitTest(&ts->bf, n);
+}
+
+
+inline
+bool
+TkTextTagSetNone(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TkIntSetNone(&ts->set) : TkBitNone(&ts->bf);
+}
+
+
+inline
+bool
+TkTextTagSetAny(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TkIntSetAny(&ts->set) : TkBitAny(&ts->bf);
+}
+
+
+inline
+TkTextTagSet *
+TkTextTagSetCopy(
+ const TkTextTagSet *src)
+{
+ assert(src);
+
+ if (src->base.isSetFlag) {
+ return (TkTextTagSet *) TkIntSetCopy(&src->set);
+ }
+ return (TkTextTagSet *) TkBitCopy(&src->bf, -1);
+}
+
+
+inline
+unsigned
+TkTextTagSetFindFirst(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TkIntSetFindFirst(&ts->set) : TkBitFindFirst(&ts->bf);
+}
+
+
+inline
+unsigned
+TkTextTagSetFindNext(
+ const TkTextTagSet *ts,
+ unsigned prev)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TkIntSetFindNext(&ts->set) : TkBitFindNext(&ts->bf, prev);
+}
+
+
+inline
+TkTextTagSet *
+TkTextTagSetAddOrErase(
+ TkTextTagSet *ts,
+ unsigned n,
+ bool value)
+{
+ assert(ts);
+ return value ? TkTextTagSetAdd(ts, n) : TkTextTagSetErase(ts, n);
+}
+
+
+inline
+const unsigned char *
+TkTextTagSetData(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TkIntSetData(&ts->set) : TkBitData(&ts->bf);
+}
+
+
+inline
+unsigned
+TkTextTagSetByteSize(
+ const TkTextTagSet *ts)
+{
+ assert(ts);
+ return ts->base.isSetFlag ? TkIntSetByteSize(&ts->set) : TkBitByteSize(&ts->bf);
+}
+
+#else /* integer set only implementation **************************************/
+
+inline TkIntSet *TkTextTagSetNew(unsigned size) { return TkIntSetNew(); }
+
+inline TkIntSet *TkTextTagSetResize(TkIntSet *ts, unsigned newSize)
+{ if (!ts) { (ts = TkIntSetNew())->refCount = 1; }; return ts; }
+
+inline void TkTextTagSetDestroy(TkIntSet **tsPtr) { TkIntSetDestroy(tsPtr); }
+
+inline unsigned TkTextTagSetRefCount(const TkIntSet *ts) { return TkIntSetRefCount(ts); }
+
+inline void TkTextTagSetIncrRefCount(TkIntSet *ts) { TkIntSetIncrRefCount(ts); }
+
+inline unsigned TkTextTagSetDecrRefCount(TkIntSet *ts) { return TkIntSetDecrRefCount(ts); }
+
+inline TkIntSet *TkTextTagSetCopy(const TkIntSet *src) { return TkIntSetCopy(src); }
+
+inline bool TkTextTagSetIsEmpty(const TkIntSet *ts) { return TkIntSetIsEmpty(ts); }
+
+inline bool TkTextTagSetIsBitField(const TkIntSet *ts) { assert(ts); return true; }
+
+inline unsigned TkTextTagSetSize(const TkIntSet *ts) { return TK_TEXT_TAG_SET_NPOS - 1; }
+
+inline unsigned TkTextTagSetCount(const TkIntSet *ts) { return TkIntSetSize(ts); }
+
+inline bool TkTextTagSetTest(const TkIntSet *ts, unsigned n) { return TkIntSetTest(ts, n); }
+
+inline bool TkTextTagSetNone(const TkIntSet *ts) { return TkIntSetNone(ts); }
+
+inline bool TkTextTagSetAny(const TkIntSet *ts) { return TkIntSetAny(ts); }
+
+inline bool TkTextTagSetIsEqual(const TkIntSet *ts1, const TkIntSet *ts2)
+{ return TkIntSetIsEqual(ts1, ts2); }
+
+inline bool TkTextTagSetContains(const TkIntSet *ts1, const TkIntSet *ts2)
+{ return TkIntSetContains(ts1, ts2); }
+
+inline bool TkTextTagSetDisjunctive(const TkIntSet *ts1, const TkIntSet *ts2)
+{ return TkIntSetDisjunctive(ts1, ts2); }
+
+inline bool TkTextTagSetIntersects(const TkIntSet *ts1, const TkIntSet *ts2)
+{ return TkIntSetIntersects(ts1, ts2); }
+
+inline bool TkTextTagSetIntersectionIsEqual(const TkIntSet *ts1, const TkIntSet *ts2,
+ const TkBitField *src)
+{ return TkIntSetIntersectionIsEqual(ts1, ts2, src); }
+
+inline bool TkTextTagBitContainsSet(const TkBitField *bf, const TkIntSet *ts)
+{ return TkIntSetIsContainedBits(ts, bf); }
+
+inline bool TkTextTagSetIsEqualBits(const TkIntSet *ts, const TkBitField *bf)
+{ return TkIntSetIsEqualBits(ts, bf); }
+
+inline bool TkTextTagSetContainsBits(const TkIntSet *ts, const TkBitField *bf)
+{ return TkIntSetContainsBits(ts, bf); }
+
+inline bool TkTextTagSetDisjunctiveBits(const TkIntSet *ts, const TkBitField *bf)
+{ return TkIntSetDisjunctiveBits(ts, bf); }
+
+inline bool TkTextTagSetIntersectsBits(const TkIntSet *ts, const TkBitField *bf)
+{ return !TkTextTagSetDisjunctiveBits(ts, bf); }
+
+inline unsigned TkTextTagSetFindFirst(const TkIntSet *ts) { return TkIntSetFindFirst(ts); }
+
+inline unsigned TkTextTagSetFindNext(const TkIntSet *ts, unsigned prev)
+{ return TkIntSetFindNext(ts); }
+
+inline unsigned TkTextTagSetFindFirstInIntersection(const TkIntSet *ts, const TkBitField *bf)
+{ return TkIntSetFindFirstInIntersection(ts, bf); }
+
+inline TkIntSet *TkTextTagSetAddOrErase(TkIntSet *ts, unsigned n, bool value)
+{ return value ? TkTextTagSetAdd(ts, n) : TkTextTagSetErase(ts, n); }
+
+inline TkIntSet *TkTextTagSetClear(TkIntSet *ts) { return TkIntSetClear(ts); }
+
+inline unsigned TkTextTagSetRangeSize(const TkIntSet *ts)
+{ return TkIntSetIsEmpty(ts) ? 0 : TkIntSetMax(ts) + 1; }
+
+inline unsigned char *TkTextTagSetData(const TkTextTagSet *ts)
+{ assert(ts); return TkIntSetData(&ts->set); }
+
+inline unsigned
+TkTextTagSetByteSize(const TkTextTagSet *ts)
+{ assert(ts); return TkIntSetByteSize(&ts->set); }
+
+#endif /* !TK_TEXT_DONT_USE_BITFIELDS */
+#undef _TK_NEED_IMPLEMENTATION
+#endif /* _TK_NEED_IMPLEMENTATION */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkTextUndo.c b/generic/tkTextUndo.c
new file mode 100644
index 0000000..1bd411e
--- /dev/null
+++ b/generic/tkTextUndo.c
@@ -0,0 +1,1073 @@
+/*
+ * tkTextUndo.c --
+ *
+ * This module provides the implementation of an undo stack.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#include "tkTextUndo.h"
+#include "tkInt.h"
+#include "tkAlloc.h"
+#include <assert.h>
+
+#if !(__STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900))
+# define _TK_NEED_IMPLEMENTATION
+# include "tkTextUndoPriv.h"
+#endif
+
+#ifndef MAX
+# define MAX(a,b) ((a) < (b) ? b : a)
+#endif
+
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? a : b)
+#endif
+
+
+typedef TkTextUndoMyAtom MyUndoAtom;
+
+
+/*
+ * Our list of undo/redo atoms is a circular double linked list.
+ * It's circular beause the "last" pointer is connected with the
+ * "root" pointer. The list starts either with the oldest undo atom,
+ * or with the newest redo atom if no undo atom exists.
+ *
+ * 'stack->last' is always pointing to the newest undo item, or
+ * NULL if no undo item exists.
+ *
+ * 'stack->root' is always pointing either to the oldest undo item,
+ * or to the oldest redo item if no undo item exists.
+ *
+ * 'stack->current' is the current atom which receives all pushed
+ * items (TkTextUndoPushItem), and is not yet linked into the list.
+ * 'stack->current' can be NULL, in this case it has to be created
+ * when the user is pushing an item.
+ *
+ * last ------------------+
+ * root --+ |
+ * V V
+ * +---+ +---+ +---+ +---+ +---+
+ * +->| A |-->| B |-->| C |-->| d |-->| e |--+
+ * | +---+ +---+ +---+ +---+ +---+ |
+ * ------------------------------------------+
+ * undo: 3 redo: 2
+ *
+ * A = oldest undo item
+ * B = second oldest undo item
+ * C = newest undo item
+ * d = newest redo item
+ * e = oldest redo item
+ */
+
+
+#define ATOM_SIZE(n) (Tk_Offset(TkTextUndoMyAtom, data) \
+ + Tk_Offset(TkTextUndoAtom, array) + (n)*sizeof(TkTextUndoSubAtom))
+
+
+enum { InitialCapacity = 20 };
+
+
+static void
+FreeItems(
+ const TkTextUndoStack stack,
+ const TkTextUndoAtom *atom)
+{
+ TkTextUndoFreeProc *freeProc = stack->freeProc;
+ const TkTextUndoSubAtom *arr;
+ unsigned i, n;
+
+ assert(atom);
+
+ if (!freeProc) {
+ return;
+ }
+
+ arr = atom->array;
+ n = atom->arraySize;
+
+ for (i = 0; i < n; ++i) {
+ freeProc(stack, &arr[i]);
+ }
+}
+
+
+static void
+Release(
+ TkTextUndoStack stack,
+ MyUndoAtom *atom)
+{
+ MyUndoAtom *first, *root, *prev;
+
+ if (!atom) {
+ return;
+ }
+
+ assert(stack->root);
+ first = atom;
+ root = stack->root;
+ prev = atom->prev;
+
+ /*
+ * Now delete all atoms starting at 'atom' until we reach the end (inclusive).
+ */
+
+ do {
+ MyUndoAtom *next = atom->next;
+ FreeItems(stack, &atom->data);
+ free(atom);
+ atom = next;
+ } while (atom != root);
+
+ /*
+ * Update the list pointers accordingly.
+ */
+
+ if (first == root) {
+ stack->root = stack->last = NULL;
+ } else {
+ root->prev = prev;
+ prev->next = root;
+ }
+}
+
+
+static void
+ResetCurrent(
+ TkTextUndoStack stack,
+ bool force)
+{
+ TkTextUndoMyAtom *current = stack->current;
+
+ if (current) {
+ FreeItems(stack, &current->data);
+ }
+
+ if (force || !current || current->capacity > InitialCapacity) {
+ static unsigned Size = ATOM_SIZE(InitialCapacity);
+ current = stack->current = memset(realloc(current, Size), 0, Size);
+ current->capacity = InitialCapacity;
+ }
+
+ current->data.arraySize = 0;
+ current->data.size = 0;
+ current->undoSize = 0;
+}
+
+
+static MyUndoAtom *
+SwapCurrent(
+ TkTextUndoStack stack,
+ MyUndoAtom *atom)
+{
+ MyUndoAtom *current = stack->current;
+
+ assert(atom != current);
+
+ if (current->capacity != current->data.size) {
+ current = stack->current = realloc(current, ATOM_SIZE(current->data.arraySize));
+ current->capacity = current->data.arraySize;
+ }
+
+ if (!atom) {
+ /*
+ * Just use the 'stack->current' item.
+ */
+ stack->current = NULL;
+ return current;
+ }
+
+ /*
+ * Exchange given 'atom' with 'stack->current', this means that
+ * 'stack->current' will be linked into the list replacing 'atom',
+ * and 'atom' will become 'stack->current'.
+ */
+
+ if (atom->next == atom) {
+ current->next = current;
+ current->prev = current;
+ } else {
+ current->next = atom->next;
+ current->prev = atom->prev;
+ atom->next->prev = current;
+ atom->prev->next = current;
+ }
+
+ stack->current = atom;
+ atom->data.arraySize = 0;
+ atom->data.size = 0;
+ atom->undoSize = 0;
+ atom->next = atom->prev = NULL;
+
+ if (stack->root == atom) {
+ stack->root = current;
+ }
+ if (stack->last == atom) {
+ stack->last = current;
+ }
+
+ return current;
+}
+
+
+static bool
+ClearRedoStack(
+ TkTextUndoStack stack)
+{
+ MyUndoAtom *atom;
+
+ if (stack->redoDepth == 0) {
+ return false;
+ }
+
+ atom = stack->last ? stack->last->next : stack->root;
+
+ assert(atom);
+ stack->redoDepth = 0;
+ stack->redoSize = 0;
+ stack->redoItems = 0;
+ Release(stack, atom);
+
+ return true;
+}
+
+
+static void
+InsertCurrentAtom(
+ TkTextUndoStack stack)
+{
+ MyUndoAtom *atom;
+ MyUndoAtom *current = stack->current;
+
+ if (!current || current->data.arraySize == 0) {
+ assert(!stack->doingUndo && !stack->doingRedo);
+ return;
+ }
+
+ if (stack->maxSize > 0 && !stack->doingRedo) {
+ unsigned newStackSize = current->data.size;
+
+ if (stack->doingUndo) {
+ newStackSize = MAX(current->undoSize, newStackSize);
+ }
+ newStackSize += stack->undoSize + stack->redoSize;
+
+ if (newStackSize > stack->maxSize) {
+ /*
+ * We do not push this atom, because the addtional size would
+ * exceed the maximal content size.
+ *
+ * Note that we must push an undo atom while performing a redo,
+ * but this case is already catched, and the size of this atom
+ * has already been taken into account (with the check of
+ * 'current->undoSize' when inserting the reverting redo atom;
+ * we assume that the new undo atom size is the same as the
+ * undo size before the redo).
+ */
+ if (stack->doingUndo) {
+ /*
+ * We do not push this redo atom while peforming an undo, so all
+ * redoes are expired, we have to delete them.
+ */
+ ClearRedoStack(stack);
+ } else {
+ /*
+ * We do not push this undo atom, so the content becomes irreversible.
+ */
+ stack->irreversible = true;
+ }
+ FreeItems(stack, &stack->current->data);
+ ResetCurrent(stack, false);
+ return;
+ }
+ }
+
+ if (stack->doingRedo) {
+ /*
+ * We'll push an undo atom while performing a redo.
+ */
+ if (!stack->last) {
+ stack->last = stack->root;
+ }
+ atom = stack->last;
+ SwapCurrent(stack, atom);
+ stack->undoDepth += 1;
+ stack->undoSize += atom->data.size;
+ stack->undoItems += atom->data.arraySize;
+ } else if (stack->doingUndo) {
+ /*
+ * We'll push a redo atom while performing an undo.
+ */
+ assert(stack->maxRedoDepth <= 0 || stack->redoDepth < stack->maxRedoDepth);
+ atom = stack->last ? stack->last->next : stack->root;
+ SwapCurrent(stack, atom);
+ stack->redoDepth += 1;
+ stack->redoSize += atom->data.size;
+ stack->redoItems += atom->data.arraySize;
+ } else if (stack->last && stack->undoDepth == stack->maxUndoDepth) {
+ /*
+ * We've reached the maximal stack limit, so delete the oldest undo
+ * before inserting the new item. The consequence is that now the content
+ * becomes irreversible. Furthermore all redo items will expire.
+ */
+ ClearRedoStack(stack);
+ assert(stack->last);
+ atom = stack->last->next;
+ stack->root = atom->next;
+ stack->last = atom;
+ stack->undoSize -= atom->data.size;
+ stack->undoItems -= atom->data.arraySize;
+ stack->irreversible = true;
+ FreeItems(stack, &atom->data);
+ SwapCurrent(stack, atom);
+ stack->undoSize += atom->data.size;
+ stack->undoItems += atom->data.arraySize;
+ } else {
+ /*
+ * Just insert the newly undo atom. Furthermore all redo items will expire.
+ */
+ ClearRedoStack(stack);
+ if (stack->last == NULL) {
+ stack->last = stack->root;
+ }
+ atom = SwapCurrent(stack, NULL);
+ if ((atom->prev = stack->last)) {
+ atom->next = stack->last->next;
+ stack->last->next->prev = atom;
+ stack->last->next = atom;
+ } else {
+ atom->next = atom->prev = stack->root = atom;
+ }
+ stack->last = atom;
+ stack->undoDepth += 1;
+ stack->undoSize += atom->data.size;
+ stack->undoItems += atom->data.arraySize;
+ }
+
+ if (!stack->doingUndo) {
+ /*
+ * Remember the size of this undo atom, probably we need it for the
+ * decision whether to push a redo atom when performing an undo.
+ */
+ atom->undoSize = atom->data.size;
+ }
+
+ /*
+ * Reset the buffer for next action.
+ */
+ ResetCurrent(stack, false);
+}
+
+
+static int
+ResetStack(
+ TkTextUndoStack stack,
+ bool irreversible)
+{
+ bool contentChanged;
+
+ assert(stack);
+
+ if (stack->doingUndo || stack->doingRedo) {
+ return TCL_ERROR;
+ }
+
+ contentChanged = stack->undoDepth > 0 || stack->redoDepth > 0 || stack->current;
+
+ if (contentChanged) {
+ Release(stack, stack->root);
+ ResetCurrent(stack, true);
+ stack->root = NULL;
+ stack->last = NULL;
+ stack->undoDepth = 0;
+ stack->redoDepth = 0;
+ stack->undoItems = 0;
+ stack->redoItems = 0;
+ stack->undoSize = 0;
+ stack->redoSize = 0;
+ stack->irreversible = irreversible;
+ stack->pushSeparator = false;
+
+ if (stack->contentChangedProc) {
+ stack->contentChangedProc(stack);
+ }
+ }
+
+ return TCL_OK;
+}
+
+
+TkTextUndoStack
+TkTextUndoCreateStack(
+ unsigned maxUndoDepth,
+ int maxRedoDepth,
+ unsigned maxSize,
+ TkTextUndoPerformProc undoProc,
+ TkTextUndoFreeProc freeProc,
+ TkTextUndoStackContentChangedProc contentChangedProc)
+{
+ TkTextUndoStack stack;
+
+ assert(undoProc);
+
+ stack = memset(malloc(sizeof(*stack)), 0, sizeof(*stack));
+ stack->undoProc = undoProc;
+ stack->freeProc = freeProc;
+ stack->contentChangedProc = contentChangedProc;
+ stack->maxUndoDepth = maxUndoDepth;
+ stack->maxRedoDepth = MAX(maxRedoDepth, -1);
+ stack->maxSize = maxSize;
+
+ return stack;
+}
+
+
+void
+TkTextUndoDestroyStack(
+ TkTextUndoStack *stackPtr)
+{
+ if (stackPtr) {
+ TkTextUndoStack stack = *stackPtr;
+
+ if (stack) {
+ assert(stack);
+ TkTextUndoClearStack(stack);
+ if (stack->current) {
+ FreeItems(stack, &stack->current->data);
+ }
+ free(stack);
+ *stackPtr = NULL;
+ }
+ }
+}
+
+
+int
+TkTextUndoResetStack(
+ TkTextUndoStack stack)
+{
+ return stack ? ResetStack(stack, false) : TCL_ERROR;
+}
+
+
+int
+TkTextUndoClearStack(
+ TkTextUndoStack stack)
+{
+ return stack ? ResetStack(stack, stack->undoDepth > 0) : TCL_ERROR;
+}
+
+
+int
+TkTextUndoClearUndoStack(
+ TkTextUndoStack stack)
+{
+ if (!stack) {
+ return TCL_OK;
+ }
+
+ if (stack->doingUndo || stack->doingRedo) {
+ return TCL_ERROR;
+ }
+
+ if (stack->undoDepth > 0) {
+ TkTextUndoMyAtom *atom;
+
+ assert(stack->last);
+ stack->undoDepth = 0;
+ stack->undoSize = 0;
+ stack->undoItems = 0;
+ atom = stack->root;
+ stack->root = stack->last->next;
+ stack->last = NULL;
+ Release(stack, atom);
+ ResetCurrent(stack, true);
+ stack->irreversible = true;
+
+ if (stack->contentChangedProc) {
+ stack->contentChangedProc(stack);
+ }
+ }
+
+ return TCL_OK;
+}
+
+
+int
+TkTextUndoClearRedoStack(
+ TkTextUndoStack stack)
+{
+ if (!stack) {
+ return TCL_OK;
+ }
+
+ if (stack->doingUndo || stack->doingRedo) {
+ return TCL_ERROR;
+ }
+
+ if (ClearRedoStack(stack) && stack->contentChangedProc) {
+ stack->contentChangedProc(stack);
+ }
+
+ return TCL_OK;
+}
+
+
+int
+TkTextUndoSetMaxStackDepth(
+ TkTextUndoStack stack,
+ unsigned maxUndoDepth,
+ int maxRedoDepth)
+{
+ assert(stack);
+
+ if (stack->doingUndo || stack->doingRedo) {
+ return TCL_ERROR;
+ }
+
+ if (maxUndoDepth > 0 || maxRedoDepth >= 0) {
+ unsigned depth = stack->maxUndoDepth;
+
+ if (depth == 0) {
+ depth = stack->undoDepth + stack->redoDepth;
+ }
+
+ if ((0 < maxUndoDepth && maxUndoDepth < depth)
+ || (0 <= maxRedoDepth && (unsigned) maxRedoDepth < (unsigned) stack->maxRedoDepth)) {
+ unsigned deleteRedos = MIN(stack->redoDepth, depth - maxUndoDepth);
+
+ if (0 <= maxRedoDepth && maxRedoDepth < stack->maxRedoDepth) {
+ deleteRedos = MIN(stack->redoDepth,
+ MAX(deleteRedos, stack->maxRedoDepth - maxRedoDepth));
+ }
+
+ stack->redoDepth -= deleteRedos;
+ depth = maxUndoDepth - deleteRedos;
+
+ if (deleteRedos > 0) {
+ MyUndoAtom *atom = stack->root;
+
+ /*
+ * We have to reduce the stack size until the depth will not
+ * exceed the given limit anymore. Start with oldest redoes,
+ * and continue with oldest undoes if necessary.
+ */
+
+ for ( ; deleteRedos > 0; --deleteRedos) {
+ atom = atom->prev;
+ stack->redoSize -= atom->data.size;
+ stack->redoItems -= atom->data.arraySize;
+ }
+
+ Release(stack, atom);
+ }
+
+ if (maxUndoDepth > 0 && stack->undoDepth > depth) {
+ MyUndoAtom *root = stack->root;
+ MyUndoAtom *atom = stack->root;
+ unsigned deleteUndos = stack->undoDepth - depth;
+
+ stack->undoDepth -= deleteUndos;
+
+ for ( ; deleteUndos > 0; --deleteUndos) {
+ stack->undoSize -= root->data.size;
+ stack->undoItems -= root->data.arraySize;
+ root = root->next;
+ }
+
+ stack->root = root;
+
+ /* We have to delete undoes, so the content becomes irreversible. */
+ stack->irreversible = true;
+
+ Release(stack, atom);
+ }
+
+ if (stack->contentChangedProc) {
+ stack->contentChangedProc(stack);
+ }
+ }
+ }
+
+ stack->maxUndoDepth = maxUndoDepth;
+ stack->maxRedoDepth = MAX(maxRedoDepth, -1);
+ return TCL_OK;
+}
+
+
+int
+TkTextUndoSetMaxStackSize(
+ TkTextUndoStack stack,
+ unsigned maxSize,
+ bool applyImmediately)
+{
+ assert(stack);
+
+ if (stack->doingUndo || stack->doingRedo) {
+ return TCL_ERROR;
+ }
+
+ if (applyImmediately
+ && 0 < maxSize
+ && maxSize < stack->undoSize + stack->redoSize) {
+ unsigned size = stack->undoSize + stack->redoSize;
+ MyUndoAtom *atom = stack->root;
+ unsigned depth = stack->redoDepth;
+
+ /*
+ * We have to reduce the stack size until the size will not exceed
+ * the given limit anymore. Start with oldest redoes, and continue
+ * with oldest undoes if necessary.
+ */
+
+ while (depth > 0 && maxSize < size) {
+ atom = atom->prev;
+ size -= atom->data.size;
+ stack->redoSize -= atom->data.size;
+ stack->redoItems -= atom->data.arraySize;
+ depth -= 1;
+ }
+
+ while (atom->data.size == 0 && atom != stack->root) {
+ stack->redoItems += atom->data.arraySize;
+ atom = atom->next;
+ depth += 1;
+ }
+
+ if (depth < stack->redoDepth) {
+ stack->redoDepth = depth;
+ Release(stack, atom);
+ }
+
+ if (maxSize < size && stack->last) {
+ MyUndoAtom *root = stack->root;
+
+ depth = stack->undoDepth;
+
+ while (depth > 0 && maxSize < size) {
+ size -= root->data.size;
+ stack->undoSize -= root->data.size;
+ stack->undoItems -= root->data.arraySize;
+ depth -= 1;
+ root = root->next;
+ }
+
+ while (root->data.size == 0 && depth < stack->undoDepth) {
+ stack->undoItems += root->data.arraySize;
+ depth += 1;
+ root = root->prev;
+ }
+
+ if (depth < stack->undoDepth) {
+ stack->undoDepth = depth;
+ atom = stack->root;
+ stack->root = root;
+ /*
+ * We have to delete undoes, so the content becomes irreversible.
+ */
+ stack->irreversible = true;
+ Release(stack, atom);
+ }
+ }
+
+ if (stack->contentChangedProc) {
+ stack->contentChangedProc(stack);
+ }
+ }
+
+ stack->maxSize = maxSize;
+ return TCL_OK;
+}
+
+
+static void
+PushSeparator(
+ TkTextUndoStack stack,
+ bool force)
+{
+ assert(stack);
+
+ if (force || stack->pushSeparator) {
+ /*
+ * When performing an undo/redo, exact one reverting undo/redo atom has
+ * to be inserted, not more. So we do not allow the push of separators
+ * as long as an undo/redo action is in progress.
+ */
+
+ if (!stack->doingUndo && !stack->doingRedo) {
+ /*
+ * Do not trigger stack->contentChangedProc here, because this has been
+ * already done via TkTextUndoPushItem/TkTextUndoPushRedoItem.
+ */
+ InsertCurrentAtom(stack);
+ }
+ }
+
+ stack->pushSeparator = false;
+}
+
+
+void
+TkTextUndoPushSeparator(
+ TkTextUndoStack stack,
+ bool immediately)
+{
+ assert(stack);
+
+ if (immediately) {
+ PushSeparator(stack, true);
+ } else {
+ /* Postpone pushing a separator until next item will be pushed. */
+ stack->pushSeparator = true;
+ }
+}
+
+
+int
+TkTextUndoPushItem(
+ TkTextUndoStack stack,
+ TkTextUndoItem item,
+ unsigned size)
+{
+ MyUndoAtom *atom;
+ TkTextUndoSubAtom *subAtom;
+
+ assert(stack);
+ assert(item);
+
+ PushSeparator(stack, false);
+
+ if (stack->doingUndo && TkTextUndoRedoStackIsFull(stack)) {
+ if (stack->freeProc) {
+ stack->freeProc(stack, item);
+ }
+ return TCL_ERROR;
+ }
+
+ atom = stack->current;
+
+ if (!atom) {
+ ResetCurrent(stack, true);
+ atom = stack->current;
+ } else if (atom->data.arraySize == atom->capacity) {
+ atom->capacity *= 2;
+ atom = stack->current = realloc(atom, ATOM_SIZE(atom->capacity));
+ }
+
+ subAtom = ((TkTextUndoSubAtom *) atom->data.array) + atom->data.arraySize++;
+ subAtom->item = item;
+ subAtom->size = size;
+ subAtom->redo = stack->doingUndo;
+ atom->data.size += size;
+ atom->data.redo = stack->doingUndo;
+
+ if (stack->contentChangedProc && !stack->doingUndo && !stack->doingRedo) {
+ stack->contentChangedProc(stack);
+ }
+
+ return TCL_OK;
+}
+
+
+int
+TkTextUndoPushRedoItem(
+ TkTextUndoStack stack,
+ TkTextUndoItem item,
+ unsigned size)
+{
+ int rc;
+
+ assert(stack);
+ assert(!TkTextUndoIsPerformingUndoRedo(stack));
+
+ PushSeparator(stack, true);
+ stack->doingUndo = true;
+ rc = TkTextUndoPushItem(stack, item, size);
+ stack->doingUndo = false;
+
+ return rc;
+}
+
+
+TkTextUndoItem
+TkTextUndoSwapLastItem(
+ TkTextUndoStack stack,
+ TkTextUndoItem item,
+ unsigned *size)
+{
+ TkTextUndoAtom *last;
+ TkTextUndoSubAtom *subAtom;
+ TkTextUndoItem oldItem;
+ unsigned oldSize;
+
+ assert(stack);
+ assert(TkTextUndoGetLastUndoSubAtom(stack));
+
+ last = stack->current ? &stack->current->data : &stack->last->data;
+ subAtom = (TkTextUndoSubAtom *) last->array + (last->arraySize - 1);
+ oldSize = subAtom->size;
+ last->size -= oldSize;
+ last->size += *size;
+ stack->undoSize -= oldSize;
+ stack->undoSize += *size;
+ oldItem = subAtom->item;
+ subAtom->item = item;
+ subAtom->size = *size;
+ *size = oldSize;
+
+ return oldItem;
+}
+
+
+int
+TkTextUndoDoUndo(
+ TkTextUndoStack stack)
+{
+ int rc;
+
+ assert(stack);
+
+ if (stack->doingUndo || stack->doingRedo) {
+ return TCL_ERROR;
+ }
+
+ InsertCurrentAtom(stack);
+
+ if (stack->undoDepth == 0) {
+ rc = TCL_ERROR;
+ } else {
+ MyUndoAtom *atom;
+
+ assert(stack->last);
+
+ stack->actual = atom = stack->last;
+ stack->doingUndo = true;
+ stack->undoDepth -= 1;
+ stack->undoSize -= stack->actual->data.size;
+ stack->undoItems -= stack->actual->data.arraySize;
+ stack->undoProc(stack, &stack->actual->data);
+ stack->last = stack->undoDepth ? stack->last->prev : NULL;
+ stack->actual = NULL;
+
+ if (!stack->current || stack->current->data.arraySize == 0) {
+ /*
+ * We didn't receive reverting items while performing an undo.
+ * So all redo items are expired, we have to delete them.
+ */
+ stack->redoDepth = 0;
+ stack->redoSize = 0;
+ stack->redoItems = 0;
+ Release(stack, stack->last ? stack->last->next : stack->root);
+ } else {
+ FreeItems(stack, &atom->data);
+ InsertCurrentAtom(stack);
+ }
+
+ stack->doingUndo = false;
+ rc = TCL_OK;
+
+ if (stack->contentChangedProc) {
+ stack->contentChangedProc(stack);
+ }
+ }
+ return rc;
+}
+
+
+int
+TkTextUndoDoRedo(
+ TkTextUndoStack stack)
+{
+ int rc;
+
+ assert(stack);
+
+ if (stack->doingUndo || stack->doingRedo) {
+ return TCL_ERROR;
+ }
+
+ InsertCurrentAtom(stack);
+
+ if (stack->redoDepth == 0) {
+ rc = TCL_ERROR;
+ } else {
+ MyUndoAtom *atom;
+
+ stack->actual = atom = stack->last ? stack->last->next : stack->root;
+ stack->doingRedo = true;
+ stack->redoDepth -= 1;
+ stack->redoSize -= atom->data.size;
+ stack->redoItems -= atom->data.arraySize;
+ stack->undoProc(stack, &atom->data);
+ stack->last = atom;
+ stack->actual = NULL;
+
+ if (!stack->current || stack->current->data.arraySize == 0) {
+ /*
+ * Oops, we did not receive reverting items while performing a redo.
+ * So we cannot apply the preceding undoes, we have to remove them.
+ * Now the content will become irreversible.
+ */
+ if (stack->undoDepth > 0) {
+ stack->undoDepth = 0;
+ stack->undoItems = 0;
+ stack->undoSize = 0;
+ atom = stack->root;
+ stack->root = stack->last->next;
+ stack->last = NULL;
+ } else {
+ stack->root = atom->next;
+ }
+ Release(stack, atom);
+ stack->irreversible = true;
+ } else {
+ FreeItems(stack, &atom->data);
+ InsertCurrentAtom(stack);
+ }
+
+ stack->doingRedo = false;
+ rc = TCL_OK;
+
+ if (stack->contentChangedProc) {
+ stack->contentChangedProc(stack);
+ }
+ }
+
+ return rc;
+}
+
+bool
+TkTextUndoStackIsFull(
+ const TkTextUndoStack stack)
+{
+ if (!stack) {
+ return true;
+ }
+ if (stack->doingUndo) {
+ return stack->maxRedoDepth >= 0 && stack->redoDepth >= stack->maxRedoDepth;
+ }
+ return stack->maxUndoDepth > 0 && stack->undoDepth >= stack->maxUndoDepth;
+}
+
+
+const TkTextUndoAtom *
+TkTextUndoFirstUndoAtom(
+ TkTextUndoStack stack)
+{
+ assert(stack);
+
+ if (stack->current && stack->current->data.arraySize && !stack->doingUndo) {
+ return &(stack->iter = stack->current)->data;
+ }
+
+ if (stack->undoDepth > 0 && stack->last != stack->actual) {
+ return &(stack->iter = stack->last)->data;
+ }
+
+ stack->iter = NULL;
+ return NULL;
+}
+
+
+const TkTextUndoAtom *
+TkTextUndoNextUndoAtom(
+ TkTextUndoStack stack)
+{
+ assert(stack);
+
+ if (stack->iter) {
+ if (stack->iter == stack->current) {
+ if (stack->undoDepth > 0 && stack->last != stack->actual) {
+ return &(stack->iter = stack->last)->data;
+ }
+ stack->iter = NULL;
+ } else if (stack->iter != stack->root && (stack->iter = stack->iter->prev) != stack->actual) {
+ return &stack->iter->data;
+ } else {
+ stack->iter = NULL;
+ }
+ }
+
+ return NULL;
+}
+
+
+const TkTextUndoAtom *
+TkTextUndoFirstRedoAtom(
+ TkTextUndoStack stack)
+{
+ assert(stack);
+
+ if (stack->redoDepth > 0 && stack->root->prev != stack->actual) {
+ return &(stack->iter = stack->root->prev)->data;
+ }
+
+ stack->iter = NULL;
+
+ if (stack->current && stack->current->data.arraySize && stack->doingUndo) {
+ return &stack->current->data;
+ }
+
+ return NULL;
+}
+
+
+const TkTextUndoAtom *
+TkTextUndoNextRedoAtom(
+ TkTextUndoStack stack)
+{
+ assert(stack);
+
+ if (stack->iter) {
+ if (stack->iter != stack->root
+ && (stack->iter = stack->iter->prev) != stack->last
+ && stack->iter != stack->actual) {
+ return &stack->iter->data;
+ }
+
+ stack->iter = NULL;
+
+ if (stack->current && stack->current->data.arraySize && stack->doingUndo) {
+ return &stack->current->data;
+ }
+ }
+
+ return NULL;
+}
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+/* Additionally we need stand-alone object code. */
+#define inline extern
+inline void TkTextUndoSetContext(TkTextUndoStack stack, TkTextUndoContext context);
+inline TkTextUndoContext TkTextUndoGetContext(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetMaxUndoDepth(const TkTextUndoStack stack);
+inline int TkTextUndoGetMaxRedoDepth(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetMaxSize(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentDepth(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentSize(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentUndoStackDepth(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentRedoStackDepth(const TkTextUndoStack stack);
+inline unsigned TkTextUndoCountUndoItems(const TkTextUndoStack stack);
+inline unsigned TkTextUndoCountRedoItems(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentUndoSize(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentRedoSize(const TkTextUndoStack stack);
+inline unsigned TkTextUndoCountCurrentUndoItems(const TkTextUndoStack stack);
+inline unsigned TkTextUndoCountCurrentRedoItems(const TkTextUndoStack stack);
+inline bool TkTextUndoContentIsIrreversible(const TkTextUndoStack stack);
+inline bool TkTextUndoContentIsModified(const TkTextUndoStack stack);
+inline bool TkTextUndoIsPerformingUndo(const TkTextUndoStack stack);
+inline bool TkTextUndoIsPerformingRedo(const TkTextUndoStack stack);
+inline bool TkTextUndoIsPerformingUndoRedo(const TkTextUndoStack stack);
+inline const TkTextUndoAtom *TkTextUndoCurrentUndoAtom(const TkTextUndoStack stack);
+inline const TkTextUndoAtom *TkTextUndoCurrentRedoAtom(const TkTextUndoStack stack);
+inline const TkTextUndoSubAtom *TkTextUndoGetLastUndoSubAtom(const TkTextUndoStack stack);
+inline bool TkTextUndoUndoStackIsFull(const TkTextUndoStack stack);
+inline bool TkTextUndoRedoStackIsFull(const TkTextUndoStack stack);
+#endif /* __STDC_VERSION__ >= 199901L */
+
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkTextUndo.h b/generic/tkTextUndo.h
new file mode 100644
index 0000000..7c48e54
--- /dev/null
+++ b/generic/tkTextUndo.h
@@ -0,0 +1,260 @@
+/*
+ * tkTextUndo.h --
+ *
+ * The implementation of an undo/redo stack. The design and implementation
+ * of tkUndo is not useful for our purposes:
+ *
+ * 1. We are not pushing an undo/redo pair on the stack. Our stack is only
+ * pushing the undo item, and applying this undo item will replace this
+ * item by a redo item (and vice versa when performing the redo; in fact
+ * there is no difference between an undo and redo item - the undo of
+ * insert is delete, the undo of delete is insert, and the same applies
+ * to redo). The advantage is that our undo (or redo) item for insert
+ * contains exact zero characters, contrary to the undo/redo pairs in
+ * tkUndo, one of the undo/redo items in this pair always contains a
+ * copy of the text content (a waste of memory).
+ *
+ * 2. tkUndo expects a script, our stack expects memory addresses (it's also
+ * an general implementation which can be shared).
+ *
+ * 3. Our stack allows to control the undo and redo stacks separately.
+ *
+ * 4. Moreover our stack supports to limit the size, not only the depth.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer.
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKTEXTUNDO
+#define _TKTEXTUNDO
+
+#include "tkBool.h"
+#include <stdint.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+/*
+ * Our (private) stack type.
+ */
+
+struct TkTextUndoStack;
+typedef struct TkTextUndoStack * TkTextUndoStack;
+
+/*
+ * The generic context type.
+ */
+
+typedef void * TkTextUndoContext;
+
+/*
+ * Basic type of an undo/redo item, the user has to know the real type.
+ */
+
+typedef void * TkTextUndoItem;
+
+/*
+ * Struct defining a single action, one or more of which may be defined (and
+ * stored in a linked list) separately for each undo and redo action of an
+ * undo atom.
+ */
+
+typedef struct TkTextUndoSubAtom {
+ TkTextUndoItem item; /* The data of this undo/redo item. */
+ uint32_t size:31; /* Size info for this item. */
+ uint32_t redo:1; /* Is redo item? */
+} TkTextUndoSubAtom;
+
+/*
+ * Struct representing a single undo+redo atom to be placed in the stack.
+ */
+
+typedef struct TkTextUndoAtom {
+ uint32_t arraySize; /* Number of elements in this array. */
+ uint32_t size:31; /* Total size of all items. */
+ uint32_t redo:1; /* Is redo atom? */
+ const TkTextUndoSubAtom array[1];
+ /* Array of undo/redo actions. */
+} TkTextUndoAtom;
+
+/*
+ * Callback to carry out undo or redo actions. This function may
+ * push redo (undo) items for this undo (redo) onto the stack.
+ * The user should push the reverting items while this action will
+ * be performed.
+ *
+ * Note that the atom is given as a const value, but it's allowed
+ * to change/reset the items of the sub-atoms.
+ */
+
+typedef void (TkTextUndoPerformProc)(TkTextUndoStack stack, const TkTextUndoAtom *atom);
+
+/*
+ * Callback proc type to free undo/redo items. This function will be
+ * called when the user is clearing the stack (destroying the stack
+ * is implicitly clearing the stack), or when the push operation
+ * is deleting the oldest undo atom (for keeping the max. depth and
+ * max. size).
+ */
+
+typedef void (TkTextUndoFreeProc)(const TkTextUndoStack stack, const TkTextUndoSubAtom *atom);
+
+/*
+ * Callback proc type for stack changes. Every time when the stack is
+ * changing this callback function will be triggered.
+ */
+
+typedef void (TkTextUndoStackContentChangedProc)(const TkTextUndoStack stack);
+
+/*
+ * Functions for constructing/destructing the stack. Use zero for unlimited
+ * stack depth, also use zero for unlimited size. 'freeProc' can be NULL,
+ * but normally this function is required. It's clear that 'undoProc' is
+ * mandatory. 'informStackChangeProc' and pushSeparatorProc can also be NULL.
+ */
+
+TkTextUndoStack TkTextUndoCreateStack(
+ unsigned maxUndoDepth, int maxRedoDepth, unsigned maxSize,
+ TkTextUndoPerformProc undoProc, TkTextUndoFreeProc freeProc,
+ TkTextUndoStackContentChangedProc contentChangedProc);
+void TkTextUndoDestroyStack(TkTextUndoStack *stackPtr);
+
+/*
+ * Managing the stack. Use zero for unlimited stack depth, also use zero
+ * for unlimited size. Setting a lower limit than the current depth is
+ * reducing the stack immediately. Setting a lower limit than the current
+ * size is also reducing the stack immediately iff 'applyImmediately' is
+ * 'true', otherwise the size will shrink when performing undo actions.
+ * It is not allowed to use these functions while an undo/redo action is
+ * performed, TCL_ERROR will be returned in this case.
+ *
+ * For convenience the functions TkTextUndoResetStack, TkTextUndoClearStack,
+ * TkTextUndoClearUndoStack, and TkTextUndoClearRedoStack, are allowing
+ * NULL arguments.
+ */
+
+int TkTextUndoSetMaxStackDepth(TkTextUndoStack stack,
+ unsigned maxUndoDepth, int maxRedoDepth);
+int TkTextUndoSetMaxStackSize(TkTextUndoStack stack,
+ unsigned maxSize, bool applyImmediately);
+int TkTextUndoResetStack(TkTextUndoStack stack);
+int TkTextUndoClearStack(TkTextUndoStack stack);
+int TkTextUndoClearUndoStack(TkTextUndoStack stack);
+int TkTextUndoClearRedoStack(TkTextUndoStack stack);
+
+/*
+ * Functions to set/get the context. This is an additional information
+ * for the user.
+ */
+
+inline void TkTextUndoSetContext(TkTextUndoStack stack, TkTextUndoContext context);
+inline TkTextUndoContext TkTextUndoGetContext(const TkTextUndoStack stack);
+
+/*
+ * Accessing attributes.
+ *
+ * The content is irreversible if:
+ * 1. The stack has exceeded the depth/size limit when adding undo atoms.
+ * 2. Setting a lower limit has caused the deletion of undo atoms.
+ * 3. Performing an redo did not push undo items.
+ * Clearing the stack is resetting this state to false.
+ *
+ * The content is modified if undo stack is not empty, or the content
+ * is irreversible.
+ */
+
+inline unsigned TkTextUndoGetMaxUndoDepth(const TkTextUndoStack stack);
+inline int TkTextUndoGetMaxRedoDepth(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetMaxSize(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentDepth(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentSize(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentUndoStackDepth(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentRedoStackDepth(const TkTextUndoStack stack);
+inline unsigned TkTextUndoCountUndoItems(const TkTextUndoStack stack);
+inline unsigned TkTextUndoCountRedoItems(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentUndoSize(const TkTextUndoStack stack);
+inline unsigned TkTextUndoGetCurrentRedoSize(const TkTextUndoStack stack);
+inline unsigned TkTextUndoCountCurrentUndoItems(const TkTextUndoStack stack);
+inline unsigned TkTextUndoCountCurrentRedoItems(const TkTextUndoStack stack);
+inline bool TkTextUndoContentIsIrreversible(const TkTextUndoStack stack);
+inline bool TkTextUndoContentIsModified(const TkTextUndoStack stack);
+inline bool TkTextUndoIsPerformingUndo(const TkTextUndoStack stack);
+inline bool TkTextUndoIsPerformingRedo(const TkTextUndoStack stack);
+inline bool TkTextUndoIsPerformingUndoRedo(const TkTextUndoStack stack);
+inline const TkTextUndoAtom *TkTextUndoCurrentUndoAtom(const TkTextUndoStack stack);
+inline const TkTextUndoAtom *TkTextUndoCurrentRedoAtom(const TkTextUndoStack stack);
+inline const TkTextUndoSubAtom *TkTextUndoGetLastUndoSubAtom(const TkTextUndoStack stack);
+
+/*
+ * Stack iterator functions.
+ */
+
+const TkTextUndoAtom *TkTextUndoFirstUndoAtom(TkTextUndoStack stack);
+const TkTextUndoAtom *TkTextUndoNextUndoAtom(TkTextUndoStack stack);
+const TkTextUndoAtom *TkTextUndoFirstRedoAtom(TkTextUndoStack stack);
+const TkTextUndoAtom *TkTextUndoNextRedoAtom(TkTextUndoStack stack);
+
+/* For convenience these functions are allowing NULL for the stack argument. */
+inline bool TkTextUndoUndoStackIsFull(const TkTextUndoStack stack);
+inline bool TkTextUndoRedoStackIsFull(const TkTextUndoStack stack);
+bool TkTextUndoStackIsFull(const TkTextUndoStack stack);
+
+/*
+ * Push the items. Pushing a separator will group items into compound edit
+ * actions. Pushing a separator without existing items will be ignored.
+ *
+ * While an undo/redo action is still in progress pushing separators will be
+ * ignored, in this case the undo/action will push automatically a single
+ * separator after the action is completed.
+ */
+
+int TkTextUndoPushItem(TkTextUndoStack stack, TkTextUndoItem item, unsigned size);
+void TkTextUndoPushSeparator(TkTextUndoStack stack, bool immediately);
+
+/*
+ * Normally redo items will be pushed while undo will be performed. The next function
+ * is only useful for the reconstruction of the stack.
+ */
+
+int TkTextUndoPushRedoItem(TkTextUndoStack stack, TkTextUndoItem item, unsigned size);
+
+/*
+ * Swap newest undo item with given item. Returns the old item. Note that this
+ * function does not check whether the maximal undo byte size will be exceeded.
+ *
+ * Parameter 'size':
+ * In: size of given item.
+ * Out: size of returned item.
+ */
+
+TkTextUndoItem TkTextUndoSwapLastItem(TkTextUndoStack stack, TkTextUndoItem item, unsigned *size);
+
+/*
+ * Perform undo/redo operations. Before the action starts a separator will be
+ * pushed. Returns an error (TCL_ERROR) if no undo (redo) action is possible.
+ */
+
+int TkTextUndoDoUndo(TkTextUndoStack stack);
+int TkTextUndoDoRedo(TkTextUndoStack stack);
+
+
+#if __STDC_VERSION__ >= 199901L || (defined(_MSC_VER) && _MSC_VER >= 1900)
+# define _TK_NEED_IMPLEMENTATION
+# include "tkTextUndoPriv.h"
+# undef _TK_NEED_IMPLEMENTATION
+#else
+# undef inline
+#endif
+
+#endif /* _TKTEXTUNDO */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkTextUndoPriv.h b/generic/tkTextUndoPriv.h
new file mode 100644
index 0000000..b531771
--- /dev/null
+++ b/generic/tkTextUndoPriv.h
@@ -0,0 +1,223 @@
+/*
+ * tkTextUndoPriv.h --
+ *
+ * Private implementation for undo stack.
+ *
+ * Copyright (c) 2015-2017 Gregor Cramer
+ *
+ * See the file "license.terms" for information on usage and redistribution of
+ * this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ */
+
+#ifndef _TKTEXTUNDO
+# error "do not include this private header file"
+#endif
+
+#ifndef _TKTEXTUNDOPRIV
+#define _TKTEXTUNDOPRIV
+
+#include <stddef.h>
+
+typedef struct TkTextUndoMyAtom {
+ unsigned capacity;
+ unsigned undoSize;
+ struct TkTextUndoMyAtom* next;
+ struct TkTextUndoMyAtom* prev;
+ TkTextUndoAtom data;
+} TkTextUndoMyAtom;
+
+struct TkTextUndoStack {
+ TkTextUndoPerformProc *undoProc;
+ /* Function for callback to perform undo/redo actions. */
+ TkTextUndoFreeProc *freeProc;
+ /* Function which frees stack items, can be NULL. */
+ TkTextUndoStackContentChangedProc *contentChangedProc;
+ /* Function which informs about stack changes. */
+ TkTextUndoContext context; /* User data. */
+ TkTextUndoMyAtom *current; /* Current undo atom (not yet pushed). */
+ TkTextUndoMyAtom *root; /* The root of the undo/redo stack. */
+ TkTextUndoMyAtom *last; /* Last added undo atom. */
+ TkTextUndoMyAtom *iter; /* Current atom in iteration loop. */
+ TkTextUndoMyAtom *actual; /* Current undo/redo atom in processing. */
+ bool irreversible; /* Whether undo actions has been released due to limited depth/size. */
+ unsigned maxUndoDepth; /* Maximal depth of the undo stack. */
+ int maxRedoDepth; /* Maximal depth of the redo stack. */
+ unsigned maxSize; /* Maximal size of the stack. */
+ unsigned undoDepth; /* Current depth of undo stack. */
+ unsigned redoDepth; /* Current depth of redo stack. */
+ unsigned undoItems; /* Current number of items on undo stack. */
+ unsigned redoItems; /* Current number of items on redo stack. */
+ unsigned undoSize; /* Total size of undo items. */
+ unsigned redoSize; /* Total size of redo items. */
+ bool doingUndo; /* Currently an undo action is performed? */
+ bool doingRedo; /* Currently a redo action is performed? */
+ bool pushSeparator; /* Push a separator before pushing a new item (iff true). */
+};
+
+#endif /* _TKTEXTUNDOPRIV */
+
+#ifdef _TK_NEED_IMPLEMENTATION
+
+#include <assert.h>
+
+#ifdef _MSC_VER
+# if _MSC_VER >= 1900
+# define inline __inline
+# else
+# define inline
+# endif
+#elif __STDC_VERSION__ < 199901L
+# define inline /* we are not C99 conform */
+#endif
+
+
+inline unsigned
+TkTextUndoGetMaxUndoDepth(const TkTextUndoStack stack)
+{ assert(stack); return stack->maxUndoDepth; }
+
+inline int
+TkTextUndoGetMaxRedoDepth(const TkTextUndoStack stack)
+{ assert(stack); return stack->maxRedoDepth; }
+
+inline unsigned
+TkTextUndoGetMaxSize(const TkTextUndoStack stack)
+{ assert(stack); return stack->maxSize; }
+
+inline bool
+TkTextUndoContentIsModified(const TkTextUndoStack stack)
+{ assert(stack); return stack->undoDepth > 0 || stack->irreversible; }
+
+inline bool
+TkTextUndoContentIsIrreversible(const TkTextUndoStack stack)
+{ assert(stack); return stack->irreversible; }
+
+inline bool
+TkTextUndoIsPerformingUndo(const TkTextUndoStack stack)
+{ assert(stack); return stack->doingUndo; }
+
+inline bool
+TkTextUndoIsPerformingRedo(const TkTextUndoStack stack)
+{ assert(stack); return stack->doingRedo; }
+
+inline bool
+TkTextUndoIsPerformingUndoRedo(const TkTextUndoStack stack)
+{ assert(stack); return stack->doingUndo || stack->doingRedo; }
+
+inline bool
+TkTextUndoUndoStackIsFull(const TkTextUndoStack stack)
+{ return !stack || (stack->maxUndoDepth > 0 && stack->undoDepth >= stack->maxUndoDepth); }
+
+inline bool
+TkTextUndoRedoStackIsFull(const TkTextUndoStack stack)
+{ return !stack || (stack->maxRedoDepth >= 0 && stack->redoDepth >= stack->maxRedoDepth); }
+
+inline unsigned
+TkTextUndoCountCurrentUndoItems(const TkTextUndoStack stack)
+{ assert(stack); return stack->current && !stack->doingUndo ? stack->current->data.arraySize : 0; }
+
+inline unsigned
+TkTextUndoCountCurrentRedoItems(const TkTextUndoStack stack)
+{ assert(stack); return stack->current && stack->doingUndo ? stack->current->data.arraySize : 0; }
+
+inline unsigned
+TkTextUndoGetCurrentUndoStackDepth(const TkTextUndoStack stack)
+{ assert(stack); return stack->undoDepth + (TkTextUndoCountCurrentUndoItems(stack) ? 1 : 0); }
+
+inline unsigned
+TkTextUndoGetCurrentRedoStackDepth(const TkTextUndoStack stack)
+{ assert(stack); return stack->redoDepth + (TkTextUndoCountCurrentRedoItems(stack) ? 1 : 0); }
+
+inline unsigned
+TkTextUndoCountUndoItems(const TkTextUndoStack stack)
+{ assert(stack); return stack->undoItems + TkTextUndoCountCurrentUndoItems(stack); }
+
+inline unsigned
+TkTextUndoCountRedoItems(const TkTextUndoStack stack)
+{ assert(stack); return stack->redoItems + TkTextUndoCountCurrentRedoItems(stack); }
+
+inline void
+TkTextUndoSetContext(TkTextUndoStack stack, TkTextUndoContext context)
+{ assert(stack); stack->context = context; }
+
+inline TkTextUndoContext
+TkTextUndoGetContext(const TkTextUndoStack stack)
+{ assert(stack); return stack->context; }
+
+inline unsigned
+TkTextUndoGetCurrentDepth(
+ const TkTextUndoStack stack)
+{
+ assert(stack);
+ return stack->undoDepth + stack->redoDepth +
+ (stack->current && stack->current->data.arraySize > 0 ? 1 : 0);
+}
+
+inline unsigned
+TkTextUndoGetCurrentUndoSize(
+ const TkTextUndoStack stack)
+{
+ assert(stack);
+ return stack->undoSize + (!stack->doingUndo && stack->current ? stack->current->undoSize : 0);
+}
+
+inline unsigned
+TkTextUndoGetCurrentRedoSize(
+ const TkTextUndoStack stack)
+{
+ assert(stack);
+ return stack->redoSize + (!stack->doingRedo && stack->current ? stack->current->undoSize : 0);
+}
+
+inline unsigned
+TkTextUndoGetCurrentSize(
+ const TkTextUndoStack stack)
+{
+ assert(stack);
+ return stack->undoSize + stack->redoSize + (stack->current ? stack->current->undoSize : 0);
+}
+
+inline const TkTextUndoAtom *
+TkTextUndoCurrentUndoAtom(
+ const TkTextUndoStack stack)
+{
+ assert(stack);
+
+ if (stack->doingUndo) {
+ return NULL;
+ }
+ return stack->current && stack->current->data.arraySize > 0 ? &stack->current->data : NULL;
+}
+
+inline const TkTextUndoAtom *
+TkTextUndoCurrentRedoAtom(
+ const TkTextUndoStack stack)
+{
+ assert(stack);
+
+ if (stack->doingRedo) {
+ return NULL;
+ }
+ return stack->current && stack->current->data.arraySize > 0 ? &stack->current->data : NULL;
+}
+
+inline const TkTextUndoSubAtom *
+TkTextUndoGetLastUndoSubAtom(
+ const TkTextUndoStack stack)
+{
+ TkTextUndoAtom *last;
+
+ assert(stack);
+
+ if (stack->current) {
+ last = &stack->current->data;
+ } else if (stack->last) {
+ last = &stack->last->data;
+ } else {
+ return NULL;
+ }
+ return last->arraySize > 0 ? last->array + (last->arraySize - 1) : NULL;
+}
+
+#undef _TK_NEED_IMPLEMENTATION
+#endif /* _TK_NEED_IMPLEMENTATION */
+/* vi:set ts=8 sw=4: */
diff --git a/generic/tkTextWind.c b/generic/tkTextWind.c
index c9fc20f..a006f0d 100644
--- a/generic/tkTextWind.c
+++ b/generic/tkTextWind.c
@@ -7,6 +7,7 @@
*
* Copyright (c) 1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -14,16 +15,35 @@
#include "tkPort.h"
#include "tkText.h"
+#include "tkTextTagSet.h"
+#include "tkTextUndo.h"
+#include <assert.h>
+
+#if NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
+
+/*
+ * Support of tk8.5.
+ */
+#ifdef CONST
+# undef CONST
+#endif
+#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5
+# define CONST
+#else
+# define CONST const
+#endif
/*
* The following structure is the official type record for the embedded window
* geometry manager:
*/
-static void EmbWinRequestProc(ClientData clientData,
- Tk_Window tkwin);
-static void EmbWinLostSlaveProc(ClientData clientData,
- Tk_Window tkwin);
+static void EmbWinRequestProc(ClientData clientData, Tk_Window tkwin);
+static void EmbWinLostSlaveProc(ClientData clientData, Tk_Window tkwin);
static const Tk_GeomMgr textGeomType = {
"text", /* name */
@@ -32,20 +52,12 @@ static const Tk_GeomMgr textGeomType = {
};
/*
- * Macro that determines the size of an embedded window segment:
- */
-
-#define EW_SEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \
- + sizeof(TkTextEmbWindow)))
-
-/*
* Prototypes for functions defined in this file:
*/
-static TkTextSegment * EmbWinCleanupProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
-static void EmbWinCheckProc(TkTextSegment *segPtr,
- TkTextLine *linePtr);
+static void EmbWinCheckProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr);
+static Tcl_Obj * EmbWinInspectProc(const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr);
static void EmbWinBboxProc(TkText *textPtr,
TkTextDispChunk *chunkPtr, int index, int y,
int lineHeight, int baseline, int *xPtr,int *yPtr,
@@ -53,18 +65,72 @@ static void EmbWinBboxProc(TkText *textPtr,
static int EmbWinConfigure(TkText *textPtr, TkTextSegment *ewPtr,
int objc, Tcl_Obj *const objv[]);
static void EmbWinDelayedUnmap(ClientData clientData);
-static int EmbWinDeleteProc(TkTextSegment *segPtr,
- TkTextLine *linePtr, int treeGone);
-static int EmbWinLayoutProc(TkText *textPtr,
- TkTextIndex *indexPtr, TkTextSegment *segPtr,
- int offset, int maxX, int maxChars,int noCharsYet,
- TkWrapMode wrapMode, TkTextDispChunk *chunkPtr);
-static void EmbWinStructureProc(ClientData clientData,
- XEvent *eventPtr);
-static void EmbWinUndisplayProc(TkText *textPtr,
- TkTextDispChunk *chunkPtr);
-static TkTextEmbWindowClient *EmbWinGetClient(const TkText *textPtr,
- TkTextSegment *ewPtr);
+static bool EmbWinDeleteProc(TkTextBTree tree, TkTextSegment *segPtr, int treeGone);
+static void EmbWinRestoreProc(TkTextSegment *segPtr);
+static int EmbWinLayoutProc(const TkTextIndex *indexPtr, TkTextSegment *segPtr,
+ int offset, int maxX, int maxChars, bool noCharsYet,
+ TkWrapMode wrapMode, TkTextSpaceMode spaceMode, TkTextDispChunk *chunkPtr);
+static void EmbWinStructureProc(ClientData clientData, XEvent *eventPtr);
+static void EmbWinDisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr,
+ int x, int y, int lineHeight, int baseline, Display *display,
+ Drawable dst, int screenY);
+static void EmbWinUndisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr);
+static TkTextEmbWindowClient *EmbWinGetClient(const TkText *textPtr, TkTextSegment *ewPtr);
+static TkTextSegment * MakeWindow(TkText *textPtr);
+static void ReleaseWindow(TkTextSegment *ewPtr);
+static void DestroyOrUnmapWindow(TkTextSegment *ewPtr);
+
+static const TkTextDispChunkProcs layoutWindowProcs = {
+ TEXT_DISP_WINDOW, /* type */
+ EmbWinDisplayProc, /* displayProc */
+ EmbWinUndisplayProc, /* undisplayProc */
+ NULL, /* measureProc */
+ EmbWinBboxProc, /* bboxProc */
+};
+
+/*
+ * We need some private undo/redo stuff.
+ */
+
+static void UndoLinkSegmentPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void RedoLinkSegmentPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool);
+static void UndoLinkSegmentDestroy(TkSharedText *, TkTextUndoToken *, bool);
+static void UndoLinkSegmentGetRange(const TkSharedText *, const TkTextUndoToken *,
+ TkTextIndex *, TkTextIndex *);
+static void RedoLinkSegmentGetRange(const TkSharedText *, const TkTextUndoToken *,
+ TkTextIndex *, TkTextIndex *);
+static Tcl_Obj *UndoLinkSegmentGetCommand(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *UndoLinkSegmentInspect(const TkSharedText *, const TkTextUndoToken *);
+static Tcl_Obj *RedoLinkSegmentInspect(const TkSharedText *, const TkTextUndoToken *);
+
+static const Tk_UndoType undoTokenLinkSegmentType = {
+ TK_TEXT_UNDO_WINDOW, /* action */
+ UndoLinkSegmentGetCommand, /* commandProc */
+ UndoLinkSegmentPerform, /* undoProc */
+ UndoLinkSegmentDestroy, /* destroyProc */
+ UndoLinkSegmentGetRange, /* rangeProc */
+ UndoLinkSegmentInspect /* inspectProc */
+};
+
+static const Tk_UndoType redoTokenLinkSegmentType = {
+ TK_TEXT_REDO_WINDOW, /* action */
+ UndoLinkSegmentGetCommand, /* commandProc */
+ RedoLinkSegmentPerform, /* undoProc */
+ UndoLinkSegmentDestroy, /* destroyProc */
+ RedoLinkSegmentGetRange, /* rangeProc */
+ RedoLinkSegmentInspect /* inspectProc */
+};
+
+typedef struct UndoTokenLinkSegment {
+ const Tk_UndoType *undoType;
+ TkTextSegment *segPtr;
+} UndoTokenLinkSegment;
+
+typedef struct RedoTokenLinkSegment {
+ const Tk_UndoType *undoType;
+ TkTextSegment *segPtr;
+ TkTextUndoIndex index;
+} RedoTokenLinkSegment;
/*
* The following structure declares the "embedded window" segment type.
@@ -72,20 +138,20 @@ static TkTextEmbWindowClient *EmbWinGetClient(const TkText *textPtr,
const Tk_SegType tkTextEmbWindowType = {
"window", /* name */
- 0, /* leftGravity */
- NULL, /* splitProc */
+ SEG_GROUP_WINDOW, /* group */
+ GRAVITY_NEUTRAL, /* leftGravity */
EmbWinDeleteProc, /* deleteProc */
- EmbWinCleanupProc, /* cleanupProc */
- NULL, /* lineChangeProc */
+ EmbWinRestoreProc, /* restoreProc */
EmbWinLayoutProc, /* layoutProc */
- EmbWinCheckProc /* checkProc */
+ EmbWinCheckProc, /* checkProc */
+ EmbWinInspectProc /* inspectProc */
};
/*
* Definitions for alignment values:
*/
-static const char *const alignStrings[] = {
+static const char *CONST alignStrings[] = {
"baseline", "bottom", "center", "top", NULL
};
@@ -99,8 +165,7 @@ typedef enum {
static const Tk_OptionSpec optionSpecs[] = {
{TK_OPTION_STRING_TABLE, "-align", NULL, NULL,
- "center", -1, Tk_Offset(TkTextEmbWindow, align),
- 0, alignStrings, 0},
+ "center", -1, Tk_Offset(TkTextEmbWindow, align), 0, alignStrings, 0},
{TK_OPTION_STRING, "-create", NULL, NULL,
NULL, -1, Tk_Offset(TkTextEmbWindow, create), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_PIXELS, "-padx", NULL, NULL,
@@ -113,6 +178,180 @@ static const Tk_OptionSpec optionSpecs[] = {
NULL, -1, Tk_Offset(TkTextEmbWindow, tkwin), TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_END, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0}
};
+
+DEBUG_ALLOC(extern unsigned tkTextCountDestroySegment);
+DEBUG_ALLOC(extern unsigned tkTextCountNewSegment);
+DEBUG_ALLOC(extern unsigned tkTextCountNewUndoToken);
+
+/*
+ * Some useful helpers.
+ */
+
+static void
+TextChanged(
+ TkSharedText *sharedTextPtr,
+ TkTextIndex *indexPtr)
+{
+ TkTextChanged(sharedTextPtr, NULL, indexPtr, indexPtr);
+
+ /*
+ * TODO: It's probably not true that all window configuration can change
+ * the line height, so we could be more efficient here and only call this
+ * when necessary.
+ */
+
+ TkTextInvalidateLineMetrics(sharedTextPtr, NULL,
+ TkTextIndexGetLine(indexPtr), 0, TK_TEXT_INVALIDATE_ONLY);
+}
+
+/*
+ * Some functions for the undo/redo mechanism.
+ */
+
+static void
+GetIndex(
+ const TkSharedText *sharedTextPtr,
+ TkTextSegment *segPtr,
+ TkTextIndex *indexPtr)
+{
+ TkTextIndexClear2(indexPtr, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(indexPtr, segPtr);
+}
+
+static Tcl_Obj *
+UndoLinkSegmentGetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("window", -1));
+ return objPtr;
+}
+
+static Tcl_Obj *
+UndoLinkSegmentInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const UndoTokenLinkSegment *token = (const UndoTokenLinkSegment *) item;
+ Tcl_Obj *objPtr = UndoLinkSegmentGetCommand(sharedTextPtr, item);
+ char buf[TK_POS_CHARS];
+ TkTextIndex index;
+
+ GetIndex(sharedTextPtr, token->segPtr, &index);
+ TkTextIndexPrint(sharedTextPtr, NULL, &index, buf);
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(buf, -1));
+ return objPtr;
+}
+
+static void
+UndoLinkSegmentPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ const UndoTokenLinkSegment *token = (const UndoTokenLinkSegment *) undoInfo->token;
+ TkTextSegment *segPtr = token->segPtr;
+ TkTextIndex index;
+
+ if (redoInfo) {
+ RedoTokenLinkSegment *redoToken;
+ redoToken = malloc(sizeof(RedoTokenLinkSegment));
+ redoToken->undoType = &redoTokenLinkSegmentType;
+ TkBTreeMakeUndoIndex(sharedTextPtr, segPtr, &redoToken->index);
+ redoInfo->token = (TkTextUndoToken *) redoToken;
+ (redoToken->segPtr = segPtr)->refCount += 1;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+ }
+
+ GetIndex(sharedTextPtr, segPtr, &index);
+ TextChanged(sharedTextPtr, &index);
+ TkBTreeUnlinkSegment(sharedTextPtr, segPtr);
+ EmbWinDeleteProc(sharedTextPtr->tree, segPtr, 0);
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+}
+
+static void
+UndoLinkSegmentDestroy(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *item,
+ bool reused)
+{
+ UndoTokenLinkSegment *token = (UndoTokenLinkSegment *) item;
+
+ assert(!reused);
+
+ if (--token->segPtr->refCount == 0) {
+ ReleaseWindow(token->segPtr);
+ }
+}
+
+static void
+UndoLinkSegmentGetRange(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item,
+ TkTextIndex *startIndex,
+ TkTextIndex *endIndex)
+{
+ const UndoTokenLinkSegment *token = (const UndoTokenLinkSegment *) item;
+
+ GetIndex(sharedTextPtr, token->segPtr, startIndex);
+ *endIndex = *startIndex;
+}
+
+static Tcl_Obj *
+RedoLinkSegmentInspect(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item)
+{
+ const RedoTokenLinkSegment *token = (const RedoTokenLinkSegment *) item;
+ Tcl_Obj *objPtr = EmbWinInspectProc(sharedTextPtr, token->segPtr);
+ char buf[TK_POS_CHARS];
+ TkTextIndex index;
+ Tcl_Obj *idxPtr;
+
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->index, &index);
+ TkTextIndexPrint(sharedTextPtr, NULL, &index, buf);
+ idxPtr = Tcl_NewStringObj(buf, -1);
+ Tcl_ListObjReplace(NULL, objPtr, 1, 0, 1, &idxPtr);
+ return objPtr;
+}
+
+static void
+RedoLinkSegmentPerform(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoInfo *undoInfo,
+ TkTextUndoInfo *redoInfo,
+ bool isRedo)
+{
+ RedoTokenLinkSegment *token = (RedoTokenLinkSegment *) undoInfo->token;
+ TkTextIndex index;
+
+ TkBTreeReInsertSegment(sharedTextPtr, &token->index, token->segPtr);
+
+ if (redoInfo) {
+ redoInfo->token = undoInfo->token;
+ token->undoType = &undoTokenLinkSegmentType;
+ }
+
+ GetIndex(sharedTextPtr, token->segPtr, &index);
+ TextChanged(sharedTextPtr, &index);
+ token->segPtr->refCount += 1;
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+}
+
+static void
+RedoLinkSegmentGetRange(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *item,
+ TkTextIndex *startIndex,
+ TkTextIndex *endIndex)
+{
+ const RedoTokenLinkSegment *token = (const RedoTokenLinkSegment *) item;
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &token->index, startIndex);
+ *endIndex = *startIndex;
+}
/*
*--------------------------------------------------------------
@@ -133,7 +372,7 @@ static const Tk_OptionSpec optionSpecs[] = {
int
TkTextWindowCmd(
- register TkText *textPtr, /* Information about text widget. */
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
@@ -147,16 +386,19 @@ TkTextWindowCmd(
enum windOptions {
WIND_CGET, WIND_CONFIGURE, WIND_CREATE, WIND_NAMES
};
- register TkTextSegment *ewPtr;
+ TkTextSegment *ewPtr;
+
+ assert(textPtr);
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
+ Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?");
return TCL_ERROR;
}
if (Tcl_GetIndexFromObjStruct(interp, objv[2], windOptionStrings,
sizeof(char *), "window option", 0, &optionIndex) != TCL_OK) {
return TCL_ERROR;
}
+
switch ((enum windOptions) optionIndex) {
case WIND_CGET: {
TkTextIndex index;
@@ -168,14 +410,13 @@ TkTextWindowCmd(
Tcl_WrongNumArgs(interp, 3, objv, "index option");
return TCL_ERROR;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
return TCL_ERROR;
}
- ewPtr = TkTextIndexToSeg(&index, NULL);
+ ewPtr = TkTextIndexGetContentSegment(&index, NULL);
if (ewPtr->typePtr != &tkTextEmbWindowType) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "no embedded window at index \"%s\"",
- Tcl_GetString(objv[3])));
+ "no embedded window at index \"%s\"", Tcl_GetString(objv[3])));
Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_WINDOW", NULL);
return TCL_ERROR;
}
@@ -184,8 +425,7 @@ TkTextWindowCmd(
* Copy over client specific value before querying.
*/
- client = EmbWinGetClient(textPtr, ewPtr);
- if (client != NULL) {
+ if ((client = EmbWinGetClient(textPtr, ewPtr))) {
ewPtr->body.ew.tkwin = client->tkwin;
} else {
ewPtr->body.ew.tkwin = NULL;
@@ -193,7 +433,7 @@ TkTextWindowCmd(
objPtr = Tk_GetOptionValue(interp, (char *) &ewPtr->body.ew,
ewPtr->body.ew.optionTable, objv[4], textPtr->tkwin);
- if (objPtr == NULL) {
+ if (!objPtr) {
return TCL_ERROR;
}
Tcl_SetObjResult(interp, objPtr);
@@ -204,17 +444,16 @@ TkTextWindowCmd(
TkTextSegment *ewPtr;
if (objc < 4) {
- Tcl_WrongNumArgs(interp, 3, objv, "index ?-option value ...?");
+ Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?");
return TCL_ERROR;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
return TCL_ERROR;
}
- ewPtr = TkTextIndexToSeg(&index, NULL);
+ ewPtr = TkTextIndexGetContentSegment(&index, NULL);
if (ewPtr->typePtr != &tkTextEmbWindowType) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "no embedded window at index \"%s\"",
- Tcl_GetString(objv[3])));
+ "no embedded window at index \"%s\"", Tcl_GetString(objv[3])));
Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_WINDOW", NULL);
return TCL_ERROR;
}
@@ -226,38 +465,31 @@ TkTextWindowCmd(
* Copy over client specific value before querying.
*/
- client = EmbWinGetClient(textPtr, ewPtr);
- if (client != NULL) {
+ if ((client = EmbWinGetClient(textPtr, ewPtr))) {
ewPtr->body.ew.tkwin = client->tkwin;
} else {
ewPtr->body.ew.tkwin = NULL;
}
- objPtr = Tk_GetOptionInfo(interp, (char *) &ewPtr->body.ew,
- ewPtr->body.ew.optionTable, (objc == 5) ? objv[4] : NULL,
+ objPtr = Tk_GetOptionInfo(
+ interp,
+ (char *) &ewPtr->body.ew,
+ ewPtr->body.ew.optionTable,
+ objc == 5 ? objv[4] : NULL,
textPtr->tkwin);
- if (objPtr == NULL) {
+ if (!objPtr) {
return TCL_ERROR;
}
Tcl_SetObjResult(interp, objPtr);
return TCL_OK;
} else {
- TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index);
-
- /*
- * It's probably not true that all window configuration can change
- * the line height, so we could be more efficient here and only
- * call this when necessary.
- */
-
- TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL,
- index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
- return EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4);
+ TextChanged(textPtr->sharedTextPtr, &index);
+ return EmbWinConfigure(textPtr, ewPtr, objc - 4, objv + 4);
}
}
case WIND_CREATE: {
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
TkTextIndex index;
- int lineIndex;
TkTextEmbWindowClient *client;
int res;
@@ -267,69 +499,76 @@ TkTextWindowCmd(
*/
if (objc < 4) {
- Tcl_WrongNumArgs(interp, 3, objv, "index ?-option value ...?");
+ Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?");
return TCL_ERROR;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[3], &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
return TCL_ERROR;
}
+ if (textPtr->state == TK_TEXT_STATE_DISABLED) {
+#if !SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("attempt to modify disabled widget"));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NOT_ALLOWED", NULL);
+ return TCL_ERROR;
+#endif /* SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET */
+ }
+
/*
- * Don't allow insertions on the last (dummy) line of the text.
+ * Don't allow insertions on the last line of the text.
*/
- lineIndex = TkBTreeLinesTo(textPtr, index.linePtr);
- if (lineIndex == TkBTreeNumLines(textPtr->sharedTextPtr->tree,
- textPtr)) {
- lineIndex--;
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineIndex, 1000000, &index);
+ if (!TkTextIndexEnsureBeforeLastChar(&index)) {
+#if SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET
+ return TCL_OK;
+#else
+ Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(
+ "cannot insert window into dead peer", -1));
+ Tcl_SetErrorCode(textPtr->interp, "TK", "TEXT", "WINDOW_CREATE_USAGE", NULL);
+ return TCL_ERROR;
+#endif
}
/*
* Create the new window segment and initialize it.
*/
- ewPtr = ckalloc(EW_SEG_SIZE);
- ewPtr->typePtr = &tkTextEmbWindowType;
- ewPtr->size = 1;
- ewPtr->body.ew.sharedTextPtr = textPtr->sharedTextPtr;
- ewPtr->body.ew.linePtr = NULL;
- ewPtr->body.ew.tkwin = NULL;
- ewPtr->body.ew.create = NULL;
- ewPtr->body.ew.align = ALIGN_CENTER;
- ewPtr->body.ew.padX = ewPtr->body.ew.padY = 0;
- ewPtr->body.ew.stretch = 0;
- ewPtr->body.ew.optionTable = Tk_CreateOptionTable(interp, optionSpecs);
-
- client = ckalloc(sizeof(TkTextEmbWindowClient));
- client->next = NULL;
- client->textPtr = textPtr;
- client->tkwin = NULL;
- client->chunkCount = 0;
- client->displayed = 0;
- client->parent = ewPtr;
- ewPtr->body.ew.clients = client;
+ ewPtr = MakeWindow(textPtr);
+ client = ewPtr->body.ew.clients;
/*
* Link the segment into the text widget, then configure it (delete it
* again if the configuration fails).
*/
- TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index);
- TkBTreeLinkSegment(ewPtr, &index);
- res = EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4);
+ TkBTreeLinkSegment(sharedTextPtr, ewPtr, &index);
+ res = EmbWinConfigure(textPtr, ewPtr, objc - 4, objv + 4);
client->tkwin = ewPtr->body.ew.tkwin;
if (res != TCL_OK) {
- TkTextIndex index2;
-
- TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
- TkBTreeDeleteIndexRange(textPtr->sharedTextPtr->tree, &index,
- &index2);
+ TkBTreeUnlinkSegment(sharedTextPtr, ewPtr);
+ TkTextWinFreeClient(NULL, client);
+ ewPtr->body.ew.clients = NULL;
+ ReleaseWindow(ewPtr);
return TCL_ERROR;
}
- TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL,
- index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
+ TextChanged(sharedTextPtr, &index);
+
+ if (!TkTextUndoStackIsFull(sharedTextPtr->undoStack)) {
+ UndoTokenLinkSegment *token;
+
+ assert(sharedTextPtr->undoStack);
+ assert(ewPtr->typePtr == &tkTextEmbWindowType);
+
+ token = malloc(sizeof(UndoTokenLinkSegment));
+ token->undoType = &undoTokenLinkSegmentType;
+ token->segPtr = ewPtr;
+ ewPtr->refCount += 1;
+ DEBUG_ALLOC(tkTextCountNewUndoToken++);
+
+ TkTextPushUndoToken(sharedTextPtr, token, 0);
+ }
+
+ TkTextUpdateAlteredFlag(sharedTextPtr);
break;
}
case WIND_NAMES: {
@@ -342,11 +581,11 @@ TkTextWindowCmd(
return TCL_ERROR;
}
resultObj = Tcl_NewObj();
- for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->windowTable,
- &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
+ for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->windowTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(
- Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr),
- -1));
+ Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr), -1));
}
Tcl_SetObjResult(interp, resultObj);
break;
@@ -358,6 +597,92 @@ TkTextWindowCmd(
/*
*--------------------------------------------------------------
*
+ * MakeWindow --
+ *
+ * This function is called to create a window segment.
+ *
+ * Results:
+ * The return value is the newly created window.
+ *
+ * Side effects:
+ * Some memory will be allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static TkTextSegment *
+MakeWindow(
+ TkText *textPtr) /* Information about text widget that contains embedded image. */
+{
+ TkTextSegment *ewPtr;
+ TkTextEmbWindowClient *client;
+
+ ewPtr = memset(malloc(SEG_SIZE(TkTextEmbWindow)), 0, SEG_SIZE(TkTextEmbWindow));
+ ewPtr->typePtr = &tkTextEmbWindowType;
+ ewPtr->size = 1;
+ ewPtr->refCount = 1;
+ ewPtr->body.ew.sharedTextPtr = textPtr->sharedTextPtr;
+ ewPtr->body.ew.align = ALIGN_CENTER;
+ ewPtr->body.ew.optionTable = Tk_CreateOptionTable(textPtr->interp, optionSpecs);
+ DEBUG_ALLOC(tkTextCountNewSegment++);
+
+ client = memset(malloc(sizeof(TkTextEmbWindowClient)), 0, sizeof(TkTextEmbWindowClient));
+ client->textPtr = textPtr;
+ client->parent = ewPtr;
+ ewPtr->body.ew.clients = client;
+
+ return ewPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextMakeWindow --
+ *
+ * This function is called to create a window segment.
+ *
+ * Results:
+ * The return value is a standard Tcl result. If TCL_ERROR is returned,
+ * then the interp's result contains an error message.
+ *
+ * Side effects:
+ * Some memory will be allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+TkTextSegment *
+TkTextMakeWindow(
+ TkText *textPtr, /* Information about text widget that contains embedded window. */
+ Tcl_Obj *options) /* Options for this window. */
+{
+ TkTextSegment *ewPtr;
+ Tcl_Obj **objv;
+ int objc;
+
+ assert(options);
+
+ if (Tcl_ListObjGetElements(textPtr->interp, options, &objc, &objv) != TCL_OK) {
+ return NULL;
+ }
+
+ ewPtr = MakeWindow(textPtr);
+
+ if (EmbWinConfigure(textPtr, ewPtr, objc, objv) == TCL_OK) {
+ Tcl_ResetResult(textPtr->interp);
+ } else {
+ TkTextWinFreeClient(NULL, ewPtr->body.ew.clients);
+ ewPtr->body.ew.clients = NULL;
+ ReleaseWindow(ewPtr);
+ ewPtr = NULL;
+ }
+
+ return ewPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
* EmbWinConfigure --
*
* This function is called to handle configuration options for an
@@ -379,43 +704,48 @@ TkTextWindowCmd(
*--------------------------------------------------------------
*/
+static bool
+IsPreservedWindow(
+ const TkTextEmbWindowClient *client)
+{
+ return client && !client->hPtr;
+}
+
static int
EmbWinConfigure(
TkText *textPtr, /* Information about text widget that contains
* embedded window. */
TkTextSegment *ewPtr, /* Embedded window to be configured. */
int objc, /* Number of strings in objv. */
- Tcl_Obj *const objv[]) /* Array of objects describing configuration
- * options. */
+ Tcl_Obj *const objv[]) /* Array of objects describing configuration options. */
{
Tk_Window oldWindow;
TkTextEmbWindowClient *client;
+ assert(textPtr);
+
/*
* Copy over client specific value before querying or setting.
*/
client = EmbWinGetClient(textPtr, ewPtr);
- if (client != NULL) {
- ewPtr->body.ew.tkwin = client->tkwin;
- } else {
- ewPtr->body.ew.tkwin = NULL;
- }
-
+ ewPtr->body.ew.tkwin = client ? client->tkwin : NULL;
oldWindow = ewPtr->body.ew.tkwin;
+
if (Tk_SetOptions(textPtr->interp, (char *) &ewPtr->body.ew,
- ewPtr->body.ew.optionTable, objc, objv, textPtr->tkwin, NULL,
- NULL) != TCL_OK) {
+ ewPtr->body.ew.optionTable, objc, objv, textPtr->tkwin, NULL, NULL) != TCL_OK) {
return TCL_ERROR;
}
- if (oldWindow != ewPtr->body.ew.tkwin) {
- if (oldWindow != NULL) {
- Tcl_DeleteHashEntry(Tcl_FindHashEntry(
- &textPtr->sharedTextPtr->windowTable,
- Tk_PathName(oldWindow)));
- Tk_DeleteEventHandler(oldWindow, StructureNotifyMask,
- EmbWinStructureProc, client);
+ if (oldWindow != ewPtr->body.ew.tkwin && (!oldWindow || !IsPreservedWindow(client))) {
+ if (oldWindow) {
+ Tcl_HashEntry *hPtr;
+
+ textPtr->sharedTextPtr->numWindows -= 1;
+ hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->windowTable, Tk_PathName(oldWindow));
+ assert(hPtr);
+ Tcl_DeleteHashEntry(hPtr);
+ Tk_DeleteEventHandler(oldWindow, StructureNotifyMask, EmbWinStructureProc, client);
Tk_ManageGeometry(oldWindow, NULL, NULL);
if (textPtr->tkwin != Tk_Parent(oldWindow)) {
Tk_UnmaintainGeometry(oldWindow, textPtr->tkwin);
@@ -423,13 +753,14 @@ EmbWinConfigure(
Tk_UnmapWindow(oldWindow);
}
}
- if (client != NULL) {
+ if (client) {
client->tkwin = NULL;
+ client->hPtr = NULL;
}
- if (ewPtr->body.ew.tkwin != NULL) {
+ if (ewPtr->body.ew.tkwin) {
Tk_Window ancestor, parent;
- Tcl_HashEntry *hPtr;
- int isNew;
+ bool cantEmbed = false;
+ bool isNew;
/*
* Make sure that the text is either the parent of the embedded
@@ -443,36 +774,32 @@ EmbWinConfigure(
break;
}
if (Tk_TopWinHierarchy(ancestor)) {
- badMaster:
- Tcl_SetObjResult(textPtr->interp, Tcl_ObjPrintf(
- "can't embed %s in %s",
- Tk_PathName(ewPtr->body.ew.tkwin),
- Tk_PathName(textPtr->tkwin)));
- Tcl_SetErrorCode(textPtr->interp, "TK", "GEOMETRY",
- "HIERARCHY", NULL);
- ewPtr->body.ew.tkwin = NULL;
- if (client != NULL) {
- client->tkwin = NULL;
- }
- return TCL_ERROR;
+ cantEmbed = true;
+ break;
}
}
- if (Tk_TopWinHierarchy(ewPtr->body.ew.tkwin)
+ if (cantEmbed
+ || Tk_TopWinHierarchy(ewPtr->body.ew.tkwin)
|| (ewPtr->body.ew.tkwin == textPtr->tkwin)) {
- goto badMaster;
+ Tcl_SetObjResult(textPtr->interp, Tcl_ObjPrintf("can't embed %s in %s",
+ Tk_PathName(ewPtr->body.ew.tkwin), Tk_PathName(textPtr->tkwin)));
+ Tcl_SetErrorCode(textPtr->interp, "TK", "GEOMETRY", "HIERARCHY", NULL);
+ ewPtr->body.ew.tkwin = NULL;
+ if (client) {
+ client->tkwin = NULL;
+ }
+ return TCL_ERROR;
}
- if (client == NULL) {
+ if (!client) {
/*
* Have to make the new client.
*/
- client = ckalloc(sizeof(TkTextEmbWindowClient));
+ client = malloc(sizeof(TkTextEmbWindowClient));
+ memset(client, 0, sizeof(TkTextEmbWindowClient));
client->next = ewPtr->body.ew.clients;
client->textPtr = textPtr;
- client->tkwin = NULL;
- client->chunkCount = 0;
- client->displayed = 0;
client->parent = ewPtr;
ewPtr->body.ew.clients = client;
}
@@ -488,15 +815,18 @@ EmbWinConfigure(
EmbWinStructureProc, client);
/*
- * Special trick! Must enter into the hash table *after* calling
+ * Special trick! Must enter into the hash table *after* calling
* Tk_ManageGeometry: if the window was already managed elsewhere
* in this text, the Tk_ManageGeometry call will cause the entry
* to be removed, which could potentially lose the new entry.
*/
- hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->windowTable,
- Tk_PathName(ewPtr->body.ew.tkwin), &isNew);
- Tcl_SetHashValue(hPtr, ewPtr);
+ client->hPtr = Tcl_CreateHashEntry(
+ &textPtr->sharedTextPtr->windowTable,
+ Tk_PathName(ewPtr->body.ew.tkwin),
+ (int *) &isNew);
+ Tcl_SetHashValue(client->hPtr, ewPtr);
+ textPtr->sharedTextPtr->numWindows += 1;
}
}
return TCL_OK;
@@ -527,32 +857,28 @@ EmbWinStructureProc(
XEvent *eventPtr) /* Describes what just happened. */
{
TkTextEmbWindowClient *client = clientData;
- TkTextSegment *ewPtr = client->parent;
- TkTextIndex index;
- Tcl_HashEntry *hPtr;
+ TkTextSegment *ewPtr;
- if (eventPtr->type != DestroyNotify) {
+ if (eventPtr->type != DestroyNotify || !client->hPtr) {
return;
}
- hPtr = Tcl_FindHashEntry(&ewPtr->body.ew.sharedTextPtr->windowTable,
- Tk_PathName(client->tkwin));
- if (hPtr != NULL) {
- /*
- * This may not exist if the entire widget is being deleted.
- */
+ ewPtr = client->parent;
- Tcl_DeleteHashEntry(hPtr);
- }
+ assert(ewPtr->typePtr);
+ assert(client->hPtr == Tcl_FindHashEntry(&ewPtr->body.ew.sharedTextPtr->windowTable,
+ Tk_PathName(client->tkwin)));
+ /*
+ * This may not exist if the entire widget is being deleted.
+ */
+
+ Tcl_DeleteHashEntry(client->hPtr);
+ ewPtr->body.ew.sharedTextPtr->numWindows -= 1;
ewPtr->body.ew.tkwin = NULL;
client->tkwin = NULL;
- index.tree = ewPtr->body.ew.sharedTextPtr->tree;
- index.linePtr = ewPtr->body.ew.linePtr;
- index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr);
- TkTextChanged(ewPtr->body.ew.sharedTextPtr, NULL, &index, &index);
- TkTextInvalidateLineMetrics(ewPtr->body.ew.sharedTextPtr, NULL,
- index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
+ client->hPtr = NULL;
+ EmbWinRequestProc(client, NULL);
}
/*
@@ -573,7 +899,6 @@ EmbWinStructureProc(
*--------------------------------------------------------------
*/
- /* ARGSUSED */
static void
EmbWinRequestProc(
ClientData clientData, /* Pointer to record for window item. */
@@ -583,12 +908,14 @@ EmbWinRequestProc(
TkTextSegment *ewPtr = client->parent;
TkTextIndex index;
- index.tree = ewPtr->body.ew.sharedTextPtr->tree;
- index.linePtr = ewPtr->body.ew.linePtr;
- index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr);
- TkTextChanged(ewPtr->body.ew.sharedTextPtr, NULL, &index, &index);
- TkTextInvalidateLineMetrics(ewPtr->body.ew.sharedTextPtr, NULL,
- index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
+ assert(ewPtr->typePtr);
+
+ if (ewPtr->sectionPtr) {
+ assert(ewPtr->sectionPtr);
+ TkTextIndexClear(&index, client->textPtr);
+ TkTextIndexSetSegment(&index, ewPtr);
+ TextChanged(ewPtr->body.ew.sharedTextPtr, &index);
+ }
}
/*
@@ -613,26 +940,25 @@ EmbWinRequestProc(
static void
EmbWinLostSlaveProc(
ClientData clientData, /* Pointer to record describing window item. */
- Tk_Window tkwin) /* Window that was claimed away by another
- * geometry manager. */
+ Tk_Window tkwin) /* Window that was claimed away by another geometry manager. */
{
TkTextEmbWindowClient *client = clientData;
TkTextSegment *ewPtr = client->parent;
+ TkText *textPtr = client->textPtr;
TkTextIndex index;
- Tcl_HashEntry *hPtr;
TkTextEmbWindowClient *loop;
- Tk_DeleteEventHandler(client->tkwin, StructureNotifyMask,
- EmbWinStructureProc, client);
- Tcl_CancelIdleCall(EmbWinDelayedUnmap, client);
- if (client->textPtr->tkwin != Tk_Parent(tkwin)) {
- Tk_UnmaintainGeometry(tkwin, client->textPtr->tkwin);
- } else {
- Tk_UnmapWindow(tkwin);
+ assert(!IsPreservedWindow(client));
+
+ assert(client->tkwin);
+ client->displayed = false;
+ Tk_DeleteEventHandler(client->tkwin, StructureNotifyMask, EmbWinStructureProc, client);
+ EmbWinDelayedUnmap(client);
+ if (client->hPtr) {
+ ewPtr->body.ew.sharedTextPtr->numWindows -= 1;
+ Tcl_DeleteHashEntry(client->hPtr);
+ client->hPtr = NULL;
}
- hPtr = Tcl_FindHashEntry(&ewPtr->body.ew.sharedTextPtr->windowTable,
- Tk_PathName(client->tkwin));
- Tcl_DeleteHashEntry(hPtr);
client->tkwin = NULL;
ewPtr->body.ew.tkwin = NULL;
@@ -649,14 +975,11 @@ EmbWinLostSlaveProc(
}
loop->next = client->next;
}
- ckfree(client);
-
- index.tree = ewPtr->body.ew.sharedTextPtr->tree;
- index.linePtr = ewPtr->body.ew.linePtr;
- index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr);
- TkTextChanged(ewPtr->body.ew.sharedTextPtr, NULL, &index, &index);
- TkTextInvalidateLineMetrics(ewPtr->body.ew.sharedTextPtr, NULL,
- index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
+ free(client);
+
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetSegment(&index, ewPtr);
+ TextChanged(ewPtr->body.ew.sharedTextPtr, &index);
}
/*
@@ -682,19 +1005,18 @@ EmbWinLostSlaveProc(
void
TkTextWinFreeClient(
- Tcl_HashEntry *hPtr, /* Hash entry corresponding to this client, or
- * NULL */
+ Tcl_HashEntry *hPtr, /* Hash entry corresponding to this client, or NULL */
TkTextEmbWindowClient *client)
- /* Client data structure, with the 'tkwin'
- * field to be cleaned up. */
+ /* Client data structure, with the 'tkwin' field to be cleaned up. */
{
- if (hPtr != NULL) {
+ if (hPtr) {
/*
* (It's possible for there to be no hash table entry for this window,
* if an error occurred while creating the window segment but before
* the window got added to the table)
*/
+ client->parent->body.ew.sharedTextPtr->numWindows -= 1;
Tcl_DeleteHashEntry(hPtr);
}
@@ -704,9 +1026,8 @@ TkTextWinFreeClient(
* everything that it would have done, and it will just get confused).
*/
- if (client->tkwin != NULL) {
- Tk_DeleteEventHandler(client->tkwin, StructureNotifyMask,
- EmbWinStructureProc, client);
+ if (client->tkwin) {
+ Tk_DeleteEventHandler(client->tkwin, StructureNotifyMask, EmbWinStructureProc, client);
Tk_DestroyWindow(client->tkwin);
}
Tcl_CancelIdleCall(EmbWinDelayedUnmap, client);
@@ -715,88 +1036,218 @@ TkTextWinFreeClient(
* Free up this client.
*/
- ckfree(client);
+ free(client);
}
/*
*--------------------------------------------------------------
*
- * EmbWinDeleteProc --
+ * EmbWinInspectProc --
*
- * This function is invoked by the text B-tree code whenever an embedded
- * window lies in a range of characters being deleted.
+ * This function is invoked to build the information for
+ * "inspect".
*
* Results:
- * Returns 0 to indicate that the deletion has been accepted.
+ * Return a TCL object containing the information for
+ * "inspect".
*
* Side effects:
- * The embedded window is deleted, if it exists, and any resources
+ * Storage is allocated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+EmbWinInspectProc(
+ const TkSharedText *sharedTextPtr,
+ const TkTextSegment *segPtr)
+{
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_Obj *objPtr2 = Tcl_NewObj();
+ TkTextTag **tagLookup = sharedTextPtr->tagLookup;
+ const TkTextTagSet *tagInfoPtr = segPtr->tagInfoPtr;
+ unsigned i = TkTextTagSetFindFirst(tagInfoPtr);
+ Tcl_DString opts;
+
+ assert(sharedTextPtr->peers);
+
+ for ( ; i != TK_TEXT_TAG_SET_NPOS; i = TkTextTagSetFindNext(tagInfoPtr, i)) {
+ const TkTextTag *tagPtr = tagLookup[i];
+ Tcl_ListObjAppendElement(NULL, objPtr2, Tcl_NewStringObj(tagPtr->name, -1));
+ }
+
+ Tcl_DStringInit(&opts);
+ TkTextInspectOptions(sharedTextPtr->peers, &segPtr->body.ew, segPtr->body.ew.optionTable,
+ &opts, false, false);
+
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(segPtr->typePtr->name, -1));
+ Tcl_ListObjAppendElement(NULL, objPtr, objPtr2);
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(Tcl_DStringValue(&opts),
+ Tcl_DStringLength(&opts)));
+
+ Tcl_DStringFree(&opts);
+ return objPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * ReleaseWindow --
+ *
+ * Free embedded window
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The embedded window is deleted, and any resources
* associated with it are released.
*
*--------------------------------------------------------------
*/
- /* ARGSUSED */
-static int
-EmbWinDeleteProc(
- TkTextSegment *ewPtr, /* Segment being deleted. */
- TkTextLine *linePtr, /* Line containing segment. */
- int treeGone) /* Non-zero means the entire tree is being
- * deleted, so everything must get cleaned
- * up. */
+static void
+ReleaseWindow(
+ TkTextSegment *ewPtr)
{
- TkTextEmbWindowClient *client;
- client = ewPtr->body.ew.clients;
+ TkTextEmbWindowClient *client = ewPtr->body.ew.clients;
- while (client != NULL) {
- TkTextEmbWindowClient *next = client->next;
- Tcl_HashEntry *hPtr = NULL;
+ assert(ewPtr->typePtr);
- if (client->tkwin != NULL) {
- hPtr = Tcl_FindHashEntry(
- &ewPtr->body.ew.sharedTextPtr->windowTable,
- Tk_PathName(client->tkwin));
+ while (client) {
+ TkTextEmbWindowClient *next = client->next;
+ if (client->hPtr) {
+ TkTextWinFreeClient(client->hPtr, client);
}
- TkTextWinFreeClient(hPtr, client);
client = next;
}
ewPtr->body.ew.clients = NULL;
+ Tk_FreeConfigOptions((char *) &ewPtr->body.ew, ewPtr->body.ew.optionTable, NULL);
+ TkTextTagSetDecrRefCount(ewPtr->tagInfoPtr);
+ FREE_SEGMENT(ewPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * DestroyOrUnmapWindow --
+ *
+ * Unmap all clients of given window.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Either destroy or only unmap the embedded window.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+DestroyOrUnmapWindow(
+ TkTextSegment *ewPtr)
+{
+ TkTextEmbWindowClient *client = ewPtr->body.ew.clients;
- Tk_FreeConfigOptions((char *) &ewPtr->body.ew, ewPtr->body.ew.optionTable,
- NULL);
+ assert(ewPtr->typePtr);
+ assert(ewPtr->refCount > 0);
- /*
- * Free up all memory allocated.
- */
+ for ( ; client; client = client->next) {
+ if (client->hPtr) {
+ client->parent->body.ew.sharedTextPtr->numWindows -= 1;
+ Tcl_DeleteHashEntry(client->hPtr);
+ client->hPtr = NULL;
+ client->displayed = false;
+ }
+ Tcl_CancelIdleCall(EmbWinDelayedUnmap, client);
+ if (client->tkwin && ewPtr->body.ew.create) {
+ Tk_DeleteEventHandler(client->tkwin, StructureNotifyMask, EmbWinStructureProc, client);
+ Tk_DestroyWindow(client->tkwin);
+ client->tkwin = NULL;
+ ewPtr->body.ew.tkwin = NULL;
+ } else {
+ EmbWinDelayedUnmap(client);
+ }
+ }
+}
+/*
+ *--------------------------------------------------------------
+ *
+ * EmbWinDeleteProc --
+ *
+ * This function is invoked by the text B-tree code whenever an embedded
+ * window lies in a range of characters being deleted.
+ *
+ * Results:
+ * Returns true to indicate that the deletion has been accepted.
+ *
+ * Side effects:
+ * Depends on the action, see ReleaseWindow and DestroyOrUnmapWindow.
+ *
+ *--------------------------------------------------------------
+ */
- ckfree(ewPtr);
- return 0;
+static bool
+EmbWinDeleteProc(
+ TkTextBTree tree,
+ TkTextSegment *ewPtr, /* Segment being deleted. */
+ int flags) /* Flags controlling the deletion. */
+{
+ assert(ewPtr->typePtr);
+
+ if (--ewPtr->refCount == 0) {
+ ReleaseWindow(ewPtr);
+ } else {
+ DestroyOrUnmapWindow(ewPtr);
+ }
+ return true;
}
/*
*--------------------------------------------------------------
*
- * EmbWinCleanupProc --
+ * EmbWinRestoreProc --
*
- * This function is invoked by the B-tree code whenever a segment
- * containing an embedded window is moved from one line to another.
+ * This function is called when a window segment will be restored
+ * from the undo chain.
*
* Results:
* None.
*
* Side effects:
- * The linePtr field of the segment gets updated.
+ * The name of the mark will be freed, and the mark will be
+ * re-entered into the hash table.
*
*--------------------------------------------------------------
*/
-static TkTextSegment *
-EmbWinCleanupProc(
- TkTextSegment *ewPtr, /* Mark segment that's being moved. */
- TkTextLine *linePtr) /* Line that now contains segment. */
+static void
+EmbWinRestoreProc(
+ TkTextSegment *ewPtr) /* Segment to reuse. */
{
- ewPtr->body.ew.linePtr = linePtr;
- return ewPtr;
+ bool isNew;
+
+ if (ewPtr->body.ew.create) {
+ /*
+ * EmbWinLayoutProc is doing the creation of the window.
+ */
+ assert(!ewPtr->body.ew.tkwin);
+ } else {
+ TkTextEmbWindowClient *client = ewPtr->body.ew.clients;
+
+ for ( ; client; client = client->next) {
+ if (client->tkwin && !client->hPtr) {
+ client->hPtr = Tcl_CreateHashEntry(
+ &ewPtr->body.ew.sharedTextPtr->windowTable,
+ Tk_PathName(client->tkwin),
+ (int *) &isNew);
+ assert(isNew);
+ Tcl_SetHashValue(client->hPtr, ewPtr);
+ ewPtr->body.ew.sharedTextPtr->numWindows += 1;
+ }
+ }
+ }
}
/*
@@ -816,49 +1267,37 @@ EmbWinCleanupProc(
*--------------------------------------------------------------
*/
- /*ARGSUSED*/
static int
EmbWinLayoutProc(
- TkText *textPtr, /* Text widget being layed out. */
- TkTextIndex *indexPtr, /* Identifies first character in chunk. */
+ const TkTextIndex *indexPtr,/* Identifies first character in chunk. */
TkTextSegment *ewPtr, /* Segment corresponding to indexPtr. */
- int offset, /* Offset within segPtr corresponding to
- * indexPtr (always 0). */
- int maxX, /* Chunk must not occupy pixels at this
- * position or higher. */
- int maxChars, /* Chunk must not include more than this many
- * characters. */
- int noCharsYet, /* Non-zero means no characters have been
- * assigned to this line yet. */
- TkWrapMode wrapMode, /* Wrap mode to use for line:
- * TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, or
- * TEXT_WRAPMODE_WORD. */
- register TkTextDispChunk *chunkPtr)
- /* Structure to fill in with information about
- * this chunk. The x field has already been
- * set by the caller. */
+ int offset, /* Offset within segPtr corresponding to indexPtr (always 0). */
+ int maxX, /* Chunk must not occupy pixels at this position or higher. */
+ int maxChars, /* Chunk must not include more than this many characters. */
+ bool noCharsYet, /* 'true' means no characters have been assigned to this line yet. */
+ TkWrapMode wrapMode, /* Wrap mode to use for line: TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE,
+ * TEXT_WRAPMODE_WORD, or TEXT_WRAPMODE_CODEPOINT. */
+ TkTextSpaceMode spaceMode, /* Not used. */
+ TkTextDispChunk *chunkPtr) /* Structure to fill in with information about this chunk. The x
+ * field has already been set by the caller. */
{
int width, height;
TkTextEmbWindowClient *client;
+ TkText *textPtr = indexPtr->textPtr;
+ bool cantEmbed = false;
- if (offset != 0) {
- Tcl_Panic("Non-zero offset in EmbWinLayoutProc");
- }
+ assert(indexPtr->textPtr);
+ assert(offset == 0);
client = EmbWinGetClient(textPtr, ewPtr);
- if (client == NULL) {
- ewPtr->body.ew.tkwin = NULL;
- } else {
- ewPtr->body.ew.tkwin = client->tkwin;
- }
+ ewPtr->body.ew.tkwin = client ? client->tkwin : NULL;
- if ((ewPtr->body.ew.tkwin == NULL) && (ewPtr->body.ew.create != NULL)) {
- int code, isNew;
+ if (!ewPtr->body.ew.tkwin && ewPtr->body.ew.create) {
+ int code;
+ bool isNew;
Tk_Window ancestor;
- Tcl_HashEntry *hPtr;
const char *before, *string;
- Tcl_DString buf, *dsPtr = NULL;
- Tcl_Obj *nameObj;
+ Tcl_DString name, buf, *dsPtr = NULL;
before = ewPtr->body.ew.create;
@@ -869,8 +1308,8 @@ EmbWinLayoutProc(
string = before;
while (*string != 0) {
- if ((*string == '%') && (string[1] == '%' || string[1] == 'W')) {
- if (dsPtr == NULL) {
+ if (string[0] == '%' && (string[1] == '%' || string[1] == 'W')) {
+ if (!dsPtr) {
Tcl_DStringInit(&buf);
dsPtr = &buf;
}
@@ -897,9 +1336,9 @@ EmbWinLayoutProc(
Tcl_DStringSetLength(dsPtr, length + spaceNeeded);
}
before += 2;
- string++;
+ string += 1;
}
- string++;
+ string += 1;
}
/*
@@ -909,7 +1348,7 @@ EmbWinLayoutProc(
* ourselves as the geometry manager for the window.
*/
- if (dsPtr != NULL) {
+ if (dsPtr) {
Tcl_DStringAppend(dsPtr, before, (int) (string-before));
code = Tcl_EvalEx(textPtr->interp, Tcl_DStringValue(dsPtr), -1, TCL_EVAL_GLOBAL);
Tcl_DStringFree(dsPtr);
@@ -917,18 +1356,17 @@ EmbWinLayoutProc(
code = Tcl_EvalEx(textPtr->interp, ewPtr->body.ew.create, -1, TCL_EVAL_GLOBAL);
}
if (code != TCL_OK) {
+ createError:
Tcl_BackgroundException(textPtr->interp, code);
goto gotWindow;
}
- nameObj = Tcl_GetObjResult(textPtr->interp);
- Tcl_IncrRefCount(nameObj);
+ Tcl_DStringInit(&name);
+ Tcl_DStringAppend(&name, Tcl_GetStringResult(textPtr->interp), -1);
Tcl_ResetResult(textPtr->interp);
- ewPtr->body.ew.tkwin = Tk_NameToWindow(textPtr->interp,
- Tcl_GetString(nameObj), textPtr->tkwin);
- Tcl_DecrRefCount(nameObj);
- if (ewPtr->body.ew.tkwin == NULL) {
- Tcl_BackgroundException(textPtr->interp, TCL_ERROR);
- goto gotWindow;
+ ewPtr->body.ew.tkwin = Tk_NameToWindow(textPtr->interp, Tcl_DStringValue(&name), textPtr->tkwin);
+ Tcl_DStringFree(&name);
+ if (!ewPtr->body.ew.tkwin) {
+ goto createError;
}
for (ancestor = textPtr->tkwin; ; ancestor = Tk_Parent(ancestor)) {
@@ -936,43 +1374,38 @@ EmbWinLayoutProc(
break;
}
if (Tk_TopWinHierarchy(ancestor)) {
- goto badMaster;
+ cantEmbed = true;
+ break;
}
}
- if (Tk_TopWinHierarchy(ewPtr->body.ew.tkwin)
- || (textPtr->tkwin == ewPtr->body.ew.tkwin)) {
- badMaster:
- Tcl_SetObjResult(textPtr->interp, Tcl_ObjPrintf(
- "can't embed %s relative to %s",
- Tk_PathName(ewPtr->body.ew.tkwin),
- Tk_PathName(textPtr->tkwin)));
- Tcl_SetErrorCode(textPtr->interp, "TK", "GEOMETRY", "HIERARCHY",
- NULL);
+ if (cantEmbed
+ || Tk_TopWinHierarchy(ewPtr->body.ew.tkwin)
+ || textPtr->tkwin == ewPtr->body.ew.tkwin) {
+ Tcl_SetObjResult(textPtr->interp, Tcl_ObjPrintf("can't embed %s relative to %s",
+ Tk_PathName(ewPtr->body.ew.tkwin), Tk_PathName(textPtr->tkwin)));
+ Tcl_SetErrorCode(textPtr->interp, "TK", "GEOMETRY", "HIERARCHY", NULL);
Tcl_BackgroundException(textPtr->interp, TCL_ERROR);
ewPtr->body.ew.tkwin = NULL;
goto gotWindow;
}
- if (client == NULL) {
+ if (!client) {
/*
* We just used a '-create' script to make a new window, which we
* now need to add to our client list.
*/
- client = ckalloc(sizeof(TkTextEmbWindowClient));
+ client = malloc(sizeof(TkTextEmbWindowClient));
+ memset(client, 0, sizeof(TkTextEmbWindowClient));
client->next = ewPtr->body.ew.clients;
client->textPtr = textPtr;
- client->tkwin = NULL;
- client->chunkCount = 0;
- client->displayed = 0;
client->parent = ewPtr;
ewPtr->body.ew.clients = client;
}
client->tkwin = ewPtr->body.ew.tkwin;
Tk_ManageGeometry(client->tkwin, &textGeomType, client);
- Tk_CreateEventHandler(client->tkwin, StructureNotifyMask,
- EmbWinStructureProc, client);
+ Tk_CreateEventHandler(client->tkwin, StructureNotifyMask, EmbWinStructureProc, client);
/*
* Special trick! Must enter into the hash table *after* calling
@@ -981,9 +1414,10 @@ EmbWinLayoutProc(
* removed, which could potentially lose the new entry.
*/
- hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->windowTable,
- Tk_PathName(client->tkwin), &isNew);
- Tcl_SetHashValue(hPtr, ewPtr);
+ client->hPtr = Tcl_CreateHashEntry(
+ &textPtr->sharedTextPtr->windowTable, Tk_PathName(client->tkwin), (int *) &isNew);
+ Tcl_SetHashValue(client->hPtr, ewPtr);
+ ewPtr->body.ew.sharedTextPtr->numWindows += 1;
}
/*
@@ -991,15 +1425,14 @@ EmbWinLayoutProc(
*/
gotWindow:
- if (ewPtr->body.ew.tkwin == NULL) {
+ if (!ewPtr->body.ew.tkwin) {
width = 0;
height = 0;
} else {
width = Tk_ReqWidth(ewPtr->body.ew.tkwin) + 2*ewPtr->body.ew.padX;
height = Tk_ReqHeight(ewPtr->body.ew.tkwin) + 2*ewPtr->body.ew.padY;
}
- if ((width > (maxX - chunkPtr->x))
- && !noCharsYet && (textPtr->wrapMode != TEXT_WRAPMODE_NONE)) {
+ if (width > maxX - chunkPtr->x && !noCharsYet && textPtr->wrapMode != TEXT_WRAPMODE_NONE) {
return 0;
}
@@ -1007,10 +1440,7 @@ EmbWinLayoutProc(
* Fill in the chunk structure.
*/
- chunkPtr->displayProc = TkTextEmbWinDisplayProc;
- chunkPtr->undisplayProc = EmbWinUndisplayProc;
- chunkPtr->measureProc = NULL;
- chunkPtr->bboxProc = EmbWinBboxProc;
+ chunkPtr->layoutProcs = &layoutWindowProcs;
chunkPtr->numBytes = 1;
if (ewPtr->body.ew.align == ALIGN_BASELINE) {
chunkPtr->minAscent = height - ewPtr->body.ew.padY;
@@ -1022,10 +1452,9 @@ EmbWinLayoutProc(
chunkPtr->minHeight = height;
}
chunkPtr->width = width;
- chunkPtr->breakIndex = -1;
- chunkPtr->breakIndex = 1;
+ chunkPtr->breakIndex = (wrapMode == TEXT_WRAPMODE_NONE) ? -1 : 1;
chunkPtr->clientData = ewPtr;
- if (client != NULL) {
+ if (client) {
client->chunkCount += 1;
}
return 1;
@@ -1051,10 +1480,10 @@ EmbWinLayoutProc(
static void
EmbWinCheckProc(
- TkTextSegment *ewPtr, /* Segment to check. */
- TkTextLine *linePtr) /* Line containing segment. */
+ const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */
+ const TkTextSegment *ewPtr) /* Segment to check. */
{
- if (ewPtr->nextPtr == NULL) {
+ if (!ewPtr->nextPtr) {
Tcl_Panic("EmbWinCheckProc: embedded window is last segment in line");
}
if (ewPtr->size != 1) {
@@ -1065,7 +1494,7 @@ EmbWinCheckProc(
/*
*--------------------------------------------------------------
*
- * TkTextEmbWinDisplayProc --
+ * EmbWinDisplayProc --
*
* This function is invoked by the text displaying code when it is time
* to actually draw an embedded window chunk on the screen.
@@ -1080,8 +1509,8 @@ EmbWinCheckProc(
*--------------------------------------------------------------
*/
-void
-TkTextEmbWinDisplayProc(
+static void
+EmbWinDisplayProc(
TkText *textPtr, /* Information about text widget. */
TkTextDispChunk *chunkPtr, /* Chunk that is to be drawn. */
int x, /* X-position in dst at which to draw this
@@ -1093,35 +1522,25 @@ TkTextEmbWinDisplayProc(
int lineHeight, /* Total height of line. */
int baseline, /* Offset of baseline from y. */
Display *display, /* Display to use for drawing (unused). */
- Drawable dst, /* Pixmap or window in which to draw
- * (unused). */
- int screenY) /* Y-coordinate in text window that
- * corresponds to y. */
+ Drawable dst, /* Pixmap or window in which to draw (unused). */
+ int screenY) /* Y-coordinate in text window that corresponds to y. */
{
int lineX, windowX, windowY, width, height;
Tk_Window tkwin;
TkTextSegment *ewPtr = chunkPtr->clientData;
TkTextEmbWindowClient *client = EmbWinGetClient(textPtr, ewPtr);
- if (client == NULL) {
+ if (!client || !(tkwin = client->tkwin)) {
return;
}
- tkwin = client->tkwin;
- if (tkwin == NULL) {
- return;
- }
-
- if ((x + chunkPtr->width) <= 0) {
+ if (x + chunkPtr->width <= 0) {
/*
* The window is off-screen; just unmap it.
*/
- if (textPtr->tkwin != Tk_Parent(tkwin)) {
- Tk_UnmaintainGeometry(tkwin, textPtr->tkwin);
- } else {
- Tk_UnmapWindow(tkwin);
- }
+ client->displayed = false;
+ EmbWinDelayedUnmap(client);
return;
}
@@ -1142,19 +1561,21 @@ TkTextEmbWinDisplayProc(
* the embedded window its clients will get freed.
*/
- client->displayed = 1;
-
if (textPtr->tkwin == Tk_Parent(tkwin)) {
- if ((windowX != Tk_X(tkwin)) || (windowY != Tk_Y(tkwin))
- || (Tk_ReqWidth(tkwin) != Tk_Width(tkwin))
- || (height != Tk_Height(tkwin))) {
+ if (windowX != Tk_X(tkwin)
+ || windowY != Tk_Y(tkwin)
+ || Tk_ReqWidth(tkwin) != Tk_Width(tkwin)
+ || height != Tk_Height(tkwin)) {
Tk_MoveResizeWindow(tkwin, windowX, windowY, width, height);
}
- Tk_MapWindow(tkwin);
+ if (!client->displayed) {
+ Tk_MapWindow(tkwin);
+ }
} else {
- Tk_MaintainGeometry(tkwin, textPtr->tkwin, windowX, windowY,
- width, height);
+ Tk_MaintainGeometry(tkwin, textPtr->tkwin, windowX, windowY, width, height);
}
+
+ client->displayed = true;
}
/*
@@ -1183,12 +1604,7 @@ EmbWinUndisplayProc(
TkTextSegment *ewPtr = chunkPtr->clientData;
TkTextEmbWindowClient *client = EmbWinGetClient(textPtr, ewPtr);
- if (client == NULL) {
- return;
- }
-
- client->chunkCount--;
- if (client->chunkCount == 0) {
+ if (client && --client->chunkCount == 0) {
/*
* Don't unmap the window immediately, since there's a good chance
* that it will immediately be redisplayed, perhaps even in the same
@@ -1197,7 +1613,7 @@ EmbWinUndisplayProc(
* the unmap becomes unnecessary.
*/
- client->displayed = 0;
+ client->displayed = false;
Tcl_DoWhenIdle(EmbWinDelayedUnmap, client);
}
}
@@ -1228,30 +1644,20 @@ static void
EmbWinBboxProc(
TkText *textPtr, /* Information about text widget. */
TkTextDispChunk *chunkPtr, /* Chunk containing desired char. */
- int index, /* Index of desired character within the
- * chunk. */
- int y, /* Topmost pixel in area allocated for this
- * line. */
+ int index, /* Index of desired character within the chunk. */
+ int y, /* Topmost pixel in area allocated for this line. */
int lineHeight, /* Total height of line. */
- int baseline, /* Location of line's baseline, in pixels
- * measured down from y. */
- int *xPtr, int *yPtr, /* Gets filled in with coords of character's
- * upper-left pixel. */
- int *widthPtr, /* Gets filled in with width of window, in
- * pixels. */
- int *heightPtr) /* Gets filled in with height of window, in
- * pixels. */
+ int baseline, /* Location of line's baseline, in pixels measured down from y. */
+ int *xPtr, int *yPtr, /* Gets filled in with coords of character's upper-left pixel. */
+ int *widthPtr, /* Gets filled in with width of window, in pixels. */
+ int *heightPtr) /* Gets filled in with height of window, in pixels. */
{
Tk_Window tkwin;
TkTextSegment *ewPtr = chunkPtr->clientData;
TkTextEmbWindowClient *client = EmbWinGetClient(textPtr, ewPtr);
- if (client == NULL) {
- tkwin = NULL;
- } else {
- tkwin = client->tkwin;
- }
- if (tkwin != NULL) {
+ tkwin = client ? client->tkwin : NULL;
+ if (tkwin) {
*widthPtr = Tk_ReqWidth(tkwin);
*heightPtr = Tk_ReqHeight(tkwin);
} else {
@@ -1307,7 +1713,7 @@ EmbWinDelayedUnmap(
{
TkTextEmbWindowClient *client = clientData;
- if (!client->displayed && (client->tkwin != NULL)) {
+ if (!client->displayed && client->tkwin) {
if (client->textPtr->tkwin != Tk_Parent(client->tkwin)) {
Tk_UnmaintainGeometry(client->tkwin, client->textPtr->tkwin);
} else {
@@ -1325,8 +1731,8 @@ EmbWinDelayedUnmap(
* index corresponding to the window's position in the text.
*
* Results:
- * The return value is 1 if there is an embedded window by the given name
- * in the text widget, 0 otherwise. If the window exists, *indexPtr is
+ * The return value is true if there is an embedded window by the given name
+ * in the text widget, false otherwise. If the window exists, *indexPtr is
* filled in with its index.
*
* Side effects:
@@ -1335,7 +1741,7 @@ EmbWinDelayedUnmap(
*--------------------------------------------------------------
*/
-int
+bool
TkTextWindowIndex(
TkText *textPtr, /* Text widget containing window. */
const char *name, /* Name of window. */
@@ -1344,20 +1750,16 @@ TkTextWindowIndex(
Tcl_HashEntry *hPtr;
TkTextSegment *ewPtr;
- if (textPtr == NULL) {
- return 0;
- }
+ assert(textPtr);
- hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->windowTable, name);
- if (hPtr == NULL) {
- return 0;
+ if (!(hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->windowTable, name))) {
+ return false;
}
ewPtr = Tcl_GetHashValue(hPtr);
- indexPtr->tree = textPtr->sharedTextPtr->tree;
- indexPtr->linePtr = ewPtr->body.ew.linePtr;
- indexPtr->byteIndex = TkTextSegToOffset(ewPtr, indexPtr->linePtr);
- return 1;
+ TkTextIndexClear(indexPtr, textPtr);
+ TkTextIndexSetSegment(indexPtr, ewPtr);
+ return true;
}
/*
@@ -1391,7 +1793,7 @@ EmbWinGetClient(
{
TkTextEmbWindowClient *client = ewPtr->body.ew.clients;
- while (client != NULL) {
+ while (client) {
if (client->textPtr == textPtr) {
return client;
}
@@ -1404,6 +1806,7 @@ EmbWinGetClient(
* Local Variables:
* mode: c
* c-basic-offset: 4
- * fill-column: 78
+ * fill-column: 105
* End:
+ * vi:set ts=8 sw=4:
*/
diff --git a/library/text.tcl b/library/text.tcl
index 59e395c..cf2c85d 100644
--- a/library/text.tcl
+++ b/library/text.tcl
@@ -6,11 +6,21 @@
# Copyright (c) 1992-1994 The Regents of the University of California.
# Copyright (c) 1994-1997 Sun Microsystems, Inc.
# Copyright (c) 1998 by Scriptics Corporation.
+# Copyright (c) 2015-2017 Gregor Cramer
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
+##########################################################################
+# TODO:
+# Currently we cannot use identifier "begin" for very first index, because
+# it has still lowest precedence, and this may clash if the application is
+# using this identifier for marks. In a later version of this file all
+# occurences of "1.0" should be replaced with "begin", as soon as "begin"
+# has highest precedence.
+##########################################################################
+
#-------------------------------------------------------------------------
# Elements of ::tk::Priv that are used in this file:
#
@@ -42,7 +52,7 @@
bind Text <1> {
tk::TextButton1 %W %x %y
- %W tag remove sel 0.0 end
+ %W tag remove sel 1.0 end
}
bind Text <B1-Motion> {
set tk::Priv(x) %x
@@ -96,10 +106,10 @@ bind Text <Double-Control-1> { # nothing }
# stop an accidental movement triggering <B1-Motion>
bind Text <Control-B1-Motion> { # nothing }
bind Text <<PrevChar>> {
- tk::TextSetCursor %W insert-1displayindices
+ tk::TextSetCursor %W insert-1displaychars
}
bind Text <<NextChar>> {
- tk::TextSetCursor %W insert+1displayindices
+ tk::TextSetCursor %W insert+1displaychars
}
bind Text <<PrevLine>> {
tk::TextSetCursor %W [tk::TextUpDownLine %W -1]
@@ -108,16 +118,16 @@ bind Text <<NextLine>> {
tk::TextSetCursor %W [tk::TextUpDownLine %W 1]
}
bind Text <<SelectPrevChar>> {
- tk::TextKeySelect %W [%W index {insert - 1displayindices}]
+ tk::TextKeySelect %W [%W index {insert - 1displaychars}]
}
bind Text <<SelectNextChar>> {
- tk::TextKeySelect %W [%W index {insert + 1displayindices}]
+ tk::TextKeySelect %W [%W index {insert + 1displaychars}]
}
bind Text <<SelectPrevLine>> {
- tk::TextKeySelect %W [tk::TextUpDownLine %W -1]
+ tk::TextKeySelect %W [tk::TextUpDownLine %W -1 yes]
}
bind Text <<SelectNextLine>> {
- tk::TextKeySelect %W [tk::TextUpDownLine %W 1]
+ tk::TextKeySelect %W [tk::TextUpDownLine %W 1 yes]
}
bind Text <<PrevWord>> {
tk::TextSetCursor %W [tk::TextPrevPos %W insert tcl_startOfPreviousWord]
@@ -195,7 +205,7 @@ bind Text <Tab> {
}
}
bind Text <Shift-Tab> {
- # Needed only to keep <Tab> binding from triggering; doesn't
+ # Needed only to keep <Tab> binding from triggering; doesn't
# have to actually do anything.
break
}
@@ -209,29 +219,37 @@ bind Text <Control-i> {
tk::TextInsert %W \t
}
bind Text <Return> {
- tk::TextInsert %W \n
- if {[%W cget -autoseparators]} {
- %W edit separator
+ if {[%W cget -state] eq "normal"} {
+ tk::TextInsert %W \n
+ if {[%W cget -autoseparators]} {
+ %W edit separator
+ }
}
}
bind Text <Delete> {
- if {[tk::TextCursorInSelection %W]} {
- %W delete sel.first sel.last
- } else {
- if {[%W compare end != insert+1c]} {
- %W delete insert
+ if {[%W cget -state] eq "normal"} {
+ if {[tk::TextCursorInSelection %W]} {
+ tk::TextDelete %W sel.first sel.last
+ } else {
+ if {[%W compare end != insert+1i]} {
+ %W delete insert
+ }
+ %W see insert
}
- %W see insert
}
}
bind Text <BackSpace> {
- if {[tk::TextCursorInSelection %W]} {
- %W delete sel.first sel.last
- } else {
- if {[%W compare insert != 1.0]} {
- %W delete insert-1c
+ if {[%W cget -state] eq "normal"} {
+ if {[tk::TextCursorInSelection %W]} {
+ tk::TextDelete %W sel.first sel.last
+ } else {
+ if {[%W compare insert != 1.0]} {
+ # ensure that this operation is triggering "watch"
+ %W mark set insert insert-1i
+ %W delete insert
+ }
+ %W see insert
}
- %W see insert
}
}
@@ -255,7 +273,7 @@ bind Text <<SelectAll>> {
bind Text <<SelectNone>> {
%W tag remove sel 1.0 end
# An operation that clears the selection must insert an autoseparator,
- # because the selection operation may have moved the insert mark
+ # because the selection operation may have moved the insert mark.
if {[%W cget -autoseparators]} {
%W edit separator
}
@@ -270,24 +288,27 @@ bind Text <<Paste>> {
tk_textPaste %W
}
bind Text <<Clear>> {
- # Make <<Clear>> an atomic operation on the Undo stack,
- # i.e. separate it from other delete operations on either side
- if {[%W cget -autoseparators]} {
- %W edit separator
- }
- catch {%W delete sel.first sel.last}
- if {[%W cget -autoseparators]} {
- %W edit separator
+ if {[%W cget -state] eq "normal"} {
+ # Make <<Clear>> an atomic operation on the Undo stack,
+ # i.e. separate it from other delete operations on either side
+ if {[%W cget -autoseparators]} {
+ %W edit separator
+ }
+ catch { tk::TextDelete %W sel.first sel.last }
+ if {[%W cget -autoseparators]} {
+ %W edit separator
+ }
}
}
bind Text <<PasteSelection>> {
- if {$tk_strictMotif || ![info exists tk::Priv(mouseMoved)]
- || !$tk::Priv(mouseMoved)} {
+ if {$tk_strictMotif || ![info exists tk::Priv(mouseMoved)] || !$tk::Priv(mouseMoved)} {
tk::TextPasteSelection %W %x %y
}
}
bind Text <Insert> {
- catch {tk::TextInsert %W [::tk::GetSelection %W PRIMARY]}
+ if {[%W cget -state] eq "normal"} {
+ catch {tk::TextInsert %W [::tk::GetSelection %W PRIMARY]}
+ }
}
bind Text <KeyPress> {
tk::TextInsert %W %A
@@ -310,12 +331,12 @@ if {[tk windowingsystem] eq "aqua"} {
# Additional emacs-like bindings:
bind Text <Control-d> {
- if {!$tk_strictMotif && [%W compare end != insert+1c]} {
+ if {[%W cget -state] eq "normal" && !$tk_strictMotif && [%W compare end != insert+1i]} {
%W delete insert
}
}
bind Text <Control-k> {
- if {!$tk_strictMotif && [%W compare end != insert+1c]} {
+ if {[%W cget -state] eq "normal" && !$tk_strictMotif && [%W compare end != insert+1i]} {
if {[%W compare insert == {insert lineend}]} {
%W delete insert
} else {
@@ -324,9 +345,9 @@ bind Text <Control-k> {
}
}
bind Text <Control-o> {
- if {!$tk_strictMotif} {
+ if {[%W cget -state] eq "normal" && !$tk_strictMotif} {
%W insert insert \n
- %W mark set insert insert-1c
+ %W mark set insert insert-1i
}
}
bind Text <Control-t> {
@@ -336,20 +357,24 @@ bind Text <Control-t> {
}
bind Text <<Undo>> {
- # An Undo operation may remove the separator at the top of the Undo stack.
- # Then the item at the top of the stack gets merged with the subsequent changes.
- # Place separators before and after Undo to prevent this.
- if {[%W cget -autoseparators]} {
- %W edit separator
- }
- catch { %W edit undo }
- if {[%W cget -autoseparators]} {
- %W edit separator
+ if {[%W cget -state] eq "normal"} {
+ # An Undo operation may remove the separator at the top of the Undo stack.
+ # Then the item at the top of the stack gets merged with the subsequent changes.
+ # Place separators before and after Undo to prevent this.
+ if {[%W cget -autoseparators]} {
+ %W edit separator
+ }
+ catch { %W edit undo }
+ if {[%W cget -autoseparators]} {
+ %W edit separator
+ }
}
}
bind Text <<Redo>> {
- catch { %W edit redo }
+ if {[%W cget -state] eq "normal"} {
+ catch { %W edit redo }
+ }
}
bind Text <Meta-b> {
@@ -358,7 +383,7 @@ bind Text <Meta-b> {
}
}
bind Text <Meta-d> {
- if {!$tk_strictMotif && [%W compare end != insert+1c]} {
+ if {!$tk_strictMotif && [%W compare end != insert+1i]} {
%W delete insert [tk::TextNextWord %W insert]
}
}
@@ -374,26 +399,26 @@ bind Text <Meta-less> {
}
bind Text <Meta-greater> {
if {!$tk_strictMotif} {
- tk::TextSetCursor %W end-1c
+ tk::TextSetCursor %W end-1i
}
}
bind Text <Meta-BackSpace> {
- if {!$tk_strictMotif} {
- %W delete [tk::TextPrevPos %W insert tcl_startOfPreviousWord] insert
+ if {[%W cget -state] eq "normal" && !$tk_strictMotif} {
+ tk::TextDelete %W [tk::TextPrevPos %W insert tcl_startOfPreviousWord] insert
}
}
bind Text <Meta-Delete> {
- if {!$tk_strictMotif} {
- %W delete [tk::TextPrevPos %W insert tcl_startOfPreviousWord] insert
+ if {[%W cget -state] eq "normal" && !$tk_strictMotif} {
+ tk::TextDelete %W [tk::TextPrevPos %W insert tcl_startOfPreviousWord] insert
}
}
# Macintosh only bindings:
if {[tk windowingsystem] eq "aqua"} {
-bind Text <Control-v> {
- tk::TextScrollPages %W 1
-}
+ bind Text <Control-v> {
+ tk::TextScrollPages %W 1
+ }
# End of Mac only bindings
}
@@ -401,8 +426,10 @@ bind Text <Control-v> {
# A few additional bindings of my own.
bind Text <Control-h> {
- if {!$tk_strictMotif && [%W compare insert != 1.0]} {
- %W delete insert-1c
+ if {[%W cget -state] eq "normal" && !$tk_strictMotif && [%W compare insert != 1.0]} {
+ # ensure that this operation is triggering "watch"
+ %W mark set insert insert-1i
+ %W delete insert
%W see insert
}
}
@@ -425,16 +452,16 @@ set ::tk::Priv(prevPos) {}
if {[tk windowingsystem] eq "aqua"} {
bind Text <MouseWheel> {
- %W yview scroll [expr {-15 * (%D)}] pixels
+ %W yview scroll [expr {-15 * (%D)}] pixels
}
bind Text <Option-MouseWheel> {
- %W yview scroll [expr {-150 * (%D)}] pixels
+ %W yview scroll [expr {-150 * (%D)}] pixels
}
bind Text <Shift-MouseWheel> {
- %W xview scroll [expr {-15 * (%D)}] pixels
+ %W xview scroll [expr {-15 * (%D)}] pixels
}
bind Text <Shift-Option-MouseWheel> {
- %W xview scroll [expr {-150 * (%D)}] pixels
+ %W xview scroll [expr {-150 * (%D)}] pixels
}
} else {
# We must make sure that positive and negative movements are rounded
@@ -486,6 +513,24 @@ if {"x11" eq [tk windowingsystem]} {
}
}
+# ::tk::TextCursorPos --
+# Given x and y coordinates, this procedure computes the "cursor"
+# position, and returns the index of the character at this position.
+#
+# Arguments:
+# w - The text window.
+# x - X-coordinate within the window.
+# y - Y-coordinate within the window.
+
+proc ::tk::TextCursorPos {w x y} {
+ if {[$w cget -blockcursor]} {
+ # If we have a block cursor, then use the actual x-position
+ # for cursor position.
+ return [$w index @$x,$y]
+ }
+ return [TextClosestGap $w $x $y]
+}
+
# ::tk::TextClosestGap --
# Given x and y coordinates, this procedure finds the closest boundary
# between characters to the given coordinates and returns the index
@@ -499,13 +544,13 @@ if {"x11" eq [tk windowingsystem]} {
proc ::tk::TextClosestGap {w x y} {
set pos [$w index @$x,$y]
set bbox [$w bbox $pos]
- if {$bbox eq ""} {
- return $pos
+ if {[llength $bbox] == 0} {
+ return $pos
}
if {($x - [lindex $bbox 0]) < ([lindex $bbox 2]/2)} {
- return $pos
+ return $pos
}
- $w index "$pos + 1 char"
+ $w index "$pos + 1i"
}
# ::tk::TextButton1 --
@@ -519,31 +564,42 @@ proc ::tk::TextClosestGap {w x y} {
# y - The x-coordinate of the button press.
proc ::tk::TextButton1 {w x y} {
- variable ::tk::Priv
-
- set Priv(selectMode) char
- set Priv(mouseMoved) 0
- set Priv(pressX) $x
- set anchorname [tk::TextAnchor $w]
- $w mark set insert [TextClosestGap $w $x $y]
- $w mark set $anchorname insert
- # Set the anchor mark's gravity depending on the click position
- # relative to the gap
- set bbox [$w bbox [$w index $anchorname]]
- if {$x > [lindex $bbox 0]} {
- $w mark gravity $anchorname right
- } else {
- $w mark gravity $anchorname left
+ variable Priv
+ # Catch the very special case with dead peers.
+ if {![$w isdead]} {
+ set Priv(selectMode) char
+ set Priv(mouseMoved) 0
+ set Priv(pressX) $x
+ set pos [TextCursorPos $w $x $y]
+ set thisLineNo [$w lineno @last,$y]
+ if {[$w lineno $pos] ne $thisLineNo} {
+ # The button has been pressed at an x position after last character.
+ # In this case [$w index @$x,$y] is returning the start of next line,
+ # but we want the end of this line.
+ set pos "$thisLineNo.end"
+ }
+ $w mark set insert $pos
+ if {[$w cget -blockcursor]} {
+ set anchor [TextClosestGap $w $x $y]
+ } else {
+ # this is already the closest gap
+ set anchor insert
+ }
+ # Set the anchor mark's gravity depending on the click position
+ # relative to the gap.
+ set bbox [$w bbox $anchor]
+ set gravity [expr {$x > [lindex $bbox 0] ? "right" : "left"}]
+ $w mark set [TextAnchor $w] $anchor $gravity
+ if {[$w cget -state] eq "normal" && [$w cget -autoseparators]} {
+ $w edit separator
+ }
}
+
# Allow focus in any case on Windows, because that will let the
# selection be displayed even for state disabled text widgets.
- if {[tk windowingsystem] eq "win32" \
- || [$w cget -state] eq "normal"} {
+ if {[tk windowingsystem] eq "win32" || [$w cget -state] eq "normal"} {
focus $w
}
- if {[$w cget -autoseparators]} {
- $w edit separator
- }
}
# ::tk::TextSelectTo --
@@ -555,30 +611,39 @@ proc ::tk::TextButton1 {w x y} {
#
# Note that the 'anchor' is implemented programmatically using
# a text widget mark, and uses a name that will be unique for each
-# text widget (even when there are multiple peers). Currently the
-# anchor is considered private to Tk, hence the name 'tk::anchor$w'.
+# text widget (even when there are multiple peers).
#
# Arguments:
# w - The text window in which the button was pressed.
# x - Mouse x position.
# y - Mouse y position.
-set ::tk::Priv(textanchoruid) 0
-
proc ::tk::TextAnchor {w} {
variable Priv
+
if {![info exists Priv(textanchor,$w)]} {
- set Priv(textanchor,$w) tk::anchor[incr Priv(textanchoruid)]
+ # This gives us a private mark, not visible with
+ # "mark names|next|previous|..".
+ set Priv(textanchor,$w) [$w mark generate]
+ # The Tk library still has a big weakness: it's not possible to
+ # bind variables to a widget, so we use a private command for this
+ # binding; this means that the variable will be unset automatically
+ # when the widget will be destroyed. This is the only proper way to
+ # handle unique identifiers.
+ $w tk_bindvar [namespace current]::Priv(textanchor,$w)
}
return $Priv(textanchor,$w)
}
proc ::tk::TextSelectTo {w x y {extend 0}} {
- variable ::tk::Priv
-
- set anchorname [tk::TextAnchor $w]
- set cur [TextClosestGap $w $x $y]
- if {[catch {$w index $anchorname}]} {
+ variable Priv
+ if {[$w isdead]} {
+ # Catch the very special case with dead peers.
+ return
+ }
+ set anchorname [TextAnchor $w]
+ set cur [TextCursorPos $w $x $y]
+ if {![$w mark exists $anchorname]} {
$w mark set $anchorname $cur
}
set anchor [$w index $anchorname]
@@ -596,24 +661,31 @@ proc ::tk::TextSelectTo {w x y {extend 0}} {
}
}
word {
- # Set initial range based only on the anchor (1 char min width)
- if {[$w mark gravity $anchorname] eq "right"} {
- set first $anchorname
- set last "$anchorname + 1c"
+ set first [$w index @$x,$y]
+ set isEmbedded [expr {[string length [$w get $first]] == 0}]
+ if {$isEmbedded} {
+ # Don't extend the range if we have an embedded item at this position
+ set last "$first+1i"
} else {
- set first "$anchorname - 1c"
- set last $anchorname
- }
- # Extend range (if necessary) based on the current point
- if {[$w compare $cur < $first]} {
- set first $cur
- } elseif {[$w compare $cur > $last]} {
- set last $cur
+ # Set initial range based only on the anchor (1 char min width)
+ if {[$w mark gravity $anchorname] eq "right"} {
+ set first $anchorname
+ set last "$anchorname + 1i"
+ } else {
+ set first "$anchorname - 1i"
+ set last $anchorname
+ }
+ # Extend range (if necessary) based on the current point
+ if {[$w compare $cur < $first]} {
+ set first $cur
+ } elseif {[$w compare $cur > $last]} {
+ set last $cur
+ }
+
+ # Now find word boundaries
+ set first [TextPrevPos $w "$first + 1i" tcl_wordBreakBefore]
+ set last [TextNextPos $w "$last - 1i" tcl_wordBreakAfter]
}
-
- # Now find word boundaries
- set first [TextPrevPos $w "$first + 1c" tcl_wordBreakBefore]
- set last [TextNextPos $w "$last - 1c" tcl_wordBreakAfter]
}
line {
# Set initial range based only on the anchor
@@ -627,12 +699,12 @@ proc ::tk::TextSelectTo {w x y {extend 0}} {
set last "$cur lineend"
}
set first [$w index $first]
- set last [$w index "$last + 1c"]
+ set last [$w index "$last + 1i"]
}
}
if {$Priv(mouseMoved) || ($Priv(selectMode) ne "char")} {
- $w tag remove sel 0.0 end
$w mark set insert $cur
+ $w tag remove sel 1.0 $first
$w tag add sel $first $last
$w tag remove sel $last end
update idletasks
@@ -649,11 +721,10 @@ proc ::tk::TextSelectTo {w x y {extend 0}} {
# index - The point to which the selection is to be extended.
proc ::tk::TextKeyExtend {w index} {
-
- set anchorname [tk::TextAnchor $w]
+ set anchorname [TextAnchor $w]
set cur [$w index $index]
- if {[catch {$w index $anchorname}]} {
- $w mark set $anchorname $cur
+ if {![$w mark exists $anchorname]} {
+ $w mark set $anchorname $cur left
}
set anchor [$w index $anchorname]
if {[$w compare $cur < $anchorname]} {
@@ -663,7 +734,7 @@ proc ::tk::TextKeyExtend {w index} {
set first $anchorname
set last $cur
}
- $w tag remove sel 0.0 $first
+ $w tag remove sel 1.0 $first
$w tag add sel $first $last
$w tag remove sel $last end
}
@@ -677,18 +748,9 @@ proc ::tk::TextKeyExtend {w index} {
# x, y - Position of the mouse.
proc ::tk::TextPasteSelection {w x y} {
- $w mark set insert [TextClosestGap $w $x $y]
- if {![catch {::tk::GetSelection $w PRIMARY} sel]} {
- set oldSeparator [$w cget -autoseparators]
- if {$oldSeparator} {
- $w configure -autoseparators 0
- $w edit separator
- }
- $w insert insert $sel
- if {$oldSeparator} {
- $w edit separator
- $w configure -autoseparators 1
- }
+ if {[$w cget -state] eq "normal"} {
+ $w mark set insert [TextCursorPos $w $x $y]
+ TextInsertSelection $w PRIMARY
}
if {[$w cget -state] eq "normal"} {
focus $w
@@ -707,7 +769,7 @@ proc ::tk::TextPasteSelection {w x y} {
# w - The text window.
proc ::tk::TextAutoScan {w} {
- variable ::tk::Priv
+ variable Priv
if {![winfo exists $w]} {
return
}
@@ -723,7 +785,7 @@ proc ::tk::TextAutoScan {w} {
return
}
TextSelectTo $w $Priv(x) $Priv(y)
- set Priv(afterId) [after 50 [list tk::TextAutoScan $w]]
+ set Priv(afterId) [after 50 [list ::tk::TextAutoScan $w]]
}
# ::tk::TextSetCursor
@@ -738,7 +800,7 @@ proc ::tk::TextAutoScan {w} {
proc ::tk::TextSetCursor {w pos} {
if {[$w compare $pos == end]} {
- set pos {end - 1 chars}
+ set pos {end - 1i}
}
$w mark set insert $pos
$w tag remove sel 1.0 end
@@ -759,8 +821,12 @@ proc ::tk::TextSetCursor {w pos} {
# actually been moved to this position yet).
proc ::tk::TextKeySelect {w new} {
- set anchorname [tk::TextAnchor $w]
- if {[$w tag nextrange sel 1.0 end] eq ""} {
+ if {[$w isdead]} {
+ # Catch the very special case with dead peers.
+ return
+ }
+ set anchorname [TextAnchor $w]
+ if {[llength [$w tag nextrange sel 1.0 end]] == 0} {
if {[$w compare $new < insert]} {
$w tag add sel $new insert
} else {
@@ -799,14 +865,14 @@ proc ::tk::TextKeySelect {w new} {
# which end of selection should be used as anchor point.
proc ::tk::TextResetAnchor {w index} {
- if {[$w tag ranges sel] eq ""} {
+ if {[llength [$w tag ranges sel]] == 0} {
# Don't move the anchor if there is no selection now; this
# makes the widget behave "correctly" when the user clicks
# once, then shift-clicks somewhere -- ie, the area between
# the two clicks will be selected. [Bug: 5929].
return
}
- set anchorname [tk::TextAnchor $w]
+ set anchorname [TextAnchor $w]
set a [$w index $index]
set b [$w index sel.first]
set c [$w index sel.last]
@@ -821,7 +887,7 @@ proc ::tk::TextResetAnchor {w index} {
scan $a "%d.%d" lineA chA
scan $b "%d.%d" lineB chB
scan $c "%d.%d" lineC chC
- if {$lineB < $lineC+2} {
+ if {$lineB < $lineC + 2} {
set total [string length [$w get $b $c]]
if {$total <= 2} {
return
@@ -833,7 +899,7 @@ proc ::tk::TextResetAnchor {w index} {
}
return
}
- if {($lineA-$lineB) < ($lineC-$lineA)} {
+ if {$lineA - $lineB < $lineC - $lineA} {
$w mark set $anchorname sel.last
} else {
$w mark set $anchorname sel.first
@@ -848,8 +914,7 @@ proc ::tk::TextResetAnchor {w index} {
# w - The text widget whose selection is to be checked
proc ::tk::TextCursorInSelection {w} {
- expr {
- [llength [$w tag ranges sel]]
+ expr {[llength [$w tag ranges sel]]
&& [$w compare sel.first <= insert]
&& [$w compare sel.last >= insert]
}
@@ -865,25 +930,20 @@ proc ::tk::TextCursorInSelection {w} {
# s - The string to insert (usually just a single character)
proc ::tk::TextInsert {w s} {
- if {$s eq "" || [$w cget -state] eq "disabled"} {
+ if {[string length $s] == 0 || [$w cget -state] ne "normal"} {
return
}
- set compound 0
if {[TextCursorInSelection $w]} {
- set oldSeparator [$w cget -autoseparators]
- if {$oldSeparator} {
- $w configure -autoseparators 0
+ if {[$w cget -autoseparators]} {
$w edit separator
- set compound 1
}
- $w delete sel.first sel.last
+ # ensure that this operation is triggering "watch"
+ $w mark set insert sel.first
+ $w replace insert sel.last $s
+ } else {
+ $w insert insert $s
}
- $w insert insert $s
$w see insert
- if {$compound && $oldSeparator} {
- $w edit separator
- $w configure -autoseparators 1
- }
}
# ::tk::TextUpDownLine --
@@ -892,25 +952,24 @@ proc ::tk::TextInsert {w s} {
# maintain the original x position across repeated operations, even though
# some lines that will get passed through don't have enough characters to
# cover the original column. Second, don't try to scroll past the
-# beginning or end of the text.
+# beginning or end of the text if we don't select.
#
# Arguments:
# w - The text window in which the cursor is to move.
# n - The number of display lines to move: -1 for up one line,
# +1 for down one line.
+# sel Boolean value whether we are selecting text.
-proc ::tk::TextUpDownLine {w n} {
- variable ::tk::Priv
+proc ::tk::TextUpDownLine {w n {sel no}} {
+ variable Priv
set i [$w index insert]
if {$Priv(prevPos) ne $i} {
set Priv(textPosOrig) $i
}
set lines [$w count -displaylines $Priv(textPosOrig) $i]
- set new [$w index \
- "$Priv(textPosOrig) + [expr {$lines + $n}] displaylines"]
- if {[$w compare $new == end] \
- || [$w compare $new == "insert display linestart"]} {
+ set new [$w index "$Priv(textPosOrig) + [expr {$lines + $n}] displaylines"]
+ if {!$sel && ([$w compare $new == end] || [$w compare $new == "insert display linestart"])} {
set new $i
}
set Priv(prevPos) $new
@@ -929,17 +988,16 @@ proc ::tk::TextUpDownLine {w n} {
proc ::tk::TextPrevPara {w pos} {
set pos [$w index "$pos linestart"]
while {1} {
- if {([$w get "$pos - 1 line"] eq "\n" && ([$w get $pos] ne "\n")) \
- || $pos eq "1.0"} {
- if {[regexp -indices -- {^[ \t]+(.)} \
- [$w get $pos "$pos lineend"] -> index]} {
+ set newPos [$w index "$pos - 1 line"]
+ if {([$w get $newPos] eq "\n" && ([$w get $pos] ne "\n")) || [$w compare $pos == 1.0]} {
+ if {[regexp -indices -- {^[ \t]+(.)} [$w get $pos "$pos lineend"] -> index]} {
set pos [$w index "$pos + [lindex $index 0] chars"]
}
- if {[$w compare $pos != insert] || [lindex [split $pos .] 0]==1} {
+ if {[$w compare $pos != insert] || [$w compare [$w index "$pos linestart"] == 1.0]} {
return $pos
}
}
- set pos [$w index "$pos - 1 line"]
+ set pos $newPos
}
}
@@ -956,18 +1014,17 @@ proc ::tk::TextNextPara {w start} {
set pos [$w index "$start linestart + 1 line"]
while {[$w get $pos] ne "\n"} {
if {[$w compare $pos == end]} {
- return [$w index "end - 1c"]
+ return [$w index "end - 1i"]
}
set pos [$w index "$pos + 1 line"]
}
while {[$w get $pos] eq "\n"} {
set pos [$w index "$pos + 1 line"]
if {[$w compare $pos == end]} {
- return [$w index "end - 1c"]
+ return [$w index "end - 1i"]
}
}
- if {[regexp -indices -- {^[ \t]+(.)} \
- [$w get $pos "$pos lineend"] -> index]} {
+ if {[regexp -indices -- {^[ \t]+(.)} [$w get $pos "$pos lineend"] -> index]} {
return [$w index "$pos + [lindex $index 0] chars"]
}
return $pos
@@ -988,7 +1045,7 @@ proc ::tk::TextNextPara {w start} {
proc ::tk::TextScrollPages {w count} {
set bbox [$w bbox insert]
$w yview scroll $count pages
- if {$bbox eq ""} {
+ if {[llength $bbox] == 0} {
return [$w index @[expr {[winfo height $w]/2}],0]
}
return [$w index @[lindex $bbox 0],[lindex $bbox 1]]
@@ -1005,27 +1062,21 @@ proc ::tk::TextScrollPages {w count} {
# w - Text window in which to transpose.
proc ::tk::TextTranspose w {
- set pos insert
- if {[$w compare $pos != "$pos lineend"]} {
- set pos [$w index "$pos + 1 char"]
- }
- set new [$w get "$pos - 1 char"][$w get "$pos - 2 char"]
- if {[$w compare "$pos - 1 char" == 1.0]} {
+ if {[$w cget -state] ne "normal" || [$w compare insert == 1.0]} {
return
}
- # ensure this is seen as an atomic op to undo
- set autosep [$w cget -autoseparators]
- if {$autosep} {
- $w configure -autoseparators 0
- $w edit separator
- }
- $w delete "$pos - 2 char" $pos
- $w insert insert $new
+ set pos insert
+ if {[$w compare insert != "insert lineend"]} {
+ append pos +1i
+ }
+ set pos [$w index $pos]
+ # ensure that this operation is triggering "watch"
+ set insPos [$w index insert]
+ $w mark set insert ${pos}-2c
+ set new [$w get insert+1i][$w get insert]
+ $w replace insert $pos $new
+ $w mark set insert $insPos
$w see insert
- if {$autosep} {
- $w edit separator
- $w configure -autoseparators $autosep
- }
}
# ::tk_textCopy --
@@ -1052,16 +1103,17 @@ proc ::tk_textCopy w {
proc ::tk_textCut w {
if {![catch {set data [$w get sel.first sel.last]}]} {
- # make <<Cut>> an atomic operation on the Undo stack,
- # i.e. separate it from other delete operations on either side
- set oldSeparator [$w cget -autoseparators]
- if {$oldSeparator} {
+ # make <<Cut>> an atomic operation on the Undo stack,
+ # i.e. separate it from other delete operations on either side
+ if {[$w cget -autoseparators]} {
$w edit separator
}
clipboard clear -displayof $w
clipboard append -displayof $w $data
- $w delete sel.first sel.last
- if {$oldSeparator} {
+ if {[$w cget -state] eq "normal"} {
+ ::tk::TextDelete $w sel.first sel.last
+ }
+ if {[$w cget -autoseparators]} {
$w edit separator
}
}
@@ -1075,20 +1127,8 @@ proc ::tk_textCut w {
# w - Name of a text widget.
proc ::tk_textPaste w {
- if {![catch {::tk::GetSelection $w CLIPBOARD} sel]} {
- set oldSeparator [$w cget -autoseparators]
- if {$oldSeparator} {
- $w configure -autoseparators 0
- $w edit separator
- }
- if {[tk windowingsystem] ne "x11"} {
- catch { $w delete sel.first sel.last }
- }
- $w insert insert $sel
- if {$oldSeparator} {
- $w edit separator
- $w configure -autoseparators 1
- }
+ if {[$w cget -state] eq "normal"} {
+ ::tk::TextInsertSelection $w CLIPBOARD
}
}
@@ -1104,8 +1144,7 @@ proc ::tk_textPaste w {
if {[tk windowingsystem] eq "win32"} {
proc ::tk::TextNextWord {w start} {
- TextNextPos $w [TextNextPos $w $start tcl_endOfWord] \
- tcl_startOfNextWord
+ TextNextPos $w [TextNextPos $w $start tcl_endOfWord] tcl_startOfNextWord
}
} else {
proc ::tk::TextNextWord {w start} {
@@ -1126,12 +1165,13 @@ proc ::tk::TextNextPos {w start op} {
set text ""
set cur $start
while {[$w compare $cur < end]} {
- set text $text[$w get -displaychars $cur "$cur lineend + 1c"]
+ set end [$w index "$cur lineend + 1i"]
+ append text [$w get -displaychars $cur $end]
set pos [$op $text 0]
if {$pos >= 0} {
return [$w index "$start + $pos display chars"]
}
- set cur [$w index "$cur lineend +1c"]
+ set cur $end
}
return end
}
@@ -1147,16 +1187,18 @@ proc ::tk::TextNextPos {w start op} {
proc ::tk::TextPrevPos {w start op} {
set text ""
+ set succ ""
set cur $start
- while {[$w compare $cur > 0.0]} {
- set text [$w get -displaychars "$cur linestart - 1c" $cur]$text
+ while {[$w compare $cur > 1.0]} {
+ append text [$w get -displaychars "$cur linestart - 1i" $cur] $succ
set pos [$op $text end]
if {$pos >= 0} {
- return [$w index "$cur linestart - 1c + $pos display chars"]
+ return [$w index "$cur linestart - 1i + $pos display chars"]
}
- set cur [$w index "$cur linestart - 1c"]
+ set cur [$w index "$cur linestart - 1i"]
+ set succ $text
}
- return 0.0
+ return 1.0
}
# ::tk::TextScanMark --
@@ -1169,7 +1211,7 @@ proc ::tk::TextPrevPos {w start op} {
# y - y location on screen
proc ::tk::TextScanMark {w x y} {
- variable ::tk::Priv
+ variable Priv
$w scan mark $x $y
set Priv(x) $x
set Priv(y) $y
@@ -1186,7 +1228,7 @@ proc ::tk::TextScanMark {w x y} {
# y - y location on screen
proc ::tk::TextScanDrag {w x y} {
- variable ::tk::Priv
+ variable Priv
# Make sure these exist, as some weird situations can trigger the
# motion binding without the initial press. [Bug #220269]
if {![info exists Priv(x)]} {
@@ -1195,7 +1237,7 @@ proc ::tk::TextScanDrag {w x y} {
if {![info exists Priv(y)]} {
set Priv(y) $y
}
- if {($x != $Priv(x)) || ($y != $Priv(y))} {
+ if {$x != $Priv(x) || $y != $Priv(y)} {
set Priv(mouseMoved) 1
}
if {[info exists Priv(mouseMoved)] && $Priv(mouseMoved)} {
@@ -1203,98 +1245,130 @@ proc ::tk::TextScanDrag {w x y} {
}
}
-# ::tk::TextUndoRedoProcessMarks --
+# ::tk::TextDelete --
#
-# This proc is executed after an undo or redo action.
-# It processes the list of undo/redo marks temporarily set in the
-# text widget to positions delimiting where changes happened, and
-# returns a flat list of ranges. The temporary marks are removed
-# from the text widget.
+# Delete the characters in given range.
+# Ensure that "watch" will be triggered, and consider
+# that "insert" may be involved in the given range.
+# This implementation avoids unnecessary mappings of indices.
+
+proc ::tk::TextDelete {w start end} {
+ # Remember old positions, use temporary marks ('mark generate'),
+ # take into account that $end may refer "insert" mark.
+ $w mark set [set insPos [$w mark generate]] insert
+ $w mark set [set endPos [$w mark generate]] $end
+ $w mark set insert $start
+ $w delete insert $endPos
+ $w mark set insert $insPos
+ $w mark unset $insPos
+ $w mark unset $endPos
+}
+
+# ::tk::TextInsertSelection --
+# This procedure inserts the selection.
#
# Arguments:
-# w - The text widget
-
-proc ::tk::TextUndoRedoProcessMarks {w} {
- set indices {}
- set undoMarks {}
-
- # only consider the temporary marks set by an undo/redo action
- foreach mark [$w mark names] {
- if {[string range $mark 0 11] eq "tk::undoMark"} {
- lappend undoMarks $mark
- }
- }
-
- # transform marks into indices
- # the number of undo/redo marks is always even, each right mark
- # completes a left mark to give a range
- # this is true because:
- # - undo/redo only deals with insertions and deletions of text
- # - insertions may move marks but not delete them
- # - when deleting text, marks located inside the deleted range
- # are not erased but moved to the start of the deletion range
- # . this is done in TkBTreeDeleteIndexRange ("This segment
- # refuses to die...")
- # . because MarkDeleteProc does nothing else than returning
- # a value indicating that marks are not deleted by this
- # deleteProc
- # . mark deletion rather happen through [.text mark unset xxx]
- # which was not used _up to this point of the code_ (it
- # is a bit later just before exiting the present proc)
- set nUndoMarks [llength $undoMarks]
- set n [expr {$nUndoMarks / 2}]
- set undoMarks [lsort -dictionary $undoMarks]
- set Lmarks [lrange $undoMarks 0 [expr {$n - 1}]]
- set Rmarks [lrange $undoMarks $n [llength $undoMarks]]
- foreach Lmark $Lmarks Rmark $Rmarks {
- lappend indices [$w index $Lmark] [$w index $Rmark]
- $w mark unset $Lmark $Rmark
- }
-
- # process ranges to:
- # - remove those already fully included in another range
- # - merge overlapping ranges
- set ind [lsort -dictionary -stride 2 $indices]
- set indices {}
-
- for {set i 0} {$i < $nUndoMarks} {incr i 2} {
- set il1 [lindex $ind $i]
- set ir1 [lindex $ind [expr {$i + 1}]]
- lappend indices $il1 $ir1
-
- for {set j [expr {$i + 2}]} {$j < $nUndoMarks} {incr j 2} {
- set il2 [lindex $ind $j]
- set ir2 [lindex $ind [expr {$j + 1}]]
-
- if {[$w compare $il2 > $ir1]} {
- # second range starts after the end of first range
- # -> further second ranges do not need to be considered
- # because ranges were sorted by increasing first index
- set j $nUndoMarks
-
- } else {
- if {[$w compare $ir2 > $ir1]} {
- # second range overlaps first range
- # -> merge them into a single range
- set indices [lreplace $indices end-1 end]
- lappend indices $il1 $ir2
-
- } else {
- # second range is fully included in first range
- # -> ignore it
-
- }
- # in both cases above, the second range shall be
- # trimmed out from the list of ranges
- set ind [lreplace $ind $j [expr {$j + 1}]]
- incr j -2
- incr nUndoMarks -2
-
- }
-
- }
-
- }
-
- return $indices
+# w - The text window.
+# x, y - Position of the mouse.
+# selection atom name of the selection
+
+proc ::tk::TextInsertSelection {w selection} {
+ if {[catch {GetSelection $w $selection} sel]} {
+ return
+ }
+ set oldSeparator [$w cget -autoseparators]
+ if {$oldSeparator} {
+ $w configure -autoseparators 0
+ $w edit separator
+ }
+ if {$selection eq "CLIPBOARD" && [tk windowingsystem] ne "x11"} {
+ catch { TextDelete $w sel.first sel.last }
+ }
+ $w insert insert $sel
+ if {$oldSeparator} {
+ $w edit separator
+ $w configure -autoseparators 1
+ }
+}
+
+# ::tk_textInsert --
+# This procedure supports the insertion of text with hyphen information.
+#
+# Arguments:
+# w - The text window.
+# args - Arguments for text insertion.
+
+proc ::tk_textInsert {w args} {
+ # Use an internal command:
+ uplevel [list $w tk_textInsert {*}$args]
}
+
+# ::tk_textReplace --
+# This procedure supports the replacement of text with hyphen information.
+#
+# Arguments:
+# w - The text window.
+# args - Arguments for text insertion.
+
+proc ::tk_textReplace {w args} {
+ # Use an internal command:
+ uplevel [list $w tk_textReplace {*}$args]
+}
+
+# ::tk_mergeRange --
+# This procedure is merging a range into a sorted list of ranges.
+# If given range is adjacent to, or intersecting a range in given
+# list, then it will be amalgamated.
+#
+# Arguments:
+# rangeListVar - Name of variable containing the list of ranges.
+# newRange - New range which should be merged into given list.
+
+proc tk_mergeRange {rangeListVar newRange} {
+ upvar $rangeListVar ranges
+
+ if {![info exists ranges]} {
+ lappend ranges $newRange
+ return $ranges
+ }
+
+ lassign $newRange s e
+ lassign [split $s .] sline scol
+ lassign [split $e .] eline ecol
+ set newRangeList {}
+ set n [llength $ranges]
+
+ for {set i 0} {$i < $n} {incr i} {
+ set range [lindex $ranges $i]
+ lassign $range s1 e1
+ lassign [split $s1 .] sline1 scol1
+ lassign [split $e1 .] eline1 ecol1
+
+ # [$w compare "$e+1i" < $s1]
+ if {$eline < $sline1 || ($eline == $sline1 && $ecol + 1 < $scol1)} {
+ lappend newRangeList [list $s $e]
+ lappend newRangeList {*}[lrange $ranges $i end]
+ set ranges $newRangeList
+ return $newRangeList
+ }
+ # [$w compare $s <= "$e1+1i"]
+ if {$sline < $eline1 || ($sline == $eline1 && $scol <= $ecol1 + 1)} {
+ # [$w compare $s > $s1]
+ if {$sline > $sline1 || ($sline == $sline1 && $scol > $scol1)} {
+ set s $s1; set sline $sline1; set scol $scol1
+ }
+ # [$w compare $e < $e1]
+ if {$eline < $eline1 || ($eline == $eline1 && $ecol < $ecol1)} {
+ set e $e1; set eline $eline1; set ecol $ecol1
+ }
+ } else {
+ lappend newRangeList $range
+ }
+ }
+
+ lappend newRangeList [list $s $e]
+ set ranges $newRangeList
+ return $newRangeList
+}
+
+# vi:set ts=8 sw=4:
diff --git a/tests/text.test b/tests/text.test
index 84ed50e..ad2a0de 100644
--- a/tests/text.test
+++ b/tests/text.test
@@ -18,9 +18,11 @@ wm withdraw .
wm minsize . 1 1
wm positionfrom . user
wm deiconify .
+
+text .t; .t debug on; destroy .t
test text-1.1 {configuration option: "autoseparators"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -39,7 +41,7 @@ test text-1.1b {configuration option: "autoseparators", default} -setup {
destroy .t
} -result {1}
test text-1.2 {configuration option: "autoseparators"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -48,7 +50,7 @@ test text-1.2 {configuration option: "autoseparators"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.3 {configuration option: "background"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -58,7 +60,7 @@ test text-1.3 {configuration option: "background"} -setup {
destroy .t
} -result {#ff00ff}
test text-1.4 {configuration option: "background"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -67,7 +69,7 @@ test text-1.4 {configuration option: "background"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.5 {configuration option: "bd"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -77,7 +79,7 @@ test text-1.5 {configuration option: "bd"} -setup {
destroy .t
} -result {4}
test text-1.6 {configuration option: "bd"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -86,7 +88,7 @@ test text-1.6 {configuration option: "bd"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.7 {configuration option: "bg"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -96,7 +98,7 @@ test text-1.7 {configuration option: "bg"} -setup {
destroy .t
} -result {blue}
test text-1.8 {configuration option: "bg"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -105,7 +107,7 @@ test text-1.8 {configuration option: "bg"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.9 {configuration option: "blockcursor"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -115,7 +117,7 @@ test text-1.9 {configuration option: "blockcursor"} -setup {
destroy .t
} -result {0}
test text-1.10 {configuration option: "blockcursor"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -124,7 +126,7 @@ test text-1.10 {configuration option: "blockcursor"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.11 {configuration option: "borderwidth"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -143,7 +145,7 @@ test text-1.12 {configuration option: "borderwidth"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.13 {configuration option: "cursor"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -153,7 +155,7 @@ test text-1.13 {configuration option: "cursor"} -setup {
destroy .t
} -result {watch}
test text-1.14 {configuration option: "cursor"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -162,7 +164,7 @@ test text-1.14 {configuration option: "cursor"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.15 {configuration option: "exportselection"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -172,7 +174,7 @@ test text-1.15 {configuration option: "exportselection"} -setup {
destroy .t
} -result {0}
test text-1.16 {configuration option: "exportselection"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -181,7 +183,7 @@ test text-1.16 {configuration option: "exportselection"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.17 {configuration option: "fg"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -191,7 +193,7 @@ test text-1.17 {configuration option: "fg"} -setup {
destroy .t
} -result {red}
test text-1.18 {configuration option: "fg"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -200,7 +202,7 @@ test text-1.18 {configuration option: "fg"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.19 {configuration option: "font"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -210,7 +212,7 @@ test text-1.19 {configuration option: "font"} -setup {
destroy .t
} -result {fixed}
test text-1.20 {configuration option: "font"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -219,7 +221,7 @@ test text-1.20 {configuration option: "font"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.21 {configuration option: "foreground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -229,7 +231,7 @@ test text-1.21 {configuration option: "foreground"} -setup {
destroy .t
} -result {#012}
test text-1.22 {configuration option: "foreground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -238,7 +240,7 @@ test text-1.22 {configuration option: "foreground"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.23 {configuration option: "height"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -248,7 +250,7 @@ test text-1.23 {configuration option: "height"} -setup {
destroy .t
} -result {5}
test text-1.24 {configuration option: "height"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -257,7 +259,7 @@ test text-1.24 {configuration option: "height"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.25 {configuration option: "highlightbackground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -267,7 +269,7 @@ test text-1.25 {configuration option: "highlightbackground"} -setup {
destroy .t
} -result {#123}
test text-1.26 {configuration option: "highlightbackground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -276,7 +278,7 @@ test text-1.26 {configuration option: "highlightbackground"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.27 {configuration option: "highlightcolor"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -286,7 +288,7 @@ test text-1.27 {configuration option: "highlightcolor"} -setup {
destroy .t
} -result {#234}
test text-1.28 {configuration option: "highlightcolor"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -295,7 +297,7 @@ test text-1.28 {configuration option: "highlightcolor"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.29 {configuration option: "highlightthickness"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -305,7 +307,7 @@ test text-1.29 {configuration option: "highlightthickness"} -setup {
destroy .t
} -result {0}
test text-1.30 {configuration option: "highlightthickness"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -314,7 +316,7 @@ test text-1.30 {configuration option: "highlightthickness"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.31 {configuration option: "inactiveselectbackground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -324,7 +326,7 @@ test text-1.31 {configuration option: "inactiveselectbackground"} -setup {
destroy .t
} -result {#ffff01234567}
test text-1.32 {configuration option: "inactiveselectbackground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -333,7 +335,7 @@ test text-1.32 {configuration option: "inactiveselectbackground"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.33 {configuration option: "insertbackground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -343,7 +345,7 @@ test text-1.33 {configuration option: "insertbackground"} -setup {
destroy .t
} -result {green}
test text-1.34 {configuration option: "insertbackground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -352,7 +354,7 @@ test text-1.34 {configuration option: "insertbackground"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.35 {configuration option: "insertborderwidth"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -362,7 +364,7 @@ test text-1.35 {configuration option: "insertborderwidth"} -setup {
destroy .t
} -result {45}
test text-1.36 {configuration option: "insertborderwidth"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -371,7 +373,7 @@ test text-1.36 {configuration option: "insertborderwidth"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.37 {configuration option: "insertofftime"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -381,7 +383,7 @@ test text-1.37 {configuration option: "insertofftime"} -setup {
destroy .t
} -result {100}
test text-1.38 {configuration option: "insertofftime"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -390,7 +392,7 @@ test text-1.38 {configuration option: "insertofftime"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.39 {configuration option: "insertontime"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -400,7 +402,7 @@ test text-1.39 {configuration option: "insertontime"} -setup {
destroy .t
} -result {47}
test text-1.40 {configuration option: "insertontime"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -409,7 +411,7 @@ test text-1.40 {configuration option: "insertontime"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.41 {configuration option: "insertwidth"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -419,7 +421,7 @@ test text-1.41 {configuration option: "insertwidth"} -setup {
destroy .t
} -result {2}
test text-1.42 {configuration option: "insertwidth"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -428,7 +430,7 @@ test text-1.42 {configuration option: "insertwidth"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.43 {configuration option: "maxundo"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -447,7 +449,7 @@ test text-1.43b {configuration option: "maxundo", default} -setup {
destroy .t
} -result {0}
test text-1.44 {configuration option: "maxundo"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -456,7 +458,7 @@ test text-1.44 {configuration option: "maxundo"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.45 {configuration option: "padx"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -466,7 +468,7 @@ test text-1.45 {configuration option: "padx"} -setup {
destroy .t
} -result {3}
test text-1.46 {configuration option: "padx"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -475,7 +477,7 @@ test text-1.46 {configuration option: "padx"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.47 {configuration option: "pady"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -485,7 +487,7 @@ test text-1.47 {configuration option: "pady"} -setup {
destroy .t
} -result {82}
test text-1.48 {configuration option: "pady"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -494,7 +496,7 @@ test text-1.48 {configuration option: "pady"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.49 {configuration option: "relief"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -504,7 +506,7 @@ test text-1.49 {configuration option: "relief"} -setup {
destroy .t
} -result {raised}
test text-1.50 {configuration option: "relief"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -513,7 +515,7 @@ test text-1.50 {configuration option: "relief"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.51 {configuration option: "selectbackground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -523,7 +525,7 @@ test text-1.51 {configuration option: "selectbackground"} -setup {
destroy .t
} -result {#ffff01234567}
test text-1.52 {configuration option: "selectbackground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -532,7 +534,7 @@ test text-1.52 {configuration option: "selectbackground"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.53 {configuration option: "selectborderwidth"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -542,7 +544,7 @@ test text-1.53 {configuration option: "selectborderwidth"} -setup {
destroy .t
} -result {21}
test text-1.54 {configuration option: "selectborderwidth"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -551,7 +553,7 @@ test text-1.54 {configuration option: "selectborderwidth"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.55 {configuration option: "selectforeground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -561,7 +563,7 @@ test text-1.55 {configuration option: "selectforeground"} -setup {
destroy .t
} -result {yellow}
test text-1.56 {configuration option: "selectforeground"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -570,7 +572,7 @@ test text-1.56 {configuration option: "selectforeground"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.57 {configuration option: "spacing1"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -580,7 +582,7 @@ test text-1.57 {configuration option: "spacing1"} -setup {
destroy .t
} -result {20}
test text-1.58 {configuration option: "spacing1"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -589,7 +591,7 @@ test text-1.58 {configuration option: "spacing1"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.59 {configuration option: "spacing1"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -599,7 +601,7 @@ test text-1.59 {configuration option: "spacing1"} -setup {
destroy .t
} -result {0}
test text-1.60 {configuration option: "spacing1"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -608,7 +610,7 @@ test text-1.60 {configuration option: "spacing1"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.61 {configuration option: "spacing2"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -618,7 +620,7 @@ test text-1.61 {configuration option: "spacing2"} -setup {
destroy .t
} -result {5}
test text-1.62 {configuration option: "spacing2"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -627,7 +629,7 @@ test text-1.62 {configuration option: "spacing2"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.63 {configuration option: "spacing2"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -637,7 +639,7 @@ test text-1.63 {configuration option: "spacing2"} -setup {
destroy .t
} -result {0}
test text-1.64 {configuration option: "spacing2"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -646,7 +648,7 @@ test text-1.64 {configuration option: "spacing2"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.65 {configuration option: "spacing3"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -656,7 +658,7 @@ test text-1.65 {configuration option: "spacing3"} -setup {
destroy .t
} -result {20}
test text-1.66 {configuration option: "spacing3"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -665,7 +667,7 @@ test text-1.66 {configuration option: "spacing3"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.67 {configuration option: "spacing3"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -675,7 +677,7 @@ test text-1.67 {configuration option: "spacing3"} -setup {
destroy .t
} -result {0}
test text-1.68 {configuration option: "spacing3"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -684,7 +686,7 @@ test text-1.68 {configuration option: "spacing3"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.69 {configuration option: "state"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -694,7 +696,7 @@ test text-1.69 {configuration option: "state"} -setup {
destroy .t
} -result {disabled}
test text-1.70 {configuration option: "state"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -703,7 +705,7 @@ test text-1.70 {configuration option: "state"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.71 {configuration option: "tabs"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -713,7 +715,7 @@ test text-1.71 {configuration option: "tabs"} -setup {
destroy .t
} -result {1i 2i 3i 4i}
test text-1.72 {configuration option: "tabs"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -722,7 +724,7 @@ test text-1.72 {configuration option: "tabs"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.73 {configuration option: "tabstyle"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -732,7 +734,7 @@ test text-1.73 {configuration option: "tabstyle"} -setup {
destroy .t
} -result {wordprocessor}
test text-1.74 {configuration option: "tabstyle"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -741,7 +743,7 @@ test text-1.74 {configuration option: "tabstyle"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.75 {configuration option: "undo"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -760,7 +762,7 @@ test text-1.75b {configuration option: "undo", default} -setup {
destroy .t
} -result {0}
test text-1.76 {configuration option: "undo"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -769,7 +771,7 @@ test text-1.76 {configuration option: "undo"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.77 {configuration option: "width"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -779,7 +781,7 @@ test text-1.77 {configuration option: "width"} -setup {
destroy .t
} -result {73}
test text-1.78 {configuration option: "width"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -788,7 +790,7 @@ test text-1.78 {configuration option: "width"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.79 {configuration option: "wrap"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -798,7 +800,7 @@ test text-1.79 {configuration option: "wrap"} -setup {
destroy .t
} -result {word}
test text-1.80 {configuration option: "wrap"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -807,7 +809,7 @@ test text-1.80 {configuration option: "wrap"} -setup {
destroy .t
} -match glob -returnCodes {error} -result {*}
test text-1.81 {text options} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -817,7 +819,7 @@ test text-1.81 {text options} -setup {
destroy .t
} -result {any old thing}
test text-1.82 {text options} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -827,7 +829,7 @@ test text-1.82 {text options} -setup {
destroy .t
} -result {-xscrollcommand xScrollCommand ScrollCommand {} {x scroll command}}
test text-1.83 {text options} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -837,7 +839,7 @@ test text-1.83 {text options} -setup {
destroy .t
} -result {-yscrollcommand yScrollCommand ScrollCommand {} {test command}}
test text-1.83.1 {configuration option: "insertunfocussed"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -847,7 +849,7 @@ test text-1.83.1 {configuration option: "insertunfocussed"} -setup {
destroy .t
} -result none
test text-1.84 {configuration option: "insertunfocussed"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -857,7 +859,7 @@ test text-1.84 {configuration option: "insertunfocussed"} -setup {
destroy .t
} -result hollow
test text-1.85 {configuration option: "insertunfocussed"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -body {
@@ -867,7 +869,7 @@ test text-1.85 {configuration option: "insertunfocussed"} -setup {
destroy .t
} -result solid
test text-1.86 {configuration option: "insertunfocussed"} -setup {
- text .t -borderwidth 2 -highlightthickness 2 -font {Courier -12 bold}
+ text .t
pack .t
update
} -returnCodes error -body {
@@ -875,6 +877,215 @@ test text-1.86 {configuration option: "insertunfocussed"} -setup {
} -cleanup {
destroy .t
} -result {bad insertunfocussed "gorp": must be hollow, none, or solid}
+test text-1.87 {configuration option: "eolcolor"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -eolcolor yellow
+ .t cget -eolcolor
+} -cleanup {
+ destroy .t
+} -result yellow
+test text-1.88 {configuration option: "eolcolor"} -setup {
+ text .t
+ pack .t
+ update
+} -returnCodes error -body {
+ .t configure -eolcolor gorp
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.89 {configuration option: "hyphenrules"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -hyphenrules {ck trema}
+ .t cget -hyphenrules
+} -cleanup {
+ destroy .t
+} -result {ck trema}
+test text-1.90 {configuration option: "hyphenrules"} -setup {
+ text .t
+ pack .t
+ update
+} -returnCodes error -body {
+ .t configure -hyphenrules gorp
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.91 {configuration option: "hyphens"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -hyphens 1
+ .t cget -hyphens
+} -cleanup {
+ destroy .t
+} -result {1}
+test text-1.92 {configuration option: "hyphens"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -hyphens eh
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.93 {configuration option: "lang"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -lang en
+ .t cget -lang
+} -cleanup {
+ destroy .t
+} -result {en}
+test text-1.94 {configuration option: "lang"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -lang gorp
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.95 {configuration option: "maxundosize"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -maxundosize 1000
+ .t cget -maxundosize
+} -cleanup {
+ destroy .t
+} -result {1000}
+test text-1.96 {configuration option: "maxundosize"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -maxundosize noway
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.97 {configuration option: "responsiveness"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -responsiveness 75
+ .t cget -responsiveness
+} -cleanup {
+ destroy .t
+} -result {75}
+test text-1.98 {configuration option: "responsiveness"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -responsiveness noway
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.99 {configuration option: "showendofline"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -showendofline 1
+ .t cget -showendofline
+} -cleanup {
+ destroy .t
+} -result {1}
+test text-1.100 {configuration option: "showendofline"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -showendofline eh
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.101 {configuration option: "showinsertforeground"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -showinsertforeground 1
+ .t cget -showinsertforeground
+} -cleanup {
+ destroy .t
+} -result {1}
+test text-1.102 {configuration option: "showinsertforeground"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -showinsertforeground eh
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.103 {configuration option: "steadymarks"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -steadymarks 1
+ .t cget -steadymarks
+} -cleanup {
+ destroy .t
+} -result {1}
+test text-1.104 {configuration option: "steadymarks"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -steadymarks eh
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.105 {configuration option: "tagging"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -tagging none
+ .t cget -tagging
+} -cleanup {
+ destroy .t
+} -result {none}
+test text-1.106 {configuration option: "tagging"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -tagging eh
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
+test text-1.107 {configuration option: "useunibreak"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -useunibreak 1
+ .t cget -useunibreak
+} -cleanup {
+ destroy .t
+} -result {1}
+test text-1.108 {configuration option: "useunibreak"} -setup {
+ text .t
+ pack .t
+ update
+} -body {
+ .t configure -useunibreak eh
+} -cleanup {
+ destroy .t
+} -match glob -returnCodes {error} -result {*}
test text-2.1 {Tk_TextCmd procedure} -body {
@@ -922,9 +1133,9 @@ test text-2.8 {Tk_TextCmd procedure} -constraints {
.t tag cget sel -relief
} -cleanup {
destroy .t
-} -result {flat}
+} -result {solid}
test text-2.9 {Tk_TextCmd procedure} -constraints {
- unix notAqua
+ unix
} -body {
catch {destroy .t}
text .t
@@ -952,7 +1163,7 @@ test text-3.2 {TextWidgetCmd procedure} -setup {
.t gorp 1.0 z 1.2
} -cleanup {
destroy .t
-} -returnCodes {error} -result {bad option "gorp": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}
+} -returnCodes {error} -result {bad option "gorp": must be bbox, brks, checksum, cget, clear, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, inspect, isclean, isdead, isempty, lineno, load, mark, peer, pendingsync, replace, scan, search, see, sync, tag, watch, window, xview, or yview}
test text-4.1 {TextWidgetCmd procedure, "bbox" option} -setup {
text .t
@@ -1174,7 +1385,7 @@ Line 7"
.t co 1.0 z 1.2
} -cleanup {
destroy .t
-} -returnCodes {error} -result {ambiguous option "co": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}
+} -returnCodes {error} -result {ambiguous option "co": must be bbox, brks, checksum, cget, clear, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, inspect, isclean, isdead, isempty, lineno, load, mark, peer, pendingsync, replace, scan, search, see, sync, tag, watch, window, xview, or yview}
# "configure" option is already covered above
test text-7.1 {TextWidgetCmd procedure, "debug" option} -setup {
@@ -1182,6 +1393,7 @@ test text-7.1 {TextWidgetCmd procedure, "debug" option} -setup {
} -body {
.t debug 0 1
} -cleanup {
+ .t debug on
destroy .t
} -returnCodes {error} -result {wrong # args: should be ".t debug boolean"}
test text-7.2 {TextWidgetCmd procedure, "debug" option} -setup {
@@ -1189,8 +1401,9 @@ test text-7.2 {TextWidgetCmd procedure, "debug" option} -setup {
} -body {
.t de 0 1
} -cleanup {
+ .t debug on
destroy .t
-} -returnCodes {error} -result {ambiguous option "de": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}
+} -returnCodes {error} -result {ambiguous option "de": must be bbox, brks, checksum, cget, clear, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, inspect, isclean, isdead, isempty, lineno, load, mark, peer, pendingsync, replace, scan, search, see, sync, tag, watch, window, xview, or yview}
test text-7.3 {TextWidgetCmd procedure, "debug" option} -setup {
text .t
} -body {
@@ -1205,6 +1418,7 @@ test text-7.4 {TextWidgetCmd procedure, "debug" option} -setup {
.t debug false
.t debug
} -cleanup {
+ .t debug on
destroy .t
} -result {0}
@@ -1215,7 +1429,7 @@ test text-8.1 {TextWidgetCmd procedure, "delete" option} -setup {
.t delete
} -cleanup {
destroy .t
-} -returnCodes {error} -result {wrong # args: should be ".t delete index1 ?index2 ...?"}
+} -returnCodes {error} -result {wrong # args: should be ".t delete ?-marks? ?-inclusive? index1 ?index2 ...?"}
test text-8.2 {TextWidgetCmd procedure, "delete" option} -setup {
text .t
} -body {
@@ -1485,19 +1699,7 @@ Line 7"
rename .t {}
rename test.t .t
destroy .t
-} -result [list {edit undo} {delete 2.1 2.4} {mark set insert 2.1} {see insert} \
- {mark set tk::undoMarkL2 2.1} {mark set tk::undoMarkR2 2.4} \
- {mark gravity tk::undoMarkL2 left} {mark gravity tk::undoMarkR2 right} \
- {insert 2.1 ef} {mark set insert 2.3} {see insert} \
- {mark set tk::undoMarkL1 2.1} {mark set tk::undoMarkR1 2.3} \
- {mark gravity tk::undoMarkL1 left} {mark gravity tk::undoMarkR1 right} \
- {mark names} \
- {index tk::undoMarkL1} {index tk::undoMarkR1} \
- {mark unset tk::undoMarkL1 tk::undoMarkR1} \
- {index tk::undoMarkL2} {index tk::undoMarkR2} \
- {mark unset tk::undoMarkL2 tk::undoMarkR2} \
- {compare 2.1 > 2.3} {compare 2.6 > 2.3} ]
-
+} -result {{edit undo}}
test text-8.23 {TextWidgetCmd procedure, "replace" option with undo} -setup {
text .t
} -body {
@@ -1589,6 +1791,16 @@ test text-8.26 {TextWidgetCmd procedure, "replace" option crash} -setup {
} -cleanup {
destroy .tt
} -result {}
+test text-8.27 {"delete" tagged newline} -setup {
+ text .tt
+} -body {
+ .tt insert end \n tag1
+ .tt delete 2.0
+ # wish8.6.5 returns empty list, this is a bug
+ .tt tag ranges tag1
+} -cleanup {
+ destroy .tt
+} -result {1.0 2.0}
test text-9.1 {TextWidgetCmd procedure, "get" option} -setup {
@@ -1597,7 +1809,7 @@ test text-9.1 {TextWidgetCmd procedure, "get" option} -setup {
.t get
} -cleanup {
destroy .t
-} -returnCodes {error} -result {wrong # args: should be ".t get ?-displaychars? ?--? index1 ?index2 ...?"}
+} -returnCodes {error} -result {wrong # args: should be ".t get ?-option? ?--? index1 ?index2 ...?"}
test text-9.2 {TextWidgetCmd procedure, "get" option} -setup {
text .t
} -body {
@@ -2049,7 +2261,7 @@ test text-10.2 {TextWidgetCmd procedure, "count" option} -setup {
.t count blah 1.0 2.0
} -cleanup {
destroy .t
-} -returnCodes {error} -result {bad option "blah" must be -chars, -displaychars, -displayindices, -displaylines, -indices, -lines, -update, -xpixels, or -ypixels}
+} -returnCodes {error} -result {bad option "blah": must be -chars, -displaychars, -displayhyphens, -displayindices, -displaylines, -displaytext, -hyphens, -indices, -lines, -text, -update, -xpixels, or -ypixels}
test text-10.3 {TextWidgetCmd procedure, "count" option} -setup {
text .t
} -body {
@@ -2589,7 +2801,7 @@ test text-10.32 {TextWidgetCmd procedure, "count" option} -setup {
.t count -lines 1.0 2.0 3.0
} -cleanup {
destroy .t
-} -returnCodes {error} -result {bad option "1.0" must be -chars, -displaychars, -displayindices, -displaylines, -indices, -lines, -update, -xpixels, or -ypixels}
+} -returnCodes {error} -result {bad option "1.0": must be -chars, -displaychars, -displayhyphens, -displayindices, -displaylines, -displaytext, -hyphens, -indices, -lines, -text, -update, -xpixels, or -ypixels}
test text-10.33 {TextWidgetCmd procedure, "count" option} -setup {
text .t
} -body {
@@ -2643,7 +2855,7 @@ test text-10.37 {TextWidgetCmd procedure, "count" option} -setup {
} -result {3}
test text-10.38 {TextWidgetCmd procedure, "count" option} -setup {
text .t -font {Courier -12} -borderwidth 2 -highlightthickness 2
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
} -body {
.t configure -width 20 -height 10
update
@@ -2672,7 +2884,7 @@ test text-10.39 {TextWidgetCmd procedure, "count" option} -setup {
} -cleanup {
destroy .t
} -result {2 6 1 5}
-test text-9.2.45 {TextWidgetCmd procedure, "count" option} -setup {
+test text-10.40 {TextWidgetCmd procedure, "count" option} -setup {
text .t
pack .t
update
@@ -2688,7 +2900,7 @@ test text-9.2.45 {TextWidgetCmd procedure, "count" option} -setup {
} -cleanup {
destroy .t
} -result {0}
-test text-9.2.46 {TextWidgetCmd procedure, "count" option} -setup {
+test text-10.41 {TextWidgetCmd procedure, "count" option} -setup {
toplevel .mytop
pack [text .mytop.t -font TkFixedFont -bd 0 -padx 0 -wrap char]
set spec [font measure TkFixedFont "Line 1+++Line 1---Li"] ; # 20 chars
@@ -2710,15 +2922,17 @@ test text-9.2.46 {TextWidgetCmd procedure, "count" option} -setup {
} -cleanup {
destroy .mytop
} -result {1 3}
-test text-9.2.47 {TextWidgetCmd procedure, "count" option} -setup {
+test text-10.42 {TextWidgetCmd procedure, "count" option} -setup {
text .t
pack .t
update
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 25} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag configure hidden -elide true
.t tag add hidden 5.7 11.0
update
@@ -2739,7 +2953,7 @@ test text-9.2.47 {TextWidgetCmd procedure, "count" option} -setup {
test text-11.1 {counting with tag priority eliding} -setup {
text .t -font {Courier -12} -borderwidth 2 -highlightthickness 2
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
} -body {
.t insert end "hello"
.t configure -wrap none
@@ -2756,7 +2970,7 @@ test text-11.1 {counting with tag priority eliding} -setup {
} -result {0 1 2 3 4 5 5 6}
test text-11.2 {counting with tag priority eliding} -setup {
text .t -font {Courier -12} -borderwidth 2 -highlightthickness 2
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
} -body {
.t insert end "hello"
.t tag configure elide1 -elide 0
@@ -2868,7 +3082,7 @@ test text-11.7 {counting with tag priority eliding} -setup {
} -result {5 5}
test text-11.8 {counting with tag priority eliding} -setup {
text .t -font {Courier -12} -borderwidth 2 -highlightthickness 2
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
set res {}
} -body {
.t insert end "hello"
@@ -2894,7 +3108,7 @@ test text-11.8 {counting with tag priority eliding} -setup {
} -result {0 0 0 0 3 2 1 1}
test text-11.9 {counting with tag priority eliding} -setup {
text .t -font {Courier -12} -borderwidth 2 -highlightthickness 2
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
set res {}
} -body {
.t tag configure WELCOME -elide 1
@@ -2965,7 +3179,7 @@ test text-11a.11 {TextWidgetCmd procedure, "sync" option} -setup {
list [catch {.yt sync mytext} msg] $msg
} -cleanup {
destroy .yt
-} -result {1 {wrong # args: should be ".yt sync ?-command command?"}}
+} -result {1 {wrong # args: should be ".yt sync ?-command ?command??"}}
test text-11a.12 {TextWidgetCmd procedure, "sync" option} -setup {
destroy .top.yt .top
} -body {
@@ -3034,6 +3248,71 @@ test text-11a.22 {TextWidgetCmd procedure, "sync" option with -command} -setup {
} -cleanup {
destroy .top.yt .top
} -result {1 0 0 1 1 2}
+test text-11a.23 {TextWidgetCmd procedure, "sync" with update} -setup {
+ destroy .yt
+} -body {
+ # the original implementation gives no result
+ text .yt
+ .yt insert end "1"
+ set res {}
+ proc updateAction {} { set ::res 1 }
+ .yt sync -command updateAction
+ while {[.yt pendingsync]} {
+ update
+ }
+ set res
+} -cleanup {
+ destroy .yt
+} -result {1}
+test text-11a.24 {TextWidgetCmd procedure, succeeding "sync"'s} -setup {
+ destroy .yt
+} -body {
+ # the original implementation gives no result
+ text .yt
+ set res {}
+ proc updateAction1 {} { lappend ::res 1 }
+ proc updateAction2 {} { lappend ::res 2 }
+ .yt sync -command updateAction1
+ .yt sync -command +updateAction2
+ while {[.yt pendingsync]} {
+ update
+ }
+ set res
+} -cleanup {
+ destroy .yt
+} -result {1 2}
+test text-11a.25 {TextWidgetCmd procedure, nested "sync"'s} -setup {
+ destroy .yt
+} -body {
+ # the original implementation gives no result
+ text .yt
+ set res {}
+ proc secondaryUpdateAction {} { lappend ::res 2 }
+ proc primaryUpdateaction {} {
+ lappend ::res 1
+ .yt sync -command secondaryUpdateAction
+ }
+ .yt sync -command primaryUpdateaction
+ while {[.yt pendingsync]} {
+ update
+ }
+ set res
+} -cleanup {
+ destroy .yt
+} -result {1 2}
+test text-11a.26 {TextWidgetCmd procedure, "sync -command"} -setup {
+ destroy .yt
+} -body {
+ # the original implementation gives wrong result
+ proc Sync {} { set ::res [.yt pendingsync] }
+ text .yt
+ after 1 [list .yt sync -command Sync]
+ after 1 [list .yt insert end "a"]
+ vwait ::res
+ set res
+} -cleanup {
+ destroy .yt
+} -result {0}
test text-11a.31 {"<<WidgetViewSync>>" event} -setup {
destroy .top.yt .top
@@ -3066,6 +3345,29 @@ test text-11a.31 {"<<WidgetViewSync>>" event} -setup {
} -cleanup {
destroy .top.yt .top
} -result {1 1 1}
+test text-11a.32 {"<<WidgetViewSync>>" event} -setup {
+ destroy .yt
+} -body {
+ # The original implementation is crashing.
+ pack [text .yt]
+ bind .yt <<WidgetViewSync>> { destroy .yt }
+ .yt sync -command update
+} -cleanup {
+ destroy .yt
+} -result {}
+test text-11a.33 {"<<WidgetViewSync>>" event after "sync"} -setup {
+ destroy .yt
+} -body {
+ # The original implementation hangs.
+ pack [text .yt]
+ bind .yt <<WidgetViewSync>> { set res 1 }
+ .yt insert end "test"
+ update
+ .yt sync
+ vwait res
+} -cleanup {
+ destroy .yt
+} -result {}
test text-11a.41 {"sync" "pendingsync" and <<WidgetViewSync>>} -setup {
destroy .top.yt .top
@@ -3073,24 +3375,35 @@ test text-11a.41 {"sync" "pendingsync" and <<WidgetViewSync>>} -setup {
set res {}
toplevel .top
pack [text .top.yt]
+ # For this test it is required that the ConfigureNotify event is
+ # not messing up the sync state, see comment below (GC).
+ while {![winfo ismapped .top.yt]} {
+ update
+ }
set content {}
for {set i 1} {$i < 300} {incr i} {
append content [string repeat "$i " 50] \n
}
bind .top.yt <<WidgetViewSync>> {lappend res Sync:%d}
.top.yt insert 1.0 $content
- vwait res ; # event dealt with by the event loop, with %d==0 i.e. we're out of sync
- # ensure the test is relevant
- lappend res "Pending:[.top.yt pendingsync]"
+ # The original test did contain the lines below, but it makes no sense,
+ # because both values (Sync:0 and Sync:1) are ok (GC).
+ # vwait res ; # event dealt with by the event loop, with %d==0 i.e. we're out of sync
+ # # ensure the test is relevant
+ # lappend res "Pending:[.top.yt pendingsync]"
+ # --------------------------------------------------------------------------
# - <<WidgetViewSync>> fires when sync returns if there was pending syncs
# - there is no more any pending sync after running 'sync'
.top.yt sync
vwait res ; # event dealt with by the event loop, with %d==1 i.e. we're in sync again
lappend res "Pending:[.top.yt pendingsync]"
+ # Without waiting for Map event the last pending state can be 1, because it can happen
+ # that the Configure event will be triggered during vwait (and this is invalidating the
+ # line-heights).
set res
} -cleanup {
destroy .top.yt .top
-} -result {Sync:0 Pending:1 Sync:1 Pending:0}
+} -result {Sync:1 Pending:0}
test text-11a.51 {<<WidgetViewSync>> calls TkSendVirtualEvent(),
NOT Tk_HandleEvent().
@@ -3100,9 +3413,7 @@ test text-11a.51 {<<WidgetViewSync>> calls TkSendVirtualEvent(),
set res {}
toplevel .top
pack [text .top.t]
- for {set i 1} {$i < 10000} {incr i} {
- .top.t insert end "Hello world!\n"
- }
+ .top.t insert end [string repeat "Hello world!\n" 10000]
bind .top.t <<WidgetViewSync>> {destroy .top.t}
.top.t tag add mytag 1.5 8000.8 ; # shall not crash
update
@@ -3131,7 +3442,7 @@ test text-12.3 {TextWidgetCmd procedure, "index" option} -setup {
.t in a b
} -cleanup {
destroy .t
-} -returnCodes {error} -result {ambiguous option "in": must be bbox, cget, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, mark, peer, pendingsync, replace, scan, search, see, sync, tag, window, xview, or yview}
+} -returnCodes {error} -result {ambiguous option "in": must be bbox, brks, checksum, cget, clear, compare, configure, count, debug, delete, dlineinfo, dump, edit, get, image, index, insert, inspect, isclean, isdead, isempty, lineno, load, mark, peer, pendingsync, replace, scan, search, see, sync, tag, watch, window, xview, or yview}
test text-12.4 {TextWidgetCmd procedure, "index" option} -setup {
text .t
} -body {
@@ -3273,7 +3584,7 @@ test text-14.1 {ConfigureText procedure} -setup {
.t configure -state foobar
} -cleanup {
destroy .t
-} -returnCodes {error} -result {bad state "foobar": must be disabled or normal}
+} -returnCodes {error} -result {bad state "foobar": must be disabled, normal, or readonly}
test text-14.2 {ConfigureText procedure} -setup {
text .t
} -body {
@@ -3332,7 +3643,7 @@ test text-14.8 {ConfigureText procedure} -setup {
.t configure -wrap bogus
} -cleanup {
destroy .t
-} -returnCodes {error} -result {bad wrap "bogus": must be char, none, or word}
+} -returnCodes {error} -result {bad wrap "bogus": must be char, none, word, or codepoint}
test text-14.9 {ConfigureText procedure} -setup {
text .t -font {Courier -12} -borderwidth 2 -highlightthickness 2
} -body {
@@ -3444,7 +3755,7 @@ test text-14.18 {ConfigureText procedure} -constraints fonts -setup {
text .top.t -font {Courier -12} -borderwidth 2 -highlightthickness 2
} -body {
.top.t configure -width 20 -height 10
- pack .top.t
+ pack append .top .top.t top
update
set geom [wm geometry .top]
set x [string range $geom 0 [string first + $geom]]
@@ -3461,7 +3772,7 @@ test text-14.19 {ConfigureText procedure} -setup {
} -body {
.top.t configure -width 20 -height 10 -setgrid 1
wm overrideredirect .top 1
- pack .top.t
+ pack append .top .top.t top
wm geometry .top +0+0
update
wm geometry .top
@@ -3478,7 +3789,7 @@ test text-14.20 {ConfigureText procedure} -setup {
} -body {
.top.t configure -width 20 -height 10 -setgrid 1
wm overrideredirect .top 1
- pack .top.t
+ pack append .top .top.t top
wm geometry .top +0+0
update
set result [wm geometry .top]
@@ -3731,9 +4042,10 @@ Line 4"
list [.t tag ranges sel] [.t get 1.0 end]
} -cleanup {
destroy .t
-} -result {{1.0 3.5} {Line 1
+} -result {{1.0 4.0} {Line 1
abcde
12345
+
}}
test text-19.9 {DeleteChars procedure} -body {
text .t
@@ -3768,7 +4080,7 @@ Line 4
test text-19.11 {DeleteChars procedure} -body {
toplevel .top
text .top.t -width 20 -height 5
- pack .top.t
+ pack append .top .top.t top
wm geometry .top +0+0
.top.t insert 1.0 "abc\n123\nx\ny\nz\nq\nr\ns"
update
@@ -3780,7 +4092,7 @@ test text-19.11 {DeleteChars procedure} -body {
test text-19.12 {DeleteChars procedure} -body {
toplevel .top
text .top.t -width 20 -height 5
- pack .top.t
+ pack append .top .top.t top
wm geometry .top +0+0
.top.t insert 1.0 "abc\n123\nx\ny\nz\nq\nr\ns"
.top.t yview 3.0
@@ -3840,11 +4152,13 @@ test text-19.16 {DeleteChars procedure, updates affecting topIndex} -setup {
wm geometry .top +0+0
update
} -body {
- .top.t insert end "abc def\n01 2a345 678 9101112\nLine 3\nLine 4\nLine 5\n6\n7\n8\n"
+ # The revised version has a fixed wrapping algorithm, so "xx" has been inserted
+ # into the next string to get the same result as before.
+ .top.t insert end "abc def\n01 2345xx 678 9101112\nLine 3\nLine 4\nLine 5\n6\n7\n8\n"
.top.t yview 2.4
.top.t delete 2.5
set x [.top.t index @0,0]
- .top.t delete 2.5
+ .top.t delete 2.5 2.8
list $x [.top.t index @0,0]
} -cleanup {
destroy .top
@@ -3853,7 +4167,7 @@ test text-19.16 {DeleteChars procedure, updates affecting topIndex} -setup {
test text-20.1 {TextFetchSelection procedure} -setup {
text .t -width 20 -height 10
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
update
} -body {
foreach i {a b c d e f g h i j k l m n o p q r s t u v w x y z} {
@@ -3868,7 +4182,7 @@ b.0b.1b.2b.3b.4
c.0c}
test text-20.2 {TextFetchSelection procedure} -setup {
text .t -width 20 -height 10
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
update
} -body {
foreach i {a b c d e f g h i j k l m n o p q r s t u v w x y z} {
@@ -3888,7 +4202,7 @@ b.0b.1b.2b.3b.4
c.0c}
test text-20.3 {TextFetchSelection procedure} -setup {
text .t -width 20 -height 10
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
update
} -body {
foreach i {a b c d e f g h i j k l m n o p q r s t u v w x y z} {
@@ -3902,7 +4216,7 @@ test text-20.3 {TextFetchSelection procedure} -setup {
} -result {m}
test text-20.4 {TextFetchSelection procedure} -setup {
text .t -width 20 -height 10
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
update
} -body {
foreach i {a b c d e f g h i j k l m n o p q r s t u v w x y z} {
@@ -3922,7 +4236,7 @@ test text-20.4 {TextFetchSelection procedure} -setup {
cj.0j.1j.2j.3j.4m}
test text-20.5 {TextFetchSelection procedure, long selections} -setup {
text .t -width 20 -height 10
- pack .t -expand 1 -fill both
+ pack append . .t {top expand fill}
update
set x ""
} -body {
@@ -3937,7 +4251,7 @@ test text-20.5 {TextFetchSelection procedure, long selections} -setup {
} -result {1}
-test text-21.1 {TkTextLostSelection procedure} -constraints {x11} -setup {
+test text-21.1 {TkTextLostSelection procedure} -constraints unix -setup {
text .t
.t insert 1.0 "Line 1"
entry .t.e
@@ -3952,7 +4266,7 @@ test text-21.1 {TkTextLostSelection procedure} -constraints {x11} -setup {
} -cleanup {
destroy .t .t2
} -result {}
-test text-21.2 {TkTextLostSelection procedure} -constraints aquaOrWin32 -setup {
+test text-21.2 {TkTextLostSelection procedure} -constraints win -setup {
text .t
.t insert 1.0 "Line 1"
entry .t.e
@@ -3996,7 +4310,7 @@ test text-22.1 {TextSearchCmd procedure, argument parsing} -body {
.t search -
} -cleanup {
destroy .t
-} -returnCodes error -result {ambiguous switch "-": must be --, -all, -backwards, -count, -elide, -exact, -forwards, -nocase, -nolinestop, -overlap, -regexp, or -strictlimits}
+} -returnCodes error -result {ambiguous switch "-": must be --, -all, -backwards, -count, -discardhyphens, -elide, -exact, -forwards, -nocase, -nolinestop, -overlap, -regexp, or -strictlimits}
test text-22.2 {TextSearchCmd procedure, -backwards option} -body {
text .t
.t insert end "xxyz xyz x. the\nfoo -forward bar xxxxx BaR foo\nxyz xxyzx"
@@ -4060,7 +4374,7 @@ test text-22.10 {TextSearchCmd procedure, -n ambiguous option} -body {
.t search -n BaR 1.1
} -cleanup {
destroy .t
-} -returnCodes error -result {ambiguous switch "-n": must be --, -all, -backwards, -count, -elide, -exact, -forwards, -nocase, -nolinestop, -overlap, -regexp, or -strictlimits}
+} -returnCodes error -result {ambiguous switch "-n": must be --, -all, -backwards, -count, -discardhyphens, -elide, -exact, -forwards, -nocase, -nolinestop, -overlap, -regexp, or -strictlimits}
test text-22.11 {TextSearchCmd procedure, -nocase option} -body {
text .t
.t insert end "xxyz xyz x. the\nfoo -forward bar xxxxx BaR foo\nxyz xxyzx"
@@ -5952,7 +6266,7 @@ test text-24.1 {TextDumpCmd procedure, bad args} -body {
.t dump
} -cleanup {
destroy .t
-} -returnCodes {error} -result {Usage: .t dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?}
+} -returnCodes {error} -result {Usage: .t dump ?-all -chars -image -mark -node -tag -text -window? ?-command script? index ?index2?}
test text-24.2 {TextDumpCmd procedure, bad args} -body {
pack [text .t]
.t insert 1.0 "One Line"
@@ -5960,7 +6274,7 @@ test text-24.2 {TextDumpCmd procedure, bad args} -body {
.t dump -all
} -cleanup {
destroy .t
-} -returnCodes {error} -result {Usage: .t dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?}
+} -returnCodes {error} -result {Usage: .t dump ?-all -chars -image -mark -node -tag -text -window? ?-command script? index ?index2?}
test text-24.3 {TextDumpCmd procedure, bad args} -body {
pack [text .t]
.t insert 1.0 "One Line"
@@ -5968,7 +6282,7 @@ test text-24.3 {TextDumpCmd procedure, bad args} -body {
.t dump -command
} -cleanup {
destroy .t
-} -returnCodes {error} -result {Usage: .t dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?}
+} -returnCodes {error} -result {Usage: .t dump ?-all -chars -image -mark -node -tag -text -window? ?-command script? index ?index2?}
test text-24.4 {TextDumpCmd procedure, bad args} -body {
pack [text .t]
.t insert 1.0 "One Line"
@@ -5976,7 +6290,7 @@ test text-24.4 {TextDumpCmd procedure, bad args} -body {
.t dump -bogus
} -cleanup {
destroy .t
-} -returnCodes {error} -result {bad option "-bogus": must be -all, -command, -image, -mark, -tag, -text, or -window}
+} -returnCodes {error} -result {bad option "-bogus": must be -all, -chars, -command, -image, -mark, -node, -tag, -text, or -window}
test text-24.5 {TextDumpCmd procedure, bad args} -body {
pack [text .t]
.t insert 1.0 "One Line"
@@ -6126,7 +6440,7 @@ test text-24.20 {TextDumpCmd procedure, marks only} -body {
test text-24.21 {TextDumpCmd procedure, windows only} -setup {
pack [text .t]
.t insert end "Line One\nLine Two\nLine Three\nLine Four"
- for {set i 0} {$i < 100} {incr i} {.t insert end "-\n"}
+ .t insert end [string repeat "-\n" 100]
button .hello -text Hello
} -body {
.t window create 3.end -window .hello
@@ -6138,7 +6452,7 @@ test text-24.21 {TextDumpCmd procedure, windows only} -setup {
test text-24.22 {TextDumpCmd procedure, windows only} -setup {
pack [text .t]
.t insert end "Line One\nLine Two\nLine Three\nLine Four"
- for {set i 0} {$i < 100} {incr i} {.t insert end "-\n"}
+ .t insert end [string repeat "-\n" 100]
button .hello -text Hello
} -body {
.t window create 3.end -window .hello
@@ -6194,7 +6508,7 @@ test text-24.25 {TextDumpCmd procedure, unicode characters} -body {
.t dump -all 1.0 2.0
} -cleanup {
destroy .t
-} -result "text \xb1\xb1\xb1 1.0 mark insert 1.3 mark current 1.3 text {\n} 1.3"
+} -result "text \xb1\xb1\xb1 1.0 mark current 1.3 mark insert 1.3 text {\n} 1.3"
test text-24.26 {TextDumpCmd procedure, unicode characters} -body {
text .t
.t delete 1.0 end
@@ -6202,14 +6516,14 @@ test text-24.26 {TextDumpCmd procedure, unicode characters} -body {
.t dump -all 1.0 2.0
} -cleanup {
destroy .t
-} -result "text abc\xb1\xb1\xb1 1.0 mark insert 1.6 mark current 1.6 text {\n} 1.6"
+} -result "text abc\xb1\xb1\xb1 1.0 mark current 1.6 mark insert 1.6 text {\n} 1.6"
test text-24.27 {TextDumpCmd procedure, peer present} -body {
text .t
.t peer create .t.t
.t dump -all 1.0 end
} -cleanup {
destroy .t
-} -result "mark insert 1.0 mark current 1.0 text {\n} 1.0"
+} -result "mark current 1.0 mark insert 1.0 text {\n} 1.0"
test text-25.1 {text widget vs hidden commands} -body {
text .t
@@ -6246,7 +6560,7 @@ test text-27.2 {TextEditCmd procedure, argument parsing} -body {
.t edit gorp
} -cleanup {
destroy .t
-} -returnCodes {error} -result {bad edit option "gorp": must be canundo, canredo, modified, redo, reset, separator, or undo}
+} -returnCodes {error} -result {bad edit option "gorp": must be altered, info, inspect, irreversible, modified, recover, redo, reset, separator, or undo}
test text-27.3 {TextEditUndo procedure, undoing changes} -body {
text .t -undo 1
pack .t
@@ -6377,7 +6691,7 @@ test text-27.14 {<<Modified>> virtual event - delete before Modified} -body {
.t insert end "nothing special"
.t edit modified 0
.t delete 1.0 1.2
- update
+ update
set ::retval
} -cleanup {
destroy .t
@@ -6404,13 +6718,13 @@ test text-27.15 {<<Selection>> virtual event} -body {
update idletasks
.t insert end "nothing special\n"
.t tag add sel 1.0 1.1
- update
+ update
set ::retval
} -cleanup {
destroy .t
} -result {selection_changed}
test text-27.16 {-maxundo configuration option} -body {
- text .t -undo 1 -autoseparators 1 -maxundo 2
+ text .t -undo 1 -autoseparators 1 -maxundo 2
pack .t
.t insert end "line 1\n"
.t delete 1.4 1.6
@@ -6423,7 +6737,7 @@ test text-27.16 {-maxundo configuration option} -body {
destroy .t
} -result "line 1\n\n"
test text-27.16a {undo configuration options with peers} -body {
- text .t -undo 1 -autoseparators 0 -maxundo 100
+ text .t -undo 1 -autoseparators 0 -maxundo 200 -maxredo 100 -maxundosize 1000
.t peer create .tt
set res [.t cget -undo]
lappend res [.tt cget -undo]
@@ -6431,12 +6745,16 @@ test text-27.16a {undo configuration options with peers} -body {
lappend res [.tt cget -autoseparators]
lappend res [.t cget -maxundo]
lappend res [.tt cget -maxundo]
+ lappend res [.t cget -maxredo]
+ lappend res [.tt cget -maxredo]
+ lappend res [.t cget -maxundosize]
+ lappend res [.tt cget -maxundosize]
.t insert end "The undo stack is common between peers"
- lappend res [.t edit canundo]
- lappend res [.tt edit canundo]
+ lappend res [llength [set [.t edit info](undocommands)]]
+ lappend res [llength [set [.tt edit info](undocommands)]]
} -cleanup {
destroy .t .tt
-} -result {1 1 0 0 100 100 1 1}
+} -result {1 1 0 0 200 200 100 100 1000 1000 1 1}
test text-27.16b {undo configuration options with peers, defaults} -body {
text .t
.t peer create .tt
@@ -6446,12 +6764,16 @@ test text-27.16b {undo configuration options with peers, defaults} -body {
lappend res [.tt cget -autoseparators]
lappend res [.t cget -maxundo]
lappend res [.tt cget -maxundo]
+ lappend res [.t cget -maxredo]
+ lappend res [.tt cget -maxredo]
+ lappend res [.t cget -maxundosize]
+ lappend res [.tt cget -maxundosize]
.t insert end "The undo stack is common between peers"
- lappend res [.t edit canundo]
- lappend res [.tt edit canundo]
+ lappend res [llength [set [.t edit info](undocommands)]]
+ lappend res [llength [set [.tt edit info](undocommands)]]
} -cleanup {
destroy .t .tt
-} -result {0 0 1 1 0 0 0 0}
+} -result {0 0 1 1 0 0 -1 -1 0 0 0 0}
test text-27.17 {bug fix 1536735 - undo with empty text} -body {
text .t -undo 1
set r [.t edit modified]
@@ -6563,31 +6885,31 @@ test text-27.22 {patch 1669632 (v) - <<Clear>> is atomic} -setup {
} -cleanup {
destroy .top.t .top
} -result "This A an example text"
-test text-27.24 {TextEditCmd procedure, canundo and canredo} -setup {
+test text-27.24 {TextEditCmd procedure, undo and redo stack depths} -setup {
destroy .t
set res {}
} -body {
text .t -undo false -autoseparators false
- lappend res [.t edit canundo] [.t edit canredo]
+ .t edit info; lappend res $(undodepth) $(redodepth)
.t configure -undo true
- lappend res [.t edit canundo] [.t edit canredo]
+ .t edit info; lappend res $(undodepth) $(redodepth)
.t insert end "DO\n"
.t edit separator
.t insert end "IT\n"
.t insert end "YOURSELF\n"
.t edit separator
- lappend res [.t edit canundo] [.t edit canredo]
+ .t edit info; lappend res $(undodepth) $(redodepth)
.t edit undo
- lappend res [.t edit canundo] [.t edit canredo]
+ .t edit info; lappend res $(undodepth) $(redodepth)
+ .t edit redo
+ .t edit info; lappend res $(undodepth) $(redodepth)
.t configure -undo false
- lappend res [.t edit canundo] [.t edit canredo]
+ .t edit info; lappend res $(undodepth) $(redodepth)
.t configure -undo true
- lappend res [.t edit canundo] [.t edit canredo]
- .t edit redo
- lappend res [.t edit canundo] [.t edit canredo]
+ .t edit info; lappend res $(undodepth) $(redodepth)
} -cleanup {
destroy .t
-} -result {0 0 0 0 1 0 1 1 0 0 1 1 1 0}
+} -result {0 0 0 0 2 0 1 1 2 0 0 0 0 0}
test text-27.25 {<<UndoStack>> virtual event} -setup {
destroy .t
set res {}
@@ -6630,67 +6952,59 @@ test text-27.25 {<<UndoStack>> virtual event} -setup {
update ; lappend res $nbUS
} -cleanup {
destroy .t
-} -result {0 0 1 2 3 4 4 5 6 6 7 8 8 9}
-test text-27.26 {edit undo and edit redo return ranges} -setup {
+} -result {0 0 4 5 6 7 8 9 10 11 12 13 14 15}
+test text-27.26 {edit undo and edit redo provides ranges} -setup {
destroy .t
set res {}
} -body {
+ proc watch {w op idx1 idx2 info userFlag} {
+ switch $op {
+ undo - redo {
+ ::tk_mergeRange ::${op}_ [list $idx1 $idx2]
+ if {[lindex $info 1]} { ;# last undo/redo action?
+ lappend ::${op} [set ::${op}_]
+ unset ::${op}_
+ }
+ }
+ }
+ }
text .t -undo true -autoseparators false
+ .t watch watch
.t insert end "Hello "
.t edit separator
.t insert end "World!\n"
.t insert 1.6 "GREAT "
.t insert end "Another edit here!!"
- lappend res [.t edit undo]
- lappend res [.t edit redo]
+ .t edit undo
+ .t edit redo
.t edit separator
.t delete 1.6
.t delete 1.9 1.10
.t insert 1.9 L
- lappend res [.t edit undo]
- lappend res [.t edit redo]
+ .t edit undo
+ .t edit redo
.t replace 1.6 1.10 Tcl/Tk
.t replace 2.8 2.12 "one bites the dust"
- lappend res [.t edit undo]
- lappend res [.t edit redo]
-} -cleanup {
- destroy .t
-} -result [list {1.6 2.0} \
- {1.6 2.19} \
- {1.6 1.7 1.10 1.12} \
- {1.6 1.7 1.9 1.11} \
- {1.6 1.16 2.8 2.19} \
- {1.6 1.16 2.8 2.30} ]
-test text-27.27 {edit undo and edit redo return ranges} -setup {
- destroy .t
- set res {}
-} -body {
- text .t -undo true -autoseparators false
- for {set i 3} {$i >= 1} {incr i -1} {
- .t insert 1.0 "Line $i\n"
- }
- lappend res [.t edit undo]
- lappend res [.t edit redo]
+ .t edit undo
+ .t edit redo
+ lappend res $undo
+ lappend res $redo
} -cleanup {
destroy .t
-} -result [list {1.0 2.0} \
- {1.0 4.0} ]
-test text-27.28 {edit undo and edit redo do not leave \
- spurious temporary marks behind them} -setup {
- destroy .t
- set res {}
-} -body {
- pack [text .t -undo true -autoseparators false]
- .t insert end "Hello World.\n"
- .t edit separator
- .t insert end "Again hello.\n"
+ unset undo
+ unset redo
+} -result {{{{1.6 2.19}} {{1.6 1.7} {1.9 1.10}} {{1.6 1.12} {2.8 2.26}}}\
+ {{{1.6 2.19}} {{1.6 1.7} {1.9 1.10}} {{1.6 1.12} {2.8 2.26}}}}
+test text-27.27 {undo with empty text, and tagged newline} -body {
+ text .t
+ .t tag add tag1 1.0
+ .t configure -undo on
+ .t delete 1.0
.t edit undo
- lappend res [lsearch [.t mark names] tk::undoMark*]
- .t edit redo
- lappend res [lsearch [.t mark names] tk::undoMark*]
+ .t tag ranges tag1
} -cleanup {
destroy .t
-} -result [list -1 -1]
+} -result {1.0 2.0}
test text-28.1 {bug fix - 624372, ControlUtfProc long lines} -body {
@@ -6820,9 +7134,11 @@ test text-31.3 {peer widgets} -body {
test text-31.4 {peer widgets} -body {
toplevel .top
pack [text .t]
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
pack [.t peer create .top.t -start 5 -end 11]
update
destroy .t .top
@@ -6830,9 +7146,11 @@ test text-31.4 {peer widgets} -body {
test text-31.5 {peer widgets} -body {
toplevel .top
pack [text .t]
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
pack [.t peer create .top.t -start 5 -end 11]
pack [.top.t peer create .top.t2]
set res [list [.top.t index end] [.top.t2 index end]]
@@ -6844,9 +7162,11 @@ test text-31.5 {peer widgets} -body {
test text-31.6 {peer widgets} -body {
toplevel .top
pack [text .t]
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
pack [.t peer create .top.t -start 5 -end 11]
pack [.top.t peer create .top.t2 -start {} -end {}]
set res [list [.top.t index end] [.top.t2 index end]]
@@ -6858,9 +7178,11 @@ test text-31.6 {peer widgets} -body {
test text-31.7 {peer widgets} -body {
toplevel .top
pack [text .t]
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
pack [.t peer create .top.t -start 5 -end 11]
update ; update
set p1 [.top.t count -update -ypixels 1.0 end]
@@ -6872,9 +7194,11 @@ test text-31.7 {peer widgets} -body {
test text-31.8 {peer widgets} -body {
toplevel .top
pack [text .t]
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
pack [.t peer create .top.t -start 5 -end 11]
update ; update
.t delete 3.0 6.0
@@ -6885,9 +7209,11 @@ test text-31.8 {peer widgets} -body {
test text-31.9 {peer widgets} -body {
toplevel .top
pack [text .t]
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
pack [.t peer create .top.t -start 5 -end 11]
update ; update
.t delete 8.0 12.0
@@ -6898,23 +7224,29 @@ test text-31.9 {peer widgets} -body {
test text-31.10 {peer widgets} -body {
toplevel .top
pack [text .t]
- for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ set str ""
+ for {set i 1} {$i < 20} {incr i} {
+ append str "Line $i\n"
}
+ .t insert end $str
pack [.t peer create .top.t -start 5 -end 11]
update ; update
.t delete 3.0 13.0
.top.t index end
} -cleanup {
destroy .t .top
-} -result {1.0}
+# NOTE: in revised implementation this peer cannot be empty,
+# so the old result "1.0" has been changed to "2.0".
+} -result {2.0}
test text-31.11 {peer widgets} -setup {
pack [text .t]
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 1.0 end-1c
lappend res [.t tag ranges sel]
.t configure -start 10 -end 20
@@ -6927,9 +7259,11 @@ test text-31.12 {peer widgets} -setup {
pack [text .t]
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 1.0 end-1c
lappend res [.t tag ranges sel]
.t configure -start 11
@@ -6942,9 +7276,11 @@ test text-31.13 {peer widgets} -setup {
pack [text .t]
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 1.0 end-1c
lappend res [.t tag ranges sel]
.t configure -end 90
@@ -6958,9 +7294,11 @@ test text-31.14 {peer widgets} -setup {
pack [text .t]
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 1.0 3.0 5.0 7.0 9.0 11.0 13.0 15.0 17.0 19.0
lappend res [.t tag prevrange sel 1.0]
.t configure -start 6 -end 12
@@ -6979,9 +7317,11 @@ test text-31.15 {peer widgets} -setup {
pack [text .t]
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 1.0 3.0 9.0 11.0 13.0 15.0 17.0 19.0
.t configure -start 6 -end 12
lappend res [.t tag ranges sel]
@@ -6999,9 +7339,11 @@ test text-31.16 {peer widgets} -setup {
pack [text .t]
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 1.0 7.0 9.0 11.0 13.0 15.0 17.0 19.0
.t configure -start 6 -end 12
lappend res [.t tag ranges sel]
@@ -7019,9 +7361,11 @@ test text-31.17 {peer widgets} -setup {
pack [text .t]
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 1.0 11.0
lappend res [.t tag ranges sel]
lappend res [catch {.t configure -start 15 -end 10}]
@@ -7038,9 +7382,11 @@ test text-31.18 {peer widgets} -setup {
pack [text .t]
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 1.0 11.0
lappend res [.t index sel.first]
lappend res [.t index sel.last]
@@ -7050,9 +7396,11 @@ test text-31.18 {peer widgets} -setup {
} -result {1.0 11.0}
test text-31.19 {peer widgets} -body {
pack [text .t]
+ set str ""
for {set i 1} {$i < 20} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag delete sel
.t index sel.first
} -cleanup {
@@ -7079,9 +7427,11 @@ test text-32.1 {line heights on creation} -setup {
$t tag configure center -justify center -spacing1 5m -spacing3 5m
$t tag configure buttons -lmargin1 1c -lmargin2 1c -rmargin 1c \
-spacing1 3m -spacing2 0 -spacing3 0
+ set str ""
for {set i 0} {$i < 40} {incr i} {
- $t insert end "${i}word "
+ append str "${i}word "
}
+ $t insert end $str
return $t
}
} -body {
@@ -7096,6 +7446,105 @@ test text-32.1 {line heights on creation} -setup {
} -cleanup {
destroy .t
} -result {1}
+test text-32.2 {peer widget -start, -end and deletion (bug 1630262)} -setup {
+ destroy .t .pt
+ set res {}
+} -body {
+ text .t
+ .t peer create .pt
+ set str ""
+ for {set i 1} {$i < 100} {incr i} {
+ append str "Line $i\n"
+ }
+ .t insert end $str
+ .t configure -startline 5
+ # none of the following delete shall crash
+ # (all did before fixing bug 1630262)
+ # 1. delete on the same line: line1 == line2 in DeleteIndexRange,
+ # and resetView is true neither for .t not for .pt
+ .pt delete 2.0 2.2
+ # 2. delete just one line: line1 < line2 in DeleteIndexRange,
+ # and resetView is true only for .t, not for .pt
+ .pt delete 2.0 3.0
+ # 3. delete several lines: line1 < line2 in DeleteIndexRange,
+ # and resetView is true only for .t, not for .pt
+ .pt delete 2.0 5.0
+ # 4. delete to the end line: line1 < line2 in DeleteIndexRange,
+ # and resetView is true only for .t, not for .pt
+ .pt delete 2.0 end
+ # this test succeeds provided there is no crash
+ set res 1
+} -cleanup {
+ destroy .pt
+} -result {1}
+test text-32.3 {peer widget -start, -end and deletion (bug 1630262)} -setup {
+ destroy .t .pt
+ set res {}
+} -body {
+ text .t
+ .t peer create .pt
+ set str ""
+ for {set i 1} {$i < 100} {incr i} {
+ append str "Line $i\n"
+ }
+ .t insert end $str
+ .t configure -startline 5
+ .pt configure -startline 3
+ # the following delete shall not crash
+ # (it did before fixing bug 1630262)
+ .pt delete 2.0 3.0
+ # moreover -startline shall be correct
+ # (was wrong before fixing bug 1630262)
+ lappend res [.t cget -start] [.pt cget -start]
+} -cleanup {
+ destroy .pt
+} -result {4 3}
+test text-32.4 {peer widget -start, -end and deletion (bug 1630262)} -setup {
+ destroy .t .pt
+ set res {}
+} -body {
+ text .t
+ .t peer create .pt
+ set str ""
+ for {set i 1} {$i < 100} {incr i} {
+ append str "Line $i\n"
+ }
+ .t insert end $str
+ .t configure -startline 5 -endline 15
+ .pt configure -startline 8 -endline 12
+ # .pt now shows a range entirely inside the range of .pt
+ # from .t, delete lines located after [.pt cget -end]
+ .t delete 9.0 10.0
+ # from .t, delete lines straddling [.pt cget -end]
+ .t delete 6.0 9.0
+ lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
+ .t configure -startline 5 -endline 12
+ .pt configure -startline 8 -endline 12
+ # .pt now shows again a range entirely inside the range of .pt
+ # from .t, delete lines located before [.pt cget -start]
+ .t delete 2.0 3.0
+ # from .t, delete lines straddling [.pt cget -start]
+ .t delete 2.0 5.0
+ lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
+ .t configure -startline 22 -endline 31
+ .pt configure -startline 42 -endline 51
+ # .t now shows a range entirely before the range of .pt
+ # from .t, delete some lines, then do it from .pt
+ .t delete 2.0 3.0
+ .t delete 2.0 5.0
+ .pt delete 2.0 5.0
+ lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
+ .t configure -startline 55 -endline 75
+ .pt configure -startline 60 -endline 70
+ # .pt now shows a range entirely inside the range of .t
+ # from .t, delete a range straddling the entire range of .pt
+ .t delete 3.0 18.0
+ lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
+} -cleanup {
+ destroy .pt .t
+# NOTE: The peer widget is empty, and in revised version always a last newline
+# exists, so the last line index has been increased by 1 (57 -> 58).
+} -result {5 11 8 10 5 8 6 8 22 27 38 44 55 60 57 58}
test text-33.1 {TextWidgetCmd procedure, "peer" option} -setup {
@@ -7115,7 +7564,7 @@ test text-33.2 {TextWidgetCmd procedure, "peer" option} -setup {
test text-33.3 {TextWidgetCmd procedure, "peer" option} -setup {
text .t
} -body {
- .t pee names
+ .t peer names
} -cleanup {
destroy .t
} -returnCodes {ok} -result {}
@@ -7154,27 +7603,33 @@ test text-33.7 {peer widget -start, -end} -body {
} -returnCodes {2} -result {}
test text-33.8 {peer widget -start, -end} -body {
text .t
+ set str ""
for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t configure -start 10 -end 5
} -cleanup {
destroy .t
} -returnCodes {error} -result {-startline must be less than or equal to -endline}
test text-33.9 {peer widget -start, -end} -body {
text .t
+ set str ""
for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t configure -start 5 -end 10
} -cleanup {
destroy .t
} -returnCodes {ok} -result {}
test text-33.10 {peer widget -start, -end} -body {
text .t
+ set str ""
for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
set res [.t index end]
lappend res [catch {.t configure -start 5 -end 10 -tab foo}]
lappend res [.t index end]
@@ -7188,9 +7643,11 @@ test text-33.10 {peer widget -start, -end} -body {
} -result {101.0 1 101.0 1 101.0 101.0}
test text-33.11 {peer widget -start, -end} -body {
text .t
+ set str ""
for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
set res [.t index end]
lappend res [catch {.t configure -start 5 -end 15}]
lappend res [.t index end]
@@ -7207,9 +7664,11 @@ test text-34.1 {peer widget -start, -end and selection} -setup {
text .t
set res {}
} -body {
+ set str ""
for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
+ append str "Line $i\n"
}
+ .t insert end $str
.t tag add sel 10.0 20.0
lappend res [.t tag ranges sel]
.t configure -start 5 -end 30
@@ -7229,103 +7688,8 @@ test text-34.1 {peer widget -start, -end and selection} -setup {
destroy .t
} -result {{10.0 20.0} {6.0 16.0} {6.0 11.0} {1.0 6.0} {1.0 2.0} {} {10.0 20.0}}
-test text-32.2 {peer widget -start, -end and deletion (bug 1630262)} -setup {
- destroy .t .pt
- set res {}
-} -body {
- text .t
- .t peer create .pt
- for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
- }
- .t configure -startline 5
- # none of the following delete shall crash
- # (all did before fixing bug 1630262)
- # 1. delete on the same line: line1 == line2 in DeleteIndexRange,
- # and resetView is true neither for .t not for .pt
- .pt delete 2.0 2.2
- # 2. delete just one line: line1 < line2 in DeleteIndexRange,
- # and resetView is true only for .t, not for .pt
- .pt delete 2.0 3.0
- # 3. delete several lines: line1 < line2 in DeleteIndexRange,
- # and resetView is true only for .t, not for .pt
- .pt delete 2.0 5.0
- # 4. delete to the end line: line1 < line2 in DeleteIndexRange,
- # and resetView is true only for .t, not for .pt
- .pt delete 2.0 end
- # this test succeeds provided there is no crash
- set res 1
-} -cleanup {
- destroy .pt
-} -result {1}
-
-test text-32.3 {peer widget -start, -end and deletion (bug 1630262)} -setup {
- destroy .t .pt
- set res {}
-} -body {
- text .t
- .t peer create .pt
- for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
- }
- .t configure -startline 5
- .pt configure -startline 3
- # the following delete shall not crash
- # (it did before fixing bug 1630262)
- .pt delete 2.0 3.0
- # moreover -startline shall be correct
- # (was wrong before fixing bug 1630262)
- lappend res [.t cget -start] [.pt cget -start]
-} -cleanup {
- destroy .pt
-} -result {4 3}
-
-test text-32.4 {peer widget -start, -end and deletion (bug 1630262)} -setup {
- destroy .t .pt
- set res {}
-} -body {
- text .t
- .t peer create .pt
- for {set i 1} {$i < 100} {incr i} {
- .t insert end "Line $i\n"
- }
- .t configure -startline 5 -endline 15
- .pt configure -startline 8 -endline 12
- # .pt now shows a range entirely inside the range of .pt
- # from .t, delete lines located after [.pt cget -end]
- .t delete 9.0 10.0
- # from .t, delete lines straddling [.pt cget -end]
- .t delete 6.0 9.0
- lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
- .t configure -startline 5 -endline 12
- .pt configure -startline 8 -endline 12
- # .pt now shows again a range entirely inside the range of .pt
- # from .t, delete lines located before [.pt cget -start]
- .t delete 2.0 3.0
- # from .t, delete lines straddling [.pt cget -start]
- .t delete 2.0 5.0
- lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
- .t configure -startline 22 -endline 31
- .pt configure -startline 42 -endline 51
- # .t now shows a range entirely before the range of .pt
- # from .t, delete some lines, then do it from .pt
- .t delete 2.0 3.0
- .t delete 2.0 5.0
- .pt delete 2.0 5.0
- lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
- .t configure -startline 55 -endline 75
- .pt configure -startline 60 -endline 70
- # .pt now shows a range entirely inside the range of .t
- # from .t, delete a range straddling the entire range of .pt
- .t delete 3.0 18.0
- lappend res [.t cget -start] [.t cget -end] [.pt cget -start] [.pt cget -end]
-} -cleanup {
- destroy .pt .t
-} -result {5 11 8 10 5 8 6 8 22 27 38 44 55 60 57 57}
-
test text-35.1 {widget dump -command alters tags} -setup {
proc Dumpy {key value index} {
-#puts "KK: $key, $value"
.t tag add $value [list $index linestart] [list $index lineend]
}
text .t
@@ -7339,7 +7703,6 @@ test text-35.1 {widget dump -command alters tags} -setup {
} -result {ok}
test text-35.2 {widget dump -command makes massive changes} -setup {
proc Dumpy {key value index} {
-#puts "KK: $key, $value"
.t delete 1.0 end
}
text .t
@@ -7353,7 +7716,6 @@ test text-35.2 {widget dump -command makes massive changes} -setup {
} -result {ok}
test text-35.3 {widget dump -command destroys widget} -setup {
proc Dumpy {key value index} {
-#puts "KK: $key, $value"
destroy .t
}
text .t
@@ -7414,3 +7776,4 @@ return
# Local Variables:
# mode: tcl
# End:
+# vi:set ts=8 sw=4:
diff --git a/tests/textBTree.test b/tests/textBTree.test
index ebd6c50..6d2b79b 100644
--- a/tests/textBTree.test
+++ b/tests/textBTree.test
@@ -14,7 +14,7 @@ eval tcltest::configure $argv
tcltest::loadTestedCommands
proc setup {} {
- .t delete 1.0 100000.0
+ .t delete 1.0 end
.t tag delete x y
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
.t tag add x 1.1
@@ -25,7 +25,7 @@ proc setup {} {
# setup procedure for tests 10.*, 11.*, 12.*
proc msetup {} {
- .t delete 1.0 100000.0
+ .t delete 1.0 end
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
.t mark set m1 1.2
.t mark set l1 1.2
@@ -784,49 +784,49 @@ test btree-13.1 {tag searching} -setup {
.t delete 1.0 100000.0
} -body {
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
- .t tag next x 2.2 2.1
+ .t tag nextrange x 2.2 2.1
} -result {}
test btree-13.2 {tag searching} -setup {
.t delete 1.0 100000.0
} -body {
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
.t tag add x 2.2 2.4
- .t tag next x 2.2 2.3
+ .t tag nextrange x 2.2 2.3
} -result {2.2 2.4}
test btree-13.3 {tag searching} -setup {
.t delete 1.0 100000.0
} -body {
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
.t tag add x 2.2 2.4
- .t tag next x 2.3 2.6
+ .t tag nextrange x 2.3 2.6
} -result {}
test btree-13.4 {tag searching} -setup {
.t delete 1.0 100000.0
} -body {
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
.t tag add x 2.5 2.8
- .t tag next x 2.1 2.6
+ .t tag nextrange x 2.1 2.6
} -result {2.5 2.8}
test btree-13.5 {tag searching} -setup {
.t delete 1.0 100000.0
} -body {
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
.t tag add x 2.5 2.8
- .t tag next x 2.1 2.5
+ .t tag nextrange x 2.1 2.5
} -result {}
test btree-13.6 {tag searching} -setup {
.t delete 1.0 100000.0
} -body {
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
.t tag add x 2.1 2.4
- .t tag next x 2.5 2.8
+ .t tag nextrange x 2.5 2.8
} -result {}
test btree-13.7 {tag searching} -setup {
.t delete 1.0 100000.0
} -body {
.t insert 1.0 "Text for first line\nSecond line\n\nLast line of info"
.t tag add x 2.5 2.8
- .t tag next x 2.1 2.4
+ .t tag nextrange x 2.1 2.4
} -result {}
test btree-13.8 {tag searching} -setup {
set bigText2 {}
@@ -837,7 +837,7 @@ test btree-13.8 {tag searching} -setup {
setup
.t insert 1.2 $bigText2
.t tag add x 190.3 191.2
- .t tag next x 3.5
+ .t tag nextrange x 3.5
} -result {190.3 191.2}
destroy .t
@@ -1038,7 +1038,7 @@ test btree-16.11 {StartSearchBack boundary case} -setup {
}
} -body {
.t tag add x 1.3 1.4
- .t tag prevr x 2.0 1.4
+ .t tag prevrange x 2.0 1.4
} -cleanup {
destroy .t
} -result {}
@@ -1050,7 +1050,7 @@ test btree-16.12 {StartSearchBack boundary case} -setup {
}
} -body {
.t tag add x 1.3 1.4
- .t tag prevr x 2.0 1.3
+ .t tag prevrange x 2.0 1.3
} -cleanup {
destroy .t
} -result {1.3 1.4}
@@ -1062,7 +1062,7 @@ test btree-16.13 {StartSearchBack boundary case} -setup {
}
} -body {
.t tag add x 1.0 1.4
- .t tag prevr x 1.3
+ .t tag prevrange x 1.3
} -cleanup {
destroy .t
} -result {1.0 1.4}
@@ -1144,7 +1144,7 @@ test btree-18.1 {tag search back, no tag} -setup {
text .t
} -body {
.t insert 1.0 "Line 1 abcd efgh ijkl\n"
- .t tag prev x 1.1 1.1
+ .t tag prevrange x 1.1 1.1
} -cleanup {
destroy .t
} -result {}
@@ -1156,7 +1156,7 @@ test btree-18.2 {tag search back, start at existing range} -setup {
.t tag add x 1.1 1.4
.t tag add x 1.8 1.11
.t tag add x 1.16
- .t tag prev x 1.1
+ .t tag prevrange x 1.1
} -cleanup {
destroy .t
} -result {}
@@ -1168,7 +1168,7 @@ test btree-18.3 {tag search back, end at existing range} -setup {
.t tag add x 1.1 1.4
.t tag add x 1.8 1.11
.t tag add x 1.16
- .t tag prev x 1.3 1.1
+ .t tag prevrange x 1.3 1.1
} -cleanup {
destroy .t
} -result {1.1 1.4}
@@ -1180,7 +1180,7 @@ test btree-18.4 {tag search back, start within range} -setup {
.t tag add x 1.1 1.4
.t tag add x 1.8 1.11
.t tag add x 1.16
- .t tag prev x 1.10 1.0
+ .t tag prevrange x 1.10 1.0
} -cleanup {
destroy .t
} -result {1.8 1.11}
@@ -1192,7 +1192,7 @@ test btree-18.5 {tag search back, start at end of range} -setup {
.t tag add x 1.1 1.4
.t tag add x 1.8 1.11
.t tag add x 1.16
- list [.t tag prev x 1.4 1.0] [.t tag prev x 1.11 1.0]
+ list [.t tag prevrange x 1.4 1.0] [.t tag prevrange x 1.11 1.0]
} -cleanup {
destroy .t
} -result {{1.1 1.4} {1.8 1.11}}
@@ -1204,7 +1204,7 @@ test btree-18.6 {tag search back, start beyond range, same level 0 node} -setup
.t tag add x 1.1 1.4
.t tag add x 1.8 1.11
.t tag add x 1.16
- .t tag prev x 3.0
+ .t tag prevrange x 3.0
} -cleanup {
destroy .t
} -result {1.16 1.17}
@@ -1215,7 +1215,7 @@ test btree-18.7 {tag search back, outside any range} -setup {
.t insert 1.0 "Line 1 abcd efgh ijkl\n"
.t tag add x 1.1 1.4
.t tag add x 1.16
- .t tag prev x 1.8 1.5
+ .t tag prevrange x 1.8 1.5
} -cleanup {
destroy .t
} -result {}
@@ -1225,7 +1225,7 @@ test btree-18.8 {tag search back, start at start of node boundary} -setup {
} -body {
setupBig
.t tag add x 2.5 2.8
- .t tag prev x 19.0
+ .t tag prevrange x 19.0
} -cleanup {
destroy .t
} -result {2.5 2.8}
@@ -1237,11 +1237,148 @@ test btree-18.9 {tag search back, large complex btree spans} -setup {
.t tag add x 1.3 1.end
.t tag add x 200.0 220.0
.t tag add x 500.0 520.0
- list [.t tag prev x end] [.t tag prev x 433.0]
+ list [.t tag prevrange x end] [.t tag prevrange x 433.0]
} -cleanup {
destroy .t
} -result {{500.0 520.0} {200.0 220.0}}
+
+test btree-19.0 {test toggle chain 1} -setup {
+ text .t
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag add x 1.15 1.17
+} -cleanup {
+ destroy .t
+} -result {}
+test btree-19.1 {test toggle chain 2} -setup {
+ text .t
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag add x 1.13 1.17
+} -cleanup {
+ destroy .t
+} -result {}
+test btree-19.2 {test toggle chain 3} -setup {
+ text .t
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag add x 1.11 1.17
+} -cleanup {
+ destroy .t
+} -result {}
+test btree-19.3 {test toggle chain 4} -setup {
+ text .t
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag add x 1.6 1.17
+} -cleanup {
+ destroy .t
+} -result {}
+ text .t
+test btree-19.4 {test toggle chain 5} -setup {
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag add x 1.6 1.21
+} -cleanup {
+ destroy .t
+} -result {}
+test btree-19.5 {test toggle chain 6} -setup {
+ text .t
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.15 1.17
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag remove 1.11 1.16
+} -cleanup {
+ destroy .t
+} -result {}
+test btree-19.6 {test toggle chain 7} -setup {
+ text .t
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.15 1.17
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag remove 1.6 1.16
+} -cleanup {
+ destroy .t
+} -result {}
+test btree-19.7 {test toggle chain 8} -setup {
+ text .t
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.15 1.17
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag remove 1.6 1.20
+} -cleanup {
+ destroy .t
+} -result {}
+test btree-19.8 {test toggle chain 9} -setup {
+ text .t
+} -body {
+ .t insert end "abcdefghijklmnopqrstuvwxyzabcdef"
+ .t tag add x 1.0 1.2
+ .t tag add x 1.5 1.7
+ .t tag add x 1.10 1.12
+ .t tag add x 1.15 1.17
+ .t tag add x 1.20 1.22
+ .t tag add x 1.25 1.28
+
+ .t tag remove 1.4 1.23
+} -cleanup {
+ destroy .t
+} -result {}
+
# cleanup
cleanupTests
return
+
+# Local Variables:
+# mode: tcl
+# End:
+# vi:set ts=8 sw=4:
diff --git a/tests/textDisp.test b/tests/textDisp.test
index f2eb47d..5fa0ac5 100644
--- a/tests/textDisp.test
+++ b/tests/textDisp.test
@@ -39,7 +39,7 @@ option add *Text.highlightThickness $twht
catch {destroy .f .t}
frame .f -width 100 -height 20
-pack .f -side left
+pack append . .f left
set fixedFont {"Courier New" -12}
# 15 on XP, 13 on Solaris 8
@@ -65,7 +65,7 @@ set bigAscent [font metrics $bigFont -ascent]
set ascentDiff [expr {$bigAscent - $fixedAscent}]
text .t -font $fixedFont -width 20 -height 10 -yscrollcommand scroll
-pack .t -expand 1 -fill both
+pack append . .t {top expand fill}
.t tag configure big -font $bigFont
.t debug on
wm geometry . {}
@@ -253,8 +253,8 @@ test textDisp-2.5 {LayoutDLine, word wrap} {textfonts} {
.t configure -wrap word
.t delete 1.0 end
.t insert 1.0 "This isx some sample text for testing."
- list [.t bbox 1.13] [.t bbox 1.19] [.t bbox 1.20] [.t bbox 1.21]
-} [list [list 96 5 $fixedWidth $fixedHeight] [list 138 5 $fixedWidth $fixedHeight] [list 145 5 0 $fixedHeight] [list 5 [expr {$fixedDiff + 18}] $fixedWidth $fixedHeight]]
+ list [.t bbox 1.13] [.t bbox 1.14] [.t bbox 1.19]
+} [list [list 96 5 49 $fixedHeight] [list 5 [expr {$fixedDiff + 18}] 7 $fixedHeight] [list 40 [expr {$fixedDiff + 18}] 7 $fixedHeight]]
test textDisp-2.6 {LayoutDLine, word wrap} {
.t configure -wrap word
.t delete 1.0 end
@@ -353,16 +353,16 @@ test textDisp-2.16 {LayoutDLine, justification} {textfonts} {
.t tag configure x -justify center
.t tag add x 1.1 1.20
.t tag add x 1.21 1.end
- list [.t bbox 1.0] [.t bbox 1.20] [.t bbox 1.41] [.t bbox 2.0]
-} [list [list 5 5 7 $fixedHeight] [list 5 [expr {$fixedDiff + 18}] 7 $fixedHeight] [list 61 [expr {2*$fixedDiff + 31}] 7 $fixedHeight] [list 5 [expr {3*$fixedDiff + 44}] 7 $fixedHeight]]
+ list [.t bbox 1.0] [.t bbox 1.20] [.t bbox 1.36] [.t bbox 2.0]
+} [list [list 5 5 7 $fixedHeight] [list 5 [expr {$fixedDiff + 18}] 7 $fixedHeight] [list 43 [expr {2*$fixedDiff + 31}] 7 $fixedHeight] [list 5 [expr {3*$fixedDiff + 44}] 7 $fixedHeight]]
test textDisp-2.17 {LayoutDLine, justification} {textfonts} {
.t configure -wrap word
.t delete 1.0 end
- .t insert 1.0 "Lots of very long words, enough to force word wrap\nThen\nmore lines"
+ .t insert 1.0 "Lots of long words, enough to force word wrap\nThen\nmore lines"
.t tag configure x -justify center
- .t tag add x 1.18
- list [.t bbox 1.0] [.t bbox 1.18] [.t bbox 1.35] [.t bbox 2.0]
-} [list [list 5 5 7 $fixedHeight] [list 15 [expr {$fixedDiff + 18}] 7 $fixedHeight] [list 5 [expr {2*$fixedDiff + 31}] 7 $fixedHeight] [list 5 [expr {3*$fixedDiff + 44}] 7 $fixedHeight]]
+ .t tag add x 1.20
+ list [.t bbox 1.0] [.t bbox 1.20] [.t bbox 1.36] [.t bbox 2.0]
+} [list [list 5 5 7 $fixedHeight] [list 19 [expr {$fixedDiff + 18}] 7 $fixedHeight] [list 5 [expr {2*$fixedDiff + 31}] 7 $fixedHeight] [list 5 [expr {3*$fixedDiff + 44}] 7 $fixedHeight]]
test textDisp-2.18 {LayoutDLine, justification} {textfonts} {
.t configure -wrap none
.t delete 1.0 end
@@ -531,6 +531,7 @@ test textDisp-3.1 {different character sizes} {textfonts} {
.t tag add big 2.11 2.14
list [.t bbox 1.1] [.t bbox 1.6] [.t dlineinfo 1.0] [.t dlineinfo 3.0]
} [list [list 12 [expr {5+$ascentDiff}] 7 $fixedHeight] [list 52 5 13 27] [list 5 5 114 27 [font metrics $bigFont -ascent]] [list 5 [expr {2* $fixedDiff + 85}] 35 $fixedHeight [expr {$fixedDiff + 10}]]]
+
.t configure -wrap char
test textDisp-4.1 {UpdateDisplayInfo, basic} {textfonts} {
.t delete 1.0 end
@@ -599,7 +600,7 @@ test textDisp-4.6 {UpdateDisplayInfo, tiny window} {
wm overrideredirect . 1
}
frame .f2 -width 20 -height 100
- pack .f2 -before .f
+ pack before .f .f2 top
wm geom . 103x103
update
.t configure -wrap none -borderwidth 2
@@ -666,7 +667,9 @@ test textDisp-4.10 {UpdateDisplayInfo, filling in extra vertical space} {
.t delete 13.0 end
update
list [.t index @0,0] $tk_textRelayout $tk_textRedraw
-} {5.0 {12.0 7.0 6.40 6.20 6.0 5.0} {5.0 6.0 6.20 6.40 7.0 12.0}}
+ # Note, the results of wish8.7 are wrong, because wish8.7 is not deleting the last
+ # line, this is a bug, so the results have been changed.
+} {6.0 {13.0 7.0 6.40 6.20 6.0} {6.0 6.20 6.40 7.0 13.0}}
test textDisp-4.11 {UpdateDisplayInfo, filling in extra vertical space} {
.t delete 1.0 end
.t insert end "1\n2\n3\n4\n5\nLine 6 is such a long line that it wraps around, not once but really quite a few times.\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17"
@@ -675,7 +678,7 @@ test textDisp-4.11 {UpdateDisplayInfo, filling in extra vertical space} {
.t delete 14.0 end
update
list [.t index @0,0] $tk_textRelayout $tk_textRedraw
-} {6.40 {13.0 7.0 6.80 6.60 6.40} {6.40 6.60 6.80 7.0 13.0}}
+} {6.60 {14.0 7.0 6.80 6.60} {6.60 6.80 7.0 14.0}}
test textDisp-4.12 {UpdateDisplayInfo, filling in extra vertical space} {
.t delete 1.0 end
.t insert end "1\n2\n3\n4\n5\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16"
@@ -711,7 +714,11 @@ test textDisp-4.14 {UpdateDisplayInfo, special handling for top/bottom lines} {
.t yview scroll 3 units
update
list $tk_textRelayout $tk_textRedraw
-} {{11.0 12.0 13.0} {11.0 12.0 13.0}}
+ # NOTE: old implementation gives the following result:
+ # {11.0 12.0 13.0} {11.0 12.0 13.0}
+ # but revised implementation has a sophisticated line caching, so
+ # the new result is empty.
+} {{} {}}
test textDisp-4.15 {UpdateDisplayInfo, special handling for top/bottom lines} {
.t tag add x 1.0 end
.t yview 4.0
@@ -727,7 +734,11 @@ test textDisp-4.16 {UpdateDisplayInfo, special handling for top/bottom lines} {
.t yview scroll -2 units
update
list $tk_textRelayout $tk_textRedraw
-} {{2.0 3.0} {2.0 3.0}}
+ # NOTE: old implementation gives the following result:
+ # {2.0 3.0} {2.0 3.0}
+ # but revised implementation has a sophisticated line caching, so
+ # the new result is empty.
+} {{} {}}
test textDisp-4.17 {UpdateDisplayInfo, horizontal scrolling} {textfonts} {
.t configure -wrap none
.t delete 1.0 end
@@ -803,6 +814,7 @@ test textDisp-4.23 {UpdateDisplayInfo, no horizontal scrolling except for -wrap
.t configure -wrap char
list [.t bbox 2.0] [.t bbox 2.16]
} [list [list 3 [expr {$fixedDiff + 16}] 7 $fixedHeight] [list 115 [expr {$fixedDiff + 16}] 7 $fixedHeight]]
+
test textDisp-5.1 {DisplayDLine, handling of spacing} {textfonts} {
.t configure -wrap char
.t delete 1.0 end
@@ -921,7 +933,7 @@ test textDisp-6.6 {scrolling in DisplayText, Expose events after scroll} {unix n
test textDisp-6.7 {DisplayText, vertical scrollbar updates} {
.t configure -wrap char
.t delete 1.0 end
- update ; .t count -update -ypixels 1.0 end ; update
+ update ; .t sync; update
set scrollInfo
} {0.0 1.0}
test textDisp-6.8 {DisplayText, vertical scrollbar updates} {
@@ -933,7 +945,7 @@ test textDisp-6.8 {DisplayText, vertical scrollbar updates} {
foreach i {2 3 4 5 6 7 8 9 10 11 12 13} {
.t insert end "\nLine $i"
}
- update ; .t count -update -ypixels 1.0 end ; update
+ update ; .t sync; update
set scrollInfo
} [list 0.0 [expr {10.0/13}]]
.t configure -yscrollcommand {} -xscrollcommand scroll
@@ -1152,7 +1164,7 @@ test textDisp-8.11 {TkTextChanged, scrollbar notification when changes are off-s
.t insert end "a\nb\nc\n"
# We need to wait for our asychronous callbacks to update the
# scrollbar
- update ; .t count -update -ypixels 1.0 end ; update
+ update ; .t sync; update
.t configure -yscrollcommand ""
set scrollInfo
} {0.0 0.625}
@@ -1181,7 +1193,9 @@ test textDisp-8.12 {TkTextChanged, moving the insert cursor redraws only past an
.t mark set insert 3.8 ; # within the same line
update
lappend res $tk_textRedraw
-} {{8.0 9.0} {8.0 12.0} {8.0 12.0} {3.0 8.0} {3.0 4.0}}
+} {{5.0 9.0} {5.0 12.0} {5.0 12.0} {3.0 5.0} {3.0 4.0}}
+# old result is: {{8.0 9.0} {8.0 12.0} {8.0 12.0} {3.0 8.0} {3.0 4.0}}
+# this has changed with new layout algorithm (GC)
test textDisp-8.13 {TkTextChanged, used to crash, see [06c1433906]} {
.t delete 1.0 end
.t insert 1.0 \nLine2\nLine3\n
@@ -1220,7 +1234,7 @@ test textDisp-9.3 {TkTextRedrawTag} {
update
list $tk_textRelayout $tk_textRedraw
} {{2.0 2.20} {2.0 2.20 eof}}
-test textDisp-9.4 {TkTextRedrawTag} {
+test textDisp-9.4 {TkTextRedrawTag} -constraints win -body {
.t configure -wrap char
.t delete 1.0 end
.t insert 1.0 "Line 1\nLine 2 is long enough to wrap around\nLine 3\nLine 4"
@@ -1230,8 +1244,9 @@ test textDisp-9.4 {TkTextRedrawTag} {
.t tag remove big 1.0 end
update
list $tk_textRelayout $tk_textRedraw
-} {{2.0 2.20} {2.0 2.20 eof}}
-test textDisp-9.5 {TkTextRedrawTag} {
+} -result {{2.0 2.20} {2.0 2.20 eof}}
+# under X11 we get the result {2.0 2.20 4.0} {2.0 2.20 4.0 eof}
+test textDisp-9.5 {TkTextRedrawTag} -constraints win -body {
.t configure -wrap char
.t delete 1.0 end
.t insert 1.0 "Line 1\nLine 2 is long enough to wrap around\nLine 3\nLine 4"
@@ -1241,7 +1256,8 @@ test textDisp-9.5 {TkTextRedrawTag} {
.t tag remove big 1.0 end
update
list $tk_textRelayout $tk_textRedraw
-} {{2.0 2.20} {2.0 2.20 eof}}
+} -result {{2.0 2.20} {2.0 2.20 eof}}
+# under X11 we get the result {2.0 2.20 3.0 4.0} {2.0 2.20 3.0 4.0 eof}
test textDisp-9.6 {TkTextRedrawTag} {
.t configure -wrap char
.t delete 1.0 end
@@ -1300,11 +1316,15 @@ test textDisp-9.11 {TkTextRedrawTag} {
.t insert 1.0 "Line 1\nLine 2 is long enough to wrap\nLine 3 is also long enough to wrap\nLine 4"
.t tag add big 1.0 2.0
update
+ set tk_textRedraw {none}
.t tag add big 1.0 2.0
update
set tk_textRedraw
-} {}
-test textDisp-9.12 {TkTextRedrawTag} {
+ # The revised implementation is recognizing that the prior "tag add"
+ # operation is not changing anything, so it won't do any redraw,
+ # thus the result is "none".
+} {none}
+test textDisp-9.12 {TkTextRedrawTag} -constraints win -body {
.t configure -wrap char
.t delete 1.0 end
for {set i 1} {$i < 5} {incr i} {
@@ -1316,7 +1336,8 @@ test textDisp-9.12 {TkTextRedrawTag} {
.t tag add hidden 3.11 4.6
update
list $tk_textRelayout $tk_textRedraw
-} {2.0 {2.0 eof}}
+} -result {2.0 {2.0 eof}}
+# under X11 we get the result {1.0 2.0} {1.0 2.0 eof}
test textDisp-9.13 {TkTextRedrawTag} {
.t configure -wrap none
.t delete 1.0 end
@@ -1395,9 +1416,11 @@ test textDisp-11.1 {TkTextSetYView} {
} {30.0}
test textDisp-11.2 {TkTextSetYView} {
.t yview 30.0
- update
+# this test case has a small timing problem, so wait a bit
+ update; after 50; update
.t yview 32.0
- update
+# this test case has a small timing problem, so wait a bit
+ update; after 50; update
list [.t index @0,0] $tk_textRedraw
} {32.0 {40.0 41.0}}
test textDisp-11.3 {TkTextSetYView} {
@@ -1422,6 +1445,7 @@ test textDisp-11.5 {TkTextSetYView} {
update
list [.t index @0,0] $tk_textRedraw
} {30.0 {}}
+# sometime the result is {30.0 {borders 30.0 31.0 32.0 33.0 34.0 35.0 36.0 37.0 38.0 39.0}}
test textDisp-11.6 {TkTextSetYView} {
.t yview 30.0
update
@@ -1469,7 +1493,11 @@ test textDisp-11.11 {TkTextSetYView} {
.t yview 197.0
update
list [.t index @0,0] $tk_textRedraw
-} {191.0 {191.0 192.0 193.0 194.0 195.0 196.0}}
+# Due to a more intelligent layout algorithm the old result for $tk_textRedraw
+# {191.0 192.0 193.0 194.0 195.0 196.0}
+# isn't valid anymore, the new result is an empty list. Now this test case is
+# a proof that the 'intelligence' of the new layout algorithm is working.
+} {191.0 {}}
test textDisp-11.12 {TkTextSetYView, wrapped line is off-screen} {
.t insert 10.0 "Long line with enough text to wrap\n"
.t yview 1.0
@@ -1906,13 +1934,13 @@ test textDisp-15.3 {ScrollByLines procedure, scrolling backwards} {
.t index @0,0
} {49.0}
test textDisp-15.4 {ScrollByLines procedure, scrolling backwards} {
- .t yview 50.20
+ .t yview 50.21
update
.t yview scroll -2 units
.t index @0,0
} {49.0}
test textDisp-15.5 {ScrollByLines procedure, scrolling backwards} {
- .t yview 50.40
+ .t yview 50.41
update
.t yview scroll -2 units
.t index @0,0
@@ -1938,7 +1966,7 @@ test textDisp-15.8 {Scrolling near end of window} {
frame .tf.f -relief sunken -borderwidth 2
pack .tf.f -padx 10 -pady 10
- text .tf.f.t -font {Courier 9} -height $textheight \
+ text .tf.f.t -font {"Courier New" 9} -height $textheight \
-width $textwidth -yscrollcommand ".tf.f.sb set"
scrollbar .tf.f.sb -command ".tf.f.t yview"
pack .tf.f.t -side left -expand 1 -fill both
@@ -1952,11 +1980,11 @@ test textDisp-15.8 {Scrolling near end of window} {
.tf.f.t insert end "\nLine $i"
}
update ; after 1000 ; update
- set refind [.tf.f.t index @0,[winfo height .tf.f.t]]
+ set refind [.tf.f.t index @0,last]
# Should scroll and should not crash!
.tf.f.t yview scroll 1 unit
# Check that it has scrolled
- set newind [.tf.f.t index @0,[winfo height .tf.f.t]]
+ set newind [.tf.f.t index @0,last]
set res [.tf.f.t compare $newind > $refind]
destroy .tf
set res
@@ -1971,13 +1999,14 @@ for {set i 2} {$i <= 200} {incr i} {
.t tag add big 100.0 105.0
.t insert 151.end { has a lot of extra text, so that it wraps around on the screen several times over.}
.t insert 153.end { also has enoug extra text to wrap.}
-update ; .t count -update -ypixels 1.0 end
-test textDisp-16.1 {TkTextYviewCmd procedure} {
+update ; .t sync; update
+test textDisp-16.1 {TkTextYviewCmd procedure} -constraints win -body {
.t yview 21.0
set x [.t yview]
.t yview 1.0
list [expr {int([lindex $x 0]*100)}] [expr {int ([lindex $x 1] * 100)}]
-} {9 14}
+} -result {9 14}
+# under X11 we get the result {9 13}
test textDisp-16.2 {TkTextYviewCmd procedure} {
list [catch {.t yview 2 3} msg] $msg
} {1 {bad option "2": must be moveto or scroll}}
@@ -2010,10 +2039,11 @@ test textDisp-16.9 {TkTextYviewCmd procedure, "moveto" option} {
test textDisp-16.10 {TkTextYviewCmd procedure, "moveto" option} {
list [catch {.t yview moveto gorp} msg] $msg
} {1 {expected floating-point number but got "gorp"}}
-test textDisp-16.11 {TkTextYviewCmd procedure, "moveto" option} {
+test textDisp-16.11 {TkTextYviewCmd procedure, "moveto" option} -constraints win -body {
.t yview moveto 0.5
.t index @0,0
-} {103.0}
+} -result {103.0}
+# under X11 we get the result {102.0}
test textDisp-16.12 {TkTextYviewCmd procedure, "moveto" option} {
.t yview moveto -1
.t index @0,0
@@ -2022,24 +2052,27 @@ test textDisp-16.13 {TkTextYviewCmd procedure, "moveto" option} {
.t yview moveto 1.1
.t index @0,0
} {191.0}
-test textDisp-16.14 {TkTextYviewCmd procedure, "moveto" option} {
+test textDisp-16.14 {TkTextYviewCmd procedure, "moveto" option} -constraints win -body {
.t yview moveto .75
.t index @0,0
-} {151.60}
-test textDisp-16.15 {TkTextYviewCmd procedure, "moveto" option} {
+} -result {151.60}
+# under X11 we get the result {151.20}
+test textDisp-16.15 {TkTextYviewCmd procedure, "moveto" option} -constraints win -body {
.t yview moveto .752
.t index @0,0
-} {151.60}
+} -result {151.60}
+# under X11 we get the result {151.20}
test textDisp-16.16 {TkTextYviewCmd procedure, "moveto" option} {textfonts} {
set count [expr {5 * $bigHeight + 150 * $fixedHeight}]
set extra [expr {0.04 * double($fixedDiff * 150) / double($count)}]
.t yview moveto [expr {.753 - $extra}]
.t index @0,0
} {151.60}
-test textDisp-16.17 {TkTextYviewCmd procedure, "moveto" option} {
+test textDisp-16.17 {TkTextYviewCmd procedure, "moveto" option} -constraints win -body {
.t yview moveto .755
.t index @0,0
-} {151.80}
+} -result {151.80}
+# under X11 we get the result {151.40}
test textDisp-16.18 {TkTextYviewCmd procedure, "moveto" roundoff} {textfonts} {
catch {destroy .top1}
toplevel .top1
@@ -2087,7 +2120,7 @@ test textDisp-16.24 {TkTextYviewCmd procedure, "scroll" option, back pages} {
.t yview scroll -3 pa
.t index @0,0
} {1.0}
-test textDisp-16.25 {TkTextYviewCmd procedure, "scroll" option, back pages} {
+test textDisp-16.25 {TkTextYviewCmd procedure, "scroll" option, back pages} -constraints win -body {
.t configure -height 1
update
.t yview 50.0
@@ -2097,7 +2130,8 @@ test textDisp-16.25 {TkTextYviewCmd procedure, "scroll" option, back pages} {
.t configure -height 10
update
set x
-} {49.0}
+} -result {49.0}
+# under X11 we get the result {48.0}
test textDisp-16.26 {TkTextYviewCmd procedure, "scroll" option, forward pages} {
.t yview 50.0
update
@@ -2339,7 +2373,7 @@ test textDisp-17.10 {TkTextScanCmd procedure, word wrapping} {textfonts} {
set x [.t index @0,0]
.t scan dragto 0 [expr {70 + $fixedDiff}]
list $x [.t index @0,0]
-} {9.0 8.0}
+} {9.15 8.31}
.t configure -xscrollcommand scroll -yscrollcommand {}
test textDisp-18.1 {GetXView procedure} {
.t configure -wrap none
@@ -2440,7 +2474,6 @@ test textDisp-18.8 {GetXView procedure} {
(horizontal scrolling command executed by text)}}
catch {rename bgerror {}}
catch {rename bogus {}}
-.t configure -xscrollcommand {} -yscrollcommand scroll
.t configure -xscrollcommand {} -yscrollcommand scroll
test textDisp-19.1 {GetYView procedure} {
@@ -2524,7 +2557,7 @@ test textDisp-19.8 {GetYView procedure} {
.t insert 10.end " is really quite long; in fact it's so long that it wraps three times"
.t yview 2.0
update
- .t count -update -ypixels 1.0 end
+ .t sync
set x $scrollInfo
} {0.0625 0.6875}
test textDisp-19.9 {GetYView procedure} {
@@ -2564,11 +2597,11 @@ test textDisp-19.10.1 {Widget manipulation causes height miscount} {
foreach i {2 3 4 5 6 7 8 9 10 11 12 13 14 15} {
.t insert end "\nLine $i"
}
- .t insert end "\nThis last line wraps around four "
- .t insert end "times with a little bit left on the last line."
+ .t insert end "\nThis last line is wrapping around four "
+ .t insert end "times with a bit left on the last line."
.t yview insert
update
- .t count -update -ypixels 1.0 end
+ .t sync
set scrollInfo
} {0.5 1.0}
test textDisp-19.11 {GetYView procedure} {
@@ -2578,11 +2611,11 @@ test textDisp-19.11 {GetYView procedure} {
foreach i {2 3 4 5 6 7 8 9 10 11 12 13 14 15} {
.t insert end "\nLine $i"
}
- .t insert end "\nThis last line wraps around four "
- .t insert end "times with a little bit left on the last line."
+ .t insert end "\nThis last line is wrapping around four "
+ .t insert end "times with a bit left on the last line."
.t yview insert
update
- .t count -update -ypixels 1.0 end
+ .t sync
set scrollInfo
} {0.5 1.0}
test textDisp-19.11.2 {TextWidgetCmd procedure, "count -displaylines"} {
@@ -2601,7 +2634,7 @@ test textDisp-19.11.5.1 {TextWidgetCmd procedure, "count -displaylines"} {
.t count -displaylines 16.0 16.5
} {0}
test textDisp-19.11.6 {TextWidgetCmd procedure, "count -displaylines"} {
- .t count -displaylines 16.0 16.24
+ .t count -displaylines 16.0 16.20
} {1}
test textDisp-19.11.7 {TextWidgetCmd procedure, "count -displaylines"} {
.t count -displaylines 16.0 16.40
@@ -2647,20 +2680,21 @@ test textDisp-19.11.16 {TextWidgetCmd procedure, "count -displaylines"} {
.t tag add elide "12.0" "14.0"
.t count -displaylines 12.0 16.0
} {2}
-test textDisp-19.11.17 {TextWidgetCmd procedure, "index +displaylines"} {
+test textDisp-19.11.17 {TextWidgetCmd procedure, "index +displaylines"} -constraints win -body {
.t tag remove elide 1.0 end
.t tag add elide "12.0" "14.0"
list [.t index "11.5 +2d lines"] \
[.t index "12.0 +2d lines"] [.t index "11.0 +2d lines"] \
[.t index "13.0 +2d lines"] [.t index "13.1 +3d lines"] \
[.t index "13.0 +4d lines"]
-} {15.5 16.0 15.0 16.0 16.21 16.39}
+# under X11 we get the result {15.5 15.0 15.0 15.0 16.0 16.15}
+} -result {15.5 16.0 15.0 16.0 16.15 16.33}
test textDisp-19.11.18 {TextWidgetCmd procedure, "index +displaylines"} {
.t tag remove elide 1.0 end
.t tag add elide "12.0" "14.0"
list [.t index "15.5 -2d lines"] \
[.t index "16.0 -2d lines"] [.t index "15.0 -2d lines"] \
- [.t index "16.0 -3d lines"] [.t index "16.23 -4d lines"] \
+ [.t index "16.0 -3d lines"] [.t index "16.20 -4d lines"] \
[.t index "16.42 -5d lines"]
} {11.5 14.0 11.0 11.0 11.2 11.3}
test textDisp-19.11.19 {TextWidgetCmd procedure, "count -displaylines"} {
@@ -2668,21 +2702,22 @@ test textDisp-19.11.19 {TextWidgetCmd procedure, "count -displaylines"} {
.t tag add elide "12.0" "16.0 +1displaylines"
.t count -displaylines 12.0 17.0
} {4}
-test textDisp-19.11.20 {TextWidgetCmd procedure, "index +displaylines"} {
+test textDisp-19.11.20 {TextWidgetCmd procedure, "index +displaylines"} -constraints win -body {
.t tag remove elide 1.0 end
.t tag add elide "12.0" "16.0 +1displaylines"
list [.t index "11.5 +2d lines"] \
[.t index "12.0 +2d lines"] [.t index "11.0 +2d lines"] \
[.t index "13.0 +2d lines"] [.t index "13.0 +3d lines"] \
[.t index "13.0 +4d lines"]
-} {16.44 16.57 16.39 16.57 16.74 17.0}
+} -result {16.38 16.50 16.33 16.50 16.67 17.0}
+# under X11 we get the result {16.38 16.33 16.33 16.33 16.50 16.67}
test textDisp-19.11.21 {TextWidgetCmd procedure, "index +displaylines"} {
.t tag remove elide 1.0 end
.t tag add elide "12.0" "16.0 +1displaylines"
list [.t index "16.44 -2d lines"] \
- [.t index "16.57 -3d lines"] [.t index "16.39 -2d lines"] \
- [.t index "16.60 -4d lines"] [.t index "16.76 -4d lines"] \
- [.t index "17.0 -5d lines"]
+ [.t index "16.56 -3d lines"] [.t index "16.39 -2d lines"] \
+ [.t index "16.59 -4d lines"] [.t index "16.75 -4d lines"] \
+ [.t index "17.7 -5d lines"]
} {11.5 11.0 11.0 10.3 11.2 11.0}
test textDisp-19.11.22 {TextWidgetCmd procedure, "index +displaylines"} {
.t tag remove elide 1.0 end
@@ -2694,12 +2729,12 @@ test textDisp-19.11.22 {TextWidgetCmd procedure, "index +displaylines"} {
test textDisp-19.11.23 {TextWidgetCmd procedure, "index +displaylines"} {
.t tag remove elide 1.0 end
.t tag add elide "12.3" "16.0 +1displaylines"
- list [.t index "11.5 +1d lines"] [.t index "11.5 +2d lines"] \
- [.t index "12.0 +1d lines"] \
- [.t index "12.0 +2d lines"] [.t index "11.0 +2d lines"] \
+ list [.t index "11.3 +1d lines"] [.t index "11.7 +2d lines"] \
+ [.t index "12.3 +1d lines"] \
+ [.t index "12.3 +2d lines"] [.t index "11.5 +2d lines"] \
[.t index "13.0 +2d lines"] [.t index "13.0 +3d lines"] \
[.t index "13.0 +4d lines"]
-} {16.23 16.44 16.39 16.57 16.39 16.60 16.77 16.79}
+} {16.18 16.41 16.37 16.55 16.39 16.55 16.76 17.0}
.t tag remove elide 1.0 end
test textDisp-19.11.24 {TextWidgetCmd procedure, "index +/-displaylines"} {
list [.t index "11.5 + -1 display lines"] \
@@ -2747,11 +2782,10 @@ test textDisp-19.14 {GetYView procedure} {
foreach i {2 3 4 5 6 7 8 9 10 11 12 13 14 15} {
.t insert end "\nLine $i"
}
- .t insert end "\nThis last line wraps around four "
- .t insert end "times with a little bit left on the last line."
+ .t insert end "\nThis last line is wrapping around four "
+ .t insert end "times with a bit left on the last line."
# Need to update so everything is calculated.
- update ; .t count -update -ypixels 1.0 end
- update ; after 10 ; update
+ update ; .t sync; update
set scrollInfo "unchanged"
.t mark set insert 3.0
.t tag configure x -background red
@@ -2767,8 +2801,8 @@ test textDisp-19.15 {GetYView procedure} {
foreach i {2 3 4 5 6 7 8 9 10 11 12 13 14 15} {
.t insert end "\nLine $i"
}
- .t insert end "\nThis last line wraps around four "
- .t insert end "times with a bit little left on the last line."
+ .t insert end "\nThis last line is wrapping around four "
+ .t insert end "times with a bit left on the last line."
update
.t configure -yscrollcommand scrollError
proc bgerror args {
@@ -2795,10 +2829,10 @@ test textDisp-19.16 {count -ypixels} {
foreach i {2 3 4 5 6 7 8 9 10 11 12 13 14 15} {
.t insert end "\nLine $i"
}
- .t insert end "\nThis last line wraps around four "
- .t insert end "times with a little bit left on the last line."
+ .t insert end "\nThis last line is wrapping around four "
+ .t insert end "times with a bit left on the last line."
# Need to update so everything is calculated.
- update ; .t count -update -ypixels 1.0 end ; update
+ update ; .t sync; update
set res {}
lappend res \
[.t count -ypixels 1.0 end] \
@@ -2865,6 +2899,7 @@ test textDisp-19.19 {count -ypixels with indices in elided lines} {
update
set res [list [.t count -ypixels 5.0 11.0] [.t count -ypixels 5.0 11.20]]
} [list [expr {1 * $fixedHeight}] [expr {2 * $fixedHeight}]]
+
.t delete 1.0 end
.t insert end "Line 1"
for {set i 2} {$i <= 200} {incr i} {
@@ -2873,28 +2908,28 @@ for {set i 2} {$i <= 200} {incr i} {
.t configure -wrap word
.t delete 50.0 51.0
.t insert 50.0 "This is a long line, one that will wrap around twice.\n"
-test textDisp-20.1 {FindDLine} {
+test textDisp-20.1 {FindDLine} {textfonts} {
.t yview 48.0
list [.t dlineinfo 46.0] [.t dlineinfo 47.0] [.t dlineinfo 49.0] \
[.t dlineinfo 58.0]
} [list {} {} [list 3 [expr {$fixedDiff + 16}] 49 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] {}]
-test textDisp-20.2 {FindDLine} {
+test textDisp-20.2 {FindDLine} {textfonts} {
.t yview 100.0
.t yview -pickplace 53.0
- list [.t dlineinfo 50.0] [.t dlineinfo 50.14] [.t dlineinfo 50.21]
-} [list [list 3 [expr {-1 - $fixedDiff/2}] 140 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {-1 - $fixedDiff/2}] 140 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {12 + $fixedDiff/2}] 133 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]]]
-test textDisp-20.3 {FindDLine} {
+ list [.t dlineinfo 50.0] [.t dlineinfo 50.14] [.t dlineinfo 50.15]
+} [list [list 3 [expr {-1 - $fixedDiff/2}] 105 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {-1 - $fixedDiff/2}] 105 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {12 + $fixedDiff/2}] 140 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]]]
+test textDisp-20.3 {FindDLine} {textfonts} {
.t yview 100.0
.t yview 49.0
- list [.t dlineinfo 50.0] [.t dlineinfo 50.24] [.t dlineinfo 57.0]
-} [list [list 3 [expr {$fixedDiff + 16}] 140 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {2*$fixedDiff + 29}] 133 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] {}]
-test textDisp-20.4 {FindDLine} {
+ list [.t dlineinfo 50.0] [.t dlineinfo 50.20] [.t dlineinfo 57.0]
+} [list [list 3 [expr {$fixedDiff + 16}] 105 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {2*$fixedDiff + 29}] 140 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] {}]
+test textDisp-20.4 {FindDLine} {textfonts} {
.t yview 100.0
.t yview 42.0
- list [.t dlineinfo 50.0] [.t dlineinfo 50.24] [.t dlineinfo 50.40]
-} [list [list 3 [expr {8*$fixedDiff + 107}] 140 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {9*$fixedDiff + 120}] 133 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] {}]
+ list [.t dlineinfo 50.0] [.t dlineinfo 50.20] [.t dlineinfo 50.40]
+} [list [list 3 [expr {8*$fixedDiff + 107}] 105 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {9*$fixedDiff + 120}] 140 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] {}]
.t config -wrap none
-test textDisp-20.5 {FindDLine} {
+test textDisp-20.5 {FindDLine} {textfonts} {
.t yview 100.0
.t yview 48.0
list [.t dlineinfo 50.0] [.t dlineinfo 50.20] [.t dlineinfo 50.40]
@@ -2905,7 +2940,7 @@ test textDisp-21.1 {TkTextPixelIndex} {textfonts} {
.t yview 48.0
list [.t index @-10,-10] [.t index @6,6] [.t index @22,6] \
[.t index @102,6] [.t index @38,[expr {$fixedHeight * 4 + 3}]] [.t index @44,67]
-} {48.0 48.0 48.2 48.7 50.45 50.45}
+} {48.0 48.0 48.2 48.7 50.40 50.40}
.t insert end \n
test textDisp-21.2 {TkTextPixelIndex} {textfonts} {
.t yview 195.0
@@ -2961,7 +2996,7 @@ test textDisp-22.1 {TkTextCharBbox} {textfonts} {
.t yview 48.0
list [.t bbox 47.2] [.t bbox 48.0] [.t bbox 50.5] [.t bbox 50.40] \
[.t bbox 58.0]
-} [list {} [list 3 3 7 $fixedHeight] [list 38 [expr {3+2*$fixedHeight}] 7 $fixedHeight] [list 3 [expr {3+4*$fixedHeight}] 7 $fixedHeight] {}]
+} [list {} [list 3 3 7 $fixedHeight] [list 38 [expr {3+2*$fixedHeight}] 7 $fixedHeight] [list 38 [expr {3+4*$fixedHeight}] 7 $fixedHeight] {}]
test textDisp-22.2 {TkTextCharBbox} {textfonts} {
.t config -wrap none
.t yview 48.0
@@ -3085,13 +3120,13 @@ test textDisp-23.1 {TkTextDLineInfo} {textfonts} {
.t yview 48.0
list [.t dlineinfo 47.3] [.t dlineinfo 48.0] [.t dlineinfo 50.40] \
[.t dlineinfo 56.0]
-} [list {} [list 3 3 49 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {4*$fixedDiff + 55}] 91 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] {}]
+} [list {} [list 3 3 49 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] [list 3 [expr {4*$fixedDiff + 55}] 126 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]] {}]
test textDisp-23.2 {TkTextDLineInfo} {textfonts} {
.t config -bd 4 -wrap word
update
.t yview 48.0
.t dlineinfo 50.40
-} [list 7 [expr {4*$fixedDiff + 59}] 91 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]]
+} [list 7 [expr {4*$fixedDiff + 59}] 126 [expr {$fixedDiff + 13}] [expr {$fixedDiff + 10}]]
.t config -bd 0
test textDisp-23.3 {TkTextDLineInfo} {textfonts} {
.t config -wrap none
@@ -3286,11 +3321,11 @@ test textDisp-24.17 {TkTextCharLayoutProc, -wrap word} {textfonts} {
test textDisp-24.18 {TkTextCharLayoutProc, -wrap word} {textfonts} {
.t configure -wrap word
.t delete 1.0 end
- .t insert 1.0 "xxThis is a line that wraps around"
+ .t insert 1.0 "xThis is a line that wraps around"
wm geom . {}
update
- list [.t bbox 1.15] [.t bbox 1.16] [.t bbox 1.17]
-} [list [list 108 3 7 $fixedHeight] [list 115 3 28 $fixedHeight] [list 3 [expr {$fixedDiff + 16}] 7 $fixedHeight]]
+ list [.t bbox 1.14] [.t bbox 1.15] [.t bbox 1.16]
+} [list [list 101 3 7 $fixedHeight] [list 108 3 35 $fixedHeight] [list 3 [expr {$fixedDiff + 16}] 7 $fixedHeight]]
test textDisp-24.19 {TkTextCharLayoutProc, -wrap word} {textfonts} {
.t configure -wrap word
.t delete 1.0 end
@@ -3699,7 +3734,7 @@ test textDisp-28.1 {"yview" option with bizarre scroll command} {
set result [.t2.t index @0,0]
update
lappend result [.t2.t index @0,0]
-} {6.0 1.0}
+} {6.0 2.0}
test textDisp-29.1 {miscellaneous: lines wrap but are still too long} {textfonts} {
catch {destroy .t2}
@@ -3821,7 +3856,7 @@ test textDisp-29.2.5 {miscellaneous: can show last character} {
set iWidth [lindex [.t2.t bbox end-2c] 2]
.t2.t xview scroll 2 units
set iWidth2 [lindex [.t2.t bbox end-2c] 2]
-
+
if {($iWidth == $iWidth2) && $iWidth >= 2} {
set result "correct"
} else {
@@ -3845,6 +3880,7 @@ test textDisp-29.3 {miscellaneous: lines wrap but are still too long} {textfonts
update
list [.t2.t xview] [winfo geom .t2.t.f] [.t2.t bbox 1.3]
} [list [list [expr {16.0/30}] 1.0] 300x50+-155+[expr {$fixedDiff + 18}] {}]
+
test textDisp-30.1 {elidden text joining multiple logical lines} {
.t2.t delete 1.0 end
.t2.t insert 1.0 "1111\n2222\n3333"
@@ -3894,7 +3930,7 @@ test textDisp-31.2 {line update index shifting} {
set res
} [list [expr {100 + $fixedHeight * 6}] [expr {100 + $fixedHeight * 8}] [expr {$fixedHeight * 9}] [expr {$fixedHeight * 7}] [expr {100 + $fixedHeight * 6}]]
-test textDisp-31.3 {line update index shifting} {
+test textDisp-31.3 {line update index shifting} -constraints win -body {
# Should do exactly the same as the above, as long
# as we are correctly tagging the correct lines for
# recalculation. The 'update' and 'delay' must be
@@ -3916,7 +3952,7 @@ test textDisp-31.3 {line update index shifting} {
update ; after 1000 ; update
lappend res [.t count -ypixels 1.0 end]
set res
-} [list [expr {100 + $fixedHeight * 6}] [expr {100 + $fixedHeight * 8}] [expr {$fixedHeight * 9}] [expr {$fixedHeight * 7}] [expr {100 + $fixedHeight * 6}]]
+} -result [list [expr {100 + $fixedHeight * 6}] [expr {100 + $fixedHeight * 8}] [expr {$fixedHeight * 9}] [expr {$fixedHeight * 7}] [expr {100 + $fixedHeight * 6}]]
test textDisp-31.4 {line embedded image height update} {
set res {}
@@ -3972,7 +4008,7 @@ test textDisp-31.6 {line update index shifting} {
set res
} [list [expr {100 + $fixedHeight * 6}] [expr {100 + $fixedHeight * 8}] [expr {$fixedHeight * 9}] [expr {$fixedHeight * 7}] [expr {100 + $fixedHeight * 6}]]
-test textDisp-31.7 {line update index shifting, elided} {
+test textDisp-31.7 {line update index shifting, elided} -constraints win -body {
# The 'update' and 'delay' must be long enough to ensure all
# asynchronous updates have been performed.
set res {}
@@ -3991,7 +4027,7 @@ test textDisp-31.7 {line update index shifting, elided} {
update ; after 1000 ; update
lappend res [.t count -ypixels 1.0 end]
set res
-} [list [expr {$fixedHeight * 1}] [expr {$fixedHeight * 3}] [expr {$fixedHeight * 3}] [expr {$fixedHeight * 2}] [expr {$fixedHeight * 1}] [expr {$fixedHeight * 1}]]
+} -result [list [expr {$fixedHeight * 1}] [expr {$fixedHeight * 3}] [expr {$fixedHeight * 3}] [expr {$fixedHeight * 2}] [expr {$fixedHeight * 1}] [expr {$fixedHeight * 1}]]
test textDisp-32.0 {everything elided} {
# Must not crash
@@ -4014,7 +4050,7 @@ test textDisp-32.1 {everything elided} {
update ; update ; update ; update
destroy .tt
} {}
-test textDisp-32.2 {elide and tags} {
+test textDisp-32.2 {elide and tags} -constraints win -body {
pack [text .tt -height 30 -width 100 -bd 0 \
-highlightthickness 0 -padx 0]
.tt insert end \
@@ -4084,7 +4120,7 @@ test textDisp-32.2 {elide and tags} {
[lindex [.tt bbox "1.0 + 0 displaychars"] 0]]
destroy .tt
set res
-} {{1.0 20 20} {1.29 0 0} {1.0 0 0} {1.29 0 20}\
+} -result {{1.0 20 20} {1.29 0 0} {1.0 0 0} {1.29 0 20}\
{1.0 20 20} {1.29 0 20} {1.0 20 20}}
test textDisp-32.3 "NULL undisplayProc problems: #1791052" -setup {
set img [image create photo -data {
@@ -4134,31 +4170,32 @@ test textDisp-33.1 {one line longer than fits in the widget} {
set result "ok"
}
} {ok}
-test textDisp-33.2 {one line longer than fits in the widget} {
- destroy .tt
- pack [text .tt -wrap char]
- .tt debug 1
- set tk_textHeightCalc ""
- .tt insert 1.0 [string repeat "more wrap + " 1]
- after 100 ; update idletasks
- # Nothing should have been recalculated.
- set tk_textHeightCalc
-} {}
+# Test case 33.2 has been removed. The problem is that the revised
+# implementation is also updating the line heights while computing
+# the display lines for displaying the content, so we cannot
+# distinguish anymore between the display process and the line
+# height calculation.
+#test textDisp-33.2 {one line longer than fits in the widget} {
+# destroy .tt
+# pack [text .tt -wrap char]
+# set tk_textHeightCalc ""
+# .tt insert 1.0 [string repeat "more wrap + " 1]
+# after 100 ; update idletasks
+# # Nothing should have been recalculated.
+# set tk_textHeightCalc
+#} {}
test textDisp-33.3 {one line longer than fits in the widget} {
destroy .tt
pack [text .tt -wrap char]
- .tt debug 1
set tk_textHeightCalc ""
.tt insert 1.0 [string repeat "more wrap + " 300]
- update ; .tt count -update -ypixels 1.0 end ; update
+ .tt sync
# Each line should have been recalculated just once
- .tt debug 0
expr {[llength $tk_textHeightCalc] == [.tt count -displaylines 1.0 end]}
} {1}
test textDisp-33.4 {one line longer than fits in the widget} {
destroy .tt
pack [text .tt -wrap char]
- .tt debug 1
set tk_textHeightCalc ""
.tt insert 1.0 [string repeat "more wrap + " 300]
update ; update ; update
@@ -4170,7 +4207,6 @@ test textDisp-33.4 {one line longer than fits in the widget} {
set result "ok"
}
set idx [.tt index "1.0 + 1 displaylines"]
- .tt debug 0
set result
} {ok}
destroy .tt
@@ -4207,7 +4243,6 @@ test textDisp-34.1 {Line heights recalculation problem: bug 2677890} -setup {
set result {}
} -body {
.t1 insert end $txt
- .t1 debug 1
set ge [winfo geometry .]
scan $ge "%dx%d+%d+%d" width height left top
update
@@ -4243,9 +4278,30 @@ test textDisp-35.1 {Init value of charHeight - Dancing scrollbar bug 1499165} -s
destroy .t1
} -result {1 1}
+test textDisp-36.1 {Display bug with 'yview insert'} -setup {
+ text .t1 -font $fixedFont -width 20 -height 3 -wrap word
+ pack .t1
+ .t1 delete 1.0 end
+ .t1 tag configure elide -elide 1
+ .t1 insert end "Line 1\nThis line is wrapping around two times."
+} -body {
+ .t1 tag add elide 1.3 2.0
+ .t1 yview insert
+ update
+ # wish8.7 now is crashing
+ .t1 yview scroll -1 pixels
+} -cleanup {
+ destroy .t1
+} -result {}
+
deleteWindows
option clear
# cleanup
cleanupTests
return
+
+# Local Variables:
+# mode: tcl
+# End:
+# vi:set ts=8 sw=4:
diff --git a/tests/textImage.test b/tests/textImage.test
index 2666ec5..37a24f1 100644
--- a/tests/textImage.test
+++ b/tests/textImage.test
@@ -17,33 +17,36 @@ imageInit
destroy .t
font create test_font -family courier -size 14
text .t -font test_font
+.t debug on
destroy .t
+proc setup {} {
+ text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
+ pack .t
+}
+
test textImage-1.1 {basic argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image
} -cleanup {
destroy .t
-} -returnCodes error -result {wrong # args: should be ".t image option ?arg ...?"}
+} -returnCodes error -result {wrong # args: should be ".t image option ?arg arg ...?"}
test textImage-1.2 {basic argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image c
} -cleanup {
destroy .t
-} -returnCodes error -result {ambiguous option "c": must be cget, configure, create, or names}
+} -returnCodes error -result {ambiguous option "c": must be bind, cget, configure, create, or names}
test textImage-1.3 {cget argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image cget
} -cleanup {
destroy .t
@@ -52,8 +55,7 @@ test textImage-1.3 {cget argument checking} -setup {
test textImage-1.4 {cget argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image cget blurf -flurp
} -cleanup {
destroy .t
@@ -62,8 +64,7 @@ test textImage-1.4 {cget argument checking} -setup {
test textImage-1.5 {cget argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image cget 1.1 -flurp
} -cleanup {
destroy .t
@@ -72,18 +73,16 @@ test textImage-1.5 {cget argument checking} -setup {
test textImage-1.6 {configure argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image configure
} -cleanup {
destroy .t
-} -returnCodes error -result {wrong # args: should be ".t image configure index ?-option value ...?"}
+} -returnCodes error -result {wrong # args: should be ".t image configure index ?option value ...?"}
test textImage-1.7 {configure argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image configure blurf
} -cleanup {
destroy .t
@@ -92,8 +91,7 @@ test textImage-1.7 {configure argument checking} -setup {
test textImage-1.8 {configure argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image configure 1.1
} -cleanup {
destroy .t
@@ -102,18 +100,16 @@ test textImage-1.8 {configure argument checking} -setup {
test textImage-1.9 {create argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create
} -cleanup {
destroy .t
-} -returnCodes error -result {wrong # args: should be ".t image create index ?-option value ...?"}
+} -returnCodes error -result {wrong # args: should be ".t image create index ?option value ...?"}
test textImage-1.10 {create argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create blurf
} -cleanup {
destroy .t
@@ -126,8 +122,7 @@ test textImage-1.11 {basic argument checking} -setup {
image create photo small -width 5 -height 5
small put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create 1000.1000 -image small
} -cleanup {
destroy .t
@@ -137,8 +132,7 @@ test textImage-1.11 {basic argument checking} -setup {
test textImage-1.12 {names argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image names dates places
} -cleanup {
destroy .t
@@ -153,8 +147,7 @@ test textImage-1.13 {names argument checking} -setup {
image create photo small -width 5 -height 5
small put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
lappend result [.t image names]
.t image create insert -image small
lappend result [.t image names]
@@ -170,12 +163,11 @@ test textImage-1.13 {names argument checking} -setup {
test textImage-1.14 {basic argument checking} -setup {
destroy .t
} -body {
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image huh
} -cleanup {
destroy .t
-} -returnCodes error -result {bad option "huh": must be cget, configure, create, or names}
+} -returnCodes error -result {bad option "huh": must be bind, cget, configure, create, or names}
test textImage-1.15 {align argument checking} -setup {
destroy .t
@@ -184,8 +176,7 @@ test textImage-1.15 {align argument checking} -setup {
image create photo small -width 5 -height 5
small put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -image small -align wrong
} -cleanup {
destroy .t
@@ -199,8 +190,7 @@ test textImage-1.16 {configure} -setup {
image create photo small -width 5 -height 5
small put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -image small
.t image configure small
} -cleanup {
@@ -216,8 +206,7 @@ test textImage-1.17 {basic cget options} -setup {
image create photo small -width 5 -height 5
small put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -image small
foreach i {align padx pady image name} {
lappend result $i:[.t image cget small -$i]
@@ -238,8 +227,7 @@ test textImage-1.18 {basic configure options} -setup {
image create photo large -width 50 -height 50
large put green -to 0 0 50 50
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -image small
foreach {option value} {align top padx 5 pady 7 image large name none} {
.t image configure small -$option $value
@@ -258,8 +246,7 @@ test textImage-1.19 {basic image naming} -setup {
image create photo small -width 5 -height 5
small put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -image small
.t image create end -image small -name small
.t image create end -image small -name small#6342
@@ -268,7 +255,7 @@ test textImage-1.19 {basic image naming} -setup {
} -cleanup {
destroy .t
image delete small
-} -result {small small#1 small#6342 small#6343}
+} -result {small small#1 small#2 small#6342}
test textImage-2.1 {debug} -setup {
destroy .t
@@ -277,15 +264,12 @@ test textImage-2.1 {debug} -setup {
image create photo small -width 5 -height 5
small put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
- .t debug 1
+ setup
.t insert end front
.t image create end -image small
.t insert end back
.t delete small
.t image names
- .t debug 0
} -cleanup {
destroy .t
image delete small
@@ -300,8 +284,7 @@ test textImage-3.1 {image change propagation} -setup {
image create photo vary -width 5 -height 5
vary put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -image vary -align top
update
lappend result base:[.t bbox vary]
@@ -324,8 +307,7 @@ test textImage-3.2 {delayed image management, see also bug 1591493} -setup {
image create photo small -width 5 -height 5
small put red -to 0 0 4 4
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -name test
update
foreach {x1 y1 w1 h1} [.t bbox test] {}
@@ -352,8 +334,7 @@ test textImage-4.1 {alignment checking - except baseline} -setup {
image create photo large -width 50 -height 50
large put green -to 0 0 50 50
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -image large
.t image create end -image small
.t insert end test
@@ -380,9 +361,9 @@ test textImage-4.2 {alignment checking - baseline} -setup {
image create photo large -width 50 -height 50
large put green -to 0 0 50 50
}
+ setup
font create test_font2 -size 5
- text .t -font test_font2 -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ .t configure -font test_font2
.t image create end -image large
.t image create end -image small -align baseline
.t insert end test
@@ -419,8 +400,7 @@ test textImage-4.3 {alignment and padding checking} -constraints {
image create photo large -width 50 -height 50
large put green -to 0 0 50 50
}
- text .t -font test_font -bd 0 -highlightthickness 0 -padx 0 -pady 0
- pack .t
+ setup
.t image create end -image large
.t image create end -image small -padx 5 -pady 10
.t insert end test
@@ -471,3 +451,4 @@ return
# Local variables:
# mode: tcl
# End:
+# vi:set ts=8 sw=4:
diff --git a/tests/textIndex.test b/tests/textIndex.test
index 3f26af5..4c89f0d 100644
--- a/tests/textIndex.test
+++ b/tests/textIndex.test
@@ -13,7 +13,7 @@ namespace import -force tcltest::test
catch {destroy .t}
text .t -font {Courier -12} -width 20 -height 10
-pack .t -expand 1 -fill both
+pack append . .t {top expand fill}
update
.t debug on
wm geometry . {}
@@ -456,7 +456,7 @@ test textIndex-12.9 {TkTextIndexForwChars: find index} {
# not (segPtr->typePtr == &tkTextCharType)
.t image create 2.4 -image textimage
- set x [.t get {2.3 + 3 chars}]
+ set x [.t get {2.3 + 3 indices}]
.t delete 2.4
set x
} "f"
@@ -557,7 +557,7 @@ test textIndex-14.8 {TkTextIndexBackChars: loop backwards over chars} {
# Still more chars, but we reached beginning of segment
.t image create 5.6 -image textimage
- set x [.t index {5.8 - 3 chars}]
+ set x [.t index {5.8 - 3 indices}]
.t delete 5.6
set x
} 5.5
@@ -565,7 +565,7 @@ test textIndex-14.9 {TkTextIndexBackChars: back over image} {
# not (segPtr->typePtr == &tkTextCharType)
.t image create 5.6 -image textimage
- set x [.t get {5.8 - 4 chars}]
+ set x [.t get {5.8 - 4 indices}]
.t delete 5.6
set x
} "G"
@@ -714,7 +714,7 @@ test textIndex-18.1 {Object indices don't cache mark names} {
} {3.4 3.0 1.0}
frame .f -width 100 -height 20
-pack .f -side left
+pack append . .f left
set fixedFont {Courier -12}
set fixedHeight [font metrics $fixedFont -linespace]
@@ -724,9 +724,8 @@ set varFont {Times -14}
set bigFont {Helvetica -24}
destroy .t
text .t -font $fixedFont -width 20 -height 10 -wrap char
-pack .t -expand 1 -fill both
+pack append . .t {top expand fill}
.t tag configure big -font $bigFont
-.t debug on
wm geometry . {}
# The statements below reset the main window; it's needed if the window
@@ -956,8 +955,29 @@ test textIndex-25.1 {IndexCountBytesOrdered, bug [3f1f79abcf]} {
destroy .t2
} {}
+test textIndex-26.1 {TkTextGetIndex bug (wish8.6)} -body {
+ catch {destroy .t}
+ text .t
+ .t insert current "Tes"
+ .t mark set "end" 1.0
+ .t insert end "t"
+ .t get 1.0 "1.0 lineend"
+} -result {Test}
+test textIndex-26.2 {TkTextGetIndex bug (wish8.6)} -body {
+ catch {destroy .t}
+ text .t
+ .t insert end "a"
+ .t mark set 1.0 1.1
+ .t index 1.0
+} -result {1.0}
+
# cleanup
rename textimage {}
catch {destroy .t}
cleanupTests
return
+
+# Local variables:
+# mode: tcl
+# End:
+# vi:set ts=8 sw=4:
diff --git a/tests/textMark.test b/tests/textMark.test
index 043ff82..3bfd089 100644
--- a/tests/textMark.test
+++ b/tests/textMark.test
@@ -13,7 +13,7 @@ tcltest::loadTestedCommands
destroy .t
text .t -width 20 -height 10
-pack .t -expand 1 -fill both
+pack append . .t {top expand fill}
update
.t debug on
wm geometry . {}
@@ -38,7 +38,7 @@ wm deiconify .
test textMark-1.1 {TkTextMarkCmd - missing option} -returnCodes error -body {
.t mark
-} -result {wrong # args: should be ".t mark option ?arg ...?"}
+} -result {wrong # args: should be ".t mark option ?arg arg ...?"}
test textMark-1.2 {TkTextMarkCmd - bogus option} -returnCodes error -body {
.t mark gorp
} -match glob -result {bad mark option "gorp": must be *}
@@ -52,7 +52,7 @@ test textMark-1.4 {TkTextMarkCmd - "gravity" option} -body {
} -result {right 1.4}
test textMark-1.5 {TkTextMarkCmd - "gravity" option} -body {
.t mark set x 1.3
- .t mark g x left
+ .t mark gravity x left
.t insert 1.3 x
list [.t mark gravity x] [.t index x]
} -result {left 1.3}
@@ -88,10 +88,10 @@ test textMark-2.3 {TkTextMarkCmd - "names" option} -setup {
test textMark-3.1 {TkTextMarkCmd - "set" option} -returnCodes error -body {
.t mark set a
-} -result {wrong # args: should be ".t mark set markName index"}
+} -result {wrong # args: should be ".t mark set markName index ?direction?"}
test textMark-3.2 {TkTextMarkCmd - "set" option} -returnCodes error -body {
- .t mark s a b c
-} -result {wrong # args: should be ".t mark set markName index"}
+ .t mark s a b c d
+} -result {wrong # args: should be ".t mark set markName index ?direction?"}
test textMark-3.3 {TkTextMarkCmd - "set" option} -body {
.t mark set a @x
} -returnCodes error -result {bad text index "@x"}
@@ -103,6 +103,9 @@ test textMark-3.5 {TkTextMarkCmd - "set" option} -body {
.t mark set a end
.t index a
} -result {8.0}
+test textMark-3.6 {TkTextMarkCmd - "set" option} -returnCodes error -body {
+ .t mark s a b c
+} -result {bad text index "b"}
test textMark-4.1 {TkTextMarkCmd - "unset" option} -body {
.t mark unset
@@ -129,10 +132,10 @@ test textMark-4.3 {TkTextMarkCmd - "unset" option} -body {
test textMark-5.1 {TkTextMarkCmd - miscellaneous} -returnCodes error -body {
.t mark
-} -result {wrong # args: should be ".t mark option ?arg ...?"}
+} -result {wrong # args: should be ".t mark option ?arg arg ...?"}
test textMark-5.2 {TkTextMarkCmd - miscellaneous} -returnCodes error -body {
.t mark foo
-} -result {bad mark option "foo": must be gravity, names, next, previous, set, or unset}
+} -result {bad mark option "foo": must be compare, exists, generate, gravity, names, next, previous, set, or unset}
test textMark-6.1 {TkTextMarkSegToIndex} -body {
.t mark set a 1.2
@@ -234,7 +237,7 @@ test textMark-7.9 {MarkFindNext - mark set in a text widget and retrieved from a
.t mark unset {*}[.t mark names]
} -body {
.t mark set mymark 1.0
- lsort [list [.pt mark next 1.0] [.pt mark next mymark] [.pt mark next insert]]
+ lsort [list [.pt mark next 1.0] [.pt mark next mymark] [.pt mark next current]]
} -result {current insert mymark}
test textMark-8.1 {MarkFindPrev - invalid mark name} -body {
@@ -304,3 +307,4 @@ return
# Local Variables:
# mode: tcl
# End:
+# vi:set ts=8 sw=4:
diff --git a/tests/textTag.test b/tests/textTag.test
index ca3dc0f..8699a66 100644
--- a/tests/textTag.test
+++ b/tests/textTag.test
@@ -11,8 +11,9 @@ namespace import ::tcltest::*
eval tcltest::configure $argv
tcltest::loadTestedCommands
-set textWidgetFont {Courier 12}
-set bigFont {Courier 24}
+set fam [font configure TkFixedFont -family]
+set textWidgetFont [list $fam 12]
+set bigFont [list $fam 24]
# what is needed is a font that is both fixed-width and featuring a
# specific size because in some tests (that will be constrained by
@@ -29,7 +30,7 @@ testConstraint haveFontSizes [expr {
destroy .t
text .t -width 20 -height 10
-pack .t -expand 1 -fill both
+pack append . .t {top expand fill}
update
.t debug on
@@ -122,7 +123,7 @@ test textTag-1.13 {configuration options} -body {
.t tag configure x -justify middle
} -cleanup {
.t tag configure x -justify [lindex [.t tag configure x -justify] 3]
-} -returnCodes error -result {bad justification "middle": must be left, right, or center}
+} -returnCodes error -result {bad justification "middle": must be left, right, full, or center}
test textTag-1.14 {tag configuration options} -body {
.t tag configure x -lmargin1 10
.t tag cget x -lmargin1
@@ -179,15 +180,15 @@ test textTag-1.21 {configuration options} -body {
.t tag configure x -overstrike [lindex [.t tag configure x -overstrike] 3]
} -returnCodes error -result {expected boolean value but got "stupid"}
test textTag-1.21a {tag configuration options} -body {
- .t tag configure x -overstrikefg red
- .t tag cget x -overstrikefg
+ .t tag configure x -overstrikecolor red
+ .t tag cget x -overstrikecolor
} -cleanup {
- .t tag configure x -overstrikefg [lindex [.t tag configure x -overstrikefg] 3]
+ .t tag configure x -overstrikecolor [lindex [.t tag configure x -overstrikecolor] 3]
} -result {red}
test textTag-1.21b {configuration options} -body {
- .t tag configure x -overstrikefg stupid
+ .t tag configure x -overstrikecolor stupid
} -cleanup {
- .t tag configure x -overstrikefg [lindex [.t tag configure x -overstrikefg] 3]
+ .t tag configure x -overstrikecolor [lindex [.t tag configure x -overstrikecolor] 3]
} -returnCodes error -result {unknown color name "stupid"}
test textTag-1.22 {tag configuration options} -body {
.t tag configure x -relief raised
@@ -300,24 +301,24 @@ test textTag-1.35 {configuration options} -body {
.t tag configure x -underline [lindex [.t tag configure x -underline] 3]
} -returnCodes error -result {expected boolean value but got "stupid"}
test textTag-1.36 {tag configuration options} -body {
- .t tag configure x -underlinefg red
- .t tag cget x -underlinefg
+ .t tag configure x -underlinecolor red
+ .t tag cget x -underlinecolor
} -cleanup {
- .t tag configure x -underlinefg [lindex [.t tag configure x -underlinefg] 3]
+ .t tag configure x -underlinecolor [lindex [.t tag configure x -underlinecolor] 3]
} -result {red}
test textTag-1.37 {configuration options} -body {
- .t tag configure x -underlinefg stupid
+ .t tag configure x -underlinecolor stupid
} -cleanup {
- .t tag configure x -underlinefg [lindex [.t tag configure x -underlinefg] 3]
+ .t tag configure x -underlinecolor [lindex [.t tag configure x -underlinecolor] 3]
} -returnCodes error -result {unknown color name "stupid"}
test textTag-2.1 {TkTextTagCmd - "add" option} -body {
.t tag
-} -returnCodes error -result {wrong # args: should be ".t tag option ?arg ...?"}
+} -returnCodes error -result {wrong # args: should be ".t tag option ?arg arg ...?"}
test textTag-2.2 {TkTextTagCmd - "add" option} -body {
.t tag gorp
-} -returnCodes error -result {bad tag option "gorp": must be add, bind, cget, configure, delete, lower, names, nextrange, prevrange, raise, ranges, or remove}
+} -returnCodes error -result {bad tag option "gorp": must be add, bind, cget, clear, configure, delete, findnext, findprev, getrange, lower, names, nextrange, prevrange, raise, ranges, or remove}
test textTag-2.3 {TkTextTagCmd - "add" option} -body {
.t tag add foo
} -returnCodes error -result {wrong # args: should be ".t tag add tagName index1 ?index2 index1 index2 ...?"}
@@ -488,7 +489,7 @@ test textTag-4.5 {TkTextTagCmd - "cget" option} -body {
test textTag-5.1 {TkTextTagCmd - "configure" option} -body {
.t tag configure
-} -returnCodes error -result {wrong # args: should be ".t tag configure tagName ?-option? ?value? ?-option value ...?"}
+} -returnCodes error -result {wrong # args: should be ".t tag configure tagName ?option? ?value? ?option value ...?"}
test textTag-5.2 {TkTextTagCmd - "configure" option} -body {
.t tag configure x -foo
} -returnCodes error -result {unknown option "-foo"}
@@ -506,11 +507,11 @@ test textTag-5.4 {TkTextTagCmd - "configure" option} -body {
} -result {-underline {} {} {} yes}
test textTag-5.4a {TkTextTagCmd - "configure" option} -body {
.t tag delete x
- .t tag configure x -underlinefg lightgreen
- .t tag configure x -underlinefg
+ .t tag configure x -underlinecolor lightgreen
+ .t tag configure x -underlinecolor
} -cleanup {
.t tag delete x
-} -result {-underlinefg {} {} {} lightgreen}
+} -result {-underlinecolor {} {} {} lightgreen}
test textTag-5.5 {TkTextTagCmd - "configure" option} -body {
.t tag delete x
.t tag configure x -overstrike on
@@ -520,11 +521,11 @@ test textTag-5.5 {TkTextTagCmd - "configure" option} -body {
} -result {on}
test textTag-5.5a {TkTextTagCmd - "configure" option} -body {
.t tag delete x
- .t tag configure x -overstrikefg lightgreen
- .t tag configure x -overstrikefg
+ .t tag configure x -overstrikecolor lightgreen
+ .t tag configure x -overstrikecolor
} -cleanup {
.t tag delete x
-} -result {-overstrikefg {} {} {} lightgreen}
+} -result {-overstrikecolor {} {} {} lightgreen}
test textTag-5.6 {TkTextTagCmd - "configure" option} -body {
.t tag configure x -overstrike foo
} -cleanup {
@@ -548,13 +549,13 @@ test textTag-5.9 {TkTextTagCmd - "configure" option} -body {
.t tag configure x -justify bogus
} -cleanup {
.t tag delete x
-} -returnCodes error -result {bad justification "bogus": must be left, right, or center}
+} -returnCodes error -result {bad justification "bogus": must be left, right, full, or center}
test textTag-5.10 {TkTextTagCmd - "configure" option} -body {
.t tag delete x
.t tag configure x -justify fill
} -cleanup {
.t tag delete x
-} -returnCodes error -result {bad justification "fill": must be left, right, or center}
+} -returnCodes error -result {bad justification "fill": must be left, right, full, or center}
test textTag-5.11 {TkTextTagCmd - "configure" option} -body {
.t tag delete x
.t tag configure x -offset 2
@@ -787,7 +788,7 @@ test textTag-8.1 {TkTextTagCmd - "names" option} -body {
.t tag names a b
} -cleanup {
.t tag delete {*}[.t tag names]
-} -returnCodes error -result {wrong # args: should be ".t tag names ?index?"}
+} -returnCodes error -result {wrong # args: should be ".t tag names ?options? ?index?"}
test textTag-8.2 {TkTextTagCmd - "names" option} -setup {
.t tag delete {*}[.t tag names]
.t tag remove sel 1.0 end
@@ -1520,3 +1521,5 @@ destroy .t
# cleanup
cleanupTests
return
+
+# vi:set ts=8 sw=4:
diff --git a/tests/textWind.test b/tests/textWind.test
index d32bd8d..524b598 100644
--- a/tests/textWind.test
+++ b/tests/textWind.test
@@ -22,7 +22,7 @@ option add *Text.font {"Courier New" -12}
deleteWindows
# Widget used in tests 1.* - 16.*
text .t -width 30 -height 6 -bd 2 -highlightthickness 2
-pack .t -expand 1 -fill both
+pack append . .t {top expand fill}
update
.t debug on
@@ -32,7 +32,7 @@ set fixedDiff [expr {$fixedHeight - 13}] ;# 2 on XP
set color [expr {[winfo depth .t] > 1 ? "green" : "black"}]
wm geometry . {}
-
+
# The statements below reset the main window; it's needed if the window
# manager is mwm to make mwm forget about a previous minimum size setting.
@@ -109,7 +109,7 @@ test textWind-1.6 {basic tests of options} -constraints fonts -setup {
.t insert end "This is the first line"
test textWind-2.1 {TkTextWindowCmd procedure} -body {
.t window
-} -returnCodes error -result {wrong # args: should be ".t window option ?arg ...?"}
+} -returnCodes error -result {wrong # args: should be ".t window option ?arg arg ...?"}
test textWind-2.2 {TkTextWindowCmd procedure, "cget" option} -body {
.t window cget
} -returnCodes error -result {wrong # args: should be ".t window cget index option"}
@@ -142,7 +142,7 @@ test textWind-2.7 {TkTextWindowCmd procedure, "cget" option} -setup {
} -returnCodes ok -result {2}
test textWind-2.8 {TkTextWindowCmd procedure} -body {
.t window co
-} -returnCodes error -result {wrong # args: should be ".t window configure index ?-option value ...?"}
+} -returnCodes error -result {wrong # args: should be ".t window configure index ?option value ...?"}
test textWind-2.9 {TkTextWindowCmd procedure} -body {
.t window configure gorp
} -returnCodes error -result {bad text index "gorp"}
@@ -203,7 +203,7 @@ test textWind-2.14 {TkTextWindowCmd procedure} -setup {
.t delete 1.0 end
} -body {
.t window create
-} -returnCodes error -result {wrong # args: should be ".t window create index ?-option value ...?"}
+} -returnCodes error -result {wrong # args: should be ".t window create index ?option value ...?"}
test textWind-2.15 {TkTextWindowCmd procedure} -setup {
.t delete 1.0 end
} -body {
@@ -769,6 +769,8 @@ test textWind-10.8 {EmbWinLayoutProc procedure, error in creating window} -setup
destroy .t2
proc bgerror args {
global msg
+# in prior versions the message {window name "t2" already exists in parent}
+# has been received, but not anymore since 8.5.
if {[lsearch -exact $msg $args] == -1} {
lappend msg $args
}
@@ -788,7 +790,7 @@ test textWind-10.8 {EmbWinLayoutProc procedure, error in creating window} -setup
} -cleanup {
destroy .t2
rename bgerror {}
-} -result {{{can't embed .t2 relative to .t}} {{window name "t2" already exists in parent}}}
+} -result {{{can't embed .t2 relative to .t}}}
test textWind-10.9 {EmbWinLayoutProc procedure, steal window from self} -setup {
.t delete 1.0 end
@@ -1458,25 +1460,13 @@ test textWind-17.10 {peer widget window configuration} -setup {
destroy .tt .t
} -result {{-window {} {} {} {}} {-window {} {} {} {}} {-window {} {} {} .t.f} {-window {} {} {} .tt.t.f}}
-test textWind-18.1 {embedded window deletion triggered by a script bound to <Map>} -setup {
- catch {destroy .t .f .f2}
-} -body {
- pack [text .t]
- for {set i 1} {$i < 100} {incr i} {.t insert end "Line $i\n"}
- .t window create end -window [frame .f -background red -width 80 -height 80]
- .t window create end -window [frame .f2 -background blue -width 80 -height 80]
- bind .f <Map> {.t delete .f}
- update
- # this shall not crash (bug 1501749)
- after 100 {.t yview end}
- tkwait visibility .f2
- update
-} -cleanup {
- destroy .t .f .f2
-} -result {}
-
option clear
# cleanup
cleanupTests
return
+
+# Local variables:
+# mode: tcl
+# End:
+# vi:set ts=8 sw=4:
diff --git a/unix/Makefile.in b/unix/Makefile.in
index 1b8677e..6a771de 100644
--- a/unix/Makefile.in
+++ b/unix/Makefile.in
@@ -227,7 +227,7 @@ INSTALL_DATA = ${INSTALL} -m 644
INSTALL_DATA_DIR = ${INSTALL} -d -m 755
# The symbol below provides support for dynamic loading and shared
-# libraries. See configure.ac for a description of what it means.
+# libraries. See configure.in for a description of what it means.
# The value of the symbol is normally set by the configure script.
SHLIB_CFLAGS = @SHLIB_CFLAGS@ -DBUILD_tk
@@ -266,7 +266,7 @@ LIBS = @LIBS@ $(X11_LIB_SWITCHES) @TCL_LIBS@
WISH_LIBS = $(TCL_LIB_SPEC) @LIBS@ $(X11_LIB_SWITCHES) @TCL_LIBS@ @EXTRA_WISH_LIBS@
# The symbols below provide support for dynamic loading and shared
-# libraries. See configure.ac for a description of what the
+# libraries. See configure.in for a description of what the
# symbols mean. The values of the symbols are normally set by the
# configure script. You shouldn't normally need to modify any of
# these definitions by hand.
@@ -358,19 +358,20 @@ CANV_OBJS = tkCanvas.o tkCanvArc.o tkCanvBmap.o tkCanvImg.o \
IMAGE_OBJS = tkImage.o tkImgBmap.o tkImgGIF.o tkImgPNG.o tkImgPPM.o \
tkImgPhoto.o tkImgPhInstance.o
-TEXT_OBJS = tkText.o tkTextBTree.o tkTextDisp.o tkTextImage.o tkTextIndex.o \
- tkTextMark.o tkTextTag.o tkTextWind.o
+TEXT_OBJS = tkText.o tkTextBTree.o tkTextDisp.o tkTextImage.o \
+ tkTextIndex.o tkTextLineBreak.o tkTextMark.o tkTextTag.o \
+ tkTextTagSet.o tkTextUndo.o tkTextWind.o
# either tkUnixFont.o (default) or tkUnixRFont.o (if --enable-xft)
#
FONT_OBJS = @UNIX_FONT_OBJS@
-GENERIC_OBJS = tk3d.o tkArgv.o tkAtom.o tkBind.o tkBitmap.o tkBusy.o \
- tkClipboard.o \
- tkCmds.o tkColor.o tkConfig.o tkConsole.o tkCursor.o tkError.o \
- tkEvent.o tkFocus.o tkFont.o tkGet.o tkGC.o tkGeometry.o tkGrab.o \
- tkGrid.o tkMain.o tkObj.o tkOldConfig.o tkOption.o tkPack.o tkPlace.o \
- tkSelect.o tkStyle.o tkUndo.o tkUtil.o tkVisual.o tkWindow.o
+GENERIC_OBJS = tk3d.o tkArgv.o tkAtom.o tkBind.o tkBitmap.o tkBitField.o \
+ tkBusy.o tkClipboard.o tkCmds.o tkColor.o tkConfig.o tkConsole.o \
+ tkCursor.o tkError.o tkEvent.o tkFocus.o tkFont.o tkGet.o tkGC.o \
+ tkGeometry.o tkGrab.o tkGrid.o tkIntSet.o tkMain.o tkObj.o tkOldConfig.o \
+ tkOption.o tkPack.o tkPlace.o tkQTree.o tkRangeList.o tkSelect.o tkStyle.o \
+ tkUtil.o tkVisual.o tkWindow.o
TTK_OBJS = \
ttkBlink.o ttkButton.o ttkCache.o ttkClamTheme.o ttkClassicTheme.o \
@@ -420,8 +421,8 @@ TTK_DECLS = \
GENERIC_SRCS = \
$(GENERIC_DIR)/tk3d.c $(GENERIC_DIR)/tkArgv.c \
$(GENERIC_DIR)/tkAtom.c $(GENERIC_DIR)/tkBind.c \
- $(GENERIC_DIR)/tkBitmap.c $(GENERIC_DIR)/tkBusy.c \
- $(GENERIC_DIR)/tkClipboard.c \
+ $(GENERIC_DIR)/tkBitField.c $(GENERIC_DIR)/tkBitmap.c \
+ $(GENERIC_DIR)/tkBusy.c $(GENERIC_DIR)/tkClipboard.c \
$(GENERIC_DIR)/tkCmds.c $(GENERIC_DIR)/tkColor.c \
$(GENERIC_DIR)/tkConfig.c $(GENERIC_DIR)/tkCursor.c \
$(GENERIC_DIR)/tkError.c $(GENERIC_DIR)/tkEvent.c \
@@ -429,10 +430,11 @@ GENERIC_SRCS = \
$(GENERIC_DIR)/tkGet.c $(GENERIC_DIR)/tkGC.c \
$(GENERIC_DIR)/tkGeometry.c $(GENERIC_DIR)/tkGrab.c \
$(GENERIC_DIR)/tkGrid.c $(GENERIC_DIR)/tkConsole.c \
+ $(GENERIC_DIR)/tkIntSet.c \
$(GENERIC_DIR)/tkMain.c $(GENERIC_DIR)/tkOption.c \
$(GENERIC_DIR)/tkPack.c $(GENERIC_DIR)/tkPlace.c \
$(GENERIC_DIR)/tkSelect.c $(GENERIC_DIR)/tkStyle.c \
- $(GENERIC_DIR)/tkUndo.c $(GENERIC_DIR)/tkUtil.c \
+ $(GENERIC_DIR)/tkUtil.c \
$(GENERIC_DIR)/tkVisual.c $(GENERIC_DIR)/tkWindow.c \
$(GENERIC_DIR)/tkButton.c $(GENERIC_DIR)/tkObj.c \
$(GENERIC_DIR)/tkEntry.c $(GENERIC_DIR)/tkFrame.c \
@@ -450,11 +452,14 @@ GENERIC_SRCS = \
$(GENERIC_DIR)/tkImgBmap.c $(GENERIC_DIR)/tkImgGIF.c \
$(GENERIC_DIR)/tkImgPNG.c $(GENERIC_DIR)/tkImgPPM.c \
$(GENERIC_DIR)/tkImgPhoto.c $(GENERIC_DIR)/tkImgPhInstance.c \
- $(GENERIC_DIR)/tkText.c \
- $(GENERIC_DIR)/tkTextBTree.c $(GENERIC_DIR)/tkTextDisp.c \
- $(GENERIC_DIR)/tkTextImage.c \
- $(GENERIC_DIR)/tkTextIndex.c $(GENERIC_DIR)/tkTextMark.c \
- $(GENERIC_DIR)/tkTextTag.c $(GENERIC_DIR)/tkTextWind.c \
+ $(GENERIC_DIR)/tkQTree.c $(GENERIC_DIR)/tkRangeList.c \
+ $(GENERIC_DIR)/tkText.c $(GENERIC_DIR)/tkTextBTree.c \
+ $(GENERIC_DIR)/tkTextDisp.c $(GENERIC_DIR)/tkTextImage.c \
+ $(GENERIC_DIR)/tkTextIndex.c $(GENERIC_DIR)/tkTextLineBreak.c \
+ $(GENERIC_DIR)/tkTextMark.c $(GENERIC_DIR)/tkTextTag.c \
+ $(GENERIC_DIR)/tkTextTag.c $(GENERIC_DIR)/tkTextTagSet.c \
+ $(GENERIC_DIR)/tkTextTagSet.c $(GENERIC_DIR)/tkTextUndo.c \
+ $(GENERIC_DIR)/tkTextUndo.c $(GENERIC_DIR)/tkTextWind.c \
$(GENERIC_DIR)/tkOldConfig.c $(GENERIC_DIR)/tkOldTest.c \
$(GENERIC_DIR)/tkSquare.c $(GENERIC_DIR)/tkTest.c \
$(GENERIC_DIR)/tkStubInit.c
@@ -723,7 +728,7 @@ install-binaries: $(TK_STUB_LIB_FILE) $(TK_LIB_FILE) ${WISH_EXE}
echo "Creating package index $(PKG_INDEX)"; \
rm -f "$(PKG_INDEX)"; \
(\
- echo "if {[catch {package present Tcl 8.6-}]} return";\
+ echo "if {[catch {package present Tcl 8.6.0}]} return";\
relative=`echo | awk '{ORS=" "; split("$(TK_PKG_DIR)",a,"/"); for (f in a) {print ".."}}'`;\
if test "x$(DLL_INSTALL_DIR)" != "x$(BIN_INSTALL_DIR)"; then \
echo "package ifneeded Tk $(MAJOR_VERSION).$(MINOR_VERSION)$(PATCH_LEVEL) [list load [file normalize [file join \$$dir $${relative}$(TK_LIB_FILE)]] Tk]";\
@@ -943,6 +948,9 @@ tkAtom.o: $(GENERIC_DIR)/tkAtom.c
tkBind.o: $(GENERIC_DIR)/tkBind.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkBind.c
+tkBitField.o: $(GENERIC_DIR)/tkBitField.c
+ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkBitField.c
+
tkBitmap.o: $(GENERIC_DIR)/tkBitmap.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkBitmap.c
@@ -1123,6 +1131,15 @@ tkImgPhInstance.o: $(GENERIC_DIR)/tkImgPhInstance.c $(GENERIC_DIR)/tkImgPhoto.h
tkOldTest.o: $(GENERIC_DIR)/tkOldTest.c
$(CC) -c $(APP_CC_SWITCHES) $(GENERIC_DIR)/tkOldTest.c
+tkQTree.o: $(GENERIC_DIR)/tkQTree.c
+ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkQTree.c
+
+tkRangeList.o: $(GENERIC_DIR)/tkRangeList.c
+ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkRangeList.c
+
+tkIntSet.o: $(GENERIC_DIR)/tkIntSet.c
+ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkIntSet.c
+
tkTest.o: $(GENERIC_DIR)/tkTest.c
$(CC) -c $(APP_CC_SWITCHES) $(GENERIC_DIR)/tkTest.c
@@ -1141,12 +1158,21 @@ tkTextImage.o: $(GENERIC_DIR)/tkTextImage.c
tkTextIndex.o: $(GENERIC_DIR)/tkTextIndex.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkTextIndex.c
+tkTextLineBreak.o: $(GENERIC_DIR)/tkTextLineBreak.c
+ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkTextLineBreak.c
+
tkTextMark.o: $(GENERIC_DIR)/tkTextMark.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkTextMark.c
tkTextTag.o: $(GENERIC_DIR)/tkTextTag.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkTextTag.c
+tkTextTagSet.o: $(GENERIC_DIR)/tkTextTagSet.c
+ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkTextTagSet.c
+
+tkTextUndo.o: $(GENERIC_DIR)/tkTextUndo.c
+ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkTextUndo.c
+
tkTextWind.o: $(GENERIC_DIR)/tkTextWind.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tkTextWind.c
@@ -1465,7 +1491,7 @@ $(TTK_DIR)/ttkStubInit.c: $(TTK_DIR)/ttk.decls
genstubs:
$(TCL_EXE) $(TOOL_DIR)/genStubs.tcl $(GENERIC_DIR) \
$(GENERIC_DIR)/tk.decls $(GENERIC_DIR)/tkInt.decls
- $(TCL_EXE) $(TOOL_DIR)/genStubs.tcl $(TTK_DIR) $(TTK_DIR)/ttk.decls
+ $(TCL_EXE) $(TTK_DIR)/ttkGenStubs.tcl $(TTK_DIR) $(TTK_DIR)/ttk.decls
#
# Target to check that all exported functions have an entry in the stubs
@@ -1528,7 +1554,7 @@ DISTNAME = tk${VERSION}${PATCH_LEVEL}
ZIPNAME = tk${MAJOR_VERSION}${MINOR_VERSION}${PATCH_LEVEL}-src.zip
DISTDIR = $(DISTROOT)/$(DISTNAME)
TCLDIR = @TCL_SRC_DIR@
-$(UNIX_DIR)/configure: $(UNIX_DIR)/configure.ac $(UNIX_DIR)/tcl.m4 \
+$(UNIX_DIR)/configure: $(UNIX_DIR)/configure.in $(UNIX_DIR)/tcl.m4 \
$(UNIX_DIR)/aclocal.m4
cd $(UNIX_DIR); autoconf
$(MAC_OSX_DIR)/configure: $(MAC_OSX_DIR)/configure.ac $(UNIX_DIR)/configure
@@ -1542,12 +1568,12 @@ dist: $(UNIX_DIR)/configure $(UNIX_DIR)/tkConfig.h.in $(UNIX_DIR)/tk.pc.in $(M
cp -p $(UNIX_DIR)/*.c $(UNIX_DIR)/*.h $(DISTDIR)/unix
cp $(TOP_DIR)/license.terms $(UNIX_DIR)/Makefile.in $(DISTDIR)/unix
chmod 664 $(DISTDIR)/unix/Makefile.in
- cp $(UNIX_DIR)/configure $(UNIX_DIR)/configure.ac $(UNIX_DIR)/tk.spec \
+ cp $(UNIX_DIR)/configure $(UNIX_DIR)/configure.in $(UNIX_DIR)/tk.spec \
$(UNIX_DIR)/aclocal.m4 $(UNIX_DIR)/tcl.m4 \
$(UNIX_DIR)/tkConfig.sh.in $(TCLDIR)/unix/install-sh \
$(UNIX_DIR)/README $(UNIX_DIR)/installManPage \
$(UNIX_DIR)/tkConfig.h.in $(UNIX_DIR)/tk.pc.in $(DISTDIR)/unix
- chmod 775 $(DISTDIR)/unix/configure $(DISTDIR)/unix/configure.ac
+ chmod 775 $(DISTDIR)/unix/configure $(DISTDIR)/unix/configure.in
mkdir $(DISTDIR)/bitmaps
@(cd $(TOP_DIR); for i in bitmaps/* ; do \
if [ -f $$i ] ; then \
@@ -1568,7 +1594,7 @@ dist: $(UNIX_DIR)/configure $(UNIX_DIR)/tkConfig.h.in $(UNIX_DIR)/tk.pc.in $(M
$(TTK_DIR)/ttkGenStubs.tcl $(DISTDIR)/generic/ttk
mkdir $(DISTDIR)/win
cp $(TOP_DIR)/win/Makefile.in $(DISTDIR)/win
- cp $(TOP_DIR)/win/configure.ac \
+ cp $(TOP_DIR)/win/configure.in \
$(TOP_DIR)/win/configure \
$(TOP_DIR)/win/tkConfig.sh.in \
$(TOP_DIR)/win/aclocal.m4 $(TOP_DIR)/win/tcl.m4 \
diff --git a/unix/tkUnixRFont.c b/unix/tkUnixRFont.c
index 36c4540..46049e6 100644
--- a/unix/tkUnixRFont.c
+++ b/unix/tkUnixRFont.c
@@ -611,11 +611,24 @@ TkpGetSubFonts(
*----------------------------------------------------------------------
*/
+/* we need backward compatibility */
+#if TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION < 7
+# define UNICHAR Tcl_UniChar
+# define TkUtfToUniChar Tcl_UtfToUniChar
+#else /* if !(TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION < 7) */
+# define UNICHAR int
+# if TCL_UTF_MAX > 4
+# define TkUtfToUniChar Tcl_UtfToUniChar
+# else /* if TCL_UTF_MAX <= 4 */
+extern int TkUtfToUniChar(const char *src, int *chPtr);
+# endif /* TCL_UTF_MAX > 4 */
+#endif /* TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION < 7 */
+
void
TkpGetFontAttrsForChar(
Tk_Window tkwin, /* Window on the font's display */
Tk_Font tkfont, /* Font to query */
- int c, /* Character of interest */
+ UNICHAR c, /* Character of interest */
TkFontAttributes *faPtr) /* Output: Font attributes */
{
UnixFtFont *fontPtr = (UnixFtFont *) tkfont;
@@ -668,7 +681,7 @@ Tk_MeasureChars(
curByte = 0;
sawNonSpace = 0;
while (numBytes > 0) {
- int unichar;
+ UNICHAR unichar;
clen = TkUtfToUniChar(source, &unichar);
c = (FcChar32) unichar;
@@ -856,6 +869,10 @@ Tk_DrawChars(
ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
+ if (maxCoord <= y) {
+ return; /* nothing to draw */
+ }
+
if (fontPtr->ftDraw == 0) {
#if DEBUG_FONTSEL
printf("Switch to drawable 0x%x\n", drawable);
@@ -876,7 +893,7 @@ Tk_DrawChars(
XftDrawSetClip(fontPtr->ftDraw, tsdPtr->clipRegion);
}
nspec = 0;
- while (numBytes > 0 && x <= maxCoord && y <= maxCoord) {
+ while (numBytes > 0) {
XftFont *ftFont;
FcChar32 c;
@@ -893,19 +910,25 @@ Tk_DrawChars(
ftFont = GetFont(fontPtr, c, 0.0);
if (ftFont) {
- specs[nspec].font = ftFont;
+ int cx = x;
+ int cy = y;
+
specs[nspec].glyph = XftCharIndex(fontPtr->display, ftFont, c);
- specs[nspec].x = x;
- specs[nspec].y = y;
XftGlyphExtents(fontPtr->display, ftFont, &specs[nspec].glyph, 1,
&metrics);
- x += metrics.xOff;
- y += metrics.yOff;
- nspec++;
- if (nspec == NUM_SPEC) {
- XftDrawGlyphFontSpec(fontPtr->ftDraw, xftcolor,
- specs, nspec);
- nspec = 0;
+ if ((x += metrics.xOff) >= maxCoord
+ || (y += metrics.yOff) >= maxCoord) {
+ break;
+ }
+ if (metrics.xOff > 0 && cx >= 0 && cy >= 0) {
+ specs[nspec].font = ftFont;
+ specs[nspec].x = cx;
+ specs[nspec].y = cy;
+ if (++nspec == NUM_SPEC) {
+ XftDrawGlyphFontSpec(fontPtr->ftDraw, xftcolor,
+ specs, nspec);
+ nspec = 0;
+ }
}
}
}
@@ -1008,8 +1031,7 @@ TkDrawAngledChars(
currentFtFont = NULL;
originX = originY = 0; /* lint */
- while (numBytes > 0 && x <= maxCoord && x >= minCoord && y <= maxCoord
- && y >= minCoord) {
+ while (numBytes > 0 && x >= minCoord && y >= minCoord) {
XftFont *ftFont;
FcChar32 c;
@@ -1047,8 +1069,17 @@ TkDrawAngledChars(
XftGlyphExtents(fontPtr->display, currentFtFont, glyphs,
nglyph, &metrics);
nglyph = 0;
- x += metrics.xOff;
- y += metrics.yOff;
+ /*
+ * Breaking at this place is sub-optimal, but the whole algorithm
+ * has a design problem, the choice of NUM_SPEC is arbitrary, and so
+ * the inter-glyph spacing will look arbitrary. This algorithm
+ * has to draw the whole string at once (or whole blocks with same
+ * font), this requires a dynamic 'glyphs' array. In case of overflow
+ * the array has to be divided until the maximal string will fit. (GC)
+ */
+ if ((x += metrics.xOff) >= maxCoord || (y += metrics.yOff) >= maxCoord) {
+ break;
+ }
}
currentFtFont = ftFont;
}
@@ -1084,8 +1115,7 @@ TkDrawAngledChars(
XftDrawSetClip(fontPtr->ftDraw, tsdPtr->clipRegion);
}
nspec = 0;
- while (numBytes > 0 && x <= maxCoord && x >= minCoord
- && y <= maxCoord && y >= minCoord) {
+ while (numBytes > 0 && x >= minCoord && y >= minCoord) {
XftFont *ftFont, *ft0Font;
FcChar32 c;
@@ -1109,10 +1139,11 @@ TkDrawAngledChars(
specs[nspec].y = ROUND16(y);
XftGlyphExtents(fontPtr->display, ft0Font, &specs[nspec].glyph, 1,
&metrics);
- x += metrics.xOff*cosA + metrics.yOff*sinA;
- y += metrics.yOff*cosA - metrics.xOff*sinA;
- nspec++;
- if (nspec == NUM_SPEC) {
+ if ((x += metrics.xOff*cosA + metrics.yOff*sinA) > maxCoord
+ || (y += metrics.yOff*cosA - metrics.xOff*sinA) > maxCoord) {
+ break;
+ }
+ if (++nspec == NUM_SPEC) {
XftDrawGlyphFontSpec(fontPtr->ftDraw, xftcolor,
specs, nspec);
nspec = 0;
diff --git a/win/Makefile.in b/win/Makefile.in
index 80d616b..565ea31 100644
--- a/win/Makefile.in
+++ b/win/Makefile.in
@@ -27,7 +27,6 @@ exec_prefix = @exec_prefix@
bindir = @bindir@
libdir = @libdir@
includedir = @includedir@
-datarootdir = @datarootdir@
mandir = @mandir@
# The following definition can be set to non-null for special systems
@@ -170,7 +169,7 @@ LDFLAGS_OPTIMIZE = @LDFLAGS_OPTIMIZE@
#CFLAGS = $(CFLAGS_DEBUG)
#CFLAGS = $(CFLAGS_OPTIMIZE)
#CFLAGS = $(CFLAGS_DEBUG) $(CFLAGS_OPTIMIZE)
-CFLAGS = @CFLAGS@ @CFLAGS_DEFAULT@ -DUNICODE -D_UNICODE -D_ATL_XP_TARGETING
+CFLAGS = @CFLAGS@ @CFLAGS_DEFAULT@ -DUNICODE -D_UNICODE
# Special compiler flags to use when building man2tcl on Windows.
MAN2TCLFLAGS = @MAN2TCLFLAGS@
@@ -285,6 +284,7 @@ TK_OBJS = \
tkArgv.$(OBJEXT) \
tkAtom.$(OBJEXT) \
tkBind.$(OBJEXT) \
+ tkBitField.$(OBJEXT) \
tkBitmap.$(OBJEXT) \
tkBusy.$(OBJEXT) \
tkButton.$(OBJEXT) \
@@ -323,6 +323,7 @@ TK_OBJS = \
tkImgPhoto.$(OBJEXT) \
tkImgPhInstance.$(OBJEXT) \
tkImgUtil.$(OBJEXT) \
+ tkIntSet.$(OBJEXT) \
tkListbox.$(OBJEXT) \
tkMacWinMenu.$(OBJEXT) \
tkMain.$(OBJEXT) \
@@ -338,6 +339,8 @@ TK_OBJS = \
tkPack.$(OBJEXT) \
tkPlace.$(OBJEXT) \
tkPointer.$(OBJEXT) \
+ tkQTree.$(OBJEXT) \
+ tkRangeList.$(OBJEXT) \
tkRectOval.$(OBJEXT) \
tkScale.$(OBJEXT) \
tkScrollbar.$(OBJEXT) \
@@ -348,11 +351,12 @@ TK_OBJS = \
tkTextDisp.$(OBJEXT) \
tkTextImage.$(OBJEXT) \
tkTextIndex.$(OBJEXT) \
+ tkTextLineBreak.$(OBJEXT) \
tkTextMark.$(OBJEXT) \
tkTextTag.$(OBJEXT) \
+ tkTextTagSet.$(OBJEXT) \
tkTextWind.$(OBJEXT) \
tkTrig.$(OBJEXT) \
- tkUndo.$(OBJEXT) \
tkUtil.$(OBJEXT) \
tkVisual.$(OBJEXT) \
tkStubInit.$(OBJEXT) \
@@ -487,7 +491,7 @@ install-binaries: binaries
@echo "Creating package index $(PKG_INDEX)";
@$(RM) $(PKG_INDEX);
@(\
- echo "if {[catch {package present Tcl 8.6-}]} return";\
+ echo "if {[catch {package present Tcl 8.6.0}]} return";\
echo "if {(\$$::tcl_platform(platform) eq \"unix\") && ([info exists ::env(DISPLAY)]";\
echo " || ([info exists ::argv] && (\"-display\" in \$$::argv)))} {";\
echo " package ifneeded Tk $(VERSION)$(PATCH_LEVEL) [list load [file normalize [file join \$$dir .. .. bin libtk$(VERSION).dll]] Tk]";\
@@ -730,7 +734,7 @@ genstubs:
"$(GENERIC_DIR_NATIVE)" \
"$(GENERIC_DIR_NATIVE)/tk.decls" \
"$(GENERIC_DIR_NATIVE)/tkInt.decls"
- $(TCL_EXE) "$(TCL_TOOL_DIR)/genStubs.tcl" \
+ $(TCL_EXE) "$(TTK_DIR)/ttkGenStubs.tcl" \
"$(TTK_DIR)" \
"$(TTK_DIR)/ttk.decls"