summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
Diffstat (limited to 'generic')
-rw-r--r--generic/tkCanvas.c6
-rw-r--r--generic/tkText.c1609
-rw-r--r--generic/tkText.h135
-rw-r--r--generic/tkTextBTree.c325
-rw-r--r--generic/tkTextDisp.c2421
-rw-r--r--generic/tkTextImage.c239
-rw-r--r--generic/tkTextIndex.c679
-rw-r--r--generic/tkTextMark.c37
-rw-r--r--generic/tkTextTag.c124
-rw-r--r--generic/tkTextWind.c38
10 files changed, 4597 insertions, 1016 deletions
diff --git a/generic/tkCanvas.c b/generic/tkCanvas.c
index 4bc01dd..e1068b0 100644
--- a/generic/tkCanvas.c
+++ b/generic/tkCanvas.c
@@ -12,7 +12,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkCanvas.c,v 1.23 2003/09/29 23:15:19 dkf Exp $
+ * RCS: @(#) $Id: tkCanvas.c,v 1.24 2003/10/31 09:02:08 vincentdarley Exp $
*/
/* #define USE_OLD_TAG_SEARCH 1 */
@@ -291,7 +291,7 @@ static int FindArea _ANSI_ARGS_((Tcl_Interp *interp,
TkCanvas *canvasPtr, Tcl_Obj *CONST *argv, Tk_Uid uid,
int enclosed));
static double GridAlign _ANSI_ARGS_((double coord, double spacing));
-CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc,
+static CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc,
Tcl_Obj *CONST *objv));
static void InitCanvas _ANSI_ARGS_((void));
#ifdef USE_OLD_TAG_SEARCH
@@ -5494,7 +5494,7 @@ CanvasSetOrigin(canvasPtr, xOrigin, yOrigin)
*----------------------------------------------------------------------
*/
/* ARGSUSED */
-CONST char **
+static CONST char **
TkGetStringsFromObjs(argc, objv)
int argc;
Tcl_Obj *CONST objv[];
diff --git a/generic/tkText.c b/generic/tkText.c
index 4ec38d7..b0b6a21 100644
--- a/generic/tkText.c
+++ b/generic/tkText.c
@@ -2,10 +2,10 @@
* tkText.c --
*
* This module provides a big chunk of the implementation of
- * multi-line editable text widgets for Tk. Among other things,
- * it provides the Tcl command interfaces to text widgets and
- * the display code. The B-tree representation of text is
- * implemented elsewhere.
+ * multi-line editable text widgets for Tk. Among other things, it
+ * provides the Tcl command interfaces to text widgets. The B-tree
+ * representation of text and its actual display are implemented
+ * elsewhere.
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1996 Sun Microsystems, Inc.
@@ -14,7 +14,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkText.c,v 1.38 2003/05/27 15:35:52 vincentdarley Exp $
+ * RCS: @(#) $Id: tkText.c,v 1.39 2003/10/31 09:02:08 vincentdarley Exp $
*/
#include "default.h"
@@ -61,12 +61,16 @@ static Tk_OptionSpec optionSpecs[] = {
DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border),
0, (ClientData) DEF_TEXT_BG_MONO, 0},
{TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL,
- (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
+ (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth",
+ TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL,
(char *) NULL, 0, -1, 0, (ClientData) "-background", 0},
+ {TK_OPTION_BOOLEAN, "-blockcursor", "blockCursor",
+ "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1,
+ Tk_Offset(TkText, insertCursorType), 0, 0, 0},
{TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
DEF_TEXT_BORDER_WIDTH, -1, Tk_Offset(TkText, borderWidth),
- 0, 0, 0},
+ 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},
@@ -76,7 +80,8 @@ static Tk_OptionSpec optionSpecs[] = {
{TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL,
(char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0},
{TK_OPTION_FONT, "-font", "font", "Font",
- DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0, 0},
+ 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},
@@ -91,7 +96,7 @@ static Tk_OptionSpec optionSpecs[] = {
0, 0, 0},
{TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
"HighlightThickness", DEF_TEXT_HIGHLIGHT_WIDTH, -1,
- Tk_Offset(TkText, highlightWidth), 0, 0, 0},
+ Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground",
DEF_TEXT_INSERT_BG,
-1, Tk_Offset(TkText, insertBorder),
@@ -112,7 +117,8 @@ static Tk_OptionSpec optionSpecs[] = {
{TK_OPTION_INT, "-maxundo", "maxUndo", "MaxUndo",
DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndo), 0, 0, 0},
{TK_OPTION_PIXELS, "-padx", "padX", "Pad",
- DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0, 0},
+ 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",
@@ -132,29 +138,30 @@ static Tk_OptionSpec optionSpecs[] = {
DEF_TEXT_SET_GRID, -1, Tk_Offset(TkText, setGrid), 0, 0, 0},
{TK_OPTION_PIXELS, "-spacing1", "spacing1", "Spacing",
DEF_TEXT_SPACING1, -1, Tk_Offset(TkText, spacing1),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0 },
+ TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY },
{TK_OPTION_PIXELS, "-spacing2", "spacing2", "Spacing",
DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0 },
+ TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY },
{TK_OPTION_PIXELS, "-spacing3", "spacing3", "Spacing",
DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0 },
+ TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY },
{TK_OPTION_STRING_TABLE, "-state", "state", "State",
DEF_TEXT_STATE, -1, Tk_Offset(TkText, state),
0, (ClientData) stateStrings, 0},
{TK_OPTION_STRING, "-tabs", "tabs", "Tabs",
DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionPtr), -1,
- TK_OPTION_NULL_OK, 0, 0},
+ TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
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), 0, 0 , 0},
{TK_OPTION_INT, "-width", "width", "Width",
- DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0, 0},
+ 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, (ClientData) wrapStrings, 0},
+ 0, (ClientData) 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},
@@ -195,6 +202,14 @@ typedef struct SearchSpec {
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 */
int startLine; /* First line to examine */
@@ -248,12 +263,16 @@ int tkTextDebug = 0;
static int ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, int objc, Tcl_Obj *CONST objv[]));
static int DeleteChars _ANSI_ARGS_((TkText *textPtr,
- Tcl_Obj *index1Obj, Tcl_Obj *index2Obj,
CONST TkTextIndex *indexPtr1,
- CONST TkTextIndex *indexPtr2));
-static void DestroyText _ANSI_ARGS_((char *memPtr));
+ CONST TkTextIndex *indexPtr2, int noViewUpdate));
+static int CountIndices _ANSI_ARGS_((CONST TkText *textPtr,
+ CONST TkTextIndex *indexPtr1,
+ CONST TkTextIndex *indexPtr2,
+ TkTextCountType type));
+static void DestroyText _ANSI_ARGS_((TkText *textPtr));
static int InsertChars _ANSI_ARGS_((TkText *textPtr,
- TkTextIndex *indexPtr, Tcl_Obj *stringPtr));
+ TkTextIndex *indexPtr, Tcl_Obj *stringPtr,
+ int noViewUpdate));
static void TextBlinkProc _ANSI_ARGS_((ClientData clientData));
static void TextCmdDeletedProc _ANSI_ARGS_((
ClientData clientData));
@@ -263,6 +282,16 @@ static int TextFetchSelection _ANSI_ARGS_((ClientData clientData,
int offset, char *buffer, int maxBytes));
static int TextIndexSortProc _ANSI_ARGS_((CONST VOID *first,
CONST VOID *second));
+static int TextInsertCmd _ANSI_ARGS_((TkText *textPtr,
+ Tcl_Interp *interp,
+ int objc, Tcl_Obj *CONST objv[],
+ CONST TkTextIndex *indexPtr, int noViewUpdate));
+static int TextReplaceCmd _ANSI_ARGS_((TkText *textPtr,
+ Tcl_Interp *interp,
+ CONST TkTextIndex *indexFromPtr,
+ CONST TkTextIndex *indexToPtr,
+ int objc, Tcl_Obj *CONST objv[],
+ int noViewUpdate));
static int TextSearchCmd _ANSI_ARGS_((TkText *textPtr,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]));
@@ -272,8 +301,10 @@ static int TextEditCmd _ANSI_ARGS_((TkText *textPtr,
static int TextWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]));
-static void TextWorldChanged _ANSI_ARGS_((
+static void TextWorldChangedCallback _ANSI_ARGS_((
ClientData instanceData));
+static void TextWorldChanged _ANSI_ARGS_((TkText *textPtr,
+ int mask));
static int TextDumpCmd _ANSI_ARGS_((TkText *textPtr,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]));
@@ -281,13 +312,15 @@ static void DumpLine _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, int what, TkTextLine *linePtr,
int start, int end, int lineno,
CONST char *command));
-static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp, char *key,
- char *value, CONST char * command,
+static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp,
+ CONST char *key,
+ CONST char *value, CONST char * command,
CONST TkTextIndex *index, int what));
static int TextEditUndo _ANSI_ARGS_((TkText *textPtr));
static int TextEditRedo _ANSI_ARGS_((TkText *textPtr));
-static Tcl_Obj* TextGetText _ANSI_ARGS_((CONST TkTextIndex * index1,
- CONST TkTextIndex * index2));
+static Tcl_Obj* TextGetText _ANSI_ARGS_((CONST TkText *textPtr,
+ CONST TkTextIndex * index1,
+ CONST TkTextIndex * index2, int visibleOnly));
static void UpdateDirtyFlag _ANSI_ARGS_((TkText *textPtr));
static void TextPushUndoAction _ANSI_ARGS_((TkText *textPtr,
Tcl_Obj *undoString, int insert,
@@ -314,7 +347,7 @@ static SearchLineIndexProc TextSearchGetLineIndex;
static Tk_ClassProcs textClass = {
sizeof(Tk_ClassProcs), /* size */
- TextWorldChanged, /* worldChangedProc */
+ TextWorldChangedCallback, /* worldChangedProc */
};
@@ -387,9 +420,16 @@ Tk_TextObjCmd(clientData, interp, objc, objv)
textPtr->relief = TK_RELIEF_FLAT;
textPtr->cursor = None;
textPtr->charWidth = 1;
+ textPtr->charHeight = 10;
textPtr->wrapMode = TEXT_WRAPMODE_CHAR;
textPtr->prevWidth = Tk_Width(new);
textPtr->prevHeight = Tk_Height(new);
+ /*
+ * This refCount will be held until DestroyText is called.
+ * Note also that the following call to 'TkTextCreateDInfo'
+ * will add more refCounts.
+ */
+ textPtr->refCount = 1;
TkTextCreateDInfo(textPtr);
TkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex);
TkTextSetYView(textPtr, &startIndex, 0);
@@ -402,7 +442,6 @@ Tk_TextObjCmd(clientData, interp, objc, objv)
textPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
textPtr->tabOptionPtr = NULL;
textPtr->stateEpoch = 0;
- textPtr->refCount = 1;
/*
* Create the "sel" tag and the "current" and "insert" marks.
@@ -486,16 +525,17 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
int index;
static CONST char *optionStrings[] = {
- "bbox", "cget", "compare", "configure", "debug", "delete",
- "dlineinfo", "dump", "edit", "get", "image", "index",
- "insert", "mark", "scan", "search", "see", "tag",
- "window", "xview", "yview", (char *) NULL
+ "bbox", "cget", "compare", "configure", "count", "debug",
+ "delete", "dlineinfo", "dump", "edit", "get", "image", "index",
+ "insert", "mark", "replace", "scan", "search", "see",
+ "tag", "window", "xview", "yview", (char *) NULL
};
enum options {
- TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_DEBUG,
- TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT, TEXT_GET,
- TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK, TEXT_SCAN,
- TEXT_SEARCH, TEXT_SEE, TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
+ 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_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE,
+ TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
};
if (objc < 2) {
@@ -507,7 +547,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
&index) != TCL_OK) {
return TCL_ERROR;
}
- Tcl_Preserve((ClientData) textPtr);
+ textPtr->refCount++;
switch ((enum options) index) {
case TEXT_BBOX: {
@@ -525,11 +565,19 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
goto done;
}
if (TkTextCharBbox(textPtr, indexPtr, &x, &y,
- &width, &height) == 0) {
- char buf[TCL_INTEGER_SPACE * 4];
+ &width, &height, NULL) == 0) {
+ Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
+
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(x));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(y));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(width));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(height));
- sprintf(buf, "%d %d %d %d", x, y, width, height);
- Tcl_SetResult(interp, buf, TCL_VOLATILE);
+ Tcl_SetObjResult(interp, listObj);
}
break;
}
@@ -616,6 +664,176 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
}
break;
}
+ case TEXT_COUNT: {
+ CONST TkTextIndex *indexFromPtr, *indexToPtr;
+ int i, found = 0, update = 0;
+ Tcl_Obj *objPtr = NULL;
+
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?options? 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) {
+ result = TCL_ERROR;
+ goto done;
+ }
+
+ for (i = 2; i < objc-2; i++) {
+ int value;
+ int length;
+ CONST char *option = Tcl_GetStringFromObj(objv[i],&length);
+ char c;
+ if (length < 2 || option[0] != '-') {
+ badOption:
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "bad option \"",
+ Tcl_GetString(objv[i]),
+ "\" must be -chars, -displaychars, -displayindices, ",
+ "-displaylines, -indices, -lines, -update, ",
+ "-xpixels, or -ypixels", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ c = option[1];
+ if (c == 'c' && !strncmp("-chars",option,length)) {
+ value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_CHARS);
+ } else if (c == 'd' && !strncmp("-displaychars", option,
+ length) && (length > 8)) {
+ value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_DISPLAY_CHARS);
+ } else if (c == 'd' && !strncmp("-displayindices", option,
+ length) && (length > 8)) {
+ value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_DISPLAY_INDICES);
+ } else if (c == 'd' && !strncmp("-displaylines", option,
+ length) && (length > 8)) {
+ TkTextLine *fromPtr, *lastPtr;
+ TkTextIndex index;
+
+ int compare = TkTextIndexCmp(indexFromPtr, indexToPtr);
+ value = 0;
+
+ if (compare == 0) goto countDone;
+
+ if (compare > 0) {
+ CONST TkTextIndex *tmpPtr = indexFromPtr;
+ indexFromPtr = indexToPtr;
+ indexToPtr = tmpPtr;
+ }
+
+ lastPtr = TkBTreeFindLine(textPtr->tree,
+ TkBTreeNumLines(textPtr->tree));
+ fromPtr = indexFromPtr->linePtr;
+ if (fromPtr == lastPtr) {
+ goto countDone;
+ }
+
+ /*
+ * Caution: we must NEVER call TkTextUpdateOneLine
+ * with the last artificial line in the widget.
+ */
+ while (fromPtr != indexToPtr->linePtr) {
+ value += TkTextUpdateOneLine(textPtr, fromPtr);
+ fromPtr = TkBTreeNextLine(fromPtr);
+ }
+ /*
+ * Now we need to adjust the count to add on the
+ * number of display lines in the last logical line,
+ * and subtract off the number of display lines
+ * overcounted in the first logical line. This logic
+ * is still ok if both indices are in the same
+ * logical line.
+ */
+ index.linePtr = indexFromPtr->linePtr;
+ index.byteIndex = 0;
+ while (1) {
+ TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL);
+ if (index.byteIndex >= indexFromPtr->byteIndex) {
+ break;
+ }
+ TkTextIndexForwBytes(&index, 1, &index);
+ value--;
+ }
+ if (indexToPtr->linePtr != lastPtr) {
+ index.linePtr = indexToPtr->linePtr;
+ index.byteIndex = 0;
+ while (1) {
+ TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL);
+ if (index.byteIndex >= indexToPtr->byteIndex) {
+ break;
+ }
+ TkTextIndexForwBytes(&index, 1, &index);
+ value++;
+ }
+ }
+
+ if (compare > 0) {
+ value = -value;
+ }
+ } else if (c == 'i' && !strncmp("-indices",option,length)) {
+ value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_INDICES);
+ } else if (c == 'l' && !strncmp("-lines",option,length)) {
+ value = TkBTreeLineIndex(indexToPtr->linePtr)
+ - TkBTreeLineIndex(indexFromPtr->linePtr);
+ } else if (c == 'u' && !strncmp("-update",option,length)) {
+ update = 1;
+ continue;
+ } else if (c == 'x' && !strncmp("-xpixels",option,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,length)) {
+ if (update) {
+ TkTextUpdateLineMetrics(textPtr,
+ TkBTreeLineIndex(indexFromPtr->linePtr),
+ TkBTreeLineIndex(indexToPtr->linePtr), -1);
+ }
+ value = TkTextIndexYPixels(textPtr, indexToPtr)
+ - TkTextIndexYPixels(textPtr, indexFromPtr);
+ } else {
+ goto badOption;
+ }
+ countDone:
+ found++;
+ if (found == 1) {
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
+ } 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, Tcl_NewIntObj(value));
+ }
+ }
+ if (found == 0) {
+ /* Use the default '-indices' */
+ int value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_INDICES);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
+ } else if (found > 1) {
+ Tcl_SetObjResult(interp, objPtr);
+ }
+ break;
+ }
case TEXT_DEBUG: {
if (objc > 3) {
Tcl_WrongNumArgs(interp, 2, objv, "boolean");
@@ -645,8 +863,29 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
/*
* Simple case requires no predetermination of indices.
*/
- result = DeleteChars(textPtr, objv[2],
- (objc == 4) ? objv[3] : NULL, NULL, NULL);
+ CONST TkTextIndex *indexPtr1, *indexPtr2;
+
+ /*
+ * Parse the starting and stopping indices.
+ */
+
+ indexPtr1 = TkTextGetIndexFromObj(textPtr->interp,
+ textPtr, objv[2]);
+ if (indexPtr1 == NULL) {
+ 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;
+ }
+ DeleteChars(textPtr, indexPtr1, indexPtr2, 0);
} else {
int i;
/*
@@ -683,7 +922,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
*/
if (objc & 1) {
indices[i] = indices[i-1];
- TkTextIndexForwChars(&indices[i], 1, &indices[i]);
+ TkTextIndexForwChars(&indices[i], 1, &indices[i],
+ COUNT_INDICES);
objc++;
}
useIdx = (char *) ckalloc((unsigned) objc);
@@ -740,8 +980,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
* We don't need to check the return value
* because all indices are preparsed above.
*/
- DeleteChars(textPtr, NULL, NULL,
- &indices[i], &indices[i+1]);
+ DeleteChars(textPtr, &indices[i],
+ &indices[i+1], 0);
}
}
ckfree((char *) indices);
@@ -765,10 +1005,20 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
}
if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width,
&height, &base) == 0) {
- char buf[TCL_INTEGER_SPACE * 5];
+ Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
- sprintf(buf, "%d %d %d %d %d", x, y, width, height, base);
- Tcl_SetResult(interp, buf, TCL_VOLATILE);
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(x));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(y));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(width));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(height));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(base));
+
+ Tcl_SetObjResult(interp, listObj);
}
break;
}
@@ -782,14 +1032,37 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
}
case TEXT_GET: {
Tcl_Obj *objPtr = NULL;
- int i, found = 0;
-
+ int i, found = 0, visible = 0;
+ CONST char *name;
+ int length;
+
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
+ Tcl_WrongNumArgs(interp, 2, objv,
+ "?-displaychars? ?--? index1 ?index2 ...?");
result = TCL_ERROR;
goto done;
}
- for (i = 2; i < objc; i += 2) {
+
+ /*
+ * Simple, restrictive argument parsing. The only options are --
+ * and -displaychars (or any unique prefix).
+ */
+ i = 2;
+ if (objc > 3) {
+ name = Tcl_GetStringFromObj(objv[i], &length);
+ if (length > 1 && name[0] == '-') {
+ if (strncmp("-displaychars", name, length) == 0) {
+ i++;
+ visible = 1;
+ name = Tcl_GetStringFromObj(objv[i], &length);
+ }
+ if ((i < objc-1) && (length == 2)
+ && (strcmp("--", name) == 0)) {
+ i++;
+ }
+ }
+ }
+ for (; i < objc; i += 2) {
CONST TkTextIndex *index1Ptr, *index2Ptr;
TkTextIndex index2;
@@ -802,7 +1075,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
goto done;
}
if (i+1 == objc) {
- TkTextIndexForwChars(index1Ptr, 1, &index2);
+ TkTextIndexForwChars(index1Ptr, 1, &index2, COUNT_INDICES);
index2Ptr = &index2;
} else {
index2Ptr = TkTextGetIndexFromObj(interp, textPtr,
@@ -822,7 +1095,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
* be a megabyte or more, we want to do it
* efficiently!
*/
- Tcl_Obj *get = TextGetText(index1Ptr, index2Ptr);
+ Tcl_Obj *get = TextGetText(textPtr, index1Ptr,
+ index2Ptr, visible);
found++;
if (found == 1) {
Tcl_SetObjResult(interp, get);
@@ -881,51 +1155,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
goto done;
}
if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- TkTextIndex index1, index2;
- int j;
-
- index1 = *indexPtr;
- for (j = 3; 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(textPtr, &index1, objv[j]);
- if (objc > (j+1)) {
- Tcl_Obj **tagNamePtrs;
- TkTextTag **oldTagArrayPtr;
- int numTags;
-
- TkTextIndexForwBytes(&index1, length, &index2);
- oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags);
- if (oldTagArrayPtr != NULL) {
- int i;
- for (i = 0; i < numTags; i++) {
- TkBTreeTag(&index1, &index2,
- oldTagArrayPtr[i], 0);
- }
- ckfree((char *) oldTagArrayPtr);
- }
- if (Tcl_ListObjGetElements(interp, objv[j+1],
- &numTags, &tagNamePtrs)
- != TCL_OK) {
- result = TCL_ERROR;
- goto done;
- } else {
- int i;
-
- for (i = 0; i < numTags; i++) {
- TkBTreeTag(&index1, &index2,
- TkTextCreateTag(textPtr,
- Tcl_GetString(tagNamePtrs[i])), 1);
- }
- index1 = index2;
- }
- }
- }
+ result = TextInsertCmd(textPtr, interp, objc-3, objv+3,
+ indexPtr, 0);
}
break;
}
@@ -933,6 +1164,113 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
result = TkTextMarkCmd(textPtr, interp, objc, objv);
break;
}
+ case TEXT_REPLACE: {
+ CONST TkTextIndex *indexFromPtr, *indexToPtr;
+
+ if (objc < 5) {
+ Tcl_WrongNumArgs(interp, 2, objv,
+ "index1 index2 chars ?tagList chars tagList ...?");
+ result = TCL_ERROR;
+ goto done;
+ }
+ indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
+ if (indexFromPtr == NULL) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[3]);
+ if (indexToPtr == NULL) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (TkTextIndexCmp(indexFromPtr, indexToPtr) > 0) {
+ Tcl_AppendResult(interp, "Index \"", Tcl_GetString(objv[3]),
+ "\" before \"", Tcl_GetString(objv[2]),
+ "\" in the text.", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (textPtr->state == TK_TEXT_STATE_NORMAL) {
+ int lineNum, byteIndex;
+ TkTextIndex index;
+ /*
+ * 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.
+ */
+ byteIndex = textPtr->topIndex.byteIndex;
+ lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr);
+
+ 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.
+ */
+ int deleteInsertOffset, insertLength, j;
+
+ insertLength = 0;
+ for (j = 4; j < objc; j += 2) {
+ insertLength += Tcl_GetCharLength(objv[j]);
+ }
+
+ /*
+ * Calculate 'deleteInsertOffset' as an offset we
+ * will apply to the insertion point after this
+ * operation.
+ */
+ deleteInsertOffset = CountIndices(textPtr, indexFromPtr,
+ &index, COUNT_CHARS);
+ if (deleteInsertOffset > insertLength) {
+ deleteInsertOffset = insertLength;
+ }
+
+ result = TextReplaceCmd(textPtr, interp,
+ indexFromPtr, indexToPtr,
+ objc, objv, 1);
+
+ if (result == TCL_OK) {
+ /*
+ * Move the insertion position to the correct
+ * place
+ */
+ TkTextIndexForwChars(indexFromPtr, deleteInsertOffset,
+ &index, COUNT_INDICES);
+ TkBTreeUnlinkSegment(textPtr->tree,
+ textPtr->insertMarkPtr,
+ textPtr->insertMarkPtr->body.mark.linePtr);
+ TkBTreeLinkSegment(textPtr->insertMarkPtr, &index);
+ }
+ } else {
+ result = TextReplaceCmd(textPtr, interp,
+ indexFromPtr, indexToPtr,
+ objc, objv, 0);
+ }
+ if (result == TCL_OK) {
+ /*
+ * Now ensure the top-line is in the right
+ * place
+ */
+ TkTextMakeByteIndex(textPtr->tree, lineNum,
+ byteIndex, &index);
+ TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST);
+ }
+ }
+ break;
+ }
case TEXT_SCAN: {
result = TkTextScanCmd(textPtr, interp, objc, objv);
break;
@@ -964,7 +1302,68 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
}
done:
- Tcl_Release((ClientData) textPtr);
+ textPtr->refCount--;
+ if (textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ }
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextReplaceCmd --
+ *
+ * This procedure is invoked to process part of the "replace" widget
+ * command for text widgets.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TextReplaceCmd(textPtr, interp, indexFromPtr, indexToPtr,
+ objc, objv, noViewUpdate)
+ TkText *textPtr; /* Information about text widget. */
+ Tcl_Interp *interp; /* Current interpreter. */
+ CONST TkTextIndex *indexFromPtr;/* Index from which to replace */
+ CONST TkTextIndex *indexToPtr; /* Index to which to replace */
+ int objc; /* Number of arguments. */
+ Tcl_Obj *CONST objv[]; /* Argument objects. */
+ int noViewUpdate; /* Don't update the view if set */
+{
+ int result;
+ /*
+ * Perform the deletion and insertion, but ensure
+ * no undo-separator is placed between the two
+ * operations. Since we are using the helper procedures
+ * 'DeleteChars' and 'TextInsertCmd' we have to pretend
+ * that the autoSeparators setting is off, so that we don't
+ * get an undo-separator between the delete and insert.
+ */
+ int origAutoSep = textPtr->autoSeparators;
+
+ if (textPtr->undo) {
+ textPtr->autoSeparators = 0;
+ if (origAutoSep && textPtr->lastEditMode != TK_TEXT_EDIT_REPLACE) {
+ TkUndoInsertUndoSeparator(textPtr->undoStack);
+ }
+ }
+
+ DeleteChars(textPtr, indexFromPtr, indexToPtr, noViewUpdate);
+ result = TextInsertCmd(textPtr, interp, objc-4, objv+4,
+ indexFromPtr, noViewUpdate);
+
+ if (textPtr->undo) {
+ textPtr->lastEditMode = TK_TEXT_EDIT_REPLACE;
+ textPtr->autoSeparators = origAutoSep;
+ }
+
return result;
}
@@ -1016,24 +1415,28 @@ TextIndexSortProc(first, second)
*
* DestroyText --
*
- * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
- * to clean up the internal structure of a text at a safe time
- * (when no-one is using it anymore).
+ * This procedure is invoked when we receive a destroy event
+ * to clean up the internal structure of a text widget. We will
+ * free up most of the internal structure and delete the
+ * associated Tcl command. If there are no outstanding
+ * references to the widget, we also free up the textPtr itself.
+ *
+ * The widget has already been flagged as deleted.
*
* Results:
* None.
*
* Side effects:
- * Everything associated with the text is freed up.
+ * Either everything or almost everything associated with the
+ * text is freed up.
*
*----------------------------------------------------------------------
*/
static void
-DestroyText(memPtr)
- char *memPtr; /* Info about text widget. */
+DestroyText(textPtr)
+ TkText *textPtr; /* Info about text widget. */
{
- register TkText *textPtr = (TkText *) memPtr;
Tcl_HashSearch search;
Tcl_HashEntry *hPtr;
TkTextTag *tagPtr;
@@ -1048,6 +1451,8 @@ DestroyText(memPtr)
*/
TkTextFreeDInfo(textPtr);
+ textPtr->dInfoPtr = NULL;
+
TkBTreeDestroy(textPtr->tree);
for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
@@ -1111,9 +1516,10 @@ ConfigureText(interp, textPtr, objc, objv)
{
Tk_SavedOptions savedOptions;
int oldExport = textPtr->exportSelection;
+ int mask = 0;
if (Tk_SetOptions(interp, (char*)textPtr, textPtr->optionTable,
- objc, objv, textPtr->tkwin, &savedOptions, NULL) != TCL_OK) {
+ objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) {
return TCL_ERROR;
}
@@ -1173,27 +1579,31 @@ ConfigureText(interp, textPtr, objc, objv)
}
textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
textPtr->selTagPtr->affectsDisplay = 0;
- if ((textPtr->selTagPtr->border != NULL)
- || (textPtr->selTagPtr->borderWidth != 0)
- || (textPtr->selTagPtr->reliefString != NULL)
- || (textPtr->selTagPtr->bgStipple != None)
- || (textPtr->selTagPtr->fgColor != NULL)
+ textPtr->selTagPtr->affectsDisplay = 0;
+ textPtr->selTagPtr->affectsDisplayGeometry = 0;
+ if ((textPtr->selTagPtr->elideString != NULL)
|| (textPtr->selTagPtr->tkfont != None)
- || (textPtr->selTagPtr->fgStipple != None)
|| (textPtr->selTagPtr->justifyString != NULL)
|| (textPtr->selTagPtr->lMargin1String != NULL)
|| (textPtr->selTagPtr->lMargin2String != NULL)
|| (textPtr->selTagPtr->offsetString != NULL)
- || (textPtr->selTagPtr->overstrikeString != NULL)
|| (textPtr->selTagPtr->rMarginString != NULL)
|| (textPtr->selTagPtr->spacing1String != NULL)
|| (textPtr->selTagPtr->spacing2String != NULL)
|| (textPtr->selTagPtr->spacing3String != NULL)
|| (textPtr->selTagPtr->tabStringPtr != NULL)
- || (textPtr->selTagPtr->underlineString != NULL)
- || (textPtr->selTagPtr->elideString != NULL)
|| (textPtr->selTagPtr->wrapMode != TEXT_WRAPMODE_NULL)) {
textPtr->selTagPtr->affectsDisplay = 1;
+ textPtr->selTagPtr->affectsDisplayGeometry = 1;
+ }
+ if ((textPtr->selTagPtr->border != NULL)
+ || (textPtr->selTagPtr->reliefString != NULL)
+ || (textPtr->selTagPtr->bgStipple != None)
+ || (textPtr->selTagPtr->fgColor != NULL)
+ || (textPtr->selTagPtr->fgStipple != None)
+ || (textPtr->selTagPtr->overstrikeString != NULL)
+ || (textPtr->selTagPtr->underlineString != NULL)) {
+ textPtr->selTagPtr->affectsDisplay = 1;
}
TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL,
textPtr->selTagPtr, 1);
@@ -1241,14 +1651,14 @@ ConfigureText(interp, textPtr, objc, objv)
textPtr->height = 1;
}
Tk_FreeSavedOptions(&savedOptions);
- TextWorldChanged((ClientData) textPtr);
+ TextWorldChanged(textPtr, mask);
return TCL_OK;
}
/*
*---------------------------------------------------------------------------
*
- * TextWorldChanged --
+ * TextWorldChangedCallback --
*
* This procedure is called when the world has changed in some
* way and the widget needs to recompute all its graphics contexts
@@ -1266,19 +1676,53 @@ ConfigureText(interp, textPtr, objc, objv)
*/
static void
-TextWorldChanged(instanceData)
+TextWorldChangedCallback(instanceData)
ClientData instanceData; /* Information about widget. */
{
TkText *textPtr;
- Tk_FontMetrics fm;
textPtr = (TkText *) instanceData;
+ TextWorldChanged(textPtr, TK_TEXT_LINE_GEOMETRY);
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TextWorldChanged --
+ *
+ * This procedure is called when the world has changed in some
+ * way and the widget needs to recompute all its graphics contexts
+ * and determine its new geometry.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Configures all tags in the Text with a empty objc/objv, for
+ * the side effect of causing all the items to recompute their
+ * geometry and to be redisplayed.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static void
+TextWorldChanged(textPtr, mask)
+ TkText *textPtr; /* Information about widget. */
+ int mask; /* OR'd collection of bits showing what
+ * has changed */
+{
+ Tk_FontMetrics fm;
textPtr->charWidth = Tk_TextWidth(textPtr->tkfont, "0", 1);
if (textPtr->charWidth <= 0) {
textPtr->charWidth = 1;
}
Tk_GetFontMetrics(textPtr->tkfont, &fm);
+
+ textPtr->charHeight = fm.linespace;
+ if (textPtr->charHeight <= 0) {
+ textPtr->charHeight = 1;
+ }
Tk_GeometryRequest(textPtr->tkwin,
textPtr->width * textPtr->charWidth + 2*textPtr->borderWidth
+ 2*textPtr->padX + 2*textPtr->highlightWidth,
@@ -1289,12 +1733,12 @@ TextWorldChanged(instanceData)
textPtr->borderWidth + textPtr->highlightWidth);
if (textPtr->setGrid) {
Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
- textPtr->charWidth, fm.linespace);
+ textPtr->charWidth, textPtr->charHeight);
} else {
Tk_UnsetGrid(textPtr->tkwin);
}
- TkTextRelayoutWindow(textPtr);
+ TkTextRelayoutWindow(textPtr, mask);
}
/*
@@ -1331,7 +1775,11 @@ TextEventProc(clientData, eventPtr)
} else if (eventPtr->type == ConfigureNotify) {
if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
|| (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
- TkTextRelayoutWindow(textPtr);
+ int mask = 0;
+ if (textPtr->prevWidth != Tk_Width(textPtr->tkwin)) {
+ mask = 1;
+ }
+ TkTextRelayoutWindow(textPtr, mask);
textPtr->prevWidth = Tk_Width(textPtr->tkwin);
textPtr->prevHeight = Tk_Height(textPtr->tkwin);
}
@@ -1360,13 +1808,13 @@ TextEventProc(clientData, eventPtr)
textPtr->flags |= DESTROYED;
/*
- * We don't delete the associated Tcl command yet, because
- * that will cause textPtr->tkWin to be nulled out, and that
- * is needed inside DestroyText to clean up certain tags
- * which might have been created (e.g. in the text widget
- * styles demo).
+ * 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.
*/
- Tcl_EventuallyFree((ClientData) textPtr, DestroyText);
+ DestroyText(textPtr);
} else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
if (eventPtr->xfocus.detail != NotifyInferior) {
Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
@@ -1385,11 +1833,15 @@ TextEventProc(clientData, eventPtr)
TkTextRedrawTag(textPtr, NULL, NULL, textPtr->selTagPtr, 1);
#endif
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- TkTextIndexForwChars(&index, 1, &index2);
+ TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES);
+ /*
+ * While we wish to redisplay, no heights have changed, so
+ * no need to call TkTextInvalidateLineMetrics
+ */
TkTextChanged(textPtr, &index, &index2);
if (textPtr->highlightWidth > 0) {
TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth,
- textPtr->highlightWidth);
+ textPtr->highlightWidth);
}
}
}
@@ -1451,18 +1903,22 @@ TextCmdDeletedProc(clientData)
* Side effects:
* The characters in "stringPtr" get added to the text just before
* the character indicated by "indexPtr".
+ *
+ * Unless 'noViewUpdate' is set, we may adjust the window
+ * contents' y-position, and scrollbar setting.
*
*----------------------------------------------------------------------
*/
static int
-InsertChars(textPtr, indexPtr, stringPtr)
+InsertChars(textPtr, indexPtr, stringPtr, noViewUpdate)
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 noViewUpdate; /* Don't update the view if set */
{
int lineIndex, resetView, offset, length;
@@ -1519,7 +1975,7 @@ InsertChars(textPtr, indexPtr, stringPtr)
UpdateDirtyFlag(textPtr);
- if (resetView) {
+ if (resetView && !noViewUpdate) {
TkTextIndex newTop;
TkTextMakeByteIndex(textPtr->tree, lineIndex, 0, &newTop);
TkTextIndexForwBytes(&newTop, offset, &newTop);
@@ -1643,69 +2099,94 @@ TextPushUndoAction (textPtr, undoString, insert, index1Ptr, index2Ptr)
/*
*----------------------------------------------------------------------
*
+ * CountIndices --
+ *
+ * This procedure implements most of the functionality of the
+ * "count" widget command.
+ *
+ * Note that 'textPtr' is only used if we need to check for elided
+ * attributes, i.e. if type is COUNT_DISPLAY_INDICES or
+ * COUNT_DISPLAY_CHARS.
+ *
+ * Results:
+ * Returns the number of characters in the range.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+CountIndices(textPtr, indexPtr1, indexPtr2, type)
+ CONST TkText *textPtr; /* Overall information about text widget. */
+ CONST TkTextIndex *indexPtr1;/* 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. */
+ TkTextCountType type; /* The kind of indices to count */
+{
+ /*
+ * Order the starting and stopping indices.
+ */
+
+ int compare = TkTextIndexCmp(indexPtr1, indexPtr2);
+
+ if (compare == 0) {
+ return 0;
+ } else if (compare > 0) {
+ return -TkTextIndexCount(textPtr, indexPtr2, indexPtr1, type);
+ } else {
+ return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* DeleteChars --
*
* This procedure implements most of the functionality of the
* "delete" widget command.
*
* Results:
- * Returns a standard Tcl result, and leaves an error message
- * in textPtr->interp if there is an error.
+ * Returns a standard Tcl result, currently always TCL_OK.
*
* Side effects:
* Characters get deleted from the text.
+ *
+ * Unless 'noViewUpdate' is set, we may adjust the window
+ * contents' y-position, and scrollbar setting.
*
*----------------------------------------------------------------------
*/
static int
-DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2)
+DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate)
TkText *textPtr; /* Overall information about text widget. */
- Tcl_Obj *index1Obj; /* Object describing location of first
- * character to delete. */
- Tcl_Obj *index2Obj; /* Object describing location of last
- * character to delete. NULL means just
- * delete the one character given by
- * index1Obj. */
CONST TkTextIndex *indexPtr1;/* 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. */
+ int noViewUpdate; /* Don't update the view if set */
{
int line1, line2, line, byteIndex, resetView;
TkTextIndex index1, index2;
/*
- * Parse the starting and stopping indices.
+ * Prepare the starting and stopping indices.
*/
- if (index1Obj != NULL) {
- indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, textPtr, index1Obj);
- if (indexPtr1 == NULL) {
- return TCL_ERROR;
- }
- index1 = *indexPtr1;
- if (index2Obj != NULL) {
- indexPtr2 = TkTextGetIndexFromObj(textPtr->interp, textPtr,
- index2Obj);
- if (indexPtr2 == NULL) {
- return TCL_ERROR;
- }
- index2 = *indexPtr2;
- } else {
- index2 = index1;
- TkTextIndexForwChars(&index2, 1, &index2);
- }
+ index1 = *indexPtr1;
+ if (indexPtr2 != NULL) {
+ index2 = *indexPtr2;
} else {
- index1 = *indexPtr1;
- if (indexPtr2 != NULL) {
- index2 = *indexPtr2;
- } else {
- index2 = index1;
- TkTextIndexForwChars(&index2, 1, &index2);
- }
+ index2 = index1;
+ TkTextIndexForwChars(&index2, 1, &index2, COUNT_INDICES);
}
/*
@@ -1736,10 +2217,10 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2)
TkTextIndex oldIndex2;
oldIndex2 = index2;
- TkTextIndexBackChars(&oldIndex2, 1, &index2);
+ TkTextIndexBackChars(&oldIndex2, 1, &index2, COUNT_INDICES);
line2--;
if ((index1.byteIndex == 0) && (line1 != 0)) {
- TkTextIndexBackChars(&index1, 1, &index1);
+ TkTextIndexBackChars(&index1, 1, &index1, COUNT_INDICES);
line1--;
}
arrayPtr = TkBTreeGetTags(&index2, &arraySize);
@@ -1751,6 +2232,54 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2)
}
}
+ 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.
+ */
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr;
+ int i;
+
+ for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
+ hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) {
+ TkTextTag *tagPtr;
+ tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
+ if (TkBTreeTag(&index1, &index2, tagPtr, 0)) {
+ /*
+ * If the tag is "sel", and we actually adjusted anything
+ * then grab the selection if we're supposed to export it
+ * and don't already have it. Also, invalidate
+ * partially-completed selection retrievals.
+ *
+ * This code copied from tkTextTag.c's 'tag remove'
+ */
+
+ if (tagPtr == textPtr->selTagPtr) {
+ XEvent event;
+ /*
+ * Send an event that the selection changed.
+ * This is equivalent to
+ * "event generate $textWidget <<Selection>>"
+ */
+
+ memset((VOID *) &event, 0, sizeof(event));
+ event.xany.type = VirtualEvent;
+ event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
+ event.xany.send_event = False;
+ event.xany.window = Tk_WindowId(textPtr->tkwin);
+ event.xany.display = Tk_Display(textPtr->tkwin);
+ ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection");
+ Tk_HandleEvent(&event);
+
+ textPtr->abortSelections = 1;
+ }
+ }
+ }
+ }
+
/*
* Tell the display what's about to happen so it can discard
* obsolete display information, then do the deletion. Also,
@@ -1815,14 +2344,15 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2)
textPtr->lastEditMode = TK_TEXT_EDIT_DELETE;
- get = TextGetText(&index1, &index2);
+ get = TextGetText(textPtr, &index1, &index2, 0);
TextPushUndoAction(textPtr, get, 0, &index1, &index2);
}
UpdateDirtyFlag(textPtr);
textPtr->stateEpoch ++;
TkBTreeDeleteChars(&index1, &index2);
- if (resetView) {
+
+ if (resetView && !noViewUpdate) {
TkTextMakeByteIndex(textPtr->tree, line, byteIndex, &index1);
TkTextSetYView(textPtr, &index1, 0);
}
@@ -2061,7 +2591,7 @@ TextBlinkProc(clientData)
{
register TkText *textPtr = (TkText *) clientData;
TkTextIndex index;
- int x, y, w, h;
+ int x, y, w, h, charWidth;
if ((textPtr->state == TK_TEXT_STATE_DISABLED) ||
!(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
@@ -2077,15 +2607,98 @@ TextBlinkProc(clientData)
textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr);
}
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- if (TkTextCharBbox(textPtr, &index, &x, &y, &w, &h) == 0) {
- TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y,
- textPtr->insertWidth, h);
+ if (TkTextCharBbox(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);
+ } else {
+ /* I-beam cursor */
+ TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y,
+ textPtr->insertWidth, h);
+ }
}
}
/*
*----------------------------------------------------------------------
*
+ * TextInsertCmd --
+ *
+ * This procedure is invoked to process the "insert" and "replace"
+ * widget commands for text widgets.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ * Unless 'noViewUpdate' is set, we may adjust the window
+ * contents' y-position, and scrollbar setting.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TextInsertCmd(textPtr, interp, objc, objv, indexPtr, noViewUpdate)
+ 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 noViewUpdate; /* Don't update the view if set */
+{
+ TkTextIndex index1, index2;
+ int j;
+
+ 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(textPtr, &index1, objv[j], noViewUpdate);
+ if (objc > (j+1)) {
+ Tcl_Obj **tagNamePtrs;
+ TkTextTag **oldTagArrayPtr;
+ int numTags;
+
+ TkTextIndexForwBytes(&index1, length, &index2);
+ oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags);
+ if (oldTagArrayPtr != NULL) {
+ int i;
+ for (i = 0; i < numTags; i++) {
+ TkBTreeTag(&index1, &index2,
+ oldTagArrayPtr[i], 0);
+ }
+ ckfree((char *) oldTagArrayPtr);
+ }
+ if (Tcl_ListObjGetElements(interp, objv[j+1],
+ &numTags, &tagNamePtrs)
+ != TCL_OK) {
+ return TCL_ERROR;
+ } else {
+ int i;
+
+ for (i = 0; i < numTags; i++) {
+ TkBTreeTag(&index1, &index2,
+ TkTextCreateTag(textPtr,
+ Tcl_GetString(tagNamePtrs[i])), 1);
+ }
+ index1 = index2;
+ }
+ }
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TextSearchCmd --
*
* This procedure is invoked to process the "search" widget command
@@ -2113,12 +2726,14 @@ TextSearchCmd(textPtr, interp, objc, objv)
static CONST char *switchStrings[] = {
"--", "-all", "-backwards", "-count", "-elide", "-exact",
- "-forwards", "-hidden", "-nocase", "-nolinestop", "-regexp", NULL
+ "-forwards", "-hidden", "-nocase", "-nolinestop",
+ "-overlap", "-regexp", "-strictlimits", NULL
};
enum SearchSwitches {
SEARCH_END, SEARCH_ALL, SEARCH_BACK, SEARCH_COUNT, SEARCH_ELIDE,
SEARCH_EXACT, SEARCH_FWD, SEARCH_HIDDEN, SEARCH_NOCASE,
- SEARCH_NOLINE, SEARCH_REGEXP
+ SEARCH_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP,
+ SEARCH_STRICTLIMITS
};
/*
@@ -2134,6 +2749,8 @@ TextSearchCmd(textPtr, interp, objc, objv)
searchSpec.resPtr = NULL;
searchSpec.searchElide = 0;
searchSpec.noLineStop = 0;
+ searchSpec.overlap = 0;
+ searchSpec.strictLimits = 0;
searchSpec.numLines = TkBTreeNumLines(textPtr->tree);
searchSpec.clientData = (ClientData)textPtr;
searchSpec.addLineProc = &TextSearchAddNextLine;
@@ -2158,7 +2775,8 @@ TextSearchCmd(textPtr, interp, objc, objv)
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad switch \"", Tcl_GetString(objv[i]),
"\": must be --, -all, -backward, -count, -elide, ",
- "-exact, -forward, -nocase, -nolinestop, or -regexp",
+ "-exact, -forward, -nocase, -nolinestop, -overlap, ",
+ "-regexp, or -strictlimits",
(char *) NULL);
return TCL_ERROR;
}
@@ -2199,9 +2817,15 @@ TextSearchCmd(textPtr, interp, objc, objv)
case SEARCH_NOCASE:
searchSpec.noCase = 1;
break;
- case SEARCH_NOLINE:
+ case SEARCH_NOLINESTOP:
searchSpec.noLineStop = 1;
break;
+ case SEARCH_OVERLAP:
+ searchSpec.overlap = 1;
+ break;
+ case SEARCH_STRICTLIMITS:
+ searchSpec.strictLimits = 1;
+ break;
case SEARCH_REGEXP:
searchSpec.exact = 0;
break;
@@ -2224,6 +2848,12 @@ TextSearchCmd(textPtr, interp, objc, objv)
return TCL_ERROR;
}
+ if (searchSpec.overlap && !searchSpec.all) {
+ Tcl_SetResult(interp, "the \"-overlap\" option requires the "
+ "\"-all\" option to be present", TCL_STATIC);
+ return TCL_ERROR;
+ }
+
/*
* Scan through all of the lines of the text circularly, starting
* at the given index. 'objv[i]' is the pattern which may be an
@@ -2487,12 +3117,17 @@ TextSearchAddNextLine(lineNum, searchSpecPtr, theLine, lenPtr)
static int
TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine,
- matchOffset, matchLength)
+ matchOffset, matchLength)
int lineNum; /* Line on which match was found */
SearchSpec *searchSpecPtr; /* Search parameters */
ClientData clientData; /* Token returned by the 'addNextLineProc',
- * TextSearchAddNextLine */
- Tcl_Obj *theLine; /* Text from current line */
+ * 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 */
@@ -2544,6 +3179,10 @@ TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine,
*/
linePtr = (TkTextLine *)clientData;
+ if (linePtr == NULL) {
+ linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
+ }
+
curIndex.tree = textPtr->tree;
curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
/* Find the starting point */
@@ -2844,7 +3483,7 @@ TextDumpCmd(textPtr, interp, objc, objv)
arg++;
atEnd = 0;
if (objc == arg) {
- TkTextIndexForwChars(&index1, 1, &index2);
+ TkTextIndexForwChars(&index1, 1, &index2, COUNT_INDICES);
} else {
int length;
char *str;
@@ -3008,8 +3647,8 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command)
static int
DumpSegment(interp, key, value, command, index, what)
Tcl_Interp *interp;
- char *key; /* Segment type key */
- char *value; /* Segment value */
+ CONST char *key; /* Segment type key */
+ CONST char *value; /* Segment value */
CONST char *command; /* Script callback */
CONST TkTextIndex *index; /* index with line/byte position info */
int what; /* Look for TK_DUMP_INDEX bit */
@@ -3267,7 +3906,10 @@ TextEditCmd(textPtr, interp, objc, objv)
* be as simple as replacing Tcl_NewObj by Tcl_NewUnicodeObj.
*
* Results:
- * Tcl_Obj of string type containing the specified text.
+ * 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 given range are returned.
*
* Side effects:
* Memory will be allocated for the new object. Remember to free it if
@@ -3277,9 +3919,12 @@ TextEditCmd(textPtr, interp, objc, objv)
*/
static Tcl_Obj*
-TextGetText(indexPtr1,indexPtr2)
- CONST TkTextIndex *indexPtr1;
- CONST TkTextIndex *indexPtr2;
+TextGetText(textPtr, indexPtr1,indexPtr2, visibleOnly)
+ CONST 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 tmpIndex;
Tcl_Obj *resultPtr = Tcl_NewObj();
@@ -3311,8 +3956,10 @@ TextGetText(indexPtr1,indexPtr2)
}
}
if (segPtr->typePtr == &tkTextCharType) {
- Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset,
- last - offset);
+ if (!visibleOnly || !TkTextIsElided(textPtr, &tmpIndex)) {
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset,
+ last - offset);
+ }
}
TkTextIndexForwBytes(&tmpIndex, last-offset, &tmpIndex);
}
@@ -3423,9 +4070,9 @@ SearchPerform(interp, searchSpecPtr, patObj, fromPtr, toPtr)
/*
* Scan through all of the lines of the text circularly, starting
- * at the given index. 'objv[i]' is the pattern which may be an
- * exact string or a regexp pattern depending on the flags set
- * above.
+ * at the given index. 'patObj' is the pattern which may be an
+ * exact string or a regexp pattern depending on the flags in
+ * searchSpecPtr.
*/
return SearchCore(interp, searchSpecPtr, patObj);
@@ -3482,10 +4129,27 @@ SearchCore(interp, searchSpecPtr, patObj)
int lineNum = searchSpecPtr->startLine;
int code = TCL_OK;
Tcl_Obj *theLine;
-
+ int alreadySearchOffset = -1;
+
Tcl_RegExp regexp = NULL; /* For regexp searches only */
- CONST char *pattern = NULL; /* For exact searches only */
- int firstNewLine = -1; /* For exact searches only */
+ /*
+ * These items are for backward regexp searches only. They are for
+ * two purposes: to allow us to report backwards matches in the
+ * correct order, even though the implementation uses repeated
+ * forward searches; and to provide for overlap checking between
+ * backwards matches on different text lines.
+ */
+#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;
+ int lastBackwardsLineMatch = -1;
+ int lastBackwardsMatchOffset = -1;
+
+ /* These two items are for exact searches only */
+ CONST char *pattern = NULL;
+ int firstNewLine = -1;
if (searchSpecPtr->exact) {
/*
@@ -3521,7 +4185,7 @@ SearchCore(interp, searchSpecPtr, patObj)
* will have to search across multiple lines.
*/
if (searchSpecPtr->exact) {
- char *nl;
+ CONST char *nl;
/*
* We only need to set the matchLength once for exact searches,
@@ -3571,13 +4235,31 @@ SearchCore(interp, searchSpecPtr, patObj)
* 'lastOffset' (in bytes if exact, chars if regexp), since
* obviously the length is the maximum offset at which
* it is possible to find something on this line, which is
- * what we 'lastOffset' represents.
+ * what 'lastOffset' represents.
*/
lineInfo = (*searchSpecPtr->addLineProc)(lineNum,
searchSpecPtr, theLine, &lastOffset);
- firstOffset = 0;
+ if (lineNum == searchSpecPtr->stopLine && searchSpecPtr->backwards) {
+ firstOffset = searchSpecPtr->stopOffset;
+ } else {
+ firstOffset = 0;
+ }
+
+ if (alreadySearchOffset != -1) {
+ if (searchSpecPtr->backwards) {
+ if (alreadySearchOffset < lastOffset) {
+ lastOffset = alreadySearchOffset;
+ }
+ } else {
+ if (alreadySearchOffset > firstOffset) {
+ firstOffset = alreadySearchOffset;
+ }
+ }
+ alreadySearchOffset = -1;
+ }
+
if (lineNum == searchSpecPtr->startLine) {
/*
* The starting line is tricky: the first time we see it
@@ -3593,18 +4275,21 @@ SearchCore(interp, searchSpecPtr, patObj)
* Only use the last part of the line.
*/
- if ((searchSpecPtr->startOffset >= lastOffset)
- && ((lastOffset != 0) || searchSpecPtr->exact)) {
+ if (searchSpecPtr->startOffset > firstOffset) {
+ firstOffset = searchSpecPtr->startOffset;
+ }
+ if ((firstOffset >= lastOffset)
+ && ((lastOffset != 0) || searchSpecPtr->exact)) {
goto nextLine;
}
-
- firstOffset = searchSpecPtr->startOffset;
} else {
/*
* Use only the first part of the line.
*/
- lastOffset = searchSpecPtr->startOffset;
+ if (searchSpecPtr->startOffset < lastOffset) {
+ lastOffset = searchSpecPtr->startOffset;
+ }
}
}
@@ -3625,18 +4310,71 @@ SearchCore(interp, searchSpecPtr, patObj)
do {
Tcl_UniChar ch;
CONST char *p;
+ int lastFullLine = lastOffset;
- p = strstr(startOfLine + firstOffset, pattern);
- if (p == NULL) {
- if (firstNewLine == -1 ||
- firstNewLine >= (lastOffset - firstOffset)) {
+ if (firstNewLine == -1) {
+ if (searchSpecPtr->strictLimits
+ && (firstOffset + matchLength > lastOffset)) {
+ /* Not enough characters to match. */
+ break;
+ }
+ /*
+ * Single line matching. We want to scan forwards
+ * or backwards as appropriate.
+ */
+ if (searchSpecPtr->backwards) {
+ /*
+ * Search back either from the previous match or
+ * from 'startOfLine + lastOffset - 1' until we
+ * find a match.
+ */
+ CONST char c = pattern[0];
+ if (alreadySearchOffset != -1) {
+ p = startOfLine + alreadySearchOffset;
+ alreadySearchOffset = -1;
+ } else {
+ p = startOfLine + lastOffset -1;
+ }
+ while (p >= startOfLine + firstOffset) {
+ if (p[0] == c && !strncmp(p, pattern, matchLength)) {
+ goto backwardsMatch;
+ }
+ p--;
+ }
break;
+ } else {
+ p = strstr(startOfLine + firstOffset, pattern);
}
+ if (p == NULL) {
+ /*
+ * Single line match failed.
+ */
+ break;
+ }
+ } else if (firstNewLine >= (lastOffset - firstOffset)) {
+ /*
+ * Multi-line match, but not enough characters to
+ * match.
+ */
+ break;
+ } else {
+ /*
+ * Multi-line match has only one possible match
+ * position, because we know where the '\n' is.
+ */
p = startOfLine + lastOffset - firstNewLine - 1;
if (strncmp(p, pattern, (unsigned)(firstNewLine + 1))) {
+ /* No match */
break;
} else {
int extraLines = 1;
+ /*
+ * If we find a match that overlaps more than one
+ * line, we will use this value to determine the
+ * first allowed starting offset for the following
+ * search (to avoid overlapping results).
+ */
+ int lastTotal = lastOffset;
int skipFirst = lastOffset - firstNewLine -1;
/*
* We may be able to match if given more text.
@@ -3644,7 +4382,7 @@ SearchCore(interp, searchSpecPtr, patObj)
* exact searches.
*/
while (1) {
- int len;
+ lastFullLine = lastTotal;
if (lineNum+extraLines>=searchSpecPtr->numLines) {
p = NULL;
@@ -3658,8 +4396,11 @@ SearchCore(interp, searchSpecPtr, patObj)
if (extraLines > maxExtraLines) {
if ((*searchSpecPtr->addLineProc)(lineNum
+ extraLines, searchSpecPtr, theLine,
- &len) == NULL) {
+ &lastTotal) == NULL) {
p = NULL;
+ if (!searchSpecPtr->backwards) {
+ linesSearched = extraLines + 1;
+ }
break;
}
maxExtraLines = extraLines;
@@ -3671,7 +4412,7 @@ SearchCore(interp, searchSpecPtr, patObj)
* Use the fact that 'matchLength = patLength'
* for exact searches
*/
- if ((len - skipFirst) >= matchLength) {
+ if ((lastTotal - skipFirst) >= matchLength) {
/*
* We now have enough text to match, so
* we make a final test and break
@@ -3686,7 +4427,7 @@ SearchCore(interp, searchSpecPtr, patObj)
* Not enough text yet, but check the prefix
*/
if (strncmp(p, pattern,
- (unsigned)(len - skipFirst))) {
+ (unsigned)(lastTotal - skipFirst))) {
p = NULL;
break;
}
@@ -3704,44 +4445,77 @@ SearchCore(interp, searchSpecPtr, patObj)
if (p == NULL) {
break;
}
+ linesSearched = extraLines;
}
}
- firstOffset = p - startOfLine;
- if (firstOffset >= lastOffset) {
+ backwardsMatch:
+ if ((p - startOfLine) >= lastOffset) {
break;
}
-
/*
* Remember the match
*/
- matchOffset = firstOffset;
+ matchOffset = p - startOfLine;
- /*
- * Move the starting point one character on from the
- * previous match, in case we are doing repeated or
- * backwards searches (for the latter, we actually do
- * repeated forward searches).
- */
- firstOffset += Tcl_UtfToUniChar(startOfLine+matchOffset, &ch);
if (searchSpecPtr->all &&
- !(*searchSpecPtr->foundMatchProc)(lineNum,
- searchSpecPtr, lineInfo, theLine, matchOffset,
- matchLength)) {
+ !(*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
+ lineInfo, theLine, matchOffset, matchLength)) {
/*
* We reached the end of the search
*/
goto searchDone;
}
- } while (searchSpecPtr->backwards || searchSpecPtr->all);
+ if (!searchSpecPtr->overlap) {
+ if (searchSpecPtr->backwards) {
+ alreadySearchOffset = p - startOfLine;
+ if (firstNewLine != -1) {
+ break;
+ } else {
+ alreadySearchOffset -= matchLength;
+ }
+ } else {
+ firstOffset = p - startOfLine + matchLength;
+ if (firstOffset >= lastOffset) {
+ /*
+ * Now, we have to be careful not to find
+ * overlapping matches either on the same or
+ * following lines. Assume that if we did find
+ * something, it goes until the last extra line
+ * we added.
+ *
+ * We can break out of the loop, since we know
+ * no more will be found.
+ */
+ if (!searchSpecPtr->backwards) {
+ alreadySearchOffset = firstOffset - lastFullLine;
+ break;
+ }
+ }
+ }
+ } else {
+ if (searchSpecPtr->backwards) {
+ alreadySearchOffset = p - startOfLine - 1;
+ if (alreadySearchOffset < 0) {
+ break;
+ }
+ } else {
+ firstOffset = p - startOfLine
+ + Tcl_UtfToUniChar(startOfLine + matchOffset, &ch);
+ }
+ }
+ } while (searchSpecPtr->all);
} else {
int maxExtraLines = 0;
-
+ int matches = 0;
+ int lastNonOverlap = -1;
+
do {
Tcl_RegExpInfo info;
int match;
-
+ int lastFullLine = lastOffset;
+
match = Tcl_RegExpExecObj(interp, regexp, theLine, firstOffset,
1, ((firstOffset > 0) ? TCL_REG_NOTBOL : 0));
if (match < 0) {
@@ -3750,24 +4524,45 @@ SearchCore(interp, searchSpecPtr, patObj)
}
Tcl_RegExpGetInfo(regexp, &info);
- if (!match) {
+ /*
+ * If we don't have a match, or if we do, but it
+ * extends to the end of the line, we must try to
+ * add more lines to get a full greedy match.
+ */
+ if (!match
+ || ((info.extendStart == info.matches[0].start)
+ && (info.matches[0].end == lastOffset))) {
int extraLines = 1;
- int curLen = 0;
+ int prevFullLine;
+ /*
+ * If we find a match that overlaps more than one
+ * line, we will use this value to determine the
+ * first allowed starting offset for the following
+ * search (to avoid overlapping results).
+ */
+ int lastTotal = lastOffset;
+
+ if ((lastBackwardsLineMatch != -1)
+ && (lastBackwardsLineMatch == (lineNum + 1))) {
+ lastNonOverlap = lastTotal;
+ }
if (info.extendStart < 0) {
+ /* No multi-line match is possible */
break;
}
/*
* We may be able to match if given more text.
* The following 'while' block handles multi-line
- * exact searches.
+ * regexp searches.
*/
while (1) {
+ prevFullLine = lastTotal;
/*
* Move firstOffset to first possible start
*/
- firstOffset += info.extendStart;
+ if (!match) firstOffset += info.extendStart;
if (firstOffset >= lastOffset) {
/*
* We're being told that the only possible
@@ -3775,8 +4570,8 @@ SearchCore(interp, searchSpecPtr, patObj)
* the line. But, that is the next line which
* we will handle when we look at that line.
*/
- if (!searchSpecPtr->backwards
- && (firstOffset == curLen)) {
+ if (!match && !searchSpecPtr->backwards
+ && (firstOffset == 0)) {
linesSearched = extraLines + 1;
}
break;
@@ -3791,17 +4586,22 @@ SearchCore(interp, searchSpecPtr, patObj)
if (extraLines > maxExtraLines) {
if ((*searchSpecPtr->addLineProc)(lineNum
+ extraLines, searchSpecPtr, theLine,
- NULL) == NULL) {
+ &lastTotal) == NULL) {
/*
* There are no more acceptable lines, so
* we can say we have searched all of these
*/
- if (!searchSpecPtr->backwards) {
+ if (!match && !searchSpecPtr->backwards) {
linesSearched = extraLines + 1;
}
break;
}
maxExtraLines = extraLines;
+ if ((lastBackwardsLineMatch != -1)
+ && (lastBackwardsLineMatch
+ == (lineNum + extraLines + 1))) {
+ lastNonOverlap = lastTotal;
+ }
}
match = Tcl_RegExpExecObj(interp, regexp, theLine,
@@ -3812,64 +4612,286 @@ SearchCore(interp, searchSpecPtr, patObj)
goto searchDone;
}
Tcl_RegExpGetInfo(regexp, &info);
- if (match || info.extendStart < 0) {
+
+ /*
+ * Unfortunately there are bugs in Tcl's regexp
+ * library, which tells us that info.extendStart
+ * is zero when it should not be (should be -1),
+ * which makes our task a bit more complicated
+ * here. We check if there was a match, and the
+ * end of the match leaves an entire extra line
+ * unmatched, then we stop searching. Clearly it
+ * still might sometimes be possible to add more
+ * text and match again, but Tcl's regexp library
+ * doesn't tell us that.
+ *
+ * This means we often add and search one more
+ * line than might be necessary if Tcl were able
+ * to give us a correct value of info.extendStart
+ * under all circumstances.
+ */
+ if ((match && ((firstOffset + info.matches[0].end)
+ != lastTotal)
+ && ((firstOffset + info.matches[0].end)
+ < prevFullLine))
+ || info.extendStart < 0) {
break;
}
+ if (match && ((firstOffset + info.matches[0].end)
+ >= prevFullLine)) {
+ linesSearched = extraLines;
+ lastFullLine = prevFullLine;
+ }
/*
* The prefix matches, so keep looking
*/
extraLines++;
}
/*
- * If we reach here, with match == 1, we've found a
+ * If we reach here with 'match == 1', we've found a
* multi-line match, which we will record in the code
* which follows directly else we started a
* multi-line match but didn't finish it off, so we
* go to the next line.
- *
- * Here is where we could perform an optimisation,
- * since we have already retrieved the contents of
- * the next line (and many more), so we shouldn't
- * really throw it all away and start again. This
- * could be particularly important for complex regexp
- * searches.
*/
if (!match) {
- /*
- * This 'break' will take us to just before
- * the 'nextLine:' below.
+ /*
+ * Here is where we could perform an optimisation,
+ * since we have already retrieved the contents of
+ * the next line (perhaps many more), so we shouldn't
+ * really throw it all away and start again. This
+ * could be particularly important for complex regexp
+ * searches.
+ *
+ * This 'break' will take us to just before the
+ * 'nextLine:' below.
*/
break;
}
- }
+ if (lastBackwardsLineMatch != -1) {
+ if ((lineNum + linesSearched)
+ == lastBackwardsLineMatch) {
+ /* Possible overlap or inclusion */
+ int thisOffset = firstOffset + info.matches[0].end
+ - info.matches[0].start;
+
+ if (lastNonOverlap != -1) {
+ /* Possible overlap or enclosure */
+ if ((thisOffset - lastNonOverlap)
+ >= (lastBackwardsMatchOffset
+ + matchLength)) {
+ /*
+ * Totally encloses previous match, so
+ * forget the previous match
+ */
+ lastBackwardsLineMatch = -1;
+ } else if ((thisOffset - lastNonOverlap)
+ > lastBackwardsMatchOffset) {
+ /*
+ * Overlap. Previous match is ok, and
+ * the current match is only ok if
+ * we are searching with -overlap.
+ */
+ if (searchSpecPtr->overlap) {
+ goto recordBackwardsMatch;
+ } else {
+ match = 0;
+ break;
+ }
+ } else {
+ /*
+ * No overlap, although the same
+ * line was reached.
+ */
+ goto recordBackwardsMatch;
+ }
+ } else {
+ /* No overlap */
+ goto recordBackwardsMatch;
+ }
+ } else if (lineNum + linesSearched
+ < lastBackwardsLineMatch) {
+ /* No overlap */
+ goto recordBackwardsMatch;
+ } else {
+ /* Totally enclosed */
+ lastBackwardsLineMatch = -1;
+ }
+ }
+
+ } else {
+ /* Matched in a single line */
+ if (lastBackwardsLineMatch != -1) {
+ recordBackwardsMatch:
+ (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch,
+ searchSpecPtr, NULL, NULL,
+ lastBackwardsMatchOffset, matchLength);
+ lastBackwardsLineMatch = -1;
+ if (!searchSpecPtr->all) {
+ goto searchDone;
+ }
+ }
+ }
+
firstOffset += info.matches[0].start;
if (firstOffset >= lastOffset) {
break;
}
/*
- * Remember the match
+ * Update our local variables with the match, if we
+ * haven't yet found anything, or if we're doing '-all'
+ * or '-backwards' _and_ this match isn't fully enclosed
+ * in the previous match.
*/
- matchOffset = firstOffset;
- matchLength = info.matches[0].end - info.matches[0].start;
+
+ if (matchOffset == -1 ||
+ ((searchSpecPtr->all || searchSpecPtr->backwards)
+ && ((firstOffset < matchOffset)
+ || ((firstOffset + info.matches[0].end
+ - info.matches[0].start)
+ > (matchOffset + matchLength))
+ )
+ )
+ ) {
+
+ matchOffset = firstOffset;
+ matchLength = info.matches[0].end - info.matches[0].start;
+
+ if (searchSpecPtr->backwards) {
+ /*
+ * To get backwards searches in the correct
+ * order, we must store them away here.
+ */
+ if (matches == matchNum) {
+ /*
+ * We've run out of space in our normal
+ * store, so we must allocate space for
+ * these backwards matches on the heap.
+ */
+ int *newArray;
+ newArray = (int*) ckalloc(4*matchNum *sizeof(int));
+ memcpy(newArray, storeMatch, matchNum*sizeof(int));
+ memcpy(newArray + 2*matchNum,
+ storeLength, matchNum*sizeof(int));
+ if (storeMatch != smArray) {
+ ckfree((char*)storeMatch);
+ }
+ matchNum *= 2;
+ storeMatch = newArray;
+ storeLength = newArray + matchNum;
+ }
+ storeMatch[matches] = matchOffset;
+ storeLength[matches] = matchLength;
+ matches++;
+ } else {
+ /*
+ * Now actually record the match, but only if we
+ * are doing an '-all' search.
+ */
+ if (searchSpecPtr->all &&
+ !(*searchSpecPtr->foundMatchProc)(lineNum,
+ searchSpecPtr, lineInfo, theLine, matchOffset,
+ matchLength)) {
+ /*
+ * We reached the end of the search
+ */
+ goto searchDone;
+ }
+ }
+ /*
+ * For forward matches, unless we allow overlaps, we
+ * move this on by the length of the current match so
+ * that we explicitly disallow overlapping matches.
+ */
+ if (matchLength > 0 && !searchSpecPtr->overlap
+ && !searchSpecPtr->backwards) {
+ firstOffset += matchLength;
+ if (firstOffset >= lastOffset) {
+ /*
+ * Now, we have to be careful not to find
+ * overlapping matches either on the same or
+ * following lines. Assume that if we did find
+ * something, it goes until the last extra line
+ * we added.
+ *
+ * We can break out of the loop, since we know
+ * no more will be found.
+ */
+ alreadySearchOffset = firstOffset - lastFullLine;
+ break;
+ }
+ /* We'll add this on again just below */
+ firstOffset --;
+ }
+ }
/*
- * Move the starting point one character on, in case
- * we are doing repeated or backwards searches (for the
- * latter, we actually do repeated forward searches).
+ * Move the starting point on, in case we are doing
+ * repeated or backwards searches (for the latter, we
+ * actually do repeated forward searches).
*/
firstOffset++;
- if (searchSpecPtr->all &&
- !(*searchSpecPtr->foundMatchProc)(lineNum,
- searchSpecPtr, lineInfo, theLine, matchOffset,
- matchLength)) {
- /*
- * We reached the end of the search
+ } while (searchSpecPtr->backwards || searchSpecPtr->all);
+
+ if (matches > 0) {
+ /*
+ * Now we have all the matches in our array, but not stored
+ * with 'foundMatchProc' yet.
+ */
+ matches--;
+ matchOffset = storeMatch[matches];
+ matchLength = storeLength[matches];
+ while (--matches >= 0) {
+ if (lineNum == searchSpecPtr->stopLine) {
+ /*
+ * It appears as if a condition like
+ * 'if (storeMatch[matches]
+ * < searchSpecPtr->stopOffset) break;'
+ *
+ * might be needed here, but no test case
+ * has been found which would exercise such
+ * a problem.
+ */
+ }
+ if (storeMatch[matches] + storeLength[matches]
+ >= matchOffset + matchLength) {
+ /*
+ * The new match totally encloses the previous
+ * one, so we overwrite the previous one.
+ */
+ matchOffset = storeMatch[matches];
+ matchLength = storeLength[matches];
+ continue;
+ }
+ if (!searchSpecPtr->overlap) {
+ if (storeMatch[matches] + storeLength[matches]
+ > matchOffset) {
+ continue;
+ }
+ }
+ (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
+ lineInfo, theLine, matchOffset, matchLength);
+ if (!searchSpecPtr->all) {
+ goto searchDone;
+ }
+ matchOffset = storeMatch[matches];
+ matchLength = storeLength[matches];
+ }
+ if (searchSpecPtr->all && matches > 0) {
+ /*
+ * We only need to do this for the '-all' case,
+ * because just below we will call the
+ * foundMatchProc for the non-all case
*/
- goto searchDone;
+ (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
+ lineInfo, theLine, matchOffset, matchLength);
+ } else {
+ lastBackwardsLineMatch = lineNum;
+ lastBackwardsMatchOffset = matchOffset;
}
- } while (searchSpecPtr->backwards || searchSpecPtr->all);
+ }
}
/*
@@ -3880,9 +4902,11 @@ SearchCore(interp, searchSpecPtr, patObj)
* and we are done.
*/
- if ((matchOffset >= 0) && !searchSpecPtr->all) {
- (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
- lineInfo, theLine, matchOffset, matchLength);
+ if ((lastBackwardsLineMatch == -1)
+ && (matchOffset >= 0)
+ && !searchSpecPtr->all) {
+ (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
+ lineInfo, theLine, matchOffset, matchLength);
goto searchDone;
}
@@ -3892,7 +4916,7 @@ SearchCore(interp, searchSpecPtr, patObj)
nextLine:
- for (; linesSearched>0 ; linesSearched--) {
+ while (linesSearched-- > 0) {
/*
* If we have just completed the 'stopLine', we are done
*/
@@ -3902,26 +4926,71 @@ SearchCore(interp, searchSpecPtr, patObj)
if (searchSpecPtr->backwards) {
lineNum--;
+
+ if (lastBackwardsLineMatch != -1
+ && ((lineNum < 0) || (lineNum + 2 < lastBackwardsLineMatch))) {
+ (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch,
+ searchSpecPtr, NULL, NULL,
+ lastBackwardsMatchOffset,
+ matchLength);
+ lastBackwardsLineMatch = -1;
+ if (!searchSpecPtr->all) {
+ goto searchDone;
+ }
+ }
+
if (lineNum < 0) {
lineNum = searchSpecPtr->numLines-1;
}
+ if (!searchSpecPtr->exact) {
+ /*
+ * The 'exact' search loops above are designed to
+ * give us an accurate picture of the number of lines
+ * which we can skip here. For 'regexp' searches, on
+ * the other hand, which can match potentially variable
+ * lengths, we cannot skip multiple lines when searching
+ * backwards. Therefore we only allow one line to be
+ * skipped here.
+ */
+ break;
+ }
} else {
lineNum++;
if (lineNum >= searchSpecPtr->numLines) {
lineNum = 0;
}
}
+ if (lineNum == searchSpecPtr->startLine && linesSearched > 0) {
+ /*
+ * We've just searched all the way round and have
+ * gone right through the start line without finding
+ * anything in the last attempt.
+ */
+ break;
+ }
}
Tcl_SetObjLength(theLine, 0);
}
searchDone:
+ if (lastBackwardsLineMatch != -1) {
+ (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch, searchSpecPtr,
+ NULL, NULL, lastBackwardsMatchOffset, matchLength);
+ }
+
/*
* Free up the cached line and pattern
*/
Tcl_DecrRefCount(theLine);
Tcl_DecrRefCount(patObj);
+ /*
+ * Free up any extra space we allocated
+ */
+ if (storeMatch != smArray) {
+ ckfree((char*)storeMatch);
+ }
+
return code;
}
diff --git a/generic/tkText.h b/generic/tkText.h
index efe872d..f59f003 100644
--- a/generic/tkText.h
+++ b/generic/tkText.h
@@ -10,7 +10,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkText.h,v 1.16 2003/09/29 23:15:20 dkf Exp $
+ * RCS: @(#) $Id: tkText.h,v 1.17 2003/10/31 09:02:09 vincentdarley Exp $
*/
#ifndef _TKTEXT
@@ -37,8 +37,9 @@
typedef struct TkTextBTree_ *TkTextBTree;
/*
- * The data structure below defines a single line of text (from newline
- * to newline, not necessarily what appears on one line of the screen).
+ * 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).
*/
typedef struct TkTextLine {
@@ -49,6 +50,15 @@ typedef struct TkTextLine {
* means end of list. */
struct TkTextSegment *segPtr; /* First in ordered list of segments
* that make up the line. */
+ int pixelHeight; /* The number of vertical
+ * pixels taken up by this
+ * line, whether currently
+ * displayed or not. This
+ * number is only updated
+ * asychronously. */
+ int pixelCalculationEpoch; /* The last epoch at which the
+ * pixel height was
+ * recalculated. */
} TkTextLine;
/*
@@ -148,6 +158,8 @@ typedef struct TkTextEmbImage {
* of image, in pixels. */
int chunkCount; /* Number of display chunks that
* refer to this image. */
+ Tk_OptionTable optionTable; /* Token representing the
+ * configuration specifications. */
} TkTextEmbImage;
/*
@@ -189,7 +201,11 @@ typedef struct TkTextIndex {
* of interest. */
int byteIndex; /* Index within line of desired
* character (0 means first one). */
- struct TkText *textPtr;
+ 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. */
} TkTextIndex;
/*
@@ -286,7 +302,7 @@ typedef enum { TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE,
} TkWrapMode;
typedef struct TkTextTag {
- char *name; /* Name of this tag. This field is actually
+ CONST char *name; /* Name of this tag. This field is actually
* a pointer to the key from the entry in
* textPtr->tagTable, so it needn't be freed
* explicitly. */
@@ -395,6 +411,10 @@ typedef struct TkTextTag {
* (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
@@ -464,6 +484,7 @@ typedef struct TkTextTabArray {
typedef enum {
TK_TEXT_EDIT_INSERT, /* insert mode */
TK_TEXT_EDIT_DELETE, /* delete mode */
+ TK_TEXT_EDIT_REPLACE, /* replace mode */
TK_TEXT_EDIT_OTHER /* none of the above */
} TkTextEditMode;
@@ -537,6 +558,8 @@ typedef struct TkText {
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
@@ -687,9 +710,10 @@ typedef struct TkText {
int isDirtyIncrement; /* Amount with which the isDirty flag is
* incremented every edit action */
- TkTextEditMode lastEditMode;/* Keeps track of what the last edit mode was
- */
-
+ TkTextEditMode lastEditMode;/* Keeps track of what the last edit
+ * mode was */
+ int insertCursorType; /* 0 = standard insertion cursor,
+ * 1 = block cursor. */
} TkText;
/*
@@ -745,7 +769,7 @@ typedef void Tk_SegCheckProc _ANSI_ARGS_((TkTextSegment *segPtr,
TkTextLine *linePtr));
typedef struct Tk_SegType {
- char *name; /* Name of this kind of segment. */
+ 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
@@ -777,6 +801,23 @@ typedef struct Tk_SegType {
* segment. */
} Tk_SegType;
+
+/*
+ * 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_IS_ELIDED 4
+#define COUNT_DISPLAY_CHARS (COUNT_CHARS | COUNT_DISPLAY)
+#define COUNT_DISPLAY_INDICES (COUNT_INDICES | COUNT_DISPLAY)
+
/*
* 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.
@@ -791,6 +832,26 @@ typedef struct Tk_SegType {
#define TK_POS_CHARS 30
+/*
+ * 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
+
+/*
+ * 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
+
+/*
+ * Used as special 'pickPlace' values in calls to TkTextSetYView.
+ * Zero or positive values indicate a number of pixels.
+ */
+#define TK_TEXT_PICKPLACE -1
+#define TK_TEXT_NOPIXELADJUST -2
+
/*
* Declarations for variables shared among the text-related files:
*/
@@ -808,6 +869,8 @@ EXTERN Tk_SegType tkTextToggleOffType;
* but shouldn't be used anywhere else in Tk (or by Tk clients):
*/
+EXTERN int TkBTreeAdjustPixelHeight _ANSI_ARGS_((TkTextLine *linePtr,
+ int newPixelHeight));
EXTERN int TkBTreeCharTagged _ANSI_ARGS_((CONST TkTextIndex *indexPtr,
TkTextTag *tagPtr));
EXTERN void TkBTreeCheck _ANSI_ARGS_((TkTextBTree tree));
@@ -819,16 +882,20 @@ EXTERN void TkBTreeDeleteChars _ANSI_ARGS_((TkTextIndex *index1Ptr,
TkTextIndex *index2Ptr));
EXTERN TkTextLine * TkBTreeFindLine _ANSI_ARGS_((TkTextBTree tree,
int line));
+EXTERN TkTextLine * TkBTreeFindPixelLine _ANSI_ARGS_((TkTextBTree tree,
+ int pixels, int *pixelOffset));
EXTERN TkTextTag ** TkBTreeGetTags _ANSI_ARGS_((CONST TkTextIndex *indexPtr,
int *numTagsPtr));
EXTERN void TkBTreeInsertChars _ANSI_ARGS_((TkTextIndex *indexPtr,
CONST char *string));
EXTERN int TkBTreeLineIndex _ANSI_ARGS_((TkTextLine *linePtr));
+EXTERN int TkBTreePixels _ANSI_ARGS_((TkTextLine *linePtr));
EXTERN void TkBTreeLinkSegment _ANSI_ARGS_((TkTextSegment *segPtr,
TkTextIndex *indexPtr));
EXTERN TkTextLine * TkBTreeNextLine _ANSI_ARGS_((TkTextLine *linePtr));
EXTERN int TkBTreeNextTag _ANSI_ARGS_((TkTextSearch *searchPtr));
EXTERN int TkBTreeNumLines _ANSI_ARGS_((TkTextBTree tree));
+EXTERN int TkBTreeNumPixels _ANSI_ARGS_((TkTextBTree tree));
EXTERN TkTextLine * TkBTreePreviousLine _ANSI_ARGS_((TkTextLine *linePtr));
EXTERN int TkBTreePrevTag _ANSI_ARGS_((TkTextSearch *searchPtr));
EXTERN void TkBTreeStartSearch _ANSI_ARGS_((TkTextIndex *index1Ptr,
@@ -837,7 +904,7 @@ EXTERN void TkBTreeStartSearch _ANSI_ARGS_((TkTextIndex *index1Ptr,
EXTERN void TkBTreeStartSearchBack _ANSI_ARGS_((TkTextIndex *index1Ptr,
TkTextIndex *index2Ptr, TkTextTag *tagPtr,
TkTextSearch *searchPtr));
-EXTERN void TkBTreeTag _ANSI_ARGS_((TkTextIndex *index1Ptr,
+EXTERN int TkBTreeTag _ANSI_ARGS_((TkTextIndex *index1Ptr,
TkTextIndex *index2Ptr, TkTextTag *tagPtr,
int add));
EXTERN void TkBTreeUnlinkSegment _ANSI_ARGS_((TkTextBTree tree,
@@ -845,10 +912,11 @@ EXTERN void TkBTreeUnlinkSegment _ANSI_ARGS_((TkTextBTree tree,
EXTERN void TkTextBindProc _ANSI_ARGS_((ClientData clientData,
XEvent *eventPtr));
EXTERN void TkTextChanged _ANSI_ARGS_((TkText *textPtr,
- TkTextIndex *index1Ptr, TkTextIndex *index2Ptr));
+ CONST TkTextIndex *index1Ptr,
+ CONST TkTextIndex *index2Ptr));
EXTERN int TkTextCharBbox _ANSI_ARGS_((TkText *textPtr,
CONST TkTextIndex *indexPtr, int *xPtr, int *yPtr,
- int *widthPtr, int *heightPtr));
+ int *widthPtr, int *heightPtr, int *charWidthPtr));
EXTERN int TkTextCharLayoutProc _ANSI_ARGS_((TkText *textPtr,
TkTextIndex *indexPtr, TkTextSegment *segPtr,
int offset, int maxX, int maxChars, int noBreakYet,
@@ -857,6 +925,10 @@ EXTERN void TkTextCreateDInfo _ANSI_ARGS_((TkText *textPtr));
EXTERN int TkTextDLineInfo _ANSI_ARGS_((TkText *textPtr,
CONST TkTextIndex *indexPtr, int *xPtr, int *yPtr,
int *widthPtr, int *heightPtr, int *basePtr));
+EXTERN void TkTextEmbWinDisplayProc _ANSI_ARGS_((
+ TkTextDispChunk *chunkPtr, int x, int y,
+ int lineHeight, int baseline, Display *display,
+ Drawable dst, int screenY));
EXTERN TkTextTag * TkTextCreateTag _ANSI_ARGS_((TkText *textPtr,
CONST char *tagName));
EXTERN void TkTextFreeDInfo _ANSI_ARGS_((TkText *textPtr));
@@ -872,21 +944,32 @@ EXTERN CONST TkTextIndex* TkTextGetIndexFromObj _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, Tcl_Obj *objPtr));
EXTERN TkTextTabArray * TkTextGetTabs _ANSI_ARGS_((Tcl_Interp *interp,
Tk_Window tkwin, Tcl_Obj *stringPtr));
+EXTERN void TkTextFindDisplayLineEnd _ANSI_ARGS_((
+ TkText *textPtr, TkTextIndex *indexPtr,
+ int end, int *xOffset));
EXTERN void TkTextIndexBackBytes _ANSI_ARGS_((
CONST TkTextIndex *srcPtr, int count,
TkTextIndex *dstPtr));
EXTERN void TkTextIndexBackChars _ANSI_ARGS_((
CONST TkTextIndex *srcPtr, int count,
- TkTextIndex *dstPtr));
+ TkTextIndex *dstPtr, TkTextCountType type));
EXTERN int TkTextIndexCmp _ANSI_ARGS_((
CONST TkTextIndex *index1Ptr,
CONST TkTextIndex *index2Ptr));
-EXTERN void TkTextIndexForwBytes _ANSI_ARGS_((
+EXTERN int TkTextIndexCount _ANSI_ARGS_((CONST TkText *textPtr,
+ CONST TkTextIndex *index1Ptr,
+ CONST TkTextIndex *index2Ptr,
+ TkTextCountType type));
+EXTERN int TkTextIndexForwBytes _ANSI_ARGS_((
CONST TkTextIndex *srcPtr, int count,
TkTextIndex *dstPtr));
EXTERN void TkTextIndexForwChars _ANSI_ARGS_((
CONST TkTextIndex *srcPtr, int count,
- TkTextIndex *dstPtr));
+ TkTextIndex *dstPtr, TkTextCountType type));
+EXTERN void TkTextIndexOfX _ANSI_ARGS_((TkText *textPtr,
+ int x, TkTextIndex *indexPtr));
+EXTERN int TkTextIndexYPixels _ANSI_ARGS_((TkText *textPtr,
+ CONST TkTextIndex *indexPtr));
EXTERN TkTextSegment * TkTextIndexToSeg _ANSI_ARGS_((
CONST TkTextIndex *indexPtr, int *offsetPtr));
EXTERN void TkTextInsertDisplayProc _ANSI_ARGS_((
@@ -898,11 +981,25 @@ EXTERN void TkTextLostSelection _ANSI_ARGS_((
EXTERN TkTextIndex * TkTextMakeCharIndex _ANSI_ARGS_((TkTextBTree tree,
int lineIndex, int charIndex,
TkTextIndex *indexPtr));
-EXTERN int TkTextIsElided _ANSI_ARGS_((TkText *textPtr,
+EXTERN int TkTextMeasureDown _ANSI_ARGS_((TkText *textPtr,
+ TkTextIndex *srcPtr, int distance));
+EXTERN int TkTextIsElided _ANSI_ARGS_((CONST TkText *textPtr,
CONST TkTextIndex *indexPtr));
EXTERN TkTextIndex * TkTextMakeByteIndex _ANSI_ARGS_((TkTextBTree tree,
int lineIndex, int byteIndex,
TkTextIndex *indexPtr));
+EXTERN int TkTextMakePixelIndex _ANSI_ARGS_((TkText *textPtr,
+ int pixelIndex, TkTextIndex *indexPtr));
+EXTERN void TkTextInvalidateLineMetrics _ANSI_ARGS_((TkText *textPtr,
+ TkTextLine *linePtr, int lineCount, int action));
+EXTERN void TkTextAsyncUpdateLineMetrics _ANSI_ARGS_((ClientData
+ clientData));
+EXTERN int TkTextUpdateLineMetrics _ANSI_ARGS_((TkText *textPtr,
+ int lineNum, int endLine, int doThisMuch));
+EXTERN int TkTextUpdateOneLine _ANSI_ARGS_((TkText *textPtr,
+ TkTextLine *linePtr));
+EXTERN void TkTextUpdateYScrollbar _ANSI_ARGS_((ClientData
+ clientData));
EXTERN int TkTextMarkCmd _ANSI_ARGS_((TkText *textPtr,
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]));
EXTERN int TkTextMarkNameToIndex _ANSI_ARGS_((TkText *textPtr,
@@ -923,7 +1020,8 @@ EXTERN void TkTextRedrawRegion _ANSI_ARGS_((TkText *textPtr,
EXTERN void TkTextRedrawTag _ANSI_ARGS_((TkText *textPtr,
TkTextIndex *index1Ptr, TkTextIndex *index2Ptr,
TkTextTag *tagPtr, int withTag));
-EXTERN void TkTextRelayoutWindow _ANSI_ARGS_((TkText *textPtr));
+EXTERN void TkTextRelayoutWindow _ANSI_ARGS_((TkText *textPtr,
+ int mask));
EXTERN int TkTextScanCmd _ANSI_ARGS_((TkText *textPtr,
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]));
EXTERN int TkTextSeeCmd _ANSI_ARGS_((TkText *textPtr,
@@ -949,9 +1047,6 @@ EXTERN int TkTextXviewCmd _ANSI_ARGS_((TkText *textPtr,
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]));
EXTERN int TkTextYviewCmd _ANSI_ARGS_((TkText *textPtr,
Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]));
-/* Use a helper from tkCanvas.c */
-EXTERN CONST char** TkGetStringsFromObjs _ANSI_ARGS_((int argc,
- Tcl_Obj *CONST *objv));
# undef TCL_STORAGE_CLASS
# define TCL_STORAGE_CLASS DLLIMPORT
diff --git a/generic/tkTextBTree.c b/generic/tkTextBTree.c
index 762d5f3..63d10c0 100644
--- a/generic/tkTextBTree.c
+++ b/generic/tkTextBTree.c
@@ -11,7 +11,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkTextBTree.c,v 1.7 2003/05/19 13:04:23 vincentdarley Exp $
+ * RCS: @(#) $Id: tkTextBTree.c,v 1.8 2003/10/31 09:02:10 vincentdarley Exp $
*/
#include "tkInt.h"
@@ -55,6 +55,7 @@ typedef struct Node {
int numChildren; /* Number of children of this node. */
int numLines; /* Total number of lines (leaves) in
* the subtree rooted here. */
+ int numPixels;
} Node;
/*
@@ -217,7 +218,7 @@ TkBTreeCreate(textPtr)
register Node *rootPtr;
register TkTextLine *linePtr, *linePtr2;
register TkTextSegment *segPtr;
-
+
/*
* The tree will initially have two empty lines. The second line
* isn't actually part of the tree's contents, but its presence
@@ -235,7 +236,13 @@ TkBTreeCreate(textPtr)
rootPtr->children.linePtr = linePtr;
rootPtr->numChildren = 2;
rootPtr->numLines = 2;
-
+ rootPtr->numPixels = textPtr->charHeight;
+ linePtr->pixelHeight = textPtr->charHeight;
+ linePtr->pixelCalculationEpoch = 0;
+ /* The last line permanently has a pixel height of zero */
+ linePtr2->pixelHeight = 0;
+ linePtr2->pixelCalculationEpoch = 1;
+
linePtr->parentPtr = rootPtr;
linePtr->nextPtr = linePtr2;
segPtr = (TkTextSegment *) ckalloc(CSEG_SIZE(1));
@@ -245,7 +252,7 @@ TkBTreeCreate(textPtr)
segPtr->size = 1;
segPtr->body.chars[0] = '\n';
segPtr->body.chars[1] = 0;
-
+
linePtr2->parentPtr = rootPtr;
linePtr2->nextPtr = NULL;
segPtr = (TkTextSegment *) ckalloc(CSEG_SIZE(1));
@@ -371,6 +378,54 @@ DeleteSummaries(summaryPtr)
/*
*----------------------------------------------------------------------
*
+ * TkBTreeAdjustPixelHeight --
+ *
+ * Adjust the pixel height of a given logical line to the
+ * specified value.
+ *
+ * Results:
+ * Total number of valid pixels currently known in the tree.
+ *
+ * Side effects:
+ * Updates overall data structures so pixel height count is
+ * consistent.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkBTreeAdjustPixelHeight(linePtr, newPixelHeight)
+ register TkTextLine *linePtr; /* The logical line to update */
+ int newPixelHeight; /* The line's known height
+ * in pixels */
+{
+ register Node *nodePtr;
+ int changeToPixelCount; /* Counts change to total number of
+ * pixels in file. */
+
+ changeToPixelCount = newPixelHeight - linePtr->pixelHeight;
+
+ /*
+ * Increment the pixel counts in all the parent nodes of the
+ * current line, then rebalance the tree if necessary.
+ */
+
+ nodePtr = linePtr->parentPtr;
+ nodePtr->numPixels += changeToPixelCount;
+
+ while (nodePtr->parentPtr != NULL) {
+ nodePtr = nodePtr->parentPtr;
+ nodePtr->numPixels += changeToPixelCount;
+ }
+
+ linePtr->pixelHeight = newPixelHeight;
+
+ return nodePtr->numPixels;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkBTreeInsertChars --
*
* Insert characters at a given position in a B-tree.
@@ -414,6 +469,8 @@ TkBTreeInsertChars(indexPtr, string)
* 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. */
prevPtr = SplitSeg(indexPtr);
linePtr = indexPtr->linePtr;
@@ -426,6 +483,7 @@ TkBTreeInsertChars(indexPtr, string)
*/
changeToLineCount = 0;
+ changeToPixelCount = 0;
while (*string != 0) {
for (eol = string; *eol != 0; eol++) {
if (*eol == '\n') {
@@ -461,11 +519,19 @@ TkBTreeInsertChars(indexPtr, string)
newLinePtr->nextPtr = linePtr->nextPtr;
linePtr->nextPtr = newLinePtr;
newLinePtr->segPtr = segPtr->nextPtr;
+ /*
+ * Set up a starting default height, which will be re-adjusted
+ * later
+ */
+ newLinePtr->pixelHeight = linePtr->pixelHeight;
+ newLinePtr->pixelCalculationEpoch = 0;
+
segPtr->nextPtr = NULL;
linePtr = newLinePtr;
curPtr = NULL;
changeToLineCount++;
-
+ changeToPixelCount += newLinePtr->pixelHeight;
+
string = eol;
}
@@ -478,15 +544,26 @@ TkBTreeInsertChars(indexPtr, string)
if (linePtr != indexPtr->linePtr) {
CleanupLine(linePtr);
}
+
+ /*
+ * 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-calculated the line height of the last line).
+ */
+ TkTextInvalidateLineMetrics(((BTree*)indexPtr->tree)->textPtr,
+ indexPtr->linePtr, changeToLineCount,
+ TK_TEXT_INVALIDATE_INSERT);
/*
- * Increment the line counts in all the parent nodes of the insertion
- * point, then rebalance the tree if necessary.
+ * Increment the line and pixel counts in all the parent nodes of the
+ * insertion point, then rebalance the tree if necessary.
*/
for (nodePtr = linePtr->parentPtr ; nodePtr != NULL;
nodePtr = nodePtr->parentPtr) {
nodePtr->numLines += changeToLineCount;
+ nodePtr->numPixels += changeToPixelCount;
}
nodePtr = linePtr->parentPtr;
nodePtr->numChildren += changeToLineCount;
@@ -649,7 +726,8 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr)
TkTextSegment *segPtr, *nextPtr;
TkTextLine *curLinePtr;
Node *curNodePtr, *nodePtr;
-
+ int changeToLineCount = 0;
+
/*
* Tricky point: split at index2Ptr first; otherwise the split
* at index2Ptr may invalidate segPtr and/or prevPtr.
@@ -675,6 +753,7 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr)
*/
curLinePtr = index1Ptr->linePtr;
+
curNodePtr = curLinePtr->parentPtr;
while (segPtr != lastPtr) {
if (segPtr == NULL) {
@@ -696,7 +775,9 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr)
for (nodePtr = curNodePtr; nodePtr != NULL;
nodePtr = nodePtr->parentPtr) {
nodePtr->numLines--;
+ nodePtr->numPixels -= curLinePtr->pixelHeight;
}
+ changeToLineCount++;
curNodePtr->numChildren--;
ckfree((char *) curLinePtr);
}
@@ -768,7 +849,9 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr)
for (nodePtr = curNodePtr; nodePtr != NULL;
nodePtr = nodePtr->parentPtr) {
nodePtr->numLines--;
+ nodePtr->numPixels -= index2Ptr->linePtr->pixelHeight;
}
+ changeToLineCount++;
curNodePtr->numChildren--;
prevLinePtr = curNodePtr->children.linePtr;
if (prevLinePtr == index2Ptr->linePtr) {
@@ -780,6 +863,7 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr)
prevLinePtr->nextPtr = index2Ptr->linePtr->nextPtr;
}
ckfree((char *) index2Ptr->linePtr);
+
Rebalance((BTree *) index2Ptr->tree, curNodePtr);
}
@@ -790,6 +874,18 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr)
CleanupLine(index1Ptr->linePtr);
/*
+ * 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.
+ */
+ if (TkBTreeNextLine(index1Ptr->linePtr) != NULL) {
+ TkTextInvalidateLineMetrics(((BTree*)index1Ptr->tree)->textPtr,
+ index1Ptr->linePtr, changeToLineCount,
+ TK_TEXT_INVALIDATE_DELETE);
+ }
+
+ /*
* Lastly, rebalance the first node of the range.
*/
@@ -865,6 +961,82 @@ TkBTreeFindLine(tree, line)
/*
*----------------------------------------------------------------------
*
+ * TkBTreeFindPixelLine --
+ *
+ * Find a particular line in a B-tree based on its pixel count.
+ *
+ * Results:
+ * The return value is a pointer to the line structure for the
+ * line which contains the pixel "pixels", or NULL if no such
+ * line exists. If the first line is of height 20, then pixels
+ * 0-19 will return it, and pixels = 20 will return the next
+ * line.
+ *
+ * If pixelOffset is non-NULL, it is set to the amount by which
+ * 'pixels' exceeds the first pixel located on the returned
+ * line. This should always be non-negative.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+TkTextLine *
+TkBTreeFindPixelLine(tree, pixels, pixelOffset)
+ TkTextBTree tree; /* B-tree in which to find line. */
+ int pixels; /* Index of desired line. */
+ int *pixelOffset; /* Used to return offset */
+{
+ BTree *treePtr = (BTree *) tree;
+ register Node *nodePtr;
+ register TkTextLine *linePtr;
+ int pixelsLeft;
+
+ nodePtr = treePtr->rootPtr;
+ pixelsLeft = pixels;
+
+ if ((pixels < 0) || (pixels > nodePtr->numPixels)) {
+ return NULL;
+ }
+
+ /*
+ * 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->numPixels <= pixelsLeft;
+ nodePtr = nodePtr->nextPtr) {
+ if (nodePtr == NULL) {
+ panic("TkBTreeFindLine ran out of nodes");
+ }
+ pixelsLeft -= nodePtr->numPixels;
+ }
+ }
+
+ /*
+ * Work through the lines attached to the level-0 node.
+ */
+
+ for (linePtr = nodePtr->children.linePtr;
+ linePtr->pixelHeight < pixelsLeft;
+ linePtr = linePtr->nextPtr) {
+ if (linePtr == NULL) {
+ panic("TkBTreeFindLine ran out of lines");
+ }
+ pixelsLeft -= linePtr->pixelHeight;
+ }
+ if (pixelOffset != NULL && linePtr != NULL) {
+ *pixelOffset = pixelsLeft;
+ }
+ return linePtr;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkBTreeNextLine --
*
* Given an existing line in a B-tree, this procedure locates the
@@ -988,6 +1160,72 @@ TkBTreePreviousLine(linePtr)
/*
*----------------------------------------------------------------------
*
+ * TkBTreePixels --
+ *
+ * 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).
+ *
+ * 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 index of linePtr within the tree, where 0
+ * corresponds to the first line in the tree.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkBTreePixels(linePtr)
+ TkTextLine *linePtr; /* Pointer to existing line in
+ * B-tree. */
+{
+ register TkTextLine *linePtr2;
+ register Node *nodePtr, *parentPtr, *nodePtr2;
+ int index;
+
+ /*
+ * First count how many lines precede this one in its level-0
+ * node.
+ */
+
+ nodePtr = linePtr->parentPtr;
+ index = 0;
+ for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr;
+ linePtr2 = linePtr2->nextPtr) {
+ if (linePtr2 == NULL) {
+ panic("TkBTreePixels couldn't find line");
+ }
+ index += linePtr2->pixelHeight;
+ }
+
+ /*
+ * Now work up through the levels of the tree one at a time,
+ * counting how many lines are in nodes preceding the current
+ * node.
+ */
+
+ for (parentPtr = nodePtr->parentPtr ; parentPtr != NULL;
+ nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) {
+ for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr;
+ nodePtr2 = nodePtr2->nextPtr) {
+ if (nodePtr2 == NULL) {
+ panic("TkBTreePixels couldn't find node");
+ }
+ index += nodePtr2->numPixels;
+ }
+ }
+ return index;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkBTreeLineIndex --
*
* Given a pointer to a line in a B-tree, return the numerical
@@ -1137,7 +1375,9 @@ TkBTreeUnlinkSegment(tree, segPtr, linePtr)
* a B-tree of text.
*
* Results:
- * None.
+ * 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).
*
* Side effects:
* The given tag is added to the given range of characters
@@ -1150,7 +1390,7 @@ TkBTreeUnlinkSegment(tree, segPtr, linePtr)
*----------------------------------------------------------------------
*/
-void
+int
TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add)
register TkTextIndex *index1Ptr; /* Indicates first character in
* range. */
@@ -1166,7 +1406,8 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add)
TkTextLine *cleanupLinePtr;
int oldState;
int changed;
-
+ int anyChanges = 0;
+
/*
* 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
@@ -1188,6 +1429,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add)
segPtr->size = 0;
segPtr->body.toggle.tagPtr = tagPtr;
segPtr->body.toggle.inNodeCounts = 0;
+ anyChanges = 1;
}
/*
@@ -1199,6 +1441,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add)
TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, &search);
cleanupLinePtr = index1Ptr->linePtr;
while (TkBTreeNextTag(&search)) {
+ anyChanges = 1;
oldState ^= 1;
segPtr = search.segPtr;
prevPtr = search.curIndex.linePtr->segPtr;
@@ -1257,6 +1500,7 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add)
segPtr->size = 0;
segPtr->body.toggle.tagPtr = tagPtr;
segPtr->body.toggle.inNodeCounts = 0;
+ anyChanges = 1;
}
/*
@@ -1264,14 +1508,17 @@ TkBTreeTag(index1Ptr, index2Ptr, tagPtr, add)
* these are different.
*/
- CleanupLine(cleanupLinePtr);
- if (cleanupLinePtr != index2Ptr->linePtr) {
- CleanupLine(index2Ptr->linePtr);
+ if (anyChanges) {
+ CleanupLine(cleanupLinePtr);
+ if (cleanupLinePtr != index2Ptr->linePtr) {
+ CleanupLine(index2Ptr->linePtr);
+ }
}
if (tkBTreeDebug) {
TkBTreeCheck(index1Ptr->tree);
}
+ return anyChanges;
}
/*
@@ -1789,7 +2036,8 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr)
searchPtr->curIndex = index0;
index1Ptr = &index0;
} else {
- TkTextIndexBackChars(index1Ptr, 1, &searchPtr->curIndex);
+ TkTextIndexBackChars(index1Ptr, 1, &searchPtr->curIndex,
+ COUNT_INDICES);
}
searchPtr->segPtr = NULL;
searchPtr->nextPtr = TkTextIndexToSeg(&searchPtr->curIndex, &offset);
@@ -1805,7 +2053,7 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr)
backOne = *index2Ptr;
searchPtr->lastPtr = NULL; /* Signals special case for 1.0 */
} else {
- TkTextIndexBackChars(index2Ptr, 1, &backOne);
+ TkTextIndexBackChars(index2Ptr, 1, &backOne, COUNT_INDICES);
searchPtr->lastPtr = TkTextIndexToSeg(&backOne, (int *) NULL);
}
searchPtr->tagPtr = tagPtr;
@@ -2451,7 +2699,7 @@ TkBTreeGetTags(indexPtr, numTagsPtr)
/* ARGSUSED */
int
TkTextIsElided(textPtr, indexPtr)
- TkText *textPtr; /* Overall information about text widget. */
+ CONST TkText *textPtr; /* Overall information about text widget. */
CONST TkTextIndex *indexPtr;/* The character in the text for which
* display information is wanted. */
{
@@ -2469,7 +2717,7 @@ TkTextIsElided(textPtr, indexPtr)
register TkTextTag *tagPtr = NULL;
register int i, index;
- /* almost always avoid malloc, so stay out of system calls */
+ /* Almost always avoid malloc, so stay out of system calls */
if (LOTSA_TAGS < numTags) {
tagCnts = (int *)ckalloc((unsigned)sizeof(int) * numTags);
tagPtrs = (TkTextTag **)ckalloc((unsigned)sizeof(TkTextTag *) * numTags);
@@ -2806,7 +3054,7 @@ CheckNodeConsistency(nodePtr)
register Summary *summaryPtr, *summaryPtr2;
register TkTextLine *linePtr;
register TkTextSegment *segPtr;
- int numChildren, numLines, toggleCount, minChildren;
+ int numChildren, numLines, numPixels, toggleCount, minChildren;
if (nodePtr->parentPtr != NULL) {
minChildren = MIN_CHILDREN;
@@ -2823,6 +3071,7 @@ CheckNodeConsistency(nodePtr)
numChildren = 0;
numLines = 0;
+ numPixels = 0;
if (nodePtr->level == 0) {
for (linePtr = nodePtr->children.linePtr; linePtr != NULL;
linePtr = linePtr->nextPtr) {
@@ -2850,6 +3099,7 @@ CheckNodeConsistency(nodePtr)
}
numChildren++;
numLines++;
+ numPixels += linePtr->pixelHeight;
}
} else {
for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL;
@@ -2881,6 +3131,7 @@ CheckNodeConsistency(nodePtr)
}
numChildren++;
numLines += childNodePtr->numLines;
+ numPixels += childNodePtr->numPixels;
}
}
if (numChildren != nodePtr->numChildren) {
@@ -2891,6 +3142,10 @@ CheckNodeConsistency(nodePtr)
panic("CheckNodeConsistency: mismatch in numLines (%d %d)",
numLines, nodePtr->numLines);
}
+ if (numPixels != nodePtr->numPixels) {
+ panic("CheckNodeConsistency: mismatch in numPixels (%d %d)",
+ numPixels, nodePtr->numPixels);
+ }
for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL;
summaryPtr = summaryPtr->nextPtr) {
@@ -2997,6 +3252,7 @@ Rebalance(treePtr, nodePtr)
newPtr->children.nodePtr = nodePtr;
newPtr->numChildren = 1;
newPtr->numLines = nodePtr->numLines;
+ newPtr->numPixels = nodePtr->numPixels;
RecomputeNodeCounts(newPtr);
treePtr->rootPtr = newPtr;
}
@@ -3209,6 +3465,7 @@ RecomputeNodeCounts(nodePtr)
}
nodePtr->numChildren = 0;
nodePtr->numLines = 0;
+ nodePtr->numPixels = 0;
/*
* Scan through the children, adding the childrens' tag counts into
@@ -3221,6 +3478,7 @@ RecomputeNodeCounts(nodePtr)
linePtr = linePtr->nextPtr) {
nodePtr->numChildren++;
nodePtr->numLines++;
+ nodePtr->numPixels += linePtr->pixelHeight;
linePtr->parentPtr = nodePtr;
for (segPtr = linePtr->segPtr; segPtr != NULL;
segPtr = segPtr->nextPtr) {
@@ -3252,6 +3510,7 @@ RecomputeNodeCounts(nodePtr)
childPtr = childPtr->nextPtr) {
nodePtr->numChildren++;
nodePtr->numLines += childPtr->numLines;
+ nodePtr->numPixels += childPtr->numPixels;
childPtr->parentPtr = nodePtr;
for (summaryPtr2 = childPtr->summaryPtr; summaryPtr2 != NULL;
summaryPtr2 = summaryPtr2->nextPtr) {
@@ -3343,6 +3602,34 @@ TkBTreeNumLines(tree)
}
/*
+ *----------------------------------------------------------------------
+ *
+ * TkBTreeNumPixels --
+ *
+ * This procedure returns a count of the number of pixels of
+ * text present in a given B-tree.
+ *
+ * Results:
+ * 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 tree is
+ * maintained with zero height, it doesn't feature in this
+ * calculation).
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkBTreeNumPixels(tree)
+ TkTextBTree tree; /* Information about tree. */
+{
+ BTree *treePtr = (BTree *) tree;
+ return treePtr->rootPtr->numPixels;
+}
+
+/*
*--------------------------------------------------------------
*
* CharSplitProc --
diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c
index 48f2963..701fe96 100644
--- a/generic/tkTextDisp.c
+++ b/generic/tkTextDisp.c
@@ -3,7 +3,9 @@
*
* This module provides facilities to display text widgets. It is
* the only place where information is kept about the screen layout
- * of text widgets.
+ * of text widgets. (Well, strictly, each TkTextLine caches its
+ * last observed pixel height, but that information is
+ * calculated here).
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
@@ -11,7 +13,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkTextDisp.c,v 1.17 2003/07/07 20:39:50 hobbs Exp $
+ * RCS: @(#) $Id: tkTextDisp.c,v 1.18 2003/10/31 09:02:10 vincentdarley Exp $
*/
#include "tkPort.h"
@@ -119,11 +121,15 @@ typedef struct DLine {
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. -1 means line isn't
- * currently visible on display and must be
- * redrawn. This is used to move lines by
- * scrolling rather than re-drawing. */
+ 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. */
@@ -171,14 +177,20 @@ typedef struct DLine {
* BOTTOM_LINE - Non-zero means that this was the bottom line
* in the window the last time that the window
* was laid out.
- * IS_DISABLED - This Dline cannot be edited.
+ * 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.
*/
#define HAS_3D_BORDER 1
#define NEW_LAYOUT 2
#define TOP_LINE 4
#define BOTTOM_LINE 8
-#define IS_DISABLED 16
+#define OLD_Y_INVALID 16
/*
* Overall display information for a text widget:
@@ -189,6 +201,10 @@ typedef struct TextDInfo {
* 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
@@ -216,11 +232,11 @@ typedef struct TextDInfo {
* Information used for scrolling:
*/
- int newByteOffset; /* Desired x scroll position, measured as the
+ int newXByteOffset; /* Desired x scroll position, measured as the
* number of average-size characters off-screen
* to the left for a line with no left
* margin. */
- int curPixelOffset; /* Actual x scroll position, measured as the
+ 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
@@ -243,7 +259,7 @@ typedef struct TextDInfo {
* left edge of the window when the scan
* started. */
int scanMarkX; /* X-position of mouse at time scan started. */
- int scanTotalScroll; /* Total scrolling (in screen lines) that has
+ int scanTotalYScroll; /* Total scrolling (in screen pixels) that has
* occurred since scanMarkY was set. */
int scanMarkY; /* Y-position of mouse at time scan started. */
@@ -264,6 +280,28 @@ typedef struct TextDInfo {
* could dump core. */
int flags; /* Various flag values: see below for
* definitions. */
+ /*
+ * 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. */
+ 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. */
+ Tcl_TimerToken scrollbarTimer; /* A token pointing to the current
+ * scrollbar update callback. */
} TextDInfo;
/*
@@ -302,6 +340,20 @@ typedef struct CharInfo {
#define REPICK_NEEDED 8
/*
+ * 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'.
+ */
+
+#define DLINE_FREE 0
+#define DLINE_UNLINK 1
+#define DLINE_FREE_TEMP 2
+
+/*
* The following counters keep statistics about redisplay that can be
* checked to see how clever this code is at reducing redisplays.
*/
@@ -352,33 +404,58 @@ static void DisplayText _ANSI_ARGS_((ClientData clientData));
static DLine * FindDLine _ANSI_ARGS_((DLine *dlPtr,
CONST TkTextIndex *indexPtr));
static void FreeDLines _ANSI_ARGS_((TkText *textPtr,
- DLine *firstPtr, DLine *lastPtr, int unlink));
+ DLine *firstPtr, DLine *lastPtr, int action));
static void FreeStyle _ANSI_ARGS_((TkText *textPtr,
TextStyle *stylePtr));
static TextStyle * GetStyle _ANSI_ARGS_((TkText *textPtr,
- TkTextIndex *indexPtr));
+ CONST TkTextIndex *indexPtr));
static void GetXView _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, int report));
static void GetYView _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, int report));
+static int GetPixelCount _ANSI_ARGS_((TkText *textPtr,
+ DLine *dlPtr));
static DLine * LayoutDLine _ANSI_ARGS_((TkText *textPtr,
- TkTextIndex *indexPtr));
+ CONST TkTextIndex *indexPtr));
static int MeasureChars _ANSI_ARGS_((Tk_Font tkfont,
CONST char *source, int maxBytes, int startX,
int maxX, int tabOrigin, int *nextXPtr));
static void MeasureUp _ANSI_ARGS_((TkText *textPtr,
- TkTextIndex *srcPtr, int distance,
- TkTextIndex *dstPtr));
+ CONST TkTextIndex *srcPtr, int distance,
+ TkTextIndex *dstPtr, int *overlap));
static int NextTabStop _ANSI_ARGS_((Tk_Font tkfont, int x,
int tabOrigin));
static void UpdateDisplayInfo _ANSI_ARGS_((TkText *textPtr));
-static void ScrollByLines _ANSI_ARGS_((TkText *textPtr,
+static void YScrollByLines _ANSI_ARGS_((TkText *textPtr,
+ int offset));
+static void YScrollByPixels _ANSI_ARGS_((TkText *textPtr,
int offset));
static int SizeOfTab _ANSI_ARGS_((TkText *textPtr,
TkTextTabArray *tabArrayPtr, int index, int x,
int maxX));
static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr,
TkRegion region));
+static int TextCalculateDisplayLineHeight _ANSI_ARGS_((
+ TkText *textPtr, CONST TkTextIndex *indexPtr,
+ int *byteCountPtr));
+static void DlineIndexOfX _ANSI_ARGS_((TkText *textPtr,
+ DLine *dlPtr, int x, TkTextIndex *indexPtr));
+static int DlineXOfIndex _ANSI_ARGS_((TkText *textPtr,
+ DLine *dlPtr, int byteIndex));
+static int TextGetScrollInfoObj _ANSI_ARGS_((Tcl_Interp *interp,
+ TkText *textPtr, int objc,
+ Tcl_Obj *CONST objv[], double *dblPtr,
+ int *intPtr));
+
+/*
+ * Result values returned by TextGetScrollInfo:
+ */
+
+#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
/*
@@ -412,10 +489,10 @@ TkTextCreateDInfo(textPtr)
dInfoPtr->copyGC = None;
gcValues.graphics_exposures = True;
dInfoPtr->scrollGC = Tk_GetGC(textPtr->tkwin, GCGraphicsExposures,
- &gcValues);
+ &gcValues);
dInfoPtr->topOfEof = 0;
- dInfoPtr->newByteOffset = 0;
- dInfoPtr->curPixelOffset = 0;
+ dInfoPtr->newXByteOffset = 0;
+ dInfoPtr->curXPixelOffset = 0;
dInfoPtr->maxLength = 0;
dInfoPtr->xScrollFirst = -1;
dInfoPtr->xScrollLast = -1;
@@ -423,10 +500,25 @@ TkTextCreateDInfo(textPtr)
dInfoPtr->yScrollLast = -1;
dInfoPtr->scanMarkIndex = 0;
dInfoPtr->scanMarkX = 0;
- dInfoPtr->scanTotalScroll = 0;
+ dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = 0;
dInfoPtr->dLinesInvalidated = 0;
dInfoPtr->flags = DINFO_OUT_OF_DATE;
+ dInfoPtr->topPixelOffset = 0;
+ dInfoPtr->newTopPixelOffset = 0;
+ dInfoPtr->currentMetricUpdateLine = -1;
+ dInfoPtr->lastMetricUpdateLine = -1;
+ dInfoPtr->lineMetricUpdateEpoch = 1;
+
+ /* Add a refCount for each of the idle call-backs */
+ textPtr->refCount++;
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(0,
+ TkTextAsyncUpdateLineMetrics, (ClientData) textPtr);
+ textPtr->refCount++;
+ dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200,
+ TkTextUpdateYScrollbar, (ClientData) textPtr);
+
+
textPtr->dInfoPtr = dInfoPtr;
}
@@ -460,7 +552,7 @@ TkTextFreeDInfo(textPtr)
* all free then styleTable will be empty.
*/
- FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, DLINE_UNLINK);
Tcl_DeleteHashTable(&dInfoPtr->styleTable);
if (dInfoPtr->copyGC != None) {
Tk_FreeGC(textPtr->display, dInfoPtr->copyGC);
@@ -469,6 +561,16 @@ TkTextFreeDInfo(textPtr)
if (dInfoPtr->flags & REDRAW_PENDING) {
Tcl_CancelIdleCall(DisplayText, (ClientData) textPtr);
}
+ if (dInfoPtr->lineUpdateTimer != NULL) {
+ Tcl_DeleteTimerHandler(dInfoPtr->lineUpdateTimer);
+ textPtr->refCount--;
+ dInfoPtr->lineUpdateTimer = NULL;
+ }
+ if (dInfoPtr->scrollbarTimer != NULL) {
+ Tcl_DeleteTimerHandler(dInfoPtr->scrollbarTimer);
+ textPtr->refCount--;
+ dInfoPtr->scrollbarTimer = NULL;
+ }
ckfree((char *) dInfoPtr);
}
@@ -493,7 +595,7 @@ TkTextFreeDInfo(textPtr)
static TextStyle *
GetStyle(textPtr, indexPtr)
TkText *textPtr; /* Overall information about text widget. */
- TkTextIndex *indexPtr; /* The character in the text for which
+ CONST TkTextIndex *indexPtr;/* The character in the text for which
* display information is wanted. */
{
TkTextTag **tagPtrs;
@@ -762,6 +864,22 @@ FreeStyle(textPtr, stylePtr)
*
* Side effects:
* Storage is allocated for the new DLine.
+ *
+ * See the comments in 'GetYView' for some thoughts on what the
+ * side-effects of this call (or its callers) should be -- the
+ * synchronisation of TkTextLine->pixelHeight with the sum of
+ * the results of this function operating on all display lines
+ * within each logical line. Ideally the code should be
+ * refactored to ensure the cached pixel 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.
*
*----------------------------------------------------------------------
*/
@@ -769,7 +887,7 @@ FreeStyle(textPtr, stylePtr)
static DLine *
LayoutDLine(textPtr, indexPtr)
TkText *textPtr; /* Overall information about text widget. */
- TkTextIndex *indexPtr; /* Beginning of display line. May not
+ CONST TkTextIndex *indexPtr;/* Beginning of display line. May not
* necessarily point to a character segment. */
{
register DLine *dlPtr; /* New display line. */
@@ -825,12 +943,12 @@ LayoutDLine(textPtr, indexPtr)
dlPtr->index = *indexPtr;
dlPtr->byteCount = 0;
dlPtr->y = 0;
- dlPtr->oldY = -1;
+ dlPtr->oldY = 0; /* Only setting this to avoid compiler warnings */
dlPtr->height = 0;
dlPtr->baseline = 0;
dlPtr->chunkPtr = NULL;
dlPtr->nextPtr = NULL;
- dlPtr->flags = NEW_LAYOUT;
+ dlPtr->flags = NEW_LAYOUT | OLD_Y_INVALID;
/*
* Special case entirely elide line as there may be 1000s or more
@@ -900,17 +1018,26 @@ LayoutDLine(textPtr, indexPtr)
while (segPtr != NULL) {
/*
- * Every 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
+ * 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. If/when we fix [Tk Bug 443848] (see below)
+ * then we will probably have to remove such zero height DLines
+ * too.
*/
if (elide && (lastChunkPtr != NULL)
&& (lastChunkPtr->displayProc == NULL /*ElideDisplayProc*/)) {
if ((elidesize = segPtr->size - byteOffset) > 0) {
curIndex.byteIndex += elidesize;
lastChunkPtr->numBytes += elidesize;
- breakByteOffset = lastChunkPtr->breakIndex = lastChunkPtr->numBytes;
+ breakByteOffset = lastChunkPtr->breakIndex
+ = lastChunkPtr->numBytes;
/*
* If have we have a tag toggle, there is a chance
* that invisibility state changed, so bail out
@@ -990,22 +1117,27 @@ LayoutDLine(textPtr, indexPtr)
}
chunkPtr->x = x;
if (elide && maxBytes) {
- /* don't free style here, as other code expects to be able to do that */
- /*breakByteOffset =*/ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes;
+ /*
+ * Don't free style here, as other code expects to be able to
+ * do that.
+ */
+ /* breakByteOffset =*/
+ chunkPtr->breakIndex = chunkPtr->numBytes = maxBytes;
chunkPtr->width = 0;
chunkPtr->minAscent = chunkPtr->minDescent = chunkPtr->minHeight = 0;
- /* would just like to point to canonical empty chunk */
+ /* Would just like to point to canonical empty chunk */
chunkPtr->displayProc = (Tk_ChunkDisplayProc *) NULL;
chunkPtr->undisplayProc = (Tk_ChunkUndisplayProc *) NULL;
chunkPtr->measureProc = ElideMeasureProc;
chunkPtr->bboxProc = ElideBboxProc;
code = 1;
- } else
- code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr,
+ } else {
+ code = (*segPtr->typePtr->layoutProc)(textPtr, &curIndex, segPtr,
byteOffset, maxX-tabSize, maxBytes, noCharsYet, wrapMode,
chunkPtr);
+ }
if (code <= 0) {
FreeStyle(textPtr, chunkPtr->stylePtr);
if (code < 0) {
@@ -1029,7 +1161,7 @@ LayoutDLine(textPtr, indexPtr)
}
break;
}
- if (chunkPtr->numBytes > 0) {
+ if (!elide && chunkPtr->numBytes > 0) {
noCharsYet = 0;
lastCharChunkPtr = chunkPtr;
}
@@ -1073,6 +1205,16 @@ LayoutDLine(textPtr, indexPtr)
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. Currently this forces a new line
+ * anyway (i.e. even though the newline is elided it
+ * still takes effect). This is because the code
+ * currently doesn't allow two or more logical lines to
+ * appear on the same display line. [Tk Bug #443848]
+ */
+ }
}
chunkPtr = NULL;
@@ -1240,7 +1382,7 @@ UpdateDisplayInfo(textPtr)
register DLine *dlPtr, *prevPtr;
TkTextIndex index;
TkTextLine *lastLinePtr;
- int y, maxY, pixelOffset, maxOffset;
+ int y, maxY, xPixelOffset, maxOffset, lineHeight;
if (!(dInfoPtr->flags & DINFO_OUT_OF_DATE)) {
return;
@@ -1254,7 +1396,12 @@ UpdateDisplayInfo(textPtr)
index = textPtr->topIndex;
dlPtr = FindDLine(dInfoPtr->dLinePtr, &index);
if ((dlPtr != NULL) && (dlPtr != dInfoPtr->dLinePtr)) {
- FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, 1);
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, dlPtr, DLINE_UNLINK);
+ }
+ if (index.byteIndex == 0) {
+ lineHeight = 0;
+ } else {
+ lineHeight = -1;
}
/*
@@ -1265,10 +1412,10 @@ UpdateDisplayInfo(textPtr)
*/
lastLinePtr = TkBTreeFindLine(textPtr->tree,
- TkBTreeNumLines(textPtr->tree));
+ TkBTreeNumLines(textPtr->tree));
dlPtr = dInfoPtr->dLinePtr;
prevPtr = NULL;
- y = dInfoPtr->y;
+ y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
maxY = dInfoPtr->maxY;
while (1) {
register DLine *newPtr;
@@ -1300,6 +1447,7 @@ UpdateDisplayInfo(textPtr)
*/
if ((dlPtr == NULL) || (dlPtr->index.linePtr != index.linePtr)) {
+
/*
* Case (b) -- must make new DLine.
*/
@@ -1325,7 +1473,7 @@ UpdateDisplayInfo(textPtr)
} else {
prevPtr->nextPtr = newPtr;
if (prevPtr->flags & HAS_3D_BORDER) {
- prevPtr->oldY = -1;
+ prevPtr->flags |= OLD_Y_INVALID;
}
}
newPtr->nextPtr = dlPtr;
@@ -1343,7 +1491,7 @@ UpdateDisplayInfo(textPtr)
if ((dlPtr->flags & HAS_3D_BORDER) && (prevPtr != NULL)
&& (prevPtr->flags & (NEW_LAYOUT))) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
goto lineOK;
}
@@ -1357,7 +1505,7 @@ UpdateDisplayInfo(textPtr)
*/
newPtr = dlPtr->nextPtr;
- FreeDLines(textPtr, dlPtr, newPtr, 0);
+ FreeDLines(textPtr, dlPtr, newPtr, DLINE_FREE);
dlPtr = newPtr;
if (prevPtr != NULL) {
prevPtr->nextPtr = newPtr;
@@ -1374,6 +1522,9 @@ UpdateDisplayInfo(textPtr)
lineOK:
dlPtr->y = y;
y += dlPtr->height;
+ if (lineHeight != -1) {
+ lineHeight += dlPtr->height;
+ }
TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
prevPtr = dlPtr;
dlPtr = dlPtr->nextPtr;
@@ -1392,10 +1543,36 @@ UpdateDisplayInfo(textPtr)
nextPtr = nextPtr->nextPtr;
}
if (nextPtr != dlPtr) {
- FreeDLines(textPtr, dlPtr, nextPtr, 0);
+ FreeDLines(textPtr, dlPtr, nextPtr, DLINE_FREE);
prevPtr->nextPtr = nextPtr;
dlPtr = nextPtr;
}
+
+ if ((lineHeight != -1)
+ && (lineHeight > prevPtr->index.linePtr->pixelHeight)) {
+ /*
+ * The logical line height we just calculated is actually
+ * larger than 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 do not exceed counts made
+ * through the BTree.
+ */
+ TkBTreeAdjustPixelHeight(prevPtr->index.linePtr,
+ lineHeight);
+ /*
+ * 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.
+ */
+ prevPtr->index.linePtr->pixelCalculationEpoch =
+ dInfoPtr->lineMetricUpdateEpoch;
+ }
+ lineHeight = 0;
}
/*
@@ -1414,75 +1591,135 @@ UpdateDisplayInfo(textPtr)
* Delete any DLine structures that don't fit on the screen.
*/
- FreeDLines(textPtr, dlPtr, (DLine *) NULL, 1);
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_UNLINK);
/*
*--------------------------------------------------------------
* If there is extra space at the bottom of the window (because
* we've hit the end of the text), then bring in more lines at
* the top of the window, if there are any, to fill in the view.
+ *
+ * Since the top line may only be partially visible, we try first
+ * to simply show more pixels from that line (newTopPixelOffset).
+ * If that isn't enough, we have to layout more lines.
*--------------------------------------------------------------
*/
if (y < maxY) {
- int lineNum, spaceLeft, bytesToCount;
- DLine *lowestPtr;
-
- /*
- * Layout an entire text line (potentially > 1 display line),
- * then link in as many display lines as fit without moving
- * the bottom line out of the window. Repeat this until
- * all the extra space has been used up or we've reached the
- * beginning of the text.
- */
-
- spaceLeft = maxY - y;
- lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr);
- bytesToCount = dInfoPtr->dLinePtr->index.byteIndex;
- if (bytesToCount == 0) {
- bytesToCount = INT_MAX;
- lineNum--;
- }
- for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) {
- index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
- index.byteIndex = 0;
- lowestPtr = NULL;
-
- do {
- dlPtr = LayoutDLine(textPtr, &index);
- dlPtr->nextPtr = lowestPtr;
- lowestPtr = dlPtr;
- if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; } /* elide */
- TkTextIndexForwBytes(&index, dlPtr->byteCount, &index);
- bytesToCount -= dlPtr->byteCount;
- } while ((bytesToCount > 0)
- && (index.linePtr == lowestPtr->index.linePtr));
+ int spaceLeft = maxY - y;
+
+ if (spaceLeft <= dInfoPtr->newTopPixelOffset) {
+ /*
+ * We can full up all the needed space just by showing
+ * more of the current top line
+ */
+ dInfoPtr->newTopPixelOffset -= spaceLeft;
+ y += spaceLeft;
+ spaceLeft = 0;
+ } else {
+ int lineNum, bytesToCount;
+ DLine *lowestPtr;
+
+ /*
+ * 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;
/*
- * Scan through the display lines from the bottom one up to
- * the top one.
+ * Layout an entire text line (potentially > 1 display line),
+ * then link in as many display lines as fit without moving
+ * the bottom line out of the window. Repeat this until
+ * all the extra space has been used up or we've reached the
+ * beginning of the text.
*/
- while (lowestPtr != NULL) {
- dlPtr = lowestPtr;
- spaceLeft -= dlPtr->height;
- if (spaceLeft < 0) {
- break;
+ spaceLeft = maxY - y;
+ lineNum = TkBTreeLineIndex(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->tree, 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(&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 > lowestPtr->index.linePtr->pixelHeight) {
+ TkBTreeAdjustPixelHeight(lowestPtr->index.linePtr,
+ pixelHeight);
+ if (index.linePtr != lowestPtr->index.linePtr) {
+ /*
+ * We examined the entire line, so can update
+ * the epoch.
+ */
+ lowestPtr->index.linePtr->pixelCalculationEpoch =
+ dInfoPtr->lineMetricUpdateEpoch;
+ }
}
- lowestPtr = dlPtr->nextPtr;
- dlPtr->nextPtr = dInfoPtr->dLinePtr;
- dInfoPtr->dLinePtr = dlPtr;
- if (tkTextDebug) {
- char string[TK_POS_CHARS];
- TkTextPrintIndex(&dlPtr->index, string);
- Tcl_SetVar2(textPtr->interp, "tk_textRelayout",
+ /*
+ * 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(&dlPtr->index, string);
+ Tcl_SetVar2(textPtr->interp, "tk_textRelayout",
(char *) NULL, string,
TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT);
+ }
+ if (spaceLeft <= 0) {
+ break;
+ }
+ }
+ FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE);
+ bytesToCount = INT_MAX;
+ }
+ /*
+ * We've filled in the space we wanted to, and we
+ * need to store any extra overlap we've just
+ * created for the top line.
+ */
+ if (lineNum >= 0) {
+ dInfoPtr->newTopPixelOffset = -spaceLeft;
+ if (spaceLeft > 0
+ || dInfoPtr->newTopPixelOffset >= dInfoPtr->dLinePtr->height) {
+ /* Bad situation */
+ panic("Pixel height problem while laying out text widget");
}
}
- FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
- bytesToCount = INT_MAX;
}
/*
@@ -1492,7 +1729,7 @@ UpdateDisplayInfo(textPtr)
*/
textPtr->topIndex = dInfoPtr->dLinePtr->index;
- y = dInfoPtr->y;
+ y = dInfoPtr->y - dInfoPtr->newTopPixelOffset;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
if (y > dInfoPtr->maxY) {
@@ -1517,21 +1754,30 @@ UpdateDisplayInfo(textPtr)
dlPtr = dInfoPtr->dLinePtr;
if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
while (1) {
if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr)
&& (dlPtr->flags & HAS_3D_BORDER)) {
- dlPtr->oldY = -1;
+ 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 ((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->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
if (dlPtr->nextPtr == NULL) {
if ((dlPtr->flags & HAS_3D_BORDER)
&& !(dlPtr->flags & BOTTOM_LINE)) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
dlPtr->flags &= ~TOP_LINE;
dlPtr->flags |= BOTTOM_LINE;
@@ -1541,6 +1787,7 @@ UpdateDisplayInfo(textPtr)
dlPtr = dlPtr->nextPtr;
}
dInfoPtr->dLinePtr->flags |= TOP_LINE;
+ dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset;
/*
* Arrange for scrollbars to be updated.
@@ -1569,18 +1816,18 @@ UpdateDisplayInfo(textPtr)
}
maxOffset = (dInfoPtr->maxLength - (dInfoPtr->maxX - dInfoPtr->x)
+ textPtr->charWidth - 1)/textPtr->charWidth;
- if (dInfoPtr->newByteOffset > maxOffset) {
- dInfoPtr->newByteOffset = maxOffset;
+ if (dInfoPtr->newXByteOffset > maxOffset) {
+ dInfoPtr->newXByteOffset = maxOffset;
}
- if (dInfoPtr->newByteOffset < 0) {
- dInfoPtr->newByteOffset = 0;
+ if (dInfoPtr->newXByteOffset < 0) {
+ dInfoPtr->newXByteOffset = 0;
}
- pixelOffset = dInfoPtr->newByteOffset * textPtr->charWidth;
- if (pixelOffset != dInfoPtr->curPixelOffset) {
- dInfoPtr->curPixelOffset = pixelOffset;
+ xPixelOffset = dInfoPtr->newXByteOffset * textPtr->charWidth;
+ if (xPixelOffset != dInfoPtr->curXPixelOffset) {
+ dInfoPtr->curXPixelOffset = xPixelOffset;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
- dlPtr->oldY = -1;
+ dlPtr->flags |= OLD_Y_INVALID;
}
}
}
@@ -1603,23 +1850,29 @@ UpdateDisplayInfo(textPtr)
*/
static void
-FreeDLines(textPtr, firstPtr, lastPtr, unlink)
- 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 unlink; /* 1 means DLines are currently linked
- * into the list rooted at
- * textPtr->dInfoPtr->dLinePtr and
- * they have to be unlinked. 0 means
- * just free without unlinking. */
+FreeDLines(textPtr, firstPtr, lastPtr, action)
+ 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 (unlink) {
+ if (action == DLINE_UNLINK) {
if (textPtr->dInfoPtr->dLinePtr == firstPtr) {
textPtr->dInfoPtr->dLinePtr = lastPtr;
} else {
@@ -1645,7 +1898,9 @@ FreeDLines(textPtr, firstPtr, lastPtr, unlink)
ckfree((char *) firstPtr);
firstPtr = nextDLinePtr;
}
- textPtr->dInfoPtr->dLinesInvalidated = 1;
+ if (action != DLINE_FREE_TEMP) {
+ textPtr->dInfoPtr->dLinesInvalidated = 1;
+ }
}
/*
@@ -1680,7 +1935,7 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
register TkTextDispChunk *chunkPtr;
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
Display *display;
- int height, x;
+ int height, y_off;
if (dlPtr->chunkPtr == NULL) return;
@@ -1710,8 +1965,8 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
if (textPtr->state == TK_TEXT_STATE_NORMAL) {
for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
chunkPtr = chunkPtr->nextPtr) {
- x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
if (chunkPtr->displayProc == TkTextInsertDisplayProc) {
+ int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
(*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
@@ -1725,7 +1980,6 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
* 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.
- * Conve
*/
for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
@@ -1738,54 +1992,59 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap)
continue;
}
- x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curPixelOffset;
- 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 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).
- */
+ /*
+ * Don't call if elide. This tax ok since not very many
+ * visible DLine's in an area, but potentially many elide
+ * ones
+ */
+ if (chunkPtr->displayProc != NULL) {
+ int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
- if (chunkPtr->displayProc != NULL)
- (*chunkPtr->displayProc)(chunkPtr, -chunkPtr->width,
- dlPtr->spaceAbove,
- dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
- dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
- dlPtr->y + dlPtr->spaceAbove);
- } else {
- /* don't call if elide. This tax ok since not very many visible DLine's in
- an area, but potentially many elide ones */
- if (chunkPtr->displayProc != NULL)
+ 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 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)(chunkPtr, x, dlPtr->spaceAbove,
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, display, pixmap,
dlPtr->y + dlPtr->spaceAbove);
}
+
if (dInfoPtr->dLinesInvalidated) {
return;
}
}
/*
- * Copy the pixmap onto the screen. If this is the 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.
+ * 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.
*/
height = dlPtr->height;
if ((height + dlPtr->y) > dInfoPtr->maxY) {
height = dInfoPtr->maxY - dlPtr->y;
}
+ if (dlPtr->y < dInfoPtr->y) {
+ y_off = dInfoPtr->y - dlPtr->y;
+ height -= y_off;
+ } else {
+ y_off = 0;
+ }
XCopyArea(display, pixmap, Tk_WindowId(textPtr->tkwin), dInfoPtr->copyGC,
- dInfoPtr->x, 0, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
- (unsigned) height, dInfoPtr->x, dlPtr->y);
+ dInfoPtr->x, y_off, (unsigned) (dInfoPtr->maxX - dInfoPtr->x),
+ (unsigned) height, dInfoPtr->x, dlPtr->y + y_off);
linesRedrawn++;
}
@@ -1857,7 +2116,7 @@ DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)
*/
display = Tk_Display(textPtr->tkwin);
- minX = dInfoPtr->curPixelOffset;
+ minX = dInfoPtr->curXPixelOffset;
xOffset = dInfoPtr->x - minX;
maxX = minX + dInfoPtr->maxX - dInfoPtr->x;
chunkPtr = dlPtr->chunkPtr;
@@ -2158,6 +2417,692 @@ DisplayLineBackground(textPtr, dlPtr, prevPtr, pixmap)
/*
*----------------------------------------------------------------------
*
+ * TkTextAsyncUpdateLineMetrics --
+ *
+ * This procedure is invoked as a background handler to update the
+ * pixel-height calculations of individual lines in an
+ * asychronous manner.
+ *
+ * Currently a timer-handler is used for this purpose, which
+ * continuously reschedules itself. It may well be better to
+ * use some other approach (e.g. a background thread). We can't
+ * use an idle-callback because of a known bug in Tcl/Tk in
+ * which idle callbacks are not allowed to re-schedule
+ * themselves. This just causes an effective infinite loop.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Line heights may be recalculated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextAsyncUpdateLineMetrics(clientData)
+ ClientData clientData; /* Information about widget. */
+{
+ register TkText *textPtr = (TkText *) clientData;
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ int lineNum;
+
+ dInfoPtr->lineUpdateTimer = NULL;
+
+ if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
+
+ /*
+ * The widget has been deleted. Don't do anything.
+ */
+
+ if (--textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ }
+ return;
+ }
+
+ lineNum = dInfoPtr->currentMetricUpdateLine;
+ if (lineNum == -1) {
+ dInfoPtr->lastMetricUpdateLine = 0;
+ }
+ /*
+ * 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);
+ if (lineNum == dInfoPtr->lastMetricUpdateLine) {
+ /*
+ * 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).
+ */
+ textPtr->refCount--;
+ if (textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ }
+ return;
+ }
+ dInfoPtr->currentMetricUpdateLine = lineNum;
+ /*
+ * Re-arm the timer. We already have a refCount on the text widget
+ * so no need to adjust that.
+ */
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
+ TkTextAsyncUpdateLineMetrics, (ClientData) textPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUpdateLineMetrics --
+ *
+ * This procedure 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 procedure
+ * 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.
+ *
+ * Note: with bad input for lineNum and endLine, this procedure can
+ * loop indefinitely.
+ *
+ * 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).
+ *
+ * Side effects:
+ * Line heights may be recalculated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch)
+ 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->tree);
+
+ while (1) {
+ /* Get a suitable line */
+ if (lineNum == -1 && linePtr == NULL) {
+ lineNum = 0;
+ linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
+ } else {
+ if (lineNum == -1 || linePtr == NULL) {
+ if (lineNum == -1) {
+ lineNum = 0;
+ }
+ linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
+ } else {
+ lineNum++;
+ linePtr = TkBTreeNextLine(linePtr);
+ }
+ if (lineNum == endLine) {
+ /*
+ * We have looped over all lines, so we're done.
+ */
+ break;
+ }
+ }
+
+ if (lineNum < totalLines) {
+ /* Now update the line's metrics if necessary */
+ if (linePtr->pixelCalculationEpoch
+ != textPtr->dInfoPtr->lineMetricUpdateEpoch) {
+ /*
+ * Update the line and update the counter, counting
+ * 10 for each line we actually re-layout.
+ */
+ TkTextUpdateOneLine(textPtr, linePtr);
+ count += 10;
+ }
+ } 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) {
+ break;
+ }
+ /* Set things up for the next loop through */
+ lineNum = -1;
+ }
+ count++;
+
+ if (doThisMuch != -1 && count >= doThisMuch) {
+ break;
+ }
+ }
+ if (doThisMuch == -1) {
+ /*
+ * If we were requested to provide a full update,
+ * then also update the scrollbar.
+ */
+ GetYView(textPtr->interp, textPtr, 1);
+ }
+ return lineNum;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextInvalidateLineMetrics --
+ *
+ * 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.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May schedule an asychronous callback.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextInvalidateLineMetrics(textPtr, linePtr, lineCount, action)
+ 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;
+
+ fromLine = TkBTreeLineIndex(linePtr);
+
+ /*
+ * Invalid the height calculations of each line in the
+ * given range.
+ */
+ linePtr->pixelCalculationEpoch = 0;
+ while (counter > 0 && linePtr != 0) {
+ linePtr = TkBTreeNextLine(linePtr);
+ if (linePtr != NULL) {
+ linePtr->pixelCalculationEpoch = 0;
+ }
+ counter--;
+ }
+ /*
+ * 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 that
+ * what we have.
+ */
+ 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;
+
+ 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;
+ }
+ }
+ } 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;
+ }
+ }
+ } else {
+ if (fromLine < dInfoPtr->currentMetricUpdateLine) {
+ dInfoPtr->currentMetricUpdateLine = fromLine;
+ }
+ if (dInfoPtr->lastMetricUpdateLine != -1
+ && toLine > dInfoPtr->lastMetricUpdateLine) {
+ dInfoPtr->lastMetricUpdateLine = toLine;
+ }
+ }
+ }
+ } else {
+ /*
+ * This invalidates the height of all lines in the widget.
+ */
+ if ((++dInfoPtr->lineMetricUpdateEpoch) == 0) {
+ dInfoPtr->lineMetricUpdateEpoch++;
+ }
+ /*
+ * This has the effect of forcing an entire new loop
+ * of update checks on all lines in the widget.
+ */
+ if (dInfoPtr->lineUpdateTimer == NULL) {
+ dInfoPtr->currentMetricUpdateLine = -1;
+ }
+ dInfoPtr->lastMetricUpdateLine = dInfoPtr->currentMetricUpdateLine;
+ }
+
+ /*
+ * Now re-set the current update calculations
+ */
+ if (dInfoPtr->lineUpdateTimer == NULL) {
+ textPtr->refCount++;
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
+ TkTextAsyncUpdateLineMetrics, (ClientData) textPtr);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextFindDisplayLineEnd --
+ *
+ * This procedure 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 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 first elided index on the display line.
+ *
+ * Similarly if the end of the current display line is elided
+ * and we are looking for the end, then the returned index will
+ * be the last elided index on the display line. (NB. This also
+ * highlights a current bug in the text widget that we cannot
+ * place two logical lines on a single display line -- even
+ * though the newline in this case is elided, it still causes
+ * a line break to be shown).
+ *
+ * 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!
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset)
+ 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. */
+{
+ if (!end && indexPtr->byteIndex == 0) {
+ /* Nothing to do */
+ if (xOffset != NULL) {
+ *xOffset = 0;
+ }
+ return;
+ } else {
+ TkTextIndex index = *indexPtr;
+ index.byteIndex = 0;
+ index.textPtr = NULL;
+
+ while (1) {
+ DLine *dlPtr;
+ int byteCount;
+
+ dlPtr = LayoutDLine(textPtr, &index);
+ byteCount = dlPtr->byteCount;
+
+ /*
+ * 'byteCount' goes up to the beginning of the next line,
+ * so equality here says we need one more line
+ */
+ if (index.byteIndex + byteCount > indexPtr->byteIndex) {
+ /* 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,
+ indexPtr->byteIndex - dlPtr->index.byteIndex);
+ }
+ indexPtr->byteIndex = index.byteIndex;
+ if (end) {
+ /*
+ * The index we want is one less than the number
+ * of bytes in the display line.
+ */
+ indexPtr->byteIndex += byteCount - sizeof(char);
+ }
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+ return;
+ }
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+ TkTextIndexForwBytes(&index, byteCount, &index);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextCalculateDisplayLineHeight --
+ *
+ * This procedure is invoked to recalculate the height of the
+ * particular display line which starts with the given index,
+ * whether that line is displayed or not.
+ *
+ * This procedure does not, in itself, update any cached
+ * information about line heights. That should be done, where
+ * necessary, by its callers.
+ *
+ * The behaviour of this procedure is _undefined_ if indexPtr
+ * is not currently at the beginning of a display line.
+ *
+ * Results:
+ * The number of vertical pixels used by the display line.
+ *
+ * If 'byteCountPtr' is non-NULL, then returns in that pointer
+ * the number of byte indices on the given display line (which
+ * can be used to update indexPtr in a loop).
+ *
+ * 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!
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TextCalculateDisplayLineHeight(textPtr, indexPtr, byteCountPtr)
+ 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. */
+{
+ DLine *dlPtr;
+ int pixelHeight;
+
+ /*
+ * 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!
+ */
+ dlPtr = LayoutDLine(textPtr, indexPtr);
+ pixelHeight = dlPtr->height;
+ if (byteCountPtr != NULL) {
+ *byteCountPtr = dlPtr->byteCount;
+ }
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+
+ return pixelHeight;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextIndexYPixels --
+ *
+ * This procedure 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:
+ * Just those of 'TextCalculateDisplayLineHeight'.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextIndexYPixels(textPtr, indexPtr)
+ 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 pixelHeight;
+ TkTextIndex index;
+
+ pixelHeight = TkBTreePixels(indexPtr->linePtr);
+
+ /*
+ * Iterate through all display-lines corresponding to the single
+ * logical line belonging to indexPtr, adding up the pixel height of
+ * each such display line as we go along, until we go past
+ * 'indexPtr'.
+ */
+
+ if (indexPtr->byteIndex == 0) {
+ return pixelHeight;
+ }
+
+ index.tree = textPtr->tree;
+ index.linePtr = indexPtr->linePtr;
+ index.byteIndex = 0;
+ index.textPtr = NULL;
+
+ while (1) {
+ int bytes, height;
+
+ /*
+ * 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.
+ */
+ height = TextCalculateDisplayLineHeight(textPtr, &index, &bytes);
+
+ index.byteIndex += bytes;
+
+ if (index.byteIndex > indexPtr->byteIndex) {
+ return pixelHeight;
+ }
+
+ if (height > 0) {
+ pixelHeight += height;
+ }
+
+ if (index.byteIndex == indexPtr->byteIndex) {
+ return pixelHeight;
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUpdateOneLine --
+ *
+ * This procedure is invoked to recalculate the height of a
+ * particular logical line, whether that line is displayed or not.
+ *
+ * It must NEVER be called for the artificial last TkTextLine
+ * which is used internally for administrative purposes only.
+ * That line must retain its initial height of 0 otherwise
+ * the pixel height calculation maintained by the B-tree will
+ * be wrong.
+ *
+ * Results:
+ * The number of display lines in the logical line. This could
+ * be zero if the line is totally elided.
+ *
+ * Side effects:
+ * Line heights may be recalculated, and a timer to update
+ * the scrollbar may be installed. Also see the called
+ * function 'TextCalculateDisplayLineHeight' for its side
+ * effects.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TkTextUpdateOneLine(textPtr, linePtr)
+ TkText *textPtr; /* Widget record for text widget. */
+ TkTextLine *linePtr; /* The line of which to calculate the
+ * height. */
+{
+ TkTextIndex index;
+ int pixelHeight, displayLines;
+
+ index.tree = textPtr->tree;
+ index.linePtr = linePtr;
+ index.byteIndex = 0;
+ index.textPtr = NULL;
+
+ /*
+ * Iterate through all display-lines corresponding to the
+ * single logical line 'linePtr', adding up the pixel height
+ * of each such display line as we go along. The final
+ * total is, therefore, the height of the logical line.
+ */
+
+ pixelHeight = 0;
+ displayLines = 0;
+
+ while (1) {
+ int bytes, height;
+
+ /*
+ * 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.
+ */
+ height = TextCalculateDisplayLineHeight(textPtr, &index, &bytes);
+
+ if (height > 0) {
+ pixelHeight += height;
+ displayLines++;
+ }
+
+ if (TkTextIndexForwBytes(&index, bytes, &index)) {
+ break;
+ }
+
+ if (index.linePtr != linePtr) {
+ break;
+ }
+ }
+
+ /*
+ * Mark the logical line as being up to date (caution: it isn't
+ * yet up to date, that will happen in TkBTreeAdjustPixelHeight
+ * just below).
+ */
+ linePtr->pixelCalculationEpoch = textPtr->dInfoPtr->lineMetricUpdateEpoch;
+
+ if (linePtr->pixelHeight == pixelHeight) {
+ return displayLines;
+ }
+
+ /*
+ * We now use the resulting 'pixelHeight' to refer to the
+ * height of the entire widget, which may be used just below
+ * for reporting/debugging purposes
+ */
+ pixelHeight = TkBTreeAdjustPixelHeight(linePtr, pixelHeight);
+
+ if (tkTextDebug) {
+ char buffer[TCL_INTEGER_SPACE + 1];
+
+ if (TkBTreeNextLine(linePtr) == NULL) {
+ panic("Mustn't ever update line height of last artificial line");
+ }
+
+ sprintf(buffer, "%d", pixelHeight);
+ Tcl_SetVar2(textPtr->interp, "tk_textNumPixels", (char *) NULL,
+ buffer, TCL_GLOBAL_ONLY);
+ }
+ if (textPtr->dInfoPtr->scrollbarTimer == NULL) {
+ textPtr->refCount++;
+ textPtr->dInfoPtr->scrollbarTimer = Tcl_CreateTimerHandler(200,
+ TkTextUpdateYScrollbar, (ClientData) textPtr);
+ }
+ return displayLines;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* DisplayText --
*
* This procedure is invoked as a when-idle handler to update the
@@ -2243,14 +3188,16 @@ DisplayText(clientData)
*/
while (dInfoPtr->flags & REPICK_NEEDED) {
- Tcl_Preserve((ClientData) textPtr);
+ textPtr->refCount++;
dInfoPtr->flags &= ~REPICK_NEEDED;
TkTextPickCurrent(textPtr, &textPtr->pickEvent);
+ if (--textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ goto end;
+ }
if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
- Tcl_Release((ClientData) textPtr);
goto end;
}
- Tcl_Release((ClientData) textPtr);
}
/*
@@ -2262,7 +3209,10 @@ DisplayText(clientData)
/*
* See if it's possible to bring some parts of the screen up-to-date
- * by scrolling (copying from other parts of the screen).
+ * by scrolling (copying from other parts of the screen). We have
+ * to be particularly careful with the top and bottom lines of the
+ * display, since these may only be partially visible and therefore
+ * not helpful for some scrolling purposes.
*/
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
@@ -2270,8 +3220,30 @@ DisplayText(clientData)
int offset, height, y, oldY;
TkRegion damageRgn;
- if ((dlPtr->oldY == -1) || (dlPtr->y == dlPtr->oldY)
- || ((dlPtr->oldY + dlPtr->height) > dInfoPtr->maxY)) {
+ /*
+ * These tests are, in order:
+ *
+ * 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
+ *
+ * If any of these tests are true, then we can't scroll this
+ * line's part of the display.
+ *
+ * Note that even if tests 3 or 4 aren't true, we may be
+ * able to scroll the line, but we still need to be sure
+ * to call embedded window display procs on top and bottom
+ * lines if they have any portion non-visible (see below).
+ */
+ 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))) {
continue;
}
@@ -2286,7 +3258,7 @@ DisplayText(clientData)
y = dlPtr->y;
for (dlPtr2 = dlPtr->nextPtr; dlPtr2 != NULL;
dlPtr2 = dlPtr2->nextPtr) {
- if ((dlPtr2->oldY == -1)
+ if ((dlPtr2->flags & OLD_Y_INVALID)
|| ((dlPtr2->oldY + offset) != dlPtr2->y)
|| ((dlPtr2->oldY + dlPtr2->height) > dInfoPtr->maxY)) {
break;
@@ -2298,18 +3270,29 @@ DisplayText(clientData)
* Reduce the height of the area being copied if necessary to
* avoid overwriting the border area.
*/
-
if ((y + height) > dInfoPtr->maxY) {
height = dInfoPtr->maxY -y;
}
oldY = dlPtr->oldY;
-
+ if (y < dInfoPtr->y) {
+ /*
+ * Adjust if the area being copied is going to overwrite
+ * the top border of the window (so the top line is only
+ * half onscreen).
+ */
+ int y_off = dInfoPtr->y - dlPtr->y;
+ height -= y_off;
+ oldY += y_off;
+ y = dInfoPtr->y;
+ }
+
/*
* Update the lines we are going to scroll to show that they
* have been copied.
*/
while (1) {
+ /* The DLine already has OLD_Y_INVALID cleared */
dlPtr->oldY = dlPtr->y;
if (dlPtr->nextPtr == dlPtr2) {
break;
@@ -2324,10 +3307,10 @@ DisplayText(clientData)
*/
for ( ; dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
- if ((dlPtr2->oldY != -1)
+ if ((!(dlPtr2->flags & OLD_Y_INVALID))
&& ((dlPtr2->oldY + dlPtr2->height) > y)
&& (dlPtr2->oldY < (y + height))) {
- dlPtr2->oldY = -1;
+ dlPtr2->flags |= OLD_Y_INVALID;
}
}
@@ -2439,14 +3422,23 @@ DisplayText(clientData)
maxHeight = -1;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
- if ((dlPtr->height > maxHeight) && (dlPtr->oldY != dlPtr->y)) {
+ if ((dlPtr->height > maxHeight)
+ && ((dlPtr->flags & OLD_Y_INVALID) || (dlPtr->oldY != dlPtr->y))) {
maxHeight = dlPtr->height;
}
bottomY = dlPtr->y + dlPtr->height;
}
- if (maxHeight > dInfoPtr->maxY) {
- maxHeight = dInfoPtr->maxY;
+ /*
+ * There used to be a line here which restricted 'maxHeight' to be no
+ * larger than 'dInfoPtr->maxY', but this is incorrect for the case
+ * where individual lines may be taller than the widget _and_ we have
+ * smooth scrolling. What we can do is restrict maxHeight to be
+ * no larger than 'dInfoPtr->maxY + dInfoPtr->topPixelOffset'.
+ */
+ if (maxHeight > (dInfoPtr->maxY + dInfoPtr->topPixelOffset)) {
+ maxHeight = (dInfoPtr->maxY + dInfoPtr->topPixelOffset);
}
+
if (maxHeight > 0) {
pixmap = Tk_GetPixmap(Tk_Display(textPtr->tkwin),
Tk_WindowId(textPtr->tkwin), Tk_Width(textPtr->tkwin),
@@ -2455,7 +3447,7 @@ DisplayText(clientData)
(dlPtr != NULL) && (dlPtr->y < dInfoPtr->maxY);
prevPtr = dlPtr, dlPtr = dlPtr->nextPtr) {
if (dlPtr->chunkPtr == NULL) continue;
- if (dlPtr->oldY != dlPtr->y) {
+ if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) {
if (tkTextDebug) {
char string[TK_POS_CHARS];
TkTextPrintIndex(&dlPtr->index, string);
@@ -2469,9 +3461,53 @@ DisplayText(clientData)
return;
}
dlPtr->oldY = dlPtr->y;
- dlPtr->flags &= ~NEW_LAYOUT;
+ dlPtr->flags &= ~(NEW_LAYOUT | OLD_Y_INVALID);
+ } else if (dlPtr->chunkPtr != NULL && ((dlPtr->y < 0)
+ || (dlPtr->y + dlPtr->height > dInfoPtr->maxY))) {
+ register TkTextDispChunk *chunkPtr;
+
+ /*
+ * It's the first or last DLine which are also
+ * overlapping the top or bottom of the window, but we
+ * decided above it wasn't necessary to display them (we
+ * were able to update them by scrolling). This is fine,
+ * except that if the lines contain any embedded windows,
+ * we must still call the display proc on them because
+ * they might need to be unmapped or they might need to
+ * be moved to reflect their new position. Otherwise,
+ * everything else moves, but the embedded window
+ * doesn't!
+ *
+ * So, we loop through all the chunks, calling the
+ * display proc of embedded windows only.
+ */
+ for (chunkPtr = dlPtr->chunkPtr; (chunkPtr != NULL);
+ chunkPtr = chunkPtr->nextPtr) {
+ int x;
+ if (chunkPtr->displayProc != TkTextEmbWinDisplayProc) {
+ continue;
+ }
+ x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset;
+ 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 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;
+ }
+ TkTextEmbWinDisplayProc(chunkPtr, x, dlPtr->spaceAbove,
+ dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
+ dlPtr->baseline - dlPtr->spaceAbove, (Display *) NULL,
+ (Drawable) None, dlPtr->y + dlPtr->spaceAbove);
+ }
+
}
- /*prevPtr = dlPtr;*/
}
Tk_FreePixmap(Tk_Display(textPtr->tkwin), pixmap);
}
@@ -2658,9 +3694,11 @@ TextInvalidateRegion(textPtr, region)
maxY = rect.y + rect.height;
for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL;
dlPtr = dlPtr->nextPtr) {
- if ((dlPtr->oldY != -1) && (TkRectInRegion(region, rect.x, dlPtr->y,
- rect.width, (unsigned int) dlPtr->height) != RectangleOut)) {
- dlPtr->oldY = -1;
+ if ((!(dlPtr->flags & OLD_Y_INVALID))
+ && (TkRectInRegion(region, rect.x, dlPtr->y,
+ rect.width, (unsigned int) dlPtr->height)
+ != RectangleOut)) {
+ dlPtr->flags |= OLD_Y_INVALID;
}
}
if (dInfoPtr->topOfEof < maxY) {
@@ -2693,6 +3731,10 @@ TextInvalidateRegion(textPtr, region)
* changed). This procedure must be called *before* a change is
* made, so that indexes in the display information are still
* valid.
+ *
+ * Note: if the range of indices may change geometry as well
+ * as simply requiring redisplay, then the caller should also
+ * call TkTextInvalidateLineMetrics.
*
* Results:
* None.
@@ -2707,10 +3749,11 @@ TextInvalidateRegion(textPtr, region)
void
TkTextChanged(textPtr, index1Ptr, index2Ptr)
- TkText *textPtr; /* Widget record for text widget. */
- TkTextIndex *index1Ptr; /* Index of first character to redisplay. */
- TkTextIndex *index2Ptr; /* Index of character just after last one
- * to redisplay. */
+ TkText *textPtr; /* Widget record for text widget. */
+ 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;
@@ -2763,7 +3806,7 @@ TkTextChanged(textPtr, index1Ptr, index2Ptr)
* Delete all the DLines from firstPtr up to but not including lastPtr.
*/
- FreeDLines(textPtr, firstPtr, lastPtr, 1);
+ FreeDLines(textPtr, firstPtr, lastPtr, DLINE_UNLINK);
}
/*
@@ -2806,6 +3849,31 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
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;
+ if (index1Ptr == NULL) {
+ startLine = NULL;
+ } else {
+ startLine = index1Ptr->linePtr;
+ }
+ if (index2Ptr == NULL) {
+ endLine = NULL;
+ } else {
+ endLine = index2Ptr->linePtr;
+ }
+ TkTextInvalidateLineMetrics(textPtr, startLine, endLine - startLine,
+ TK_TEXT_INVALIDATE_ONLY);
+ }
+
/*
* 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).
@@ -2912,7 +3980,7 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
* be re-layed out and redrawn.
*/
- FreeDLines(textPtr, dlPtr, endPtr, 1);
+ FreeDLines(textPtr, dlPtr, endPtr, DLINE_UNLINK);
dlPtr = endPtr;
/*
@@ -2947,8 +4015,10 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag)
*/
void
-TkTextRelayoutWindow(textPtr)
+TkTextRelayoutWindow(textPtr, mask)
TkText *textPtr; /* Widget record for text widget. */
+ int mask; /* OR'd collection of bits showing what
+ * has changed */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
GC new;
@@ -2981,7 +4051,7 @@ TkTextRelayoutWindow(textPtr)
* Throw away all the current layout information.
*/
- FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, 1);
+ FreeDLines(textPtr, dInfoPtr->dLinePtr, (DLine *) NULL, DLINE_UNLINK);
dInfoPtr->dLinePtr = NULL;
/*
@@ -3016,7 +4086,7 @@ TkTextRelayoutWindow(textPtr)
*/
if (textPtr->topIndex.byteIndex != 0) {
- MeasureUp(textPtr, &textPtr->topIndex, 0, &textPtr->topIndex);
+ TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
}
/*
@@ -3026,6 +4096,25 @@ TkTextRelayoutWindow(textPtr)
dInfoPtr->xScrollFirst = dInfoPtr->xScrollLast = -1;
dInfoPtr->yScrollFirst = dInfoPtr->yScrollLast = -1;
+
+ if (mask & TK_TEXT_LINE_GEOMETRY) {
+ /*
+ * 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->currentMetricUpdateLine = -1;
+
+ if (dInfoPtr->lineUpdateTimer == NULL) {
+ textPtr->refCount++;
+ dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1,
+ TkTextAsyncUpdateLineMetrics, (ClientData) textPtr);
+ }
+ }
}
/*
@@ -3052,17 +4141,27 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
TkText *textPtr; /* Widget record for text widget. */
TkTextIndex *indexPtr; /* Position that is to appear somewhere
* in the view. */
- int pickPlace; /* 0 means topLine must appear at top of
- * screen. 1 means we get to pick where it
- * appears: minimize screen motion or else
- * display line at center of screen. */
+ 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;
- Tk_FontMetrics fm;
-
+ int lineHeight;
+
/*
* If the specified position is the extra line at the end of the
* text, round it back to the last real line.
@@ -3070,11 +4169,20 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
if (lineIndex == TkBTreeNumLines(indexPtr->tree)) {
- TkTextIndexBackChars(indexPtr, 1, &rounded);
+ TkTextIndexBackChars(indexPtr, 1, &rounded, COUNT_INDICES);
indexPtr = &rounded;
}
- if (!pickPlace) {
+ if (pickPlace == -2) {
+ if (textPtr->topIndex.linePtr == indexPtr->linePtr
+ && textPtr->topIndex.byteIndex == indexPtr->byteIndex) {
+ pickPlace = dInfoPtr->topPixelOffset;
+ } else {
+ pickPlace = 0;
+ }
+ }
+
+ if (pickPlace != -1) {
/*
* The specified position must go at the top of the screen.
* Just leave all the DLine's alone: we may be able to reuse
@@ -3082,11 +4190,11 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
* without redisplaying it all.
*/
- if (indexPtr->byteIndex == 0) {
- textPtr->topIndex = *indexPtr;
- } else {
- MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
+ textPtr->topIndex = *indexPtr;
+ if (indexPtr->byteIndex != 0) {
+ TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
}
+ dInfoPtr->newTopPixelOffset = pickPlace;
goto scheduleUpdate;
}
@@ -3111,6 +4219,16 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
dlPtr = NULL;
} else if ((dlPtr->index.linePtr == indexPtr->linePtr)
&& (dlPtr->index.byteIndex <= indexPtr->byteIndex)) {
+ if (dInfoPtr->dLinePtr == dlPtr
+ && dInfoPtr->topPixelOffset != 0) {
+ /*
+ * It is on the top line, but that line is hanging
+ * off the top of the screen. Change the top
+ * overlap to zero and update.
+ */
+ dInfoPtr->newTopPixelOffset = 0;
+ goto scheduleUpdate;
+ }
return;
}
}
@@ -3119,37 +4237,50 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
* The desired line isn't already on-screen. Figure out what
* it means to be "close" to the top or bottom of the screen.
* Close means within 1/3 of the screen height or within three
- * lines, whichever is greater. Add one extra line also, to
- * account for the way MeasureUp rounds.
+ * lines, whichever is greater.
+ *
+ * If the line is not close, place it in the center of the
+ * window.
*/
- Tk_GetFontMetrics(textPtr->tkfont, &fm);
- bottomY = (dInfoPtr->y + dInfoPtr->maxY + fm.linespace)/2;
+ lineHeight = TextCalculateDisplayLineHeight(textPtr, indexPtr, NULL);
+ /*
+ * It would be better if 'bottomY' were calculated using the
+ * actual height of the given line, not 'textPtr->charHeight'.
+ */
+ bottomY = (dInfoPtr->y + dInfoPtr->maxY + lineHeight)/2;
close = (dInfoPtr->maxY - dInfoPtr->y)/3;
- if (close < 3*fm.linespace) {
- close = 3*fm.linespace;
+ if (close < 3*textPtr->charHeight) {
+ close = 3*textPtr->charHeight;
}
- close += fm.linespace;
if (dlPtr != NULL) {
+ int overlap;
/*
* The desired line is above the top of screen. If it is
* "close" to the top of the window then make it the top
- * line on the screen.
+ * line on the screen. MeasureUp counts from the bottom
+ * of the given index upwards, so we add an extra half line
+ * to be sure we count far enough.
*/
- MeasureUp(textPtr, &textPtr->topIndex, close, &tmpIndex);
+ MeasureUp(textPtr, &textPtr->topIndex, close + textPtr->charHeight/2,
+ &tmpIndex, &overlap);
if (TkTextIndexCmp(&tmpIndex, indexPtr) <= 0) {
- MeasureUp(textPtr, indexPtr, 0, &textPtr->topIndex);
+ textPtr->topIndex = *indexPtr;
+ TkTextFindDisplayLineEnd(textPtr, &textPtr->topIndex, 0, NULL);
+ 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, &tmpIndex);
+ MeasureUp(textPtr, indexPtr, close + lineHeight
+ - textPtr->charHeight/2, &tmpIndex, &overlap);
if (FindDLine(dInfoPtr->dLinePtr, &tmpIndex) != NULL) {
bottomY = dInfoPtr->maxY - dInfoPtr->y;
}
@@ -3163,7 +4294,8 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
* is a half-line lower than the center of the window.
*/
- MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex);
+ MeasureUp(textPtr, indexPtr, bottomY, &textPtr->topIndex,
+ &dInfoPtr->newTopPixelOffset);
scheduleUpdate:
if (!(dInfoPtr->flags & REDRAW_PENDING)) {
@@ -3175,11 +4307,72 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
/*
*--------------------------------------------------------------
*
- * MeasureUp --
+ * TkTextMeasureDown --
*
* Given one index, find the index of the first character
* on the highest display line that would be displayed no more
- * than "distance" pixels above the given index.
+ * than "distance" pixels below the top of the given index.
+ *
+ * Results:
+ * The srcPtr is manipulated in place to reflect the new
+ * position. We return the number of pixels by which 'distance'
+ * overlaps the srcPtr.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+int
+TkTextMeasureDown(textPtr, srcPtr, distance)
+ 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. */
+{
+ TkTextLine *lastLinePtr;
+ DLine *dlPtr;
+ TkTextIndex loop;
+
+ lastLinePtr = TkBTreeFindLine(textPtr->tree,
+ TkBTreeNumLines(textPtr->tree));
+
+ do {
+ dlPtr = LayoutDLine(textPtr, srcPtr);
+ dlPtr->nextPtr = NULL;
+
+ if (distance < dlPtr->height) {
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
+ break;
+ }
+ distance -= dlPtr->height;
+ TkTextIndexForwBytes(srcPtr, dlPtr->byteCount, &loop);
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
+ if (loop.linePtr == lastLinePtr) {
+ break;
+ }
+ *srcPtr = loop;
+ } while (distance > 0);
+
+ return distance;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * MeasureUp --
+ *
+ * Given one index, find the index of the first character on the
+ * highest display line that would be displayed no more than
+ * "distance" pixels above the given index.
+ *
+ * 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.
*
* Results:
* *dstPtr is filled in with the index of the first character
@@ -3187,9 +4380,8 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
* up "distance" 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 the index of the next lower line
- * instead (i.e. the returned index will be completely visible
- * at or below the given y-coordinate).
+ * coordinate, then return any excess pixels in *overlap, if
+ * that is non-NULL.
*
* Side effects:
* None.
@@ -3198,30 +4390,31 @@ TkTextSetYView(textPtr, indexPtr, pickPlace)
*/
static void
-MeasureUp(textPtr, srcPtr, distance, dstPtr)
+MeasureUp(textPtr, srcPtr, distance, dstPtr, overlap)
TkText *textPtr; /* Text widget in which to measure. */
- TkTextIndex *srcPtr; /* Index of character from which to start
+ 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'. */
{
int lineNum; /* Number of current line. */
int bytesToCount; /* Maximum number of bytes to measure in
* current line. */
- TkTextIndex bestIndex; /* Best candidate seen so far for result. */
TkTextIndex index;
DLine *dlPtr, *lowestPtr;
- int noBestYet; /* 1 means bestIndex hasn't been set. */
- noBestYet = 1;
bytesToCount = srcPtr->byteIndex + 1;
index.tree = srcPtr->tree;
for (lineNum = TkBTreeLineIndex(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
@@ -3243,17 +4436,20 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr)
/*
* 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.
+ * 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.
*/
for (dlPtr = lowestPtr; dlPtr != NULL; dlPtr = dlPtr->nextPtr) {
distance -= dlPtr->height;
- if (distance < 0) {
- *dstPtr = (noBestYet) ? dlPtr->index : bestIndex;
+ if (distance <= 0) {
+ *dstPtr = dlPtr->index;
+ if (overlap != NULL) {
+ *overlap = -distance;
+ }
break;
}
- bestIndex = dlPtr->index;
- noBestYet = 0;
}
/*
@@ -3261,8 +4457,8 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr)
* for the next display line to lay out.
*/
- FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
- if (distance < 0) {
+ FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE);
+ if (distance <= 0) {
return;
}
bytesToCount = INT_MAX; /* Consider all chars. in next line. */
@@ -3274,6 +4470,9 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr)
*/
TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr);
+ if (overlap != NULL) {
+ *overlap = 0;
+ }
}
/*
@@ -3310,8 +4509,7 @@ TkTextSeeCmd(textPtr, interp, objc, objv)
TkTextDispChunk *chunkPtr;
if (objc != 3) {
- Tcl_AppendResult(interp, "wrong # args: should be \"",
- Tcl_GetString(objv[0]), " see index\"", (char *) NULL);
+ Tcl_WrongNumArgs(interp, 2, objv, "index");
return TCL_ERROR;
}
if (TkTextGetObjIndex(interp, textPtr, objv[2], &index) != TCL_OK) {
@@ -3324,14 +4522,14 @@ TkTextSeeCmd(textPtr, interp, objc, objv)
*/
if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) {
- TkTextIndexBackChars(&index, 1, &index);
+ TkTextIndexBackChars(&index, 1, &index, COUNT_INDICES);
}
/*
* First get the desired position into the vertical range of the window.
*/
- TkTextSetYView(textPtr, &index, 1);
+ TkTextSetYView(textPtr, &index, TK_TEXT_PICKPLACE);
/*
* Now make sure that the character is in view horizontally.
@@ -3376,24 +4574,24 @@ TkTextSeeCmd(textPtr, interp, objc, objv)
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
&height);
- delta = x - dInfoPtr->curPixelOffset;
+ delta = x - dInfoPtr->curXPixelOffset;
oneThird = lineWidth/3;
if (delta < 0) {
if (delta < -oneThird) {
- dInfoPtr->newByteOffset = (x - lineWidth/2)
+ dInfoPtr->newXByteOffset = (x - lineWidth/2)
/ textPtr->charWidth;
} else {
- dInfoPtr->newByteOffset -= ((-delta) + textPtr->charWidth - 1)
+ dInfoPtr->newXByteOffset -= ((-delta) + textPtr->charWidth - 1)
/ textPtr->charWidth;
}
} else {
delta -= (lineWidth - width);
if (delta > 0) {
if (delta > oneThird) {
- dInfoPtr->newByteOffset = (x - lineWidth/2)
+ dInfoPtr->newXByteOffset = (x - lineWidth/2)
/ textPtr->charWidth;
} else {
- dInfoPtr->newByteOffset += (delta + textPtr->charWidth - 1)
+ dInfoPtr->newXByteOffset += (delta + textPtr->charWidth - 1)
/ textPtr->charWidth;
}
} else {
@@ -3449,7 +4647,7 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
return TCL_OK;
}
- newOffset = dInfoPtr->newByteOffset;
+ newOffset = dInfoPtr->newXByteOffset;
type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
switch (type) {
case TK_SCROLL_ERROR:
@@ -3461,8 +4659,8 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
if (fraction < 0) {
fraction = 0;
}
- newOffset = (int) (((fraction * dInfoPtr->maxLength) / textPtr->charWidth)
- + 0.5);
+ newOffset = (int) (((fraction * dInfoPtr->maxLength)
+ / textPtr->charWidth) + 0.5);
break;
case TK_SCROLL_PAGES:
charsPerPage = ((dInfoPtr->maxX - dInfoPtr->x) / textPtr->charWidth)
@@ -3477,7 +4675,7 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
break;
}
- dInfoPtr->newByteOffset = newOffset;
+ dInfoPtr->newXByteOffset = newOffset;
dInfoPtr->flags |= DINFO_OUT_OF_DATE;
if (!(dInfoPtr->flags & REDRAW_PENDING)) {
dInfoPtr->flags |= REDRAW_PENDING;
@@ -3489,7 +4687,87 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
/*
*----------------------------------------------------------------------
*
- * ScrollByLines --
+ * YScrollByPixels --
+ *
+ * This procedure is called to scroll a text widget up or down
+ * by a given number of pixels.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The view in textPtr's window changes to reflect the value
+ * of "offset".
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+YScrollByPixels(textPtr, offset)
+ 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.
+ */
+ offset -= TextCalculateDisplayLineHeight(textPtr,
+ &textPtr->topIndex, NULL) - dInfoPtr->topPixelOffset;
+ MeasureUp(textPtr, &textPtr->topIndex, -offset,
+ &textPtr->topIndex, &dInfoPtr->newTopPixelOffset);
+ } else if (offset > 0) {
+ DLine *dlPtr;
+ TkTextLine *lastLinePtr;
+ TkTextIndex new;
+ /*
+ * Scrolling down by pixels. Layout lines starting at
+ * the top index and count through the desired vertical
+ * distance.
+ */
+
+ lastLinePtr = TkBTreeFindLine(textPtr->tree,
+ TkBTreeNumLines(textPtr->tree));
+ offset += dInfoPtr->topPixelOffset;
+ dInfoPtr->newTopPixelOffset = 0;
+ while (offset > 0) {
+ dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
+ dlPtr->nextPtr = NULL;
+ TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount,
+ &new);
+ if (offset <= dlPtr->height) {
+ /* Adjust the top overlap accordingly */
+ dInfoPtr->newTopPixelOffset = offset;
+ }
+ offset -= dlPtr->height;
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP);
+ if (new.linePtr == lastLinePtr || offset <= 0) {
+ break;
+ }
+ textPtr->topIndex = new;
+ }
+ } else {
+ /* offset = 0, so no scrolling required */
+ return;
+ }
+ if (!(dInfoPtr->flags & REDRAW_PENDING)) {
+ Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
+ }
+ dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * YScrollByLines --
*
* This procedure is called to scroll a text widget up or down
* by a given number of lines.
@@ -3505,9 +4783,9 @@ TkTextXviewCmd(textPtr, interp, objc, objv)
*/
static void
-ScrollByLines(textPtr, offset)
+YScrollByLines(textPtr, offset)
TkText *textPtr; /* Widget to scroll. */
- int offset; /* Amount by which to scroll, in *screen*
+ 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
@@ -3556,7 +4834,7 @@ ScrollByLines(textPtr, offset)
* for the next display line to lay out.
*/
- FreeDLines(textPtr, lowestPtr, (DLine *) NULL, 0);
+ FreeDLines(textPtr, lowestPtr, (DLine *) NULL, DLINE_FREE);
if (offset >= 0) {
goto scheduleUpdate;
}
@@ -3565,10 +4843,12 @@ ScrollByLines(textPtr, offset)
/*
* Ran off the beginning of the text. Return the first character
- * in the text.
+ * in the text, and make sure we haven't left anything
+ * overlapping the top window border.
*/
TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex);
+ dInfoPtr->newTopPixelOffset = 0;
} else {
/*
* Scrolling down, to show later information in the text.
@@ -3582,7 +4862,7 @@ ScrollByLines(textPtr, offset)
if (dlPtr->length == 0 && dlPtr->height == 0) offset++;
dlPtr->nextPtr = NULL;
TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new);
- FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
+ FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE);
if (new.linePtr == lastLinePtr) {
break;
}
@@ -3625,14 +4905,11 @@ TkTextYviewCmd(textPtr, interp, objc, objv)
* objv[1] is "yview". */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- int pickPlace, lineNum, type, bytesInLine;
- Tk_FontMetrics fm;
+ int pickPlace, type;
int pixels, count;
size_t switchLength;
double fraction;
- TkTextIndex index, new;
- TkTextLine *lastLinePtr;
- DLine *dlPtr;
+ TkTextIndex index;
if (dInfoPtr->flags & DINFO_OUT_OF_DATE) {
UpdateDisplayInfo(textPtr);
@@ -3651,18 +4928,17 @@ TkTextYviewCmd(textPtr, interp, objc, objv)
if (Tcl_GetString(objv[2])[0] == '-') {
switchLength = strlen(Tcl_GetString(objv[2]));
if ((switchLength >= 2)
- && (strncmp(Tcl_GetString(objv[2]), "-pickplace", switchLength) == 0)) {
+ && (strncmp(Tcl_GetString(objv[2]),
+ "-pickplace", switchLength) == 0)) {
pickPlace = 1;
if (objc != 4) {
- Tcl_AppendResult(interp, "wrong # args: should be \"",
- Tcl_GetString(objv[0]),
- " yview -pickplace lineNum|index\"",
- (char *) NULL);
+ Tcl_WrongNumArgs(interp, 3, objv, "lineNum|index");
return TCL_ERROR;
}
}
}
if ((objc == 3) || pickPlace) {
+ int lineNum;
if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) {
TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
TkTextSetYView(textPtr, &index, 0);
@@ -3678,7 +4954,7 @@ TkTextYviewCmd(textPtr, interp, objc, objv)
&index) != TCL_OK) {
return TCL_ERROR;
}
- TkTextSetYView(textPtr, &index, pickPlace);
+ TkTextSetYView(textPtr, &index, (pickPlace ? TK_TEXT_PICKPLACE : 0));
return TCL_OK;
}
@@ -3686,79 +4962,67 @@ TkTextYviewCmd(textPtr, interp, objc, objv)
* New syntax: dispatch based on objv[2].
*/
- type = Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count);
+ type = TextGetScrollInfoObj(interp, textPtr, objc, objv,
+ &fraction, &count);
switch (type) {
- case TK_SCROLL_ERROR:
+ case TKTEXT_SCROLL_ERROR:
return TCL_ERROR;
- case TK_SCROLL_MOVETO:
+ case TKTEXT_SCROLL_MOVETO:
if (fraction > 1.0) {
fraction = 1.0;
}
if (fraction < 0) {
fraction = 0;
}
- fraction *= TkBTreeNumLines(textPtr->tree);
- lineNum = (int) fraction;
- TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index);
- bytesInLine = TkBTreeBytesInLine(index.linePtr);
- index.byteIndex = (int)((bytesInLine * (fraction-lineNum)) + 0.5);
- if (index.byteIndex >= bytesInLine) {
- TkTextMakeByteIndex(textPtr->tree, lineNum + 1, 0, &index);
- }
- TkTextSetYView(textPtr, &index, 0);
+ fraction *= (TkBTreeNumPixels(textPtr->tree)-1);
+ /*
+ * 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, (int) (0.5 + fraction),
+ &index);
+ TkTextSetYView(textPtr, &index, pixels);
break;
- case TK_SCROLL_PAGES:
+ 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.
*/
-
- Tk_GetFontMetrics(textPtr->tkfont, &fm);
- if (count < 0) {
- pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*(-count)
- + fm.linespace;
- MeasureUp(textPtr, &textPtr->topIndex, pixels, &new);
- if (TkTextIndexCmp(&textPtr->topIndex, &new) == 0) {
- /*
- * A page of scrolling ended up being less than one line.
- * Scroll one line anyway.
+ int height = dInfoPtr->maxY - dInfoPtr->y;
+ if (textPtr->charHeight * 4 >= height) {
+ /*
+ * A single line is more than a quarter of the
+ * display. We choose to scroll by 3/4 of the
+ * height instead.
+ */
+ pixels = 3*height/4;
+ if (pixels < textPtr->charHeight) {
+ /*
+ * 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.
*/
-
- count = -1;
- goto scrollByLines;
+ if (textPtr->charHeight < height) {
+ pixels = textPtr->charHeight;
+ } else {
+ pixels = height;
+ }
}
- textPtr->topIndex = new;
+ pixels *= count;
} else {
- /*
- * Scrolling down by pages. Layout lines starting at the
- * top index and count through the desired vertical distance.
- */
-
- pixels = (dInfoPtr->maxY - 2*fm.linespace - dInfoPtr->y)*count;
- lastLinePtr = TkBTreeFindLine(textPtr->tree,
- TkBTreeNumLines(textPtr->tree));
- do {
- dlPtr = LayoutDLine(textPtr, &textPtr->topIndex);
- dlPtr->nextPtr = NULL;
- TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount,
- &new);
- pixels -= dlPtr->height;
- FreeDLines(textPtr, dlPtr, (DLine *) NULL, 0);
- if (new.linePtr == lastLinePtr) {
- break;
- }
- textPtr->topIndex = new;
- } while (pixels > 0);
+ pixels = (height - 2*textPtr->charHeight)*count;
}
- if (!(dInfoPtr->flags & REDRAW_PENDING)) {
- Tcl_DoWhenIdle(DisplayText, (ClientData) textPtr);
- }
- dInfoPtr->flags |= REDRAW_PENDING|DINFO_OUT_OF_DATE|REPICK_NEEDED;
+ YScrollByPixels(textPtr, pixels);
break;
- case TK_SCROLL_UNITS:
- scrollByLines:
- ScrollByLines(textPtr, count);
+ }
+ case TKTEXT_SCROLL_PIXELS:
+ YScrollByPixels(textPtr, count);
+ break;
+ case TKTEXT_SCROLL_UNITS:
+ YScrollByLines(textPtr, count);
break;
}
return TCL_OK;
@@ -3794,13 +5058,13 @@ TkTextScanCmd(textPtr, interp, objc, objv)
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
TkTextIndex index;
int c, x, y, totalScroll, newByte, maxByte, gain=10;
- Tk_FontMetrics fm;
size_t length;
if ((objc != 5) && (objc != 6)) {
Tcl_AppendResult(interp, "wrong # args: should be \"",
Tcl_GetString(objv[0]), " scan mark x y\" or \"",
- Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"", (char *) NULL);
+ Tcl_GetString(objv[0]), " scan dragto x y ?gain?\"",
+ (char *) NULL);
return TCL_ERROR;
}
if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK) {
@@ -3809,8 +5073,9 @@ TkTextScanCmd(textPtr, interp, objc, objv)
if (Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
return TCL_ERROR;
}
- if ((objc == 6) && (Tcl_GetIntFromObj(interp, objv[5], &gain) != TCL_OK))
- return TCL_ERROR;
+ 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)) {
@@ -3839,24 +5104,24 @@ TkTextScanCmd(textPtr, interp, objc, objv)
dInfoPtr->scanMarkIndex = maxByte;
dInfoPtr->scanMarkX = x;
}
- dInfoPtr->newByteOffset = newByte;
+ dInfoPtr->newXByteOffset = newByte;
- Tk_GetFontMetrics(textPtr->tkfont, &fm);
- totalScroll = (gain*(dInfoPtr->scanMarkY - y)) / fm.linespace;
- if (totalScroll != dInfoPtr->scanTotalScroll) {
+ totalScroll = gain*(dInfoPtr->scanMarkY - y);
+ if (totalScroll != dInfoPtr->scanTotalYScroll) {
index = textPtr->topIndex;
- ScrollByLines(textPtr, totalScroll-dInfoPtr->scanTotalScroll);
- dInfoPtr->scanTotalScroll = totalScroll;
+ YScrollByPixels(textPtr, totalScroll-dInfoPtr->scanTotalYScroll);
+ dInfoPtr->scanTotalYScroll = totalScroll;
if ((index.linePtr == textPtr->topIndex.linePtr) &&
(index.byteIndex == textPtr->topIndex.byteIndex)) {
- dInfoPtr->scanTotalScroll = 0;
+ dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = y;
}
}
- } else if ((c == 'm') && (strncmp(Tcl_GetString(objv[2]), "mark", length) == 0)) {
- dInfoPtr->scanMarkIndex = dInfoPtr->newByteOffset;
+ } else if ((c == 'm')
+ && (strncmp(Tcl_GetString(objv[2]), "mark", length) == 0)) {
+ dInfoPtr->scanMarkIndex = dInfoPtr->newXByteOffset;
dInfoPtr->scanMarkX = x;
- dInfoPtr->scanTotalScroll = 0;
+ dInfoPtr->scanTotalYScroll = 0;
dInfoPtr->scanMarkY = y;
} else {
Tcl_AppendResult(interp, "bad scan option \"", Tcl_GetString(objv[2]),
@@ -3906,12 +5171,12 @@ GetXView(interp, textPtr, report)
* scrollbar if it has changed. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- char buffer[TCL_DOUBLE_SPACE * 2 + 1];
double first, last;
int code;
-
+ Tcl_Obj *listObj;
+
if (dInfoPtr->maxLength > 0) {
- first = ((double) dInfoPtr->curPixelOffset)
+ first = ((double) dInfoPtr->curXPixelOffset)
/ dInfoPtr->maxLength;
last = first + ((double) (dInfoPtr->maxX - dInfoPtr->x))
/ dInfoPtr->maxLength;
@@ -3923,8 +5188,10 @@ GetXView(interp, textPtr, report)
last = 1.0;
}
if (!report) {
- sprintf(buffer, "%g %g", first, last);
- Tcl_SetResult(interp, buffer, TCL_VOLATILE);
+ listObj = Tcl_NewListObj(0,NULL);
+ 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->xScrollFirst, dInfoPtr->maxLength) &&
@@ -3933,19 +5200,67 @@ GetXView(interp, textPtr, report)
}
dInfoPtr->xScrollFirst = first;
dInfoPtr->xScrollLast = last;
- sprintf(buffer, " %g %g", first, last);
- code = Tcl_VarEval(interp, textPtr->xScrollCmd,
- buffer, (char *) NULL);
- if (code != TCL_OK) {
- Tcl_AddErrorInfo(interp,
+ if (textPtr->xScrollCmd != NULL) {
+ listObj = Tcl_NewStringObj(textPtr->xScrollCmd, -1);
+ code = Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(first));
+ if (code == TCL_OK) {
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
+ code = Tcl_EvalObjEx(interp, listObj,
+ TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
+ }
+ if (code != TCL_OK) {
+ Tcl_AddErrorInfo(interp,
"\n (horizontal scrolling command executed by text)");
- Tcl_BackgroundError(interp);
+ Tcl_BackgroundError(interp);
+ }
}
}
/*
*----------------------------------------------------------------------
*
+ * GetPixelCount --
+ *
+ * How many pixels are there between the absolute top of the
+ * widget and the top of the given DLine.
+ *
+ * Results:
+ * The number of pixels.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+GetPixelCount(textPtr, dlPtr)
+ TkText *textPtr; /* Information about text widget. */
+ DLine *dlPtr; /* Information about the layout
+ * of a given index */
+{
+ TkTextLine *linePtr = dlPtr->index.linePtr;
+ /*
+ * Get the pixel count of one pixel beyond the
+ * botton of the given line.
+ */
+ int count = TkBTreePixels(linePtr) + linePtr->pixelHeight;
+
+ /*
+ * Now we have to subtract off the distance between the top of this
+ * dlPtr and the next logical line.
+ */
+ do {
+ count -= dlPtr->height;
+ dlPtr = dlPtr->nextPtr;
+ } while (dlPtr != NULL && (dlPtr->index.linePtr == linePtr));
+
+ return count;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* GetYView --
*
* This procedure computes the fractions that indicate what's
@@ -3967,7 +5282,7 @@ GetXView(interp, textPtr, report)
*
*----------------------------------------------------------------------
*/
-
+
static void
GetYView(interp, textPtr, report)
Tcl_Interp *interp; /* If "report" is FALSE, string
@@ -3978,53 +5293,153 @@ GetYView(interp, textPtr, report)
* scrollbar if it has changed. */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- char buffer[TCL_DOUBLE_SPACE * 2 + 1];
double first, last;
DLine *dlPtr;
- int totalLines, code, count;
-
+ int totalPixels, code, count;
+ Tcl_Obj *listObj;
+
dlPtr = dInfoPtr->dLinePtr;
- totalLines = TkBTreeNumLines(textPtr->tree);
- first = (double) TkBTreeLineIndex(dlPtr->index.linePtr)
- + (double) dlPtr->index.byteIndex
- / TkBTreeBytesInLine(dlPtr->index.linePtr);
- first /= totalLines;
+
+ if (dlPtr == NULL) {
+ return;
+ }
+
+ totalPixels = TkBTreeNumPixels(textPtr->tree);
+
+ /*
+ * Get the pixel count for the first display visible pixel of the
+ * first visible line. If the first visible line is only partially
+ * visible, then we use 'topPixelOffset' to get the difference.
+ */
+ count = GetPixelCount(textPtr, dlPtr);
+ first = ((double) (count + dInfoPtr->topPixelOffset))/((double)totalPixels);
+
+ /*
+ * Add on the total number of visible pixels to get the count to
+ * the last visible pixel.
+ */
while (1) {
- if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
+ int extra;
+ count += dlPtr->height;
+
+ extra = dlPtr->y + dlPtr->height - dInfoPtr->maxY;
+ if (extra > 0) {
/*
- * The last line is only partially visible, so don't
- * count its characters in what's visible.
+ * This much of the last line is not visible, so don't
+ * count these pixels. Since we've reached the bottom
+ * of the window, we break out of the loop.
*/
- count = 0;
+ count -= extra;
break;
}
if (dlPtr->nextPtr == NULL) {
- count = dlPtr->byteCount;
break;
}
dlPtr = dlPtr->nextPtr;
}
- last = ((double) TkBTreeLineIndex(dlPtr->index.linePtr))
- + ((double) (dlPtr->index.byteIndex + count))
- / (TkBTreeBytesInLine(dlPtr->index.linePtr));
- last /= totalLines;
+
+ if (count > totalPixels) {
+ /*
+ * It can be possible, if we do not update each line's
+ * pixelHeight cache when we lay out individual DLines that
+ * the count generated here is more up-to-date than that
+ * maintained by the BTree. In such a case, the best we can
+ * do here is to fix up 'count' and continue, which might
+ * result in small, temporary perturbations to the size of
+ * the scrollbar. This is basically harmless, but in a
+ * perfect world we would not have this problem.
+ *
+ * For debugging purposes, if anyone wishes to improve the text
+ * widget further, the following 'panic' can be activated. In
+ * principle it should be possible to ensure the BTree is always
+ * at least as up to date as the display, so in the future we
+ * might be able to leave the 'panic' in permanently when we
+ * believe we have resolved the cache synchronisation issue.
+ *
+ * However, to achieve that goal would, I think, require a
+ * fairly substantial refactorisation of the code in this
+ * file so that there is much more obvious and explicit
+ * coordination between calls to LayoutDLine and updating
+ * of each TkTextLine's pixelHeight. The complicated bit
+ * is that LayoutDLine deals with individual display lines,
+ * but pixelHeight is for a logical line.
+ */
+#if 0
+ char buffer[200];
+
+ sprintf(buffer,
+ "Counted more pixels (%d) than expected (%d) total pixels in text widget scroll bar calculation.",
+ count, totalPixels);
+ panic(buffer);
+#endif
+ count = totalPixels;
+ }
+
+ last = ((double) (count))/((double)totalPixels);
+
if (!report) {
- sprintf(buffer, "%g %g", first, last);
- Tcl_SetResult(interp, buffer, TCL_VOLATILE);
+ listObj = Tcl_NewListObj(0,NULL);
+ 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, totalLines) &&
- FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalLines)) {
+ if (FP_EQUAL_SCALE(first, dInfoPtr->yScrollFirst, totalPixels) &&
+ FP_EQUAL_SCALE(last, dInfoPtr->yScrollLast, totalPixels)) {
return;
}
dInfoPtr->yScrollFirst = first;
dInfoPtr->yScrollLast = last;
- sprintf(buffer, " %g %g", first, last);
- code = Tcl_VarEval(interp, textPtr->yScrollCmd, buffer, (char *) NULL);
- if (code != TCL_OK) {
- Tcl_AddErrorInfo(interp,
- "\n (vertical scrolling command executed by text)");
- Tcl_BackgroundError(interp);
+ if (textPtr->yScrollCmd != NULL) {
+ listObj = Tcl_NewStringObj(textPtr->yScrollCmd, -1);
+ code = Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewDoubleObj(first));
+ if (code == TCL_OK) {
+ Tcl_ListObjAppendElement(interp, listObj, Tcl_NewDoubleObj(last));
+ code = Tcl_EvalObjEx(interp, listObj,
+ TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL);
+ }
+ if (code != TCL_OK) {
+ Tcl_AddErrorInfo(interp,
+ "\n (vertical scrolling command executed by text)");
+ Tcl_BackgroundError(interp);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextUpdateYScrollbar --
+ *
+ * This procedure is called to update the vertical scrollbar
+ * asychronously as the pixel height calculations progress for
+ * lines in the widget.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * See 'GetYView'. In particular the scrollbar position and size
+ * may be changed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextUpdateYScrollbar(clientData)
+ ClientData clientData; /* Information about widget. */
+{
+ register TkText *textPtr = (TkText *) clientData;
+
+ textPtr->dInfoPtr->scrollbarTimer = NULL;
+
+ if (!(textPtr->flags & DESTROYED)) {
+ GetYView(textPtr->interp, textPtr, 1);
+ }
+
+ if (--textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
}
}
@@ -4128,8 +5543,7 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
* index of the character nearest to (x,y). */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
- register DLine *dlPtr, *validdlPtr;
- register TkTextDispChunk *chunkPtr;
+ register DLine *dlPtr, *validDlPtr;
/*
* Make sure that all of the layout information about what's
@@ -4161,9 +5575,10 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
* Find the display line containing the desired y-coordinate.
*/
- for (dlPtr = validdlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height);
- dlPtr = dlPtr->nextPtr) {
- if (dlPtr->chunkPtr !=NULL) validdlPtr = dlPtr;
+ 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.
@@ -4174,8 +5589,43 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
break;
}
}
- if (dlPtr->chunkPtr == NULL) dlPtr = validdlPtr;
+ if (dlPtr->chunkPtr == NULL) dlPtr = validDlPtr;
+
+ DlineIndexOfX(textPtr, dlPtr, x, indexPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * DlineIndexOfX --
+ *
+ * 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.
+ *
+ * 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(textPtr, dlPtr, x, indexPtr)
+ 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. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ register TkTextDispChunk *chunkPtr;
/*
* Scan through the line's chunks to find the one that contains
@@ -4185,13 +5635,13 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
*/
*indexPtr = dlPtr->index;
- x = x - dInfoPtr->x + dInfoPtr->curPixelOffset;
+ x = x - dInfoPtr->x + dInfoPtr->curXPixelOffset;
for (chunkPtr = dlPtr->chunkPtr; x >= (chunkPtr->x + chunkPtr->width);
indexPtr->byteIndex += chunkPtr->numBytes,
chunkPtr = chunkPtr->nextPtr) {
if (chunkPtr->nextPtr == NULL) {
indexPtr->byteIndex += chunkPtr->numBytes;
- TkTextIndexBackChars(indexPtr, 1, indexPtr);
+ TkTextIndexBackChars(indexPtr, 1, indexPtr, COUNT_INDICES);
return;
}
}
@@ -4209,6 +5659,110 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
/*
*----------------------------------------------------------------------
*
+ * TkTextIndexOfX --
+ *
+ * Given a logical x coordinate (i.e. distance in pixels from the
+ * beginning of the display line, not taking into account any
+ * information about the window, scrolling etc.) on the display
+ * line starting with the given index, adjust that index to refer to
+ * the object under the x coordinate.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextIndexOfX(textPtr, x, indexPtr)
+ 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. */
+{
+ DLine *dlPtr = LayoutDLine(textPtr, indexPtr);
+ DlineIndexOfX(textPtr, dlPtr, x + textPtr->dInfoPtr->x
+ - textPtr->dInfoPtr->curXPixelOffset, indexPtr);
+ FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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.
+ *
+ * NB. The 'byteIndex' is relative to the display line, NOT the
+ * logical line.
+ *
+ * Results:
+ * The x coordinate.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+DlineXOfIndex(textPtr, dlPtr, byteIndex)
+ 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. */
+{
+ TextDInfo *dInfoPtr = textPtr->dInfoPtr;
+ register TkTextDispChunk *chunkPtr;
+ TkTextIndex index;
+ int x;
+
+ if (byteIndex == 0) return 0;
+
+ /*
+ * Scan through the line's chunks to find the one that contains
+ * the desired byte index.
+ */
+
+ index = dlPtr->index;
+ chunkPtr = dlPtr->chunkPtr;
+ while (byteIndex > 0) {
+ if (byteIndex < chunkPtr->numBytes) {
+ int y, width, height;
+ (*chunkPtr->bboxProc)(chunkPtr, byteIndex,
+ dlPtr->y + dlPtr->spaceAbove,
+ dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
+ dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width,
+ &height);
+ break;
+ } else {
+ byteIndex -= chunkPtr->numBytes;
+ }
+ if (chunkPtr->nextPtr == NULL || byteIndex == 0) {
+ x = chunkPtr->x + chunkPtr->width;
+ break;
+ }
+ chunkPtr = chunkPtr->nextPtr;
+ }
+
+ return x;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TkTextCharBbox --
*
* Given an index, find the bounding box of the screen area
@@ -4228,13 +5782,18 @@ TkTextPixelIndex(textPtr, x, y, indexPtr)
*/
int
-TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr)
+TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, charWidthPtr)
TkText *textPtr; /* Widget record for text widget. */
CONST TkTextIndex *indexPtr;/* Index of character whose bounding
* box is desired. */
int *xPtr, *yPtr; /* Filled with character's upper-left
* coordinate. */
int *widthPtr, *heightPtr; /* Filled in with character's dimensions. */
+ int *charWidthPtr; /* If the 'character' isn't really a
+ * character (e.g. end of a line) and
+ * therefore takes up a very large
+ * width, this is used to return a
+ * smaller width */
{
TextDInfo *dInfoPtr = textPtr->dInfoPtr;
DLine *dlPtr;
@@ -4286,17 +5845,24 @@ TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr)
dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow,
dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr,
heightPtr);
- *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curPixelOffset;
+ *xPtr = *xPtr + dInfoPtr->x - dInfoPtr->curXPixelOffset;
if ((byteIndex == (chunkPtr->numBytes - 1)) && (chunkPtr->nextPtr == NULL)) {
/*
* Last character in display line. Give it all the space up to
* the line.
*/
+ if (charWidthPtr != NULL) {
+ *charWidthPtr = dInfoPtr->maxX - *xPtr;
+ }
if (*xPtr > dInfoPtr->maxX) {
*xPtr = dInfoPtr->maxX;
}
*widthPtr = dInfoPtr->maxX - *xPtr;
+ } else {
+ if (charWidthPtr != NULL) {
+ *charWidthPtr = *widthPtr;
+ }
}
if ((*xPtr + *widthPtr) <= dInfoPtr->x) {
return -1;
@@ -4369,7 +5935,7 @@ TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr)
}
dlx = (dlPtr->chunkPtr != NULL? dlPtr->chunkPtr->x: 0);
- *xPtr = dInfoPtr->x - dInfoPtr->curPixelOffset + dlx;
+ *xPtr = dInfoPtr->x - dInfoPtr->curXPixelOffset + dlx;
*widthPtr = dlPtr->length - dlx;
*yPtr = dlPtr->y;
if ((dlPtr->y + dlPtr->height) > dInfoPtr->maxY) {
@@ -4454,8 +6020,9 @@ TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes,
* 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. */
+ 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
@@ -4643,7 +6210,8 @@ CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
* Draw the text, underline, and overstrike for this chunk.
*/
- if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes) && (stylePtr->fgGC != None)) {
+ if (!sValuePtr->elide && (ciPtr->numBytes > offsetBytes)
+ && (stylePtr->fgGC != None)) {
int numBytes = ciPtr->numBytes - offsetBytes;
char *string = ciPtr->chars + offsetBytes;
@@ -4759,7 +6327,7 @@ static void
CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr,
widthPtr, heightPtr)
TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */
- int byteIndex; /* Byte offset of desired character
+ int byteIndex; /* Byte offset of desired character
* within the chunk. */
int y; /* Topmost pixel in area allocated
* for this line. */
@@ -5235,3 +6803,102 @@ MeasureChars(tkfont, source, maxBytes, startX, maxX, tabOrigin, nextXPtr)
*nextXPtr = curX;
return start - source;
}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextGetScrollInfoObj --
+ *
+ * This procedure is invoked to parse "yview" scrolling commands for
+ * text widgets using the new scrolling command syntax ("moveto" or
+ * "scroll" options). It extends the public Tk_GetScrollInfoObj
+ * function with the addition of "pixels" as a valid unit alongside
+ * "pages" and "units". It is a shame the core API isn't 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 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 interp's result contains an error
+ * message.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TextGetScrollInfoObj(interp, textPtr, objc, objv, dblPtr, intPtr)
+ 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. */
+{
+ char c;
+ size_t length;
+ CONST char *arg2;
+
+ arg2 = Tcl_GetStringFromObj(objv[2], &length);
+ c = arg2[0];
+ if ((c == 'm') && (strncmp(arg2, "moveto", length) == 0)) {
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "moveto fraction");
+ return TKTEXT_SCROLL_ERROR;
+ }
+ if (Tcl_GetDoubleFromObj(interp, objv[3], dblPtr) != TCL_OK) {
+ return TKTEXT_SCROLL_ERROR;
+ }
+ return TKTEXT_SCROLL_MOVETO;
+ } else if ((c == 's') && (strncmp(arg2, "scroll", length) == 0)) {
+ CONST char *arg4;
+
+ if (objc != 5) {
+ Tcl_WrongNumArgs(interp, 2, objv,
+ "scroll number units|pages|pixels");
+ return TKTEXT_SCROLL_ERROR;
+ }
+ arg4 = Tcl_GetStringFromObj(objv[4], &length);
+ c = arg4[0];
+ if ((c == 'p') && (length == 1)) {
+ Tcl_AppendResult(interp, "ambiguous argument \"", arg4,
+ "\": must be units, pages or pixels", (char *) NULL);
+ return TKTEXT_SCROLL_ERROR;
+ } else if ((c == 'p') && (strncmp(arg4, "pages", length) == 0)) {
+ if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
+ return TKTEXT_SCROLL_ERROR;
+ }
+ return TKTEXT_SCROLL_PAGES;
+ } else if ((c == 'p') && (strncmp(arg4, "pixels", length) == 0)) {
+ if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[3],
+ intPtr) != TCL_OK) {
+ return TKTEXT_SCROLL_ERROR;
+ }
+ return TKTEXT_SCROLL_PIXELS;
+ } else if ((c == 'u') && (strncmp(arg4, "units", length) == 0)) {
+ if (Tcl_GetIntFromObj(interp, objv[3], intPtr) != TCL_OK) {
+ return TKTEXT_SCROLL_ERROR;
+ }
+ return TKTEXT_SCROLL_UNITS;
+ } else {
+ Tcl_AppendResult(interp, "bad argument \"", arg4,
+ "\": must be units, pages or pixels", (char *) NULL);
+ return TKTEXT_SCROLL_ERROR;
+ }
+ }
+ Tcl_AppendResult(interp, "unknown option \"", arg2,
+ "\": must be moveto or scroll", (char *) NULL);
+ return TKTEXT_SCROLL_ERROR;
+}
diff --git a/generic/tkTextImage.c b/generic/tkTextImage.c
index ef3dd93..35e4138 100644
--- a/generic/tkTextImage.c
+++ b/generic/tkTextImage.c
@@ -10,7 +10,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkTextImage.c,v 1.8 2003/09/29 23:15:20 dkf Exp $
+ * RCS: @(#) $Id: tkTextImage.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $
*/
#include "tk.h"
@@ -18,15 +18,6 @@
#include "tkPort.h"
/*
- * Definitions for alignment values:
- */
-
-#define ALIGN_BOTTOM 0
-#define ALIGN_CENTER 1
-#define ALIGN_TOP 2
-#define ALIGN_BASELINE 3
-
-/*
* Macro that determines the size of an embedded image segment:
*/
@@ -37,12 +28,6 @@
* Prototypes for procedures defined in this file:
*/
-static int AlignParseProc _ANSI_ARGS_((ClientData clientData,
- Tcl_Interp *interp, Tk_Window tkwin,
- CONST char *value, char *widgRec, int offset));
-static char * AlignPrintProc _ANSI_ARGS_((ClientData clientData,
- Tk_Window tkwin, char *widgRec, int offset,
- Tcl_FreeProc **freeProcPtr));
static TkTextSegment * EmbImageCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr,
TkTextLine *linePtr));
static void EmbImageCheckProc _ANSI_ARGS_((TkTextSegment *segPtr,
@@ -84,30 +69,40 @@ static Tk_SegType tkTextEmbImageType = {
};
/*
+ * Definitions for alignment values:
+ */
+
+static char *alignStrings[] = {
+ "baseline", "bottom", "center", "top", (char *) NULL
+};
+
+typedef enum {
+ ALIGN_BASELINE, ALIGN_BOTTOM, ALIGN_CENTER, ALIGN_TOP
+} alignMode;
+
+/*
* Information used for parsing image configuration options:
*/
-static Tk_CustomOption alignOption = {AlignParseProc, AlignPrintProc,
- (ClientData) NULL};
-
-static Tk_ConfigSpec configSpecs[] = {
- {TK_CONFIG_CUSTOM, "-align", (char *) NULL, (char *) NULL,
- "center", 0, TK_CONFIG_DONT_SET_DEFAULT, &alignOption},
- {TK_CONFIG_PIXELS, "-padx", (char *) NULL, (char *) NULL,
- "0", Tk_Offset(TkTextEmbImage, padX),
- TK_CONFIG_DONT_SET_DEFAULT},
- {TK_CONFIG_PIXELS, "-pady", (char *) NULL, (char *) NULL,
- "0", Tk_Offset(TkTextEmbImage, padY),
- TK_CONFIG_DONT_SET_DEFAULT},
- {TK_CONFIG_STRING, "-image", (char *) NULL, (char *) NULL,
- (char *) NULL, Tk_Offset(TkTextEmbImage, imageString),
- TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK},
- {TK_CONFIG_STRING, "-name", (char *) NULL, (char *) NULL,
- (char *) NULL, Tk_Offset(TkTextEmbImage, imageName),
- TK_CONFIG_DONT_SET_DEFAULT|TK_CONFIG_NULL_OK},
- {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
- (char *) NULL, 0, 0}
+static Tk_OptionSpec optionSpecs[] = {
+ {TK_OPTION_STRING_TABLE, "-align", (char *) NULL, (char *) NULL,
+ "center", -1, Tk_Offset(TkTextEmbImage, align),
+ 0, (ClientData) alignStrings, 0},
+ {TK_OPTION_PIXELS, "-padx", (char *) NULL, (char *) NULL,
+ "0", -1, Tk_Offset(TkTextEmbImage, padX),
+ 0, 0, 0},
+ {TK_OPTION_PIXELS, "-pady", (char *) NULL, (char *) NULL,
+ "0", -1, Tk_Offset(TkTextEmbImage, padY),
+ 0, 0, 0},
+ {TK_OPTION_STRING, "-image", (char *) NULL, (char *) NULL,
+ (char *) NULL, -1, Tk_Offset(TkTextEmbImage, imageString),
+ TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_STRING, "-name", (char *) NULL, (char *) NULL,
+ (char *) NULL, -1, Tk_Offset(TkTextEmbImage, imageName),
+ TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_END}
};
+
/*
*--------------------------------------------------------------
@@ -155,7 +150,8 @@ TkTextImageCmd(textPtr, interp, objc, objv)
return TCL_ERROR;
}
switch ((enum opts) idx) {
- case CMD_CGET:
+ case CMD_CGET: {
+ Tcl_Obj *objPtr;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 3, objv, "index option");
return TCL_ERROR;
@@ -169,9 +165,16 @@ TkTextImageCmd(textPtr, interp, objc, objv)
Tcl_GetString(objv[3]), "\"", (char *) NULL);
return TCL_ERROR;
}
- return Tk_ConfigureValue(interp, textPtr->tkwin, configSpecs,
- (char *) &eiPtr->body.ei, Tcl_GetString(objv[4]), 0);
- case CMD_CONF:
+ objPtr = Tk_GetOptionValue(interp, (char *) &eiPtr->body.ei,
+ eiPtr->body.ei.optionTable, objv[4], textPtr->tkwin);
+ if (objPtr == NULL) {
+ return TCL_ERROR;
+ } else {
+ Tcl_SetObjResult(interp, objPtr);
+ return TCL_OK;
+ }
+ }
+ case CMD_CONF: {
if (objc < 4) {
Tcl_WrongNumArgs(interp, 3, objv, "index ?option value ...?");
return TCL_ERROR;
@@ -185,16 +188,29 @@ TkTextImageCmd(textPtr, interp, objc, objv)
Tcl_GetString(objv[3]), "\"", (char *) NULL);
return TCL_ERROR;
}
- if (objc == 4) {
- return Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
- (char *) &eiPtr->body.ei, (char *) NULL, 0);
- } else if (objc == 5) {
- return Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs,
- (char *) &eiPtr->body.ei, Tcl_GetString(objv[4]), 0);
+ if (objc <= 5) {
+ Tcl_Obj* objPtr = Tk_GetOptionInfo(interp, (char *) &eiPtr->body.ei,
+ eiPtr->body.ei.optionTable,
+ (objc == 5) ? objv[4] : (Tcl_Obj *) NULL,
+ textPtr->tkwin);
+ if (objPtr == NULL) {
+ return TCL_ERROR;
+ } else {
+ Tcl_SetObjResult(interp, objPtr);
+ return TCL_OK;
+ }
} else {
TkTextChanged(textPtr, &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, index.linePtr, 0,
+ TK_TEXT_INVALIDATE_ONLY);
return EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4);
}
+ }
case CMD_CREATE: {
int lineIndex;
@@ -237,6 +253,7 @@ TkTextImageCmd(textPtr, interp, objc, objv)
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
@@ -248,10 +265,12 @@ TkTextImageCmd(textPtr, interp, objc, objv)
if (EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4) != TCL_OK) {
TkTextIndex index2;
- TkTextIndexForwChars(&index, 1, &index2);
+ TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES);
TkBTreeDeleteChars(&index, &index2);
return TCL_ERROR;
}
+ TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0,
+ TK_TEXT_INVALIDATE_ONLY);
return TCL_OK;
}
case CMD_NAMES: {
@@ -312,16 +331,12 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv)
int count = 0; /* The counter for picking a unique name */
int conflict = 0; /* True if we have a name conflict */
unsigned int len; /* length of image name */
- CONST char **argv;
- argv = TkGetStringsFromObjs(objc, objv);
- if (Tk_ConfigureWidget(textPtr->interp, textPtr->tkwin, configSpecs,
- objc, argv, (char *) &eiPtr->body.ei,TK_CONFIG_ARGV_ONLY)
- != TCL_OK) {
- if (argv) ckfree((char *) argv);
+ if (Tk_SetOptions(textPtr->interp, (char*)&eiPtr->body.ei,
+ eiPtr->body.ei.optionTable,
+ objc, objv, textPtr->tkwin, NULL, NULL) != TCL_OK) {
return TCL_ERROR;
}
- if (argv) ckfree((char *) argv);
/*
* Create the image. Save the old image around and don't free it
@@ -331,8 +346,9 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv)
*/
if (eiPtr->body.ei.imageString != NULL) {
- image = Tk_GetImage(textPtr->interp, textPtr->tkwin, eiPtr->body.ei.imageString,
- EmbImageProc, (ClientData) eiPtr);
+ image = Tk_GetImage(textPtr->interp, textPtr->tkwin,
+ eiPtr->body.ei.imageString, EmbImageProc,
+ (ClientData) eiPtr);
if (image == NULL) {
return TCL_ERROR;
}
@@ -350,8 +366,8 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv)
/*
* 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 available, otherwise tack on a #nn and use it. If a name is
+ * already associated with this image, delete the name.
*/
name = eiPtr->body.ei.imageName;
@@ -403,99 +419,6 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv)
/*
*--------------------------------------------------------------
*
- * AlignParseProc --
- *
- * This procedure is invoked by Tk_ConfigureWidget during
- * option processing to handle "-align" options for embedded
- * images.
- *
- * Results:
- * A standard Tcl return value.
- *
- * Side effects:
- * The alignment for the embedded image may change.
- *
- *--------------------------------------------------------------
- */
-
- /* ARGSUSED */
-static int
-AlignParseProc(clientData, interp, tkwin, value, widgRec, offset)
- ClientData clientData; /* Not used.*/
- Tcl_Interp *interp; /* Used for reporting errors. */
- Tk_Window tkwin; /* Window for text widget. */
- CONST char *value; /* Value of option. */
- char *widgRec; /* Pointer to TkTextEmbWindow
- * structure. */
- int offset; /* Offset into item (ignored). */
-{
- register TkTextEmbImage *embPtr = (TkTextEmbImage *) widgRec;
-
- if (strcmp(value, "baseline") == 0) {
- embPtr->align = ALIGN_BASELINE;
- } else if (strcmp(value, "bottom") == 0) {
- embPtr->align = ALIGN_BOTTOM;
- } else if (strcmp(value, "center") == 0) {
- embPtr->align = ALIGN_CENTER;
- } else if (strcmp(value, "top") == 0) {
- embPtr->align = ALIGN_TOP;
- } else {
- Tcl_AppendResult(interp, "bad alignment \"", value,
- "\": must be baseline, bottom, center, or top",
- (char *) NULL);
- return TCL_ERROR;
- }
- return TCL_OK;
-}
-
-/*
- *--------------------------------------------------------------
- *
- * AlignPrintProc --
- *
- * This procedure is invoked by the Tk configuration code
- * to produce a printable string for the "-align" configuration
- * option for embedded images.
- *
- * Results:
- * The return value is a string describing the embedded
- * images's current alignment.
- *
- * Side effects:
- * None.
- *
- *--------------------------------------------------------------
- */
-
- /* ARGSUSED */
-static char *
-AlignPrintProc(clientData, tkwin, widgRec, offset, freeProcPtr)
- ClientData clientData; /* Ignored. */
- Tk_Window tkwin; /* Window for text widget. */
- char *widgRec; /* Pointer to TkTextEmbImage
- * structure. */
- int offset; /* Ignored. */
- Tcl_FreeProc **freeProcPtr; /* Pointer to variable to fill in with
- * information about how to reclaim
- * storage for return string. */
-{
- switch (((TkTextEmbImage *) widgRec)->align) {
- case ALIGN_BASELINE:
- return "baseline";
- case ALIGN_BOTTOM:
- return "bottom";
- case ALIGN_CENTER:
- return "center";
- case ALIGN_TOP:
- return "top";
- default:
- return "??";
- }
-}
-
-/*
- *--------------------------------------------------------------
- *
* EmbImageDeleteProc --
*
* This procedure is invoked by the text B-tree code whenever
@@ -536,11 +459,8 @@ EmbImageDeleteProc(eiPtr, linePtr, treeGone)
}
Tk_FreeImage(eiPtr->body.ei.image);
}
- Tk_FreeOptions(configSpecs, (char *) &eiPtr->body.ei,
- eiPtr->body.ei.textPtr->display, 0);
- if (eiPtr->body.ei.name != NULL) {
- ckfree(eiPtr->body.ei.name);
- }
+ Tk_FreeConfigOptions((char *) &eiPtr->body.ei, eiPtr->body.ei.optionTable,
+ eiPtr->body.ei.textPtr->tkwin);
ckfree((char *) eiPtr);
return 0;
}
@@ -896,4 +816,11 @@ EmbImageProc(clientData, x, y, width, height, imgWidth, imgHeight)
index.linePtr = eiPtr->body.ei.linePtr;
index.byteIndex = TkTextSegToOffset(eiPtr, eiPtr->body.ei.linePtr);
TkTextChanged(eiPtr->body.ei.textPtr, &index, &index);
+ /*
+ * 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.textPtr,
+ index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
}
diff --git a/generic/tkTextIndex.c b/generic/tkTextIndex.c
index 06396db..66caa5a 100644
--- a/generic/tkTextIndex.c
+++ b/generic/tkTextIndex.c
@@ -10,7 +10,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkTextIndex.c,v 1.8 2003/10/10 15:56:22 dkf Exp $
+ * RCS: @(#) $Id: tkTextIndex.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $
*/
#include "default.h"
@@ -25,18 +25,28 @@
#define LAST_CHAR 1000000
/*
+ * Modifiers for index parsing: 'display', 'any' or nothing.
+ */
+#define TKINDEX_NONE 0
+#define TKINDEX_DISPLAY 1
+#define TKINDEX_ANY 2
+
+/*
* Forward declarations for procedures defined later in this file:
*/
-static CONST char * ForwBack _ANSI_ARGS_((CONST char *string,
- TkTextIndex *indexPtr));
-static CONST char * StartEnd _ANSI_ARGS_((CONST char *string,
- TkTextIndex *indexPtr));
+static CONST char * ForwBack _ANSI_ARGS_((TkText *textPtr,
+ CONST char *string, TkTextIndex *indexPtr));
+static CONST char * StartEnd _ANSI_ARGS_((TkText *textPtr,
+ CONST char *string, TkTextIndex *indexPtr));
static int GetIndex _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, CONST char *string,
TkTextIndex *indexPtr, int *canCachePtr));
-
+/*
+ * The "textindex" Tcl_Obj definition:
+ */
+
static void DupTextIndexInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr,
Tcl_Obj *copyPtr));
static void FreeTextIndexInternalRep _ANSI_ARGS_((Tcl_Obj *listPtr));
@@ -96,7 +106,6 @@ DupTextIndexInternalRep(srcPtr, copyPtr)
SET_TEXTINDEX(copyPtr, dupIndexPtr);
SET_INDEXEPOCH(copyPtr, epoch);
}
-
/*
* This will not be called except by TkTextNewIndexObj below.
* This is because if a TkTextIndex is no longer valid, it is
@@ -117,7 +126,6 @@ UpdateStringOfTextIndex(objPtr)
strcpy(objPtr->bytes, buffer);
objPtr->length = len;
}
-
static int
SetTextIndexFromAny(interp, objPtr)
Tcl_Interp *interp; /* Used for error reporting if not NULL. */
@@ -128,7 +136,7 @@ SetTextIndexFromAny(interp, objPtr)
-1);
return TCL_ERROR;
}
-
+
/*
*---------------------------------------------------------------------------
*
@@ -176,7 +184,7 @@ MakeObjIndex(textPtr, objPtr, origPtr)
}
return indexPtr;
}
-
+
CONST TkTextIndex*
TkTextGetIndexFromObj(interp, textPtr, objPtr)
Tcl_Interp *interp; /* Use this for error reporting. */
@@ -274,6 +282,62 @@ TkTextNewIndexObj(textPtr, indexPtr)
/*
*---------------------------------------------------------------------------
*
+ * TkTextMakePixelIndex --
+ *
+ * Given a pixel index and a byte index, look things up in the B-tree
+ * and fill in a TkTextIndex structure.
+ *
+ * Results:
+ *
+ * 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.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TkTextMakePixelIndex(textPtr, pixelIndex, indexPtr)
+ 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. */
+{
+ int pixelOffset = 0;
+
+ indexPtr->tree = textPtr->tree;
+ indexPtr->textPtr = textPtr;
+
+ if (pixelIndex < 0) {
+ pixelIndex = 0;
+ }
+ indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->tree, pixelIndex,
+ &pixelOffset);
+ if (indexPtr->linePtr == NULL) {
+ indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->tree,
+ TkBTreeNumPixels(textPtr->tree)-1, &pixelOffset);
+ indexPtr->byteIndex = 0;
+ return pixelOffset;
+ }
+ indexPtr->byteIndex = 0;
+
+ if (pixelOffset <= 0) {
+ return 0;
+ }
+ return TkTextMeasureDown(textPtr, indexPtr, pixelOffset);
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
* TkTextMakeByteIndex --
*
* Given a line index and a byte index, look things up in the B-tree
@@ -293,7 +357,7 @@ TkTextNewIndexObj(textPtr, indexPtr)
TkTextIndex *
TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr)
- TkTextBTree tree; /* Tree that lineIndex and charIndex refer
+ TkTextBTree tree; /* Tree that lineIndex and byteIndex refer
* to. */
int lineIndex; /* Index of desired line (0 means first
* line of text). */
@@ -827,9 +891,9 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr)
}
if ((*cp == '+') || (*cp == '-')) {
- cp = ForwBack(cp, indexPtr);
+ cp = ForwBack(textPtr, cp, indexPtr);
} else {
- cp = StartEnd(cp, indexPtr);
+ cp = StartEnd(textPtr, cp, indexPtr);
}
if (cp == NULL) {
goto error;
@@ -965,7 +1029,8 @@ TkTextIndexCmp(index1Ptr, index2Ptr)
*/
static CONST char *
-ForwBack(string, indexPtr)
+ForwBack(textPtr, string, indexPtr)
+ TkText *textPtr; /* Information about text widget. */
CONST char *string; /* String to parse for additional info
* about modifier (count and units).
* Points to "+" or "-" that starts
@@ -974,7 +1039,7 @@ ForwBack(string, indexPtr)
{
register CONST char *p, *units;
char *end;
- int count, lineIndex;
+ int count, lineIndex, modifier;
size_t length;
/*
@@ -996,8 +1061,12 @@ ForwBack(string, indexPtr)
/*
* Find the end of this modifier (next space or + or - character),
- * then parse the unit specifier and update the position
- * accordingly.
+ * then check if there is a textual 'display' or 'any' modifier.
+ * These modifiers can be their own word (in which case they can
+ * be abbreviated) or they can follow on to the actual unit in
+ * a single word (in which case no abbreviation is allowed). So,
+ * 'display lines', 'd lines', 'displaylin' are all ok, but 'dline'
+ * is not.
*/
units = p;
@@ -1005,43 +1074,166 @@ ForwBack(string, indexPtr)
p++;
}
length = p - units;
+ if ((*units == 'd') && (strncmp(units, "display",
+ (length > 7 ? 7 : length)) == 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;
+ if (length > 3) {
+ p -= (length - 3);
+ }
+ } else {
+ modifier = TKINDEX_NONE;
+ }
+
+ /*
+ * 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++;
+ }
+ units = p;
+ while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) {
+ p++;
+ }
+ length = p - units;
+ }
+
+ /*
+ * Finally parse the units.
+ */
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 {
+ type = COUNT_DISPLAY_CHARS;
+ }
if (*string == '+') {
- TkTextIndexForwChars(indexPtr, count, indexPtr);
+ TkTextIndexForwChars(indexPtr, count, indexPtr, type);
} else {
- TkTextIndexBackChars(indexPtr, count, indexPtr);
+ TkTextIndexBackChars(indexPtr, count, indexPtr, type);
+ }
+ } else if ((*units == 'i') && (strncmp(units, "indices", length) == 0)) {
+ TkTextCountType type;
+ if (modifier == TKINDEX_DISPLAY) {
+ type = COUNT_DISPLAY_INDICES;
+ } else {
+ type = COUNT_INDICES;
}
- } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) {
- lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
if (*string == '+') {
- lineIndex += count;
+ TkTextIndexForwChars(indexPtr, count, indexPtr, type);
+ } else {
+ TkTextIndexBackChars(indexPtr, count, indexPtr, type);
+ }
+ } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) {
+ if (modifier == TKINDEX_DISPLAY) {
+ /*
+ * Find the appropriate pixel offset of the current position
+ * within its display line. This also has the side-effect of
+ * moving indexPtr, but that doesn't matter since we will do
+ * it again below.
+ *
+ * Then find the right display line, and finally calculated
+ * the index we want in that display line, based on the
+ * original pixel offset.
+ */
+ int xOffset, forward;
+ if (TkTextIsElided(textPtr, indexPtr)) {
+ /* Go forward to the first non-elided index */
+ TkTextIndexForwChars(indexPtr, 0, indexPtr,
+ COUNT_DISPLAY_INDICES | COUNT_IS_ELIDED);
+ }
+ /*
+ * Unlike the Forw/BackChars code, the display line code
+ * is sensitive to whether we are genuinely going
+ * forwards or backwards. So, we need to determine that.
+ * This is important in the case where we have "+ -3
+ * displaylines", for example.
+ */
+ if ((count < 0) ^ (*string == '-')) {
+ forward = 0;
+ } else {
+ forward = 1;
+ }
+ count = abs(count);
+ if (count == 0) {
+ return p;
+ }
+ 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(indexPtr, 1, indexPtr,
+ COUNT_DISPLAY_INDICES
+ | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr)));
+ }
+ } 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(indexPtr, 1, indexPtr,
+ COUNT_DISPLAY_INDICES
+ | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr)));
+ }
+ TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
+ }
+ /*
+ * This call assumes indexPtr is the beginning of a display line
+ * and moves it to the 'xOffset' position of that line, which is
+ * just what we want.
+ */
+ TkTextIndexOfX(textPtr, xOffset, indexPtr);
} else {
- lineIndex -= count;
+ lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
+ if (*string == '+') {
+ lineIndex += count;
+ } else {
+ lineIndex -= count;
+
+ /*
+ * The check below retains the character position, even
+ * if the line runs off the start of the file. Without
+ * it, the character position will get reset to 0 by
+ * TkTextMakeIndex.
+ */
+ if (lineIndex < 0) {
+ lineIndex = 0;
+ }
+ }
/*
- * The check below retains the character position, even
- * if the line runs off the start of the file. Without
- * it, the character position will get reset to 0 by
- * TkTextMakeIndex.
+ * This doesn't work quite right if using a proportional font or
+ * UTF-8 characters with varying numbers of bytes, or if there
+ * are embedded windows, images, etc. The cursor will bop
+ * around, keeping a constant number of bytes (not characters)
+ * from the left edge (but making sure not to split any UTF-8
+ * characters), regardless of the x-position the index
+ * corresponds to. The proper way to do this is to get the
+ * x-position of the index and then pick the character at the
+ * same x-position in the new line.
*/
- if (lineIndex < 0) {
- lineIndex = 0;
- }
+ TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex,
+ indexPtr);
}
- /*
- * This doesn't work quite right if using a proportional font or
- * UTF-8 characters with varying numbers of bytes. The cursor will
- * bop around, keeping a constant number of bytes (not characters)
- * from the left edge (but making sure not to split any UTF-8
- * characters), regardless of the x-position the index corresponds
- * to. The proper way to do this is to get the x-position of the
- * index and then pick the character at the same x-position in the
- * new line.
- */
-
- TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex,
- indexPtr);
} else {
return NULL;
}
@@ -1060,6 +1252,9 @@ ForwBack(string, indexPtr)
* *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" bytes left.
+ *
+ * In this latter case, the function returns '1' to indicate
+ * that not all of 'byteCount' could be used.
*
* Side effects:
* None.
@@ -1067,7 +1262,7 @@ ForwBack(string, indexPtr)
*---------------------------------------------------------------------------
*/
-void
+int
TkTextIndexForwBytes(srcPtr, byteCount, dstPtr)
CONST TkTextIndex *srcPtr; /* Source index. */
int byteCount; /* How many bytes forward to move. May be
@@ -1080,7 +1275,7 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr)
if (byteCount < 0) {
TkTextIndexBackBytes(srcPtr, -byteCount, dstPtr);
- return;
+ return 0;
}
*dstPtr = *srcPtr;
@@ -1102,13 +1297,13 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr)
*/
if (dstPtr->byteIndex < lineLength) {
- return;
+ return 0;
}
dstPtr->byteIndex -= lineLength;
linePtr = TkBTreeNextLine(dstPtr->linePtr);
if (linePtr == NULL) {
dstPtr->byteIndex = lineLength - 1;
- return;
+ return 1;
}
dstPtr->linePtr = linePtr;
}
@@ -1120,12 +1315,18 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr)
* TkTextIndexForwChars --
*
* Given an index for a text widget, this procedure creates a new
- * index that points "count" characters ahead of the source index.
+ * index that points "count" items of type given by "type" ahead of
+ * the source index. "count" can be zero, which is useful in
+ * the case where one wishes to move forward by display
+ * (non-elided) chars or indices or one wishes to move forward
+ * by chars, skipping any intervening indices. In this case
+ * dstPtr will point to the first acceptable index which is
+ * encountered.
*
* Results:
- * *dstPtr is modified to refer to the character "count" characters
+ * *dstPtr is modified to refer to the character "count" items
* after srcPtr, or to the last character in the TkText if there
- * aren't "count" characters left in the file.
+ * aren't sufficient items left in the widget.
*
* Side effects:
* None.
@@ -1134,22 +1335,28 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr)
*/
void
-TkTextIndexForwChars(srcPtr, charCount, dstPtr)
+TkTextIndexForwChars(srcPtr, charCount, dstPtr, type)
CONST TkTextIndex *srcPtr; /* Source index. */
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;
int byteOffset;
char *start, *end, *p;
Tcl_UniChar ch;
+ int elide = 0;
+ int checkElided = (type & COUNT_DISPLAY);
if (charCount < 0) {
- TkTextIndexBackChars(srcPtr, -charCount, dstPtr);
+ TkTextIndexBackChars(srcPtr, -charCount, dstPtr, type);
return;
}
+ if (checkElided) {
+ elide = ((type & COUNT_IS_ELIDED) ? 1 : 0);
+ }
*dstPtr = *srcPtr;
@@ -1166,23 +1373,49 @@ TkTextIndexForwChars(srcPtr, charCount, dstPtr)
*/
for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
- 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);
- return;
+ /*
+ * 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)) {
+ if (segPtr->body.toggle.tagPtr->elideString != NULL) {
+ if (elide) {
+ elide = (segPtr->typePtr == &tkTextToggleOffType)
+ & !segPtr->body.toggle.tagPtr->elide;
+ } else {
+ elide = (segPtr->typePtr == &tkTextToggleOnType)
+ & segPtr->body.toggle.tagPtr->elide;
+ }
}
- charCount--;
}
- } else {
- if (charCount < segPtr->size - byteOffset) {
- dstPtr->byteIndex += charCount;
- return;
+ }
+
+ 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);
+ return;
+ }
+ charCount--;
+ }
+ } else {
+ if (type & COUNT_INDICES) {
+ if (charCount < segPtr->size - byteOffset) {
+ dstPtr->byteIndex += charCount;
+ return;
+ }
+ charCount -= segPtr->size - byteOffset;
+ }
}
- charCount -= segPtr->size - byteOffset;
}
+
dstPtr->byteIndex += segPtr->size - byteOffset;
byteOffset = 0;
}
@@ -1207,6 +1440,155 @@ TkTextIndexForwChars(srcPtr, charCount, dstPtr)
/*
*---------------------------------------------------------------------------
*
+ * TkTextIndexCount --
+ *
+ * Given an ordered pair of indices in a text widget, this
+ * procedure counts how many characters (not bytes) are between
+ * the two indices.
+ *
+ * It is illegal to call this procedure with unordered indices.
+ *
+ * Note that 'textPtr' is only used if we need to check for
+ * elided attributes, i.e. if type is COUNT_DISPLAY_INDICES or
+ * COUNT_DISPLAY_CHARS.
+ *
+ * Results:
+ * The number of characters in the given range, which meet
+ * the appropriate 'type' attributes.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type)
+ CONST TkText *textPtr; /* Overall information about text widget. */
+ 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. */
+ TkTextCountType type; /* The kind of indices to count */
+{
+ TkTextLine *linePtr1;
+ TkTextSegment *segPtr, *seg2Ptr = NULL;
+ int byteOffset, maxBytes;
+ int count = 0;
+ int elide = 0;
+ int checkElided = (type & COUNT_DISPLAY);
+
+ /*
+ * 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;
+
+ seg2Ptr = TkTextIndexToSeg(indexPtr2, &maxBytes);
+
+ if (checkElided) {
+ elide = TkTextIsElided(textPtr, indexPtr1);
+ }
+
+ while (1) {
+ /*
+ * 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)) {
+ if (segPtr->body.toggle.tagPtr->elideString != NULL) {
+ if (elide) {
+ elide = (segPtr->typePtr == &tkTextToggleOffType)
+ & !segPtr->body.toggle.tagPtr->elide;
+ } else {
+ elide = (segPtr->typePtr == &tkTextToggleOnType)
+ & segPtr->body.toggle.tagPtr->elide;
+ }
+ }
+ }
+ if (elide) {
+ if (segPtr == seg2Ptr) {
+ return count;
+ }
+ byteOffset = 0;
+ continue;
+ }
+ }
+
+ 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;
+ }
+ }
+ 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;
+ }
+ }
+ count += byteLen;
+ }
+ }
+ if (segPtr == seg2Ptr) {
+ return count;
+ }
+ 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.
+ */
+
+ linePtr1 = TkBTreeNextLine(linePtr1);
+ if (linePtr1 == NULL) {
+ panic("Reached end of text widget when counting characters");
+ }
+ segPtr = linePtr1->segPtr;
+ }
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
* TkTextIndexBackBytes --
*
* Given an index for a text widget, this procedure creates a new
@@ -1274,12 +1656,18 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr)
* TkTextIndexBackChars --
*
* Given an index for a text widget, this procedure creates a new
- * index that points "count" characters earlier than the source index.
+ * index that points "count" items of type given by "type" earlier
+ * than the source index. "count" can be zero, which is useful in
+ * the case where one wishes to move backward by display
+ * (non-elided) chars or indices or one wishes to move backward by
+ * chars, skipping any intervening indices. In this case the
+ * returned index *dstPtr will point just _after_ the first
+ * acceptable index which is encountered.
*
* Results:
- * *dstPtr is modified to refer to the character "count" characters
- * before srcPtr, or to the first character in the file if there
- * aren't "count" characters earlier than srcPtr.
+ * *dstPtr is modified to refer to the character "count" items
+ * before srcPtr, or to the first index in the window if there
+ * aren't sufficient items earlier than srcPtr.
*
* Side effects:
* None.
@@ -1288,21 +1676,27 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr)
*/
void
-TkTextIndexBackChars(srcPtr, charCount, dstPtr)
+TkTextIndexBackChars(srcPtr, charCount, dstPtr, type)
CONST TkTextIndex *srcPtr; /* Source index. */
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;
int lineIndex, segSize;
CONST char *p, *start, *end;
+ int elide = 0;
+ int checkElided = (type & COUNT_DISPLAY);
- if (charCount <= 0) {
- TkTextIndexForwChars(srcPtr, -charCount, dstPtr);
+ if (charCount < 0) {
+ TkTextIndexForwChars(srcPtr, -charCount, dstPtr, type);
return;
}
-
+ if (checkElided) {
+ elide = ((type & COUNT_IS_ELIDED) ? 1 : 0);
+ }
+
*dstPtr = *srcPtr;
/*
@@ -1320,25 +1714,50 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr)
segSize -= segPtr->size;
}
while (1) {
- 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);
- return;
- }
- if (p == start) {
- break;
+ /*
+ * 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)) {
+ if (segPtr->body.toggle.tagPtr->elideString != NULL) {
+ if (elide) {
+ elide = (segPtr->typePtr == &tkTextToggleOnType)
+ & !segPtr->body.toggle.tagPtr->elide;
+ } else {
+ elide = (segPtr->typePtr == &tkTextToggleOffType)
+ & segPtr->body.toggle.tagPtr->elide;
+ }
}
- charCount--;
}
- } else {
- if (charCount <= segSize) {
- dstPtr->byteIndex -= charCount;
- return;
+ }
+
+ 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);
+ return;
+ }
+ if (p == start) {
+ break;
+ }
+ charCount--;
+ }
+ } else {
+ if (type & COUNT_INDICES) {
+ if (charCount <= segSize) {
+ dstPtr->byteIndex -= charCount;
+ return;
+ }
+ charCount -= segSize;
+ }
}
- charCount -= segSize;
}
dstPtr->byteIndex -= segSize;
@@ -1405,18 +1824,20 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr)
*/
static CONST char *
-StartEnd(string, indexPtr)
+StartEnd(textPtr, string, indexPtr)
+ 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 modifer
* word. */
- TkTextIndex *indexPtr; /* Index to mdoify based on string. */
+ TkTextIndex *indexPtr; /* Index to modify based on string. */
{
CONST char *p;
int c, offset;
size_t length;
register TkTextSegment *segPtr;
-
+ int modifier;
+
/*
* Find the end of the modifier word.
*/
@@ -1425,17 +1846,57 @@ StartEnd(string, indexPtr)
/* Empty loop body. */
}
length = p-string;
+ if ((*string == 'd') && (strncmp(string, "display",
+ (length > 7 ? 7 : length)) == 0)) {
+ modifier = TKINDEX_DISPLAY;
+ if (length > 7) {
+ p -= (length - 7);
+ }
+ } else if ((*string == 'a') && (strncmp(string, "any",
+ (length > 3 ? 3 : length)) == 0)) {
+ modifier = TKINDEX_ANY;
+ if (length > 3) {
+ p -= (length - 3);
+ }
+ } else {
+ modifier = TKINDEX_NONE;
+ }
+
+ /*
+ * 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++;
+ }
+ string = p;
+ while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) {
+ p++;
+ }
+ length = p - string;
+ }
+
if ((*string == 'l') && (strncmp(string, "lineend", length) == 0)
&& (length >= 5)) {
- indexPtr->byteIndex = 0;
- for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL;
- segPtr = segPtr->nextPtr) {
- indexPtr->byteIndex += segPtr->size;
+ if (modifier == TKINDEX_DISPLAY) {
+ TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL);
+ } 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);
}
- indexPtr->byteIndex -= sizeof(char);
} else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0)
&& (length >= 5)) {
- indexPtr->byteIndex = 0;
+ if (modifier == TKINDEX_DISPLAY) {
+ TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
+ } else {
+ indexPtr->byteIndex = 0;
+ }
} else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0)
&& (length >= 5)) {
int firstChar = 1;
@@ -1446,6 +1907,10 @@ StartEnd(string, indexPtr)
* a character that isn't part of a word and stop there.
*/
+ if (modifier == TKINDEX_DISPLAY) {
+ TkTextIndexForwChars(indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES
+ | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr)));
+ }
segPtr = TkTextIndexToSeg(indexPtr, &offset);
while (1) {
if (segPtr->typePtr == &tkTextCharType) {
@@ -1462,12 +1927,21 @@ StartEnd(string, indexPtr)
}
}
if (firstChar) {
- TkTextIndexForwChars(indexPtr, 1, indexPtr);
+ if (modifier == TKINDEX_DISPLAY) {
+ TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_DISPLAY_INDICES
+ | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr)));
+ } else {
+ TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_INDICES);
+ }
}
} else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0)
&& (length >= 5)) {
int firstChar = 1;
+ if (modifier == TKINDEX_DISPLAY) {
+ TkTextIndexForwChars(indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES
+ | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr)));
+ }
/*
* Starting with the current character, look for one that's not
* part of a word and keep moving backward until you find one.
@@ -1495,7 +1969,12 @@ StartEnd(string, indexPtr)
}
}
if (!firstChar) {
- TkTextIndexForwChars(indexPtr, 1, indexPtr);
+ if (modifier == TKINDEX_DISPLAY) {
+ TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_DISPLAY_INDICES
+ | (COUNT_IS_ELIDED * TkTextIsElided(textPtr, indexPtr)));
+ } else {
+ TkTextIndexForwChars(indexPtr, 1, indexPtr, COUNT_INDICES);
+ }
}
} else {
return NULL;
diff --git a/generic/tkTextMark.c b/generic/tkTextMark.c
index d48e10a..7fcb275 100644
--- a/generic/tkTextMark.c
+++ b/generic/tkTextMark.c
@@ -10,7 +10,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkTextMark.c,v 1.8 2003/05/19 14:37:20 dkf Exp $
+ * RCS: @(#) $Id: tkTextMark.c,v 1.9 2003/10/31 09:02:11 vincentdarley Exp $
*/
#include "tkInt.h"
@@ -253,7 +253,7 @@ TkTextMarkCmd(textPtr, interp, objc, objv)
TkTextSegment *
TkTextSetMark(textPtr, name, indexPtr)
TkText *textPtr; /* Text widget in which to create mark. */
- CONST char *name; /* Name of mark to set. */
+ CONST char *name; /* Name of mark to set. */
TkTextIndex *indexPtr; /* Where to set mark. */
{
Tcl_HashEntry *hPtr;
@@ -274,11 +274,15 @@ TkTextSetMark(textPtr, name, indexPtr)
if (markPtr == textPtr->insertMarkPtr) {
TkTextIndex index, index2;
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- TkTextIndexForwChars(&index, 1, &index2);
+ TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES);
+ /*
+ * While we wish to redisplay, no heights have changed, so
+ * no need to call TkTextInvalidateLineMetrics.
+ */
TkTextChanged(textPtr, &index, &index2);
if (TkBTreeLineIndex(indexPtr->linePtr)
== TkBTreeNumLines(textPtr->tree)) {
- TkTextIndexBackChars(indexPtr, 1, &insertIndex);
+ TkTextIndexBackChars(indexPtr, 1, &insertIndex, COUNT_INDICES);
indexPtr = &insertIndex;
}
}
@@ -303,7 +307,11 @@ TkTextSetMark(textPtr, name, indexPtr)
if (markPtr == textPtr->insertMarkPtr) {
TkTextIndex index2;
- TkTextIndexForwChars(indexPtr, 1, &index2);
+ TkTextIndexForwChars(indexPtr, 1, &index2, COUNT_INDICES);
+ /*
+ * While we wish to redisplay, no heights have changed, so
+ * no need to call TkTextInvalidateLineMetrics
+ */
TkTextChanged(textPtr, indexPtr, &index2);
}
return markPtr;
@@ -540,9 +548,20 @@ TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
* corresponds to y. */
{
TkText *textPtr = (TkText *) chunkPtr->clientData;
+ TkTextIndex index;
int halfWidth = textPtr->insertWidth/2;
-
- if ((x + halfWidth) < 0) {
+ int rightSideWidth;
+ int ix = 0, iy = 0, iw = 0, ih = 0, charWidth = 0;
+
+ if(textPtr->insertCursorType) {
+ TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
+ TkTextCharBbox(textPtr, &index, &ix, &iy, &iw, &ih, &charWidth);
+ rightSideWidth = charWidth + halfWidth;
+ } else {
+ rightSideWidth = halfWidth;
+ }
+
+ if ((x + rightSideWidth) < 0) {
/*
* The insertion cursor is off-screen.
* Indicate caret at 0,0 and return.
@@ -564,11 +583,11 @@ TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
if (textPtr->flags & INSERT_ON) {
Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
- x - halfWidth, y, textPtr->insertWidth, height,
+ x - halfWidth, y, charWidth + textPtr->insertWidth, height,
textPtr->insertBorderWidth, TK_RELIEF_RAISED);
} else if (textPtr->selBorder == textPtr->insertBorder) {
Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border,
- x - halfWidth, y, textPtr->insertWidth, height,
+ x - halfWidth, y, charWidth + textPtr->insertWidth, height,
0, TK_RELIEF_FLAT);
}
}
diff --git a/generic/tkTextTag.c b/generic/tkTextTag.c
index e3bf289..00a9193 100644
--- a/generic/tkTextTag.c
+++ b/generic/tkTextTag.c
@@ -11,7 +11,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkTextTag.c,v 1.10 2003/05/27 15:35:53 vincentdarley Exp $
+ * RCS: @(#) $Id: tkTextTag.c,v 1.11 2003/10/31 09:02:12 vincentdarley Exp $
*/
#include "default.h"
@@ -162,7 +162,8 @@ TkTextTagCmd(textPtr, interp, objc, objv)
}
tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(objv[3]));
for (i = 4; i < objc; i += 2) {
- if (TkTextGetObjIndex(interp, textPtr, objv[i], &index1) != TCL_OK) {
+ if (TkTextGetObjIndex(interp, textPtr, objv[i],
+ &index1) != TCL_OK) {
return TCL_ERROR;
}
if (objc > (i+1)) {
@@ -175,7 +176,7 @@ TkTextTagCmd(textPtr, interp, objc, objv)
}
} else {
index2 = index1;
- TkTextIndexForwChars(&index2, 1, &index2);
+ TkTextIndexForwChars(&index2, 1, &index2, COUNT_INDICES);
}
if (tagPtr->affectsDisplay) {
@@ -188,38 +189,40 @@ TkTextTagCmd(textPtr, interp, objc, objv)
TkTextEventuallyRepick(textPtr);
}
- TkBTreeTag(&index1, &index2, tagPtr, addTag);
-
- /*
- * If the tag is "sel" then grab the selection if we're supposed
- * to export it and don't already have it. Also, invalidate
- * partially-completed selection retrievals.
- */
-
- if (tagPtr == textPtr->selTagPtr) {
- XEvent event;
+ if (TkBTreeTag(&index1, &index2, tagPtr, addTag)) {
/*
- * Send an event that the selection changed.
- * This is equivalent to
- * "event generate $textWidget <<Selection>>"
+ * 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.
*/
- memset((VOID *) &event, 0, sizeof(event));
- event.xany.type = VirtualEvent;
- event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
- event.xany.send_event = False;
- event.xany.window = Tk_WindowId(textPtr->tkwin);
- event.xany.display = Tk_Display(textPtr->tkwin);
- ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection");
- Tk_HandleEvent(&event);
-
- if (addTag && textPtr->exportSelection
- && !(textPtr->flags & GOT_SELECTION)) {
- Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY,
- TkTextLostSelection, (ClientData) textPtr);
- textPtr->flags |= GOT_SELECTION;
+ if (tagPtr == textPtr->selTagPtr) {
+ XEvent event;
+ /*
+ * Send an event that the selection changed.
+ * This is equivalent to
+ * "event generate $textWidget <<Selection>>"
+ */
+
+ memset((VOID *) &event, 0, sizeof(event));
+ event.xany.type = VirtualEvent;
+ event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
+ event.xany.send_event = False;
+ event.xany.window = Tk_WindowId(textPtr->tkwin);
+ event.xany.display = Tk_Display(textPtr->tkwin);
+ ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection");
+ Tk_HandleEvent(&event);
+
+ if (addTag && textPtr->exportSelection
+ && !(textPtr->flags & GOT_SELECTION)) {
+ Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY,
+ TkTextLostSelection, (ClientData) textPtr);
+ textPtr->flags |= GOT_SELECTION;
+ }
+ textPtr->abortSelections = 1;
}
- textPtr->abortSelections = 1;
}
}
break;
@@ -464,26 +467,37 @@ TkTextTagCmd(textPtr, interp, objc, objv)
textPtr->selFgColorPtr = tagPtr->fgColor;
}
tagPtr->affectsDisplay = 0;
- if ((tagPtr->border != NULL)
- || (tagPtr->reliefString != NULL)
- || (tagPtr->bgStipple != None)
- || (tagPtr->fgColor != NULL) || (tagPtr->tkfont != None)
- || (tagPtr->fgStipple != None)
+ tagPtr->affectsDisplayGeometry = 0;
+ if ((tagPtr->elideString != NULL)
+ || (tagPtr->tkfont != None)
|| (tagPtr->justifyString != NULL)
|| (tagPtr->lMargin1String != NULL)
|| (tagPtr->lMargin2String != NULL)
|| (tagPtr->offsetString != NULL)
- || (tagPtr->overstrikeString != NULL)
|| (tagPtr->rMarginString != NULL)
|| (tagPtr->spacing1String != NULL)
|| (tagPtr->spacing2String != NULL)
|| (tagPtr->spacing3String != NULL)
|| (tagPtr->tabStringPtr != NULL)
- || (tagPtr->underlineString != NULL)
- || (tagPtr->elideString != NULL)
|| (tagPtr->wrapMode != TEXT_WRAPMODE_NULL)) {
tagPtr->affectsDisplay = 1;
+ tagPtr->affectsDisplayGeometry = 1;
+ }
+ if ((tagPtr->border != NULL)
+ || (tagPtr->reliefString != NULL)
+ || (tagPtr->bgStipple != None)
+ || (tagPtr->fgColor != NULL)
+ || (tagPtr->fgStipple != None)
+ || (tagPtr->overstrikeString != NULL)
+ || (tagPtr->underlineString != NULL)) {
+ tagPtr->affectsDisplay = 1;
}
+ /*
+ * This line is totally unnecessary if this is a new
+ * tag, since it can't possibly have been applied to
+ * anything yet. We might wish to test for that
+ * case specially
+ */
TkTextRedrawTag(textPtr, (TkTextIndex *) NULL,
(TkTextIndex *) NULL, tagPtr, 1);
return result;
@@ -582,7 +596,8 @@ TkTextTagCmd(textPtr, interp, objc, objv)
case TAG_NAMES: {
TkTextTag **arrayPtr;
int arraySize;
-
+ Tcl_Obj *listObj;
+
if ((objc != 3) && (objc != 4)) {
Tcl_WrongNumArgs(interp, 3, objv, "?index?");
return TCL_ERROR;
@@ -609,10 +624,13 @@ TkTextTagCmd(textPtr, interp, objc, objv)
}
}
SortTags(arraySize, arrayPtr);
+ listObj = Tcl_NewListObj(0, NULL);
for (i = 0; i < arraySize; i++) {
tagPtr = arrayPtr[i];
- Tcl_AppendElement(interp, tagPtr->name);
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewStringObj(tagPtr->name,-1));
}
+ Tcl_SetObjResult(interp, listObj);
ckfree((char *) arrayPtr);
break;
}
@@ -778,7 +796,7 @@ TkTextTagCmd(textPtr, interp, objc, objv)
}
case TAG_RANGES: {
TkTextSearch tSearch;
- char position[TK_POS_CHARS];
+ Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "tagName");
@@ -793,13 +811,14 @@ TkTextTagCmd(textPtr, interp, objc, objv)
0, &last);
TkBTreeStartSearch(&first, &last, tagPtr, &tSearch);
if (TkBTreeCharTagged(&first, tagPtr)) {
- TkTextPrintIndex(&first, position);
- Tcl_AppendElement(interp, position);
+ Tcl_ListObjAppendElement(interp, listObj,
+ TkTextNewIndexObj(textPtr, &first));
}
while (TkBTreeNextTag(&tSearch)) {
- TkTextPrintIndex(&tSearch.curIndex, position);
- Tcl_AppendElement(interp, position);
+ Tcl_ListObjAppendElement(interp, listObj,
+ TkTextNewIndexObj(textPtr, &tSearch.curIndex));
}
+ Tcl_SetObjResult(interp, listObj);
break;
}
}
@@ -883,6 +902,7 @@ TkTextCreateTag(textPtr, tagName)
tagPtr->elide = 0;
tagPtr->wrapMode = TEXT_WRAPMODE_NULL;
tagPtr->affectsDisplay = 0;
+ tagPtr->affectsDisplayGeometry = 0;
textPtr->numTags++;
Tcl_SetHashValue(hPtr, tagPtr);
tagPtr->optionTable = Tk_CreateOptionTable(textPtr->interp, tagOptionSpecs);
@@ -1132,7 +1152,7 @@ TkTextBindProc(clientData, eventPtr)
# define AnyButtonMask (Button1Mask|Button2Mask|Button3Mask\
|Button4Mask|Button5Mask)
- Tcl_Preserve((ClientData) textPtr);
+ textPtr->refCount++;
/*
* This code simulates grabs for mouse buttons by keeping track
@@ -1197,12 +1217,16 @@ TkTextBindProc(clientData, eventPtr)
oldState = eventPtr->xbutton.state;
eventPtr->xbutton.state &= ~(Button1Mask|Button2Mask
|Button3Mask|Button4Mask|Button5Mask);
- TkTextPickCurrent(textPtr, eventPtr);
+ if (!(textPtr->flags & DESTROYED)) {
+ TkTextPickCurrent(textPtr, eventPtr);
+ }
eventPtr->xbutton.state = oldState;
}
done:
- Tcl_Release((ClientData) textPtr);
+ if (--textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ }
}
/*
@@ -1224,7 +1248,7 @@ TkTextBindProc(clientData, eventPtr)
* 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
- * with calls to Tcl_Preserve and Tcl_Release.
+ * by incrementing the refCount of the text widget.
*
*--------------------------------------------------------------
*/
diff --git a/generic/tkTextWind.c b/generic/tkTextWind.c
index cc0449e..25991ac 100644
--- a/generic/tkTextWind.c
+++ b/generic/tkTextWind.c
@@ -11,7 +11,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkTextWind.c,v 1.7 2003/05/19 13:04:24 vincentdarley Exp $
+ * RCS: @(#) $Id: tkTextWind.c,v 1.8 2003/10/31 09:02:12 vincentdarley Exp $
*/
#include "tk.h"
@@ -59,10 +59,6 @@ static void EmbWinDelayedUnmap _ANSI_ARGS_((
ClientData clientData));
static int EmbWinDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr,
TkTextLine *linePtr, int treeGone));
-static void EmbWinDisplayProc _ANSI_ARGS_((
- TkTextDispChunk *chunkPtr, int x, int y,
- int lineHeight, int baseline, Display *display,
- Drawable dst, int screenY));
static int EmbWinLayoutProc _ANSI_ARGS_((TkText *textPtr,
TkTextIndex *indexPtr, TkTextSegment *segPtr,
int offset, int maxX, int maxChars,
@@ -231,6 +227,13 @@ TkTextWindowCmd(textPtr, interp, objc, objv)
}
} else {
TkTextChanged(textPtr, &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, index.linePtr, 0,
+ TK_TEXT_INVALIDATE_ONLY);
return EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4);
}
break;
@@ -289,10 +292,12 @@ TkTextWindowCmd(textPtr, interp, objc, objv)
if (EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4) != TCL_OK) {
TkTextIndex index2;
- TkTextIndexForwChars(&index, 1, &index2);
+ TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES);
TkBTreeDeleteChars(&index, &index2);
return TCL_ERROR;
}
+ TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0,
+ TK_TEXT_INVALIDATE_ONLY);
break;
}
case WIND_NAMES: {
@@ -463,6 +468,8 @@ EmbWinStructureProc(clientData, eventPtr)
index.linePtr = ewPtr->body.ew.linePtr;
index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr);
TkTextChanged(ewPtr->body.ew.textPtr, &index, &index);
+ TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr,
+ index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
}
/*
@@ -497,6 +504,8 @@ EmbWinRequestProc(clientData, tkwin)
index.linePtr = ewPtr->body.ew.linePtr;
index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr);
TkTextChanged(ewPtr->body.ew.textPtr, &index, &index);
+ TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr,
+ index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
}
/*
@@ -542,6 +551,8 @@ EmbWinLostSlaveProc(clientData, tkwin)
index.linePtr = ewPtr->body.ew.linePtr;
index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr);
TkTextChanged(ewPtr->body.ew.textPtr, &index, &index);
+ TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr,
+ index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY);
}
/*
@@ -763,7 +774,7 @@ EmbWinLayoutProc(textPtr, indexPtr, ewPtr, offset, maxX, maxChars,
* Fill in the chunk structure.
*/
- chunkPtr->displayProc = EmbWinDisplayProc;
+ chunkPtr->displayProc = TkTextEmbWinDisplayProc;
chunkPtr->undisplayProc = EmbWinUndisplayProc;
chunkPtr->measureProc = (Tk_ChunkMeasureProc *) NULL;
chunkPtr->bboxProc = EmbWinBboxProc;
@@ -819,7 +830,7 @@ EmbWinCheckProc(ewPtr, linePtr)
/*
*--------------------------------------------------------------
*
- * EmbWinDisplayProc --
+ * TkTextEmbWinDisplayProc --
*
* This procedure is invoked by the text displaying code
* when it is time to actually draw an embedded window
@@ -835,8 +846,9 @@ EmbWinCheckProc(ewPtr, linePtr)
*--------------------------------------------------------------
*/
-static void
-EmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY)
+void
+TkTextEmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline,
+ display, dst, screenY)
TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */
int x; /* X-position in dst at which to
* draw this chunk (differs from
@@ -848,8 +860,10 @@ EmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY)
* the chunk itself). */
int lineHeight; /* 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 */
+ 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. */
{