diff options
author | vincentdarley <vincentdarley> | 2004-09-10 12:13:38 (GMT) |
---|---|---|
committer | vincentdarley <vincentdarley> | 2004-09-10 12:13:38 (GMT) |
commit | 09324dada308a84a1d5ba8b14bff2a5ce8b6eaf9 (patch) | |
tree | c17ff6a17da4273024607033b6c1bd7bf35d2d8f /generic | |
parent | 77f2c1e62ab0760dc6ee615d6bbcb81b11d76a6f (diff) | |
download | tk-09324dada308a84a1d5ba8b14bff2a5ce8b6eaf9.zip tk-09324dada308a84a1d5ba8b14bff2a5ce8b6eaf9.tar.gz tk-09324dada308a84a1d5ba8b14bff2a5ce8b6eaf9.tar.bz2 |
text widget 'peer' subcommand -- TIP#169 implementation
Diffstat (limited to 'generic')
-rw-r--r-- | generic/tkTest.c | 11 | ||||
-rw-r--r-- | generic/tkText.c | 1632 | ||||
-rw-r--r-- | generic/tkText.h | 355 | ||||
-rw-r--r-- | generic/tkTextBTree.c | 1045 | ||||
-rw-r--r-- | generic/tkTextDisp.c | 489 | ||||
-rw-r--r-- | generic/tkTextImage.c | 71 | ||||
-rw-r--r-- | generic/tkTextIndex.c | 216 | ||||
-rw-r--r-- | generic/tkTextMark.c | 247 | ||||
-rw-r--r-- | generic/tkTextTag.c | 486 | ||||
-rw-r--r-- | generic/tkTextWind.c | 510 | ||||
-rw-r--r-- | generic/tkUndo.c | 302 | ||||
-rw-r--r-- | generic/tkUndo.h | 118 |
12 files changed, 4155 insertions, 1327 deletions
diff --git a/generic/tkTest.c b/generic/tkTest.c index f3f5931..7a5256f 100644 --- a/generic/tkTest.c +++ b/generic/tkTest.c @@ -13,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: tkTest.c,v 1.23 2004/03/17 18:15:44 das Exp $ + * RCS: @(#) $Id: tkTest.c,v 1.24 2004/09/10 12:13:40 vincentdarley Exp $ */ #include "tkInt.h" @@ -2338,7 +2338,8 @@ TesttextCmd(clientData, interp, argc, argv) lineIndex = atoi(argv[3]) - 1; byteIndex = atoi(argv[4]); - TkTextMakeByteIndex(textPtr->tree, lineIndex, byteIndex, &index); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineIndex, + byteIndex, &index); } else if (strncmp(argv[2], "forwbytes", len) == 0) { if (argc != 5) { return TCL_ERROR; @@ -2347,7 +2348,7 @@ TesttextCmd(clientData, interp, argc, argv) return TCL_ERROR; } byteOffset = atoi(argv[4]); - TkTextIndexForwBytes(&index, byteOffset, &index); + TkTextIndexForwBytes(textPtr, &index, byteOffset, &index); } else if (strncmp(argv[2], "backbytes", len) == 0) { if (argc != 5) { return TCL_ERROR; @@ -2356,13 +2357,13 @@ TesttextCmd(clientData, interp, argc, argv) return TCL_ERROR; } byteOffset = atoi(argv[4]); - TkTextIndexBackBytes(&index, byteOffset, &index); + TkTextIndexBackBytes(textPtr, &index, byteOffset, &index); } else { return TCL_ERROR; } TkTextSetMark(textPtr, "insert", &index); - TkTextPrintIndex(&index, buf); + TkTextPrintIndex(textPtr, &index, buf); sprintf(buf + strlen(buf), " %d", index.byteIndex); Tcl_AppendResult(interp, buf, NULL); diff --git a/generic/tkText.c b/generic/tkText.c index fecbdb0..a920b20 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -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.51 2004/06/09 22:39:08 vincentdarley Exp $ + * RCS: @(#) $Id: tkText.c,v 1.52 2004/09/10 12:13:40 vincentdarley Exp $ */ #include "default.h" @@ -38,6 +38,13 @@ #include "tkText.h" +/* + * Used to avoid having to allocate and deallocate arrays on the + * fly for commonly used procedures. Must be > 0. + */ + +#define PIXEL_CLIENTS 5 + /* * The 'TkTextState' enum in tkText.h is used to define a type for the * -state option of the Text widget. These values are used as indices @@ -59,6 +66,36 @@ static char *wrapStrings[] = { }; /* + * The following functions and custom option type are used to define the + * "line" option type, and thereby handle the text widget '-startline', + * '-endline' configuration options which are of that type. + * + * We do not need a 'freeProc' because all changes to these two options + * are handled through the TK_TEXT_LINE_RANGE flag in the optionSpecs + * list, and the internal storage is just a pointer, which therefore + * doesn't need freeing. + */ +static int SetLineStartEnd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, Tk_Window tkwin, + Tcl_Obj **value, char *recordPtr, int internalOffset, + char *oldInternalPtr, int flags)); +static Tcl_Obj* GetLineStartEnd _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin, + char *recordPtr, int internalOffset)); +static void RestoreLineStartEnd _ANSI_ARGS_((ClientData clientData, + Tk_Window tkwin, char *internalPtr, + char *oldInternalPtr)); +static int ObjectIsEmpty _ANSI_ARGS_((Tcl_Obj *objPtr)); + +static Tk_ObjCustomOption lineOption = { + "line", /* name */ + SetLineStartEnd, /* setProc */ + GetLineStartEnd, /* getProc */ + RestoreLineStartEnd, /* restoreProc */ + (Tk_CustomOptionFreeProc *)NULL, /* freeProc */ + 0 +}; + +/* * Information used to parse text configuration options: */ @@ -83,6 +120,9 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor), TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_CUSTOM, "-endline", (char *) NULL, (char *) NULL, + NULL, -1, Tk_Offset(TkText, end), TK_OPTION_NULL_OK, + (ClientData) &lineOption, TK_TEXT_LINE_RANGE}, {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection", "ExportSelection", DEF_TEXT_EXPORT_SELECTION, -1, Tk_Offset(TkText, exportSelection), 0, 0, 0}, @@ -106,6 +146,15 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness", DEF_TEXT_HIGHLIGHT_WIDTH, -1, Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY}, + {TK_OPTION_BORDER, "-inactiveselectbackground", "inactiveSelectBackground", + "Foreground", +#ifdef ALWAYS_SHOW_SELECTION + DEF_TEXT_SELECT_COLOR, +#else + NULL, +#endif + -1, Tk_Offset(TkText, inactiveSelBorder), + TK_OPTION_NULL_OK, (ClientData) DEF_TEXT_SELECT_MONO, 0}, {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground", DEF_TEXT_INSERT_BG, -1, Tk_Offset(TkText, insertBorder), @@ -154,6 +203,9 @@ static Tk_OptionSpec optionSpecs[] = { {TK_OPTION_PIXELS, "-spacing3", "spacing3", "Spacing", DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3), TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY }, + {TK_OPTION_CUSTOM, "-startline", (char *) NULL, (char *) NULL, + NULL, -1, Tk_Offset(TkText, start), TK_OPTION_NULL_OK, + (ClientData) &lineOption, TK_TEXT_LINE_RANGE}, {TK_OPTION_STRING_TABLE, "-state", "state", "State", DEF_TEXT_STATE, -1, Tk_Offset(TkText, state), 0, (ClientData) stateStrings, 0}, @@ -271,36 +323,43 @@ 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, +static int DeleteChars _ANSI_ARGS_((TkSharedText *sharedPtr, + TkText *textPtr, CONST TkTextIndex *indexPtr1, - CONST TkTextIndex *indexPtr2, int noViewUpdate)); + CONST TkTextIndex *indexPtr2, int viewUpdate)); 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, +static int InsertChars _ANSI_ARGS_((TkSharedText *sharedTextPtr, + TkText *textPtr, TkTextIndex *indexPtr, Tcl_Obj *stringPtr, - int noViewUpdate)); + int viewUpdate)); static void TextBlinkProc _ANSI_ARGS_((ClientData clientData)); static void TextCmdDeletedProc _ANSI_ARGS_(( ClientData clientData)); +static int CreateWidget _ANSI_ARGS_((TkSharedText *sharedPtr, + Tk_Window tkwin, Tcl_Interp *interp, + CONST TkText *parent, + int objc, Tcl_Obj *CONST objv[])); static void TextEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); 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, +static int TextInsertCmd _ANSI_ARGS_((TkSharedText *sharedTextPtr, + TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[], - CONST TkTextIndex *indexPtr, int noViewUpdate)); + CONST TkTextIndex *indexPtr, int viewUpdate)); static int TextReplaceCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, CONST TkTextIndex *indexFromPtr, CONST TkTextIndex *indexToPtr, int objc, Tcl_Obj *CONST objv[], - int noViewUpdate)); + int viewUpdate)); static int TextSearchCmd _ANSI_ARGS_((TkText *textPtr, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); @@ -310,6 +369,9 @@ static int TextEditCmd _ANSI_ARGS_((TkText *textPtr, static int TextWidgetObjCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); +static int SharedTextObjCmd _ANSI_ARGS_((ClientData clientData, + Tcl_Interp *interp, + int objc, Tcl_Obj *CONST objv[])); static void TextWorldChangedCallback _ANSI_ARGS_(( ClientData instanceData)); static void TextWorldChanged _ANSI_ARGS_((TkText *textPtr, @@ -321,7 +383,8 @@ 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, +static int DumpSegment _ANSI_ARGS_((TkText *textPtr, + Tcl_Interp *interp, CONST char *key, CONST char *value, CONST char * command, CONST TkTextIndex *index, int what)); @@ -330,7 +393,7 @@ static int TextEditRedo _ANSI_ARGS_((TkText *textPtr)); 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 UpdateDirtyFlag _ANSI_ARGS_((TkSharedText *sharedPtr)); static void TextPushUndoAction _ANSI_ARGS_((TkText *textPtr, Tcl_Obj *undoString, int insert, CONST TkTextIndex *index1Ptr, @@ -338,6 +401,10 @@ static void TextPushUndoAction _ANSI_ARGS_((TkText *textPtr, static int TextSearchIndexInLine _ANSI_ARGS_(( CONST SearchSpec *searchSpecPtr, TkTextLine *linePtr, int byteIndex)); +static int TextPeerCmd _ANSI_ARGS_((TkText *textPtr, + Tcl_Interp *interp, int objc, + Tcl_Obj *CONST objv[])); +static TkUndoProc TextUndoRedoCallback; /* * Declarations of the three search procs required by @@ -386,16 +453,52 @@ Tk_TextObjCmd(clientData, interp, objc, objv) Tcl_Obj *CONST objv[]; /* Argument objects. */ { Tk_Window tkwin = (Tk_Window) clientData; - Tk_Window new; - Tk_OptionTable optionTable; - register TkText *textPtr; - TkTextIndex startIndex; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?"); return TCL_ERROR; } + return CreateWidget(NULL, tkwin, interp, NULL, objc, objv); +} + +/* + *-------------------------------------------------------------- + * + * CreateWidget -- + * + * This procedure is invoked to process the "text" Tcl command, + * (when called by Tk_TextObjCmd) and the "$text peer create" + * text widget sub-command (called from TextPeerCmd). + * + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result, places the name of the widget + * created into the interp's result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +static int +CreateWidget(sharedPtr, tkwin, interp, parent, objc, objv) + TkSharedText *sharedPtr; /* Shared widget info, or null */ + Tk_Window tkwin; /* Main window associated with + * interpreter. */ + Tcl_Interp *interp; /* Current interpreter. */ + CONST TkText *parent; /* If non-NULL then take default + * start, end from this parent. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ +{ + register TkText *textPtr; + Tk_OptionTable optionTable; + TkTextIndex startIndex; + Tk_Window new; + /* * Create the window. */ @@ -408,7 +511,9 @@ Tk_TextObjCmd(clientData, interp, objc, objv) /* * Create the text widget and initialize everything to zero, - * then set the necessary initial (non-NULL) values. + * then set the necessary initial (non-NULL) values. It is + * important that the 'set' tag and 'insert', 'current' mark + * pointers are all NULL to start. */ textPtr = (TkText *) ckalloc(sizeof(TkText)); @@ -420,11 +525,61 @@ Tk_TextObjCmd(clientData, interp, objc, objv) textPtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(textPtr->tkwin), TextWidgetObjCmd, (ClientData) textPtr, TextCmdDeletedProc); - textPtr->tree = TkBTreeCreate(textPtr); - Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS); - Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS); - Tcl_InitHashTable(&textPtr->windowTable, TCL_STRING_KEYS); - Tcl_InitHashTable(&textPtr->imageTable, TCL_STRING_KEYS); + + if (sharedPtr == NULL) { + sharedPtr = (TkSharedText *) ckalloc(sizeof(TkSharedText)); + memset((VOID *) sharedPtr, 0, sizeof(TkSharedText)); + + sharedPtr->refCount = 0; + sharedPtr->peers = NULL; + sharedPtr->tree = TkBTreeCreate(sharedPtr); + + Tcl_InitHashTable(&sharedPtr->tagTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&sharedPtr->markTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&sharedPtr->windowTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&sharedPtr->imageTable, TCL_STRING_KEYS); + sharedPtr->undoStack = TkUndoInitStack(interp,0); + sharedPtr->undo = 1; + sharedPtr->isDirtyIncrement = 1; + sharedPtr->autoSeparators = 1; + sharedPtr->lastEditMode = TK_TEXT_EDIT_OTHER; + sharedPtr->stateEpoch = 0; + } + + /* Add the new widget to the shared list */ + textPtr->sharedTextPtr = sharedPtr; + sharedPtr->refCount++; + textPtr->next = sharedPtr->peers; + sharedPtr->peers = textPtr; + /* + * This refCount will be held until DestroyText is called. + * Note also that the later call to 'TkTextCreateDInfo' + * will add more refCounts. + */ + textPtr->refCount = 1; + + /* + * Specify start and end lines in the B-tree. The default + * is the same as the parent, but this can be adjusted to + * display more or less if the start, end where given + * as configuration options. + */ + if (parent != NULL) { + textPtr->start = parent->start; + textPtr->end = parent->end; + } else { + textPtr->start = NULL; + textPtr->end = NULL; + } + + /* + * Register with the B-tree. In some sense it would be best + * if we could do this later (after configuration options), + * so that any changes to start,end do not require a total + * recalculation. + */ + TkBTreeAddClient(sharedPtr->tree, textPtr, textPtr->charHeight); + textPtr->state = TK_TEXT_STATE_NORMAL; textPtr->relief = TK_RELIEF_FLAT; textPtr->cursor = None; @@ -433,33 +588,31 @@ Tk_TextObjCmd(clientData, interp, objc, objv) 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; + /* This will add refCounts to textPtr */ TkTextCreateDInfo(textPtr); - TkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + 0, 0, &startIndex); TkTextSetYView(textPtr, &startIndex, 0); textPtr->exportSelection = 1; textPtr->pickEvent.type = LeaveNotify; - textPtr->undoStack = TkUndoInitStack(interp,0); - textPtr->undo = 1; - textPtr->isDirtyIncrement = 1; - textPtr->autoSeparators = 1; - textPtr->lastEditMode = TK_TEXT_EDIT_OTHER; + textPtr->undo = textPtr->sharedTextPtr->undo; + textPtr->maxUndo = textPtr->sharedTextPtr->maxUndo; + textPtr->autoSeparators = textPtr->sharedTextPtr->autoSeparators; textPtr->tabOptionPtr = NULL; - textPtr->stateEpoch = 0; /* * Create the "sel" tag and the "current" and "insert" marks. */ textPtr->selBorder = NULL; + textPtr->inactiveSelBorder = NULL; textPtr->selBorderWidth = 0; textPtr->selBorderWidthPtr = NULL; textPtr->selFgColorPtr = NULL; + /* + * Note: it is important that textPtr->selTagPtr is NULL before + * this initial call. + */ textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel", NULL); textPtr->selTagPtr->reliefString = (char *) ckalloc(sizeof(DEF_TEXT_SELECT_RELIEF)); @@ -477,7 +630,7 @@ Tk_TextObjCmd(clientData, interp, objc, objv) Tk_SetClass(textPtr->tkwin, "Text"); Tk_SetClassProcs(textPtr->tkwin, &textClass, (ClientData) textPtr); - textPtr->optionTable = optionTable; + textPtr->optionTable = optionTable; Tk_CreateEventHandler(textPtr->tkwin, ExposureMask|StructureNotifyMask|FocusChangeMask, @@ -499,8 +652,8 @@ Tk_TextObjCmd(clientData, interp, objc, objv) return TCL_ERROR; } - Tcl_SetStringObj(Tcl_GetObjResult(interp), Tk_PathName(textPtr->tkwin), - -1); + Tcl_SetObjResult(interp, + Tcl_NewStringObj(Tk_PathName(textPtr->tkwin),-1)); return TCL_OK; } @@ -536,14 +689,14 @@ TextWidgetObjCmd(clientData, interp, objc, objv) static CONST char *optionStrings[] = { "bbox", "cget", "compare", "configure", "count", "debug", "delete", "dlineinfo", "dump", "edit", "get", "image", "index", - "insert", "mark", "replace", "scan", "search", "see", + "insert", "mark", "peer", "replace", "scan", "search", "see", "tag", "window", "xview", "yview", (char *) NULL }; enum options { TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_COUNT, TEXT_DEBUG, TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT, TEXT_GET, TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK, - TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE, + TEXT_PEER, TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE, TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW }; @@ -740,8 +893,9 @@ TextWidgetObjCmd(clientData, interp, objc, objv) indexToPtr = tmpPtr; } - lastPtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); + lastPtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); fromPtr = indexFromPtr->linePtr; if (fromPtr == lastPtr) { goto countDone; @@ -753,7 +907,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) */ while (fromPtr != indexToPtr->linePtr) { value += TkTextUpdateOneLine(textPtr, fromPtr, 0, NULL); - fromPtr = TkBTreeNextLine(fromPtr); + fromPtr = TkBTreeNextLine(textPtr, fromPtr); } /* * Now we need to adjust the count to add on the @@ -770,7 +924,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) if (index.byteIndex >= indexFromPtr->byteIndex) { break; } - TkTextIndexForwBytes(&index, 1, &index); + TkTextIndexForwBytes(textPtr, &index, 1, &index); value--; } if (indexToPtr->linePtr != lastPtr) { @@ -781,7 +935,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) if (index.byteIndex >= indexToPtr->byteIndex) { break; } - TkTextIndexForwBytes(&index, 1, &index); + TkTextIndexForwBytes(textPtr, &index, 1, &index); value++; } } @@ -793,8 +947,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) value = CountIndices(textPtr, indexFromPtr, indexToPtr, COUNT_INDICES); } else if (c == 'l' && !strncmp("-lines",option,length)) { - value = TkBTreeLineIndex(indexToPtr->linePtr) - - TkBTreeLineIndex(indexFromPtr->linePtr); + value = TkBTreeLinesTo(textPtr, indexToPtr->linePtr) + - TkBTreeLinesTo(textPtr, indexFromPtr->linePtr); } else if (c == 'u' && !strncmp("-update",option,length)) { update = 1; continue; @@ -809,8 +963,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } else if (c == 'y' && !strncmp("-ypixels",option,length)) { if (update) { TkTextUpdateLineMetrics(textPtr, - TkBTreeLineIndex(indexFromPtr->linePtr), - TkBTreeLineIndex(indexToPtr->linePtr), -1); + TkBTreeLinesTo(textPtr, indexFromPtr->linePtr), + TkBTreeLinesTo(textPtr, indexToPtr->linePtr), -1); } value = TkTextIndexYPixels(textPtr, indexToPtr) - TkTextIndexYPixels(textPtr, indexFromPtr); @@ -895,7 +1049,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } else { indexPtr2 = NULL; } - DeleteChars(textPtr, indexPtr1, indexPtr2, 0); + DeleteChars(NULL, textPtr, indexPtr1, indexPtr2, 1); } else { int i; /* @@ -990,8 +1144,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) * We don't need to check the return value * because all indices are preparsed above. */ - DeleteChars(textPtr, &indices[i], - &indices[i+1], 0); + DeleteChars(NULL, textPtr, &indices[i], + &indices[i+1], 1); } } ckfree((char *) indices); @@ -1085,7 +1239,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv) goto done; } if (i+1 == objc) { - TkTextIndexForwChars(NULL, index1Ptr, 1, &index2, COUNT_INDICES); + TkTextIndexForwChars(NULL, index1Ptr, + 1, &index2, COUNT_INDICES); index2Ptr = &index2; } else { index2Ptr = TkTextGetIndexFromObj(interp, textPtr, @@ -1165,8 +1320,9 @@ TextWidgetObjCmd(clientData, interp, objc, objv) goto done; } if (textPtr->state == TK_TEXT_STATE_NORMAL) { - result = TextInsertCmd(textPtr, interp, objc-3, objv+3, - indexPtr, 0); + result = TextInsertCmd(NULL, textPtr, interp, + objc-3, objv+3, + indexPtr, 1); } break; } @@ -1174,6 +1330,10 @@ TextWidgetObjCmd(clientData, interp, objc, objv) result = TkTextMarkCmd(textPtr, interp, objc, objv); break; } + case TEXT_PEER: { + result = TextPeerCmd(textPtr, interp, objc, objv); + break; + } case TEXT_REPLACE: { CONST TkTextIndex *indexFromPtr, *indexToPtr; @@ -1219,7 +1379,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv) * off-screen. */ byteIndex = textPtr->topIndex.byteIndex; - lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr); + lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr); TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); @@ -1250,31 +1410,32 @@ TextWidgetObjCmd(clientData, interp, objc, objv) result = TextReplaceCmd(textPtr, interp, indexFromPtr, indexToPtr, - objc, objv, 1); + objc, objv, 0); if (result == TCL_OK) { /* * Move the insertion position to the correct * place */ - TkTextIndexForwChars(NULL, indexFromPtr, deleteInsertOffset, + TkTextIndexForwChars(NULL, indexFromPtr, + deleteInsertOffset, &index, COUNT_INDICES); - TkBTreeUnlinkSegment(textPtr->tree, - textPtr->insertMarkPtr, + TkBTreeUnlinkSegment(textPtr->insertMarkPtr, textPtr->insertMarkPtr->body.mark.linePtr); TkBTreeLinkSegment(textPtr->insertMarkPtr, &index); } } else { result = TextReplaceCmd(textPtr, interp, indexFromPtr, indexToPtr, - objc, objv, 0); + objc, objv, 1); } if (result == TCL_OK) { /* * Now ensure the top-line is in the right * place */ - TkTextMakeByteIndex(textPtr->tree, lineNum, + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, + textPtr, lineNum, byteIndex, &index); TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST); } @@ -1320,6 +1481,190 @@ TextWidgetObjCmd(clientData, interp, objc, objv) } /* + *-------------------------------------------------------------- + * + * SharedTextObjCmd -- + * + * This procedure is invoked to process commands on the shared + * portion of a text widget. Currently it is not actually exported + * as a Tcl command, and is only used internally to process parts + * of undo/redo scripts. See the user documentation for 'text' for + * details on what it does - the only subcommands it currently + * supports are 'insert' and 'delete'. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation for "text". + * + *-------------------------------------------------------------- + */ + +static int +SharedTextObjCmd(clientData, interp, objc, objv) + ClientData clientData; /* Information about shared test + * B-tree. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ +{ + register TkSharedText *sharedPtr = (TkSharedText *) clientData; + int result = TCL_OK; + int index; + + static CONST char *optionStrings[] = { + "delete", "insert", (char *) NULL + }; + enum options { + TEXT_DELETE, TEXT_INSERT + }; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum options) index) { + case TEXT_DELETE: { + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?"); + result = TCL_ERROR; + goto done; + } + if (objc < 5) { + /* + * Simple case requires no predetermination of indices. + */ + TkTextIndex index1; + + /* + * Parse the starting and stopping indices. + */ + + result = TkTextSharedGetObjIndex(interp, sharedPtr, + objv[2], &index1); + if (result != TCL_OK) { + goto done; + } + if (objc == 4) { + TkTextIndex index2; + result = TkTextSharedGetObjIndex(interp, sharedPtr, + objv[3], &index2); + if (result != TCL_OK) { + goto done; + } + DeleteChars(sharedPtr, NULL, &index1, &index2, 1); + } else { + DeleteChars(sharedPtr, NULL, &index1, NULL, 1); + } + } else { + /* Too many arguments */ + result = TCL_ERROR; + } + break; + } + case TEXT_INSERT: { + TkTextIndex index1; + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, + "index chars ?tagList chars tagList ...?"); + result = TCL_ERROR; + goto done; + } + result = TkTextSharedGetObjIndex(interp, sharedPtr, + objv[2], &index1); + if (result != TCL_OK) { + goto done; + } + result = TextInsertCmd(sharedPtr, NULL, interp, objc-3, objv+3, + &index1, 1); + break; + } + } + + done: + return result; +} + +/* + *-------------------------------------------------------------- + * + * TextPeerCmd -- + * + * This procedure is invoked to process the "text peer" Tcl + * command. See the user documentation for details on what it + * does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +static int +TextPeerCmd(textPtr, interp, objc, objv) + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ +{ + Tk_Window tkwin = textPtr->tkwin; + int index; + + static CONST char *peerOptionStrings[] = { + "create", "names", (char *) NULL + }; + enum peerOptions { + PEER_CREATE, PEER_NAMES + }; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[2], peerOptionStrings, + "peer option", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum editOptions)index) { + case PEER_CREATE: { + if (objc < 4) { + Tcl_WrongNumArgs(interp, 3, objv, "pathName ?options?"); + return TCL_ERROR; + } + return CreateWidget(textPtr->sharedTextPtr, tkwin, + interp, textPtr, objc-2, objv+2); + } + case PEER_NAMES: { + TkText *tPtr = textPtr->sharedTextPtr->peers; + if (objc > 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); + return TCL_ERROR; + } + while (tPtr != NULL) { + if (tPtr != textPtr) { + Tcl_AppendElement(interp, Tk_PathName(tPtr->tkwin)); + } + tPtr = tPtr->next; + } + } + } + + return TCL_OK; +} + +/* *---------------------------------------------------------------------- * * TextReplaceCmd -- @@ -1332,20 +1677,24 @@ TextWidgetObjCmd(clientData, interp, objc, objv) * * Side effects: * See the user documentation. + * + * If 'viewUpdate' is false, then textPtr->topIndex may no longer + * be a valid index after this function returns. The caller is + * responsible for ensuring a correct index is in place. * *---------------------------------------------------------------------- */ static int TextReplaceCmd(textPtr, interp, indexFromPtr, indexToPtr, - objc, objv, noViewUpdate) + objc, objv, viewUpdate) 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 viewUpdate; /* Update vertical view if set. */ { int result; /* @@ -1356,22 +1705,23 @@ TextReplaceCmd(textPtr, interp, indexFromPtr, indexToPtr, * that the autoSeparators setting is off, so that we don't * get an undo-separator between the delete and insert. */ - int origAutoSep = textPtr->autoSeparators; + int origAutoSep = textPtr->sharedTextPtr->autoSeparators; - if (textPtr->undo) { - textPtr->autoSeparators = 0; - if (origAutoSep && textPtr->lastEditMode != TK_TEXT_EDIT_REPLACE) { - TkUndoInsertUndoSeparator(textPtr->undoStack); + if (textPtr->sharedTextPtr->undo) { + textPtr->sharedTextPtr->autoSeparators = 0; + if (origAutoSep + && textPtr->sharedTextPtr->lastEditMode != TK_TEXT_EDIT_REPLACE) { + TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack); } } - DeleteChars(textPtr, indexFromPtr, indexToPtr, noViewUpdate); - result = TextInsertCmd(textPtr, interp, objc-4, objv+4, - indexFromPtr, noViewUpdate); + DeleteChars(NULL, textPtr, indexFromPtr, indexToPtr, viewUpdate); + result = TextInsertCmd(NULL, textPtr, interp, objc-4, objv+4, + indexFromPtr, viewUpdate); - if (textPtr->undo) { - textPtr->lastEditMode = TK_TEXT_EDIT_REPLACE; - textPtr->autoSeparators = origAutoSep; + if (textPtr->sharedTextPtr->undo) { + textPtr->sharedTextPtr->lastEditMode = TK_TEXT_EDIT_REPLACE; + textPtr->sharedTextPtr->autoSeparators = origAutoSep; } return result; @@ -1450,7 +1800,8 @@ DestroyText(textPtr) Tcl_HashSearch search; Tcl_HashEntry *hPtr; TkTextTag *tagPtr; - + TkSharedText *sharedTextPtr = textPtr->sharedTextPtr; + /* * Free up all the stuff that requires special handling. We have * already called let Tk_FreeConfigOptions to handle all the standard @@ -1463,33 +1814,119 @@ DestroyText(textPtr) TkTextFreeDInfo(textPtr); textPtr->dInfoPtr = NULL; - TkBTreeDestroy(textPtr->tree); - for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); - hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { - tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); - TkTextFreeTag(textPtr, tagPtr); - } - Tcl_DeleteHashTable(&textPtr->tagTable); - for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search); - hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { - ckfree((char *) Tcl_GetHashValue(hPtr)); - } - Tcl_DeleteHashTable(&textPtr->markTable); + /* + * Remove ourselves from the peer list + */ + if (sharedTextPtr->peers == textPtr) { + sharedTextPtr->peers = textPtr->next; + } else { + TkText *nextPtr = sharedTextPtr->peers; + while (nextPtr != NULL) { + if (nextPtr->next == textPtr) { + nextPtr->next = textPtr->next; + break; + } + nextPtr = nextPtr->next; + } + } + + /* + * Always clean up the widget-specific tags first. Common tags + * (i.e. most) will only be cleaned up when the shared structure + * is cleaned up. + * + * We also need to clean up widget-specific marks ('insert', + * 'current'), since otherwise marks will never disappear from + * the B-tree. + */ + + TkTextDeleteTag(textPtr, textPtr->selTagPtr); + TkBTreeUnlinkSegment(textPtr->insertMarkPtr, + textPtr->insertMarkPtr->body.mark.linePtr); + ckfree((char *) textPtr->insertMarkPtr); + TkBTreeUnlinkSegment(textPtr->currentMarkPtr, + textPtr->currentMarkPtr->body.mark.linePtr); + ckfree((char *) textPtr->currentMarkPtr); + + /* + * Now we've cleaned up everything of relevance to us in the B-tree, + * so we disassociate outselves from it. + * + * When the refCount reaches zero, it's time to clean up + * the shared portion of the text widget + */ + sharedTextPtr->refCount--; + + if (sharedTextPtr->refCount > 0) { + TkBTreeRemoveClient(sharedTextPtr->tree, textPtr); + + /* Free up any embedded windows which belong to this widget. */ + for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->windowTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + TkTextEmbWindowClient *loop; + TkTextSegment *ewPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + + loop = ewPtr->body.ew.clients; + if (loop->textPtr == textPtr) { + ewPtr->body.ew.clients = loop->next; + TkTextWinFreeClient(hPtr, loop); + } else { + TkTextEmbWindowClient *client = ewPtr->body.ew.clients; + client = loop->next; + while (client != NULL) { + if (client->textPtr == textPtr) { + loop->next = client->next; + TkTextWinFreeClient(hPtr, client); + break; + } else { + loop = loop->next; + } + client = loop->next; + } + } + } + } else { + /* + * No need to call 'TkBTreeRemoveClient' first, since this + * will do everything in one go, more quickly. + */ + TkBTreeDestroy(sharedTextPtr->tree); + + for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); + /* + * No need to use 'TkTextDeleteTag' since we've already + * removed the B-tree completely + */ + TkTextFreeTag(textPtr, tagPtr); + } + Tcl_DeleteHashTable(&sharedTextPtr->tagTable); + for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->markTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + ckfree((char *) Tcl_GetHashValue(hPtr)); + } + Tcl_DeleteHashTable(&sharedTextPtr->markTable); + TkUndoFreeStack(sharedTextPtr->undoStack); + + Tcl_DeleteHashTable(&sharedTextPtr->windowTable); + Tcl_DeleteHashTable(&sharedTextPtr->imageTable); + + if (sharedTextPtr->bindingTable != NULL) { + Tk_DeleteBindingTable(sharedTextPtr->bindingTable); + } + } + if (textPtr->tabArrayPtr != NULL) { ckfree((char *) textPtr->tabArrayPtr); } if (textPtr->insertBlinkHandler != NULL) { Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler); } - if (textPtr->bindingTable != NULL) { - Tk_DeleteBindingTable(textPtr->bindingTable); - } - TkUndoFreeStack(textPtr->undoStack); textPtr->tkwin = NULL; textPtr->refCount--; - Tcl_DeleteCommandFromToken(textPtr->interp, - textPtr->widgetCmd); + Tcl_DeleteCommandFromToken(textPtr->interp, textPtr->widgetCmd); if (textPtr->refCount == 0) { ckfree((char *) textPtr); } @@ -1533,7 +1970,15 @@ ConfigureText(interp, textPtr, objc, objv) return TCL_ERROR; } - TkUndoSetDepth(textPtr->undoStack, textPtr->maxUndo); + /* + * Copy down shared flags + */ + textPtr->sharedTextPtr->undo = textPtr->undo; + textPtr->sharedTextPtr->maxUndo = textPtr->maxUndo; + textPtr->sharedTextPtr->autoSeparators = textPtr->autoSeparators; + + TkUndoSetDepth(textPtr->sharedTextPtr->undoStack, + textPtr->sharedTextPtr->maxUndo); /* * A few other options also need special processing, such as parsing @@ -1542,6 +1987,78 @@ ConfigureText(interp, textPtr, objc, objv) Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border); + if (mask & TK_TEXT_LINE_RANGE) { + int start, end, current; + + /* + * Line start and/or end have been adjusted. We need to validate + * the first displayed line and arrange for re-layout. + */ + TkBTreeClientRangeChanged(textPtr, textPtr->charHeight); + + if (textPtr->start != NULL) { + start = TkBTreeLinesTo(NULL, textPtr->start); + } else { + start = 0; + } + if (textPtr->end != NULL) { + end = TkBTreeLinesTo(NULL, textPtr->end); + } else { + end = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL); + } + if (start > end) { + Tcl_AppendResult(interp, "-startline must be less than or equal to -endline", NULL); + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; + } + current = TkBTreeLinesTo(NULL, textPtr->topIndex.linePtr); + if (current < start || current > end) { + TkTextSearch search; + TkTextIndex index1, first, last; + int selChanged = 0; + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, + start, 0, &index1); + TkTextSetYView(textPtr, &index1, 0); + /* + * We may need to adjust the selection. So we have to + * check whether the "sel" tag was applied to anything + * outside the current start,end. + */ + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, 0, 0, &first); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL), + 0, &last); + TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search); + if (!TkBTreeCharTagged(&first, textPtr->selTagPtr) + && !TkBTreeNextTag(&search)) { + /* Nothing tagged with "sel" */ + } else { + int line = TkBTreeLinesTo(NULL, search.curIndex.linePtr); + if (line < start) { + selChanged = 1; + } else { + TkTextLine *linePtr = search.curIndex.linePtr; + while (TkBTreeNextTag(&search)) { + linePtr = search.curIndex.linePtr; + } + line = TkBTreeLinesTo(NULL, linePtr); + if (line >= end) { + selChanged = 1; + } + } + } + if (selChanged) { + /* + * Send an event that the selection has changed, and + * abort any partial-selections in progress. + */ + TkTextSelectionEvent(textPtr); + textPtr->abortSelections = 1; + } + } + } + /* * Don't allow negative spacings. */ @@ -1589,7 +2106,6 @@ ConfigureText(interp, textPtr, objc, objv) } textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr; textPtr->selTagPtr->affectsDisplay = 0; - textPtr->selTagPtr->affectsDisplay = 0; textPtr->selTagPtr->affectsDisplayGeometry = 0; if ((textPtr->selTagPtr->elideString != NULL) || (textPtr->selTagPtr->tkfont != None) @@ -1615,7 +2131,7 @@ ConfigureText(interp, textPtr, objc, objv) || (textPtr->selTagPtr->underlineString != NULL)) { textPtr->selTagPtr->affectsDisplay = 1; } - TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL, + TkTextRedrawTag(NULL, textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL, textPtr->selTagPtr, 1); /* @@ -1627,9 +2143,9 @@ ConfigureText(interp, textPtr, objc, objv) TkTextSearch search; TkTextIndex first, last; - TkTextMakeByteIndex(textPtr->tree, 0, 0, &first); - TkTextMakeByteIndex(textPtr->tree, - TkBTreeNumLines(textPtr->tree), 0, &last); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, &first); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &last); TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search); if (TkBTreeCharTagged(&first, textPtr->selTagPtr) || TkBTreeNextTag(&search)) { @@ -1805,7 +2321,7 @@ TextEventProc(clientData, eventPtr) * Hence we don't want the automatic config options freeing * process to delete them as well. */ - + textPtr->selBorder = NULL; textPtr->selBorderWidthPtr = NULL; textPtr->selBorderWidth = 0; @@ -1846,16 +2362,16 @@ TextEventProc(clientData, eventPtr) textPtr->flags &= ~(GOT_FOCUS | INSERT_ON); textPtr->insertBlinkHandler = (Tcl_TimerToken) NULL; } -#ifndef ALWAYS_SHOW_SELECTION - TkTextRedrawTag(textPtr, NULL, NULL, textPtr->selTagPtr, 1); -#endif + if (textPtr->inactiveSelBorder != textPtr->selBorder) { + TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, 1); + } TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES); /* * While we wish to redisplay, no heights have changed, so * no need to call TkTextInvalidateLineMetrics */ - TkTextChanged(textPtr, &index, &index2); + TkTextChanged(NULL, textPtr, &index, &index2); if (textPtr->highlightWidth > 0) { TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth, textPtr->highlightWidth); @@ -1921,25 +2437,34 @@ TextCmdDeletedProc(clientData) * 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 + * If 'viewUpdate' is true, we may adjust the window * contents' y-position, and scrollbar setting. * *---------------------------------------------------------------------- */ static int -InsertChars(textPtr, indexPtr, stringPtr, noViewUpdate) +InsertChars(sharedTextPtr, textPtr, indexPtr, stringPtr, viewUpdate) + TkSharedText *sharedTextPtr; TkText *textPtr; /* Overall information about text widget. */ TkTextIndex *indexPtr; /* Where to insert new characters. May be * modified if the index is not valid * for insertion (e.g. if at "end"). */ Tcl_Obj *stringPtr; /* Null-terminated string containing new * information to add to text. */ - int noViewUpdate; /* Don't update the view if set */ + int viewUpdate; /* Update the view if set */ { - int lineIndex, resetView, offset, length; + int lineIndex, length; + TkText *tPtr; + int *lineAndByteIndex; + int resetViewCount; + int pixels[2*PIXEL_CLIENTS]; CONST char *string = Tcl_GetStringFromObj(stringPtr, &length); + + if (sharedTextPtr == NULL) { + sharedTextPtr = textPtr->sharedTextPtr; + } /* * Don't allow insertions on the last (dummy) line of the text. @@ -1947,10 +2472,11 @@ InsertChars(textPtr, indexPtr, stringPtr, noViewUpdate) * modified. */ - lineIndex = TkBTreeLineIndex(indexPtr->linePtr); - if (lineIndex == TkBTreeNumLines(textPtr->tree)) { + lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr); + if (lineIndex == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) { lineIndex--; - TkTextMakeByteIndex(textPtr->tree, lineIndex, 1000000, indexPtr); + TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineIndex, + 1000000, indexPtr); } /* @@ -1960,51 +2486,83 @@ InsertChars(textPtr, indexPtr, stringPtr, noViewUpdate) * after the insertion, since the insertion could invalidate it. */ - resetView = offset = 0; - if (indexPtr->linePtr == textPtr->topIndex.linePtr) { - resetView = 1; - offset = textPtr->topIndex.byteIndex; - if (offset > indexPtr->byteIndex) { - offset += length; + resetViewCount = 0; + if (sharedTextPtr->refCount > PIXEL_CLIENTS) { + lineAndByteIndex = (int*)ckalloc(sizeof(int)* + 2*sharedTextPtr->refCount); + } else { + lineAndByteIndex = pixels; + } + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; + tPtr = tPtr->next) { + lineAndByteIndex[resetViewCount] = -1; + if (indexPtr->linePtr == tPtr->topIndex.linePtr) { + lineAndByteIndex[resetViewCount] = + TkBTreeLinesTo(tPtr, indexPtr->linePtr); + lineAndByteIndex[resetViewCount+1] = tPtr->topIndex.byteIndex; + if (lineAndByteIndex[resetViewCount+1] > indexPtr->byteIndex) { + lineAndByteIndex[resetViewCount+1] += length; + } } + resetViewCount += 2; } - TkTextChanged(textPtr, indexPtr, indexPtr); - textPtr->stateEpoch ++; - TkBTreeInsertChars(indexPtr, string); + + TkTextChanged(sharedTextPtr, NULL, indexPtr, indexPtr); + + sharedTextPtr->stateEpoch++; + + TkBTreeInsertChars(sharedTextPtr->tree, indexPtr, string); /* * Push the insertion on the undo stack */ - if (textPtr->undo) { + if (sharedTextPtr->undo) { TkTextIndex toIndex; - if (textPtr->autoSeparators && - textPtr->lastEditMode != TK_TEXT_EDIT_INSERT) { - TkUndoInsertUndoSeparator(textPtr->undoStack); + if (sharedTextPtr->autoSeparators && + sharedTextPtr->lastEditMode != TK_TEXT_EDIT_INSERT) { + TkUndoInsertUndoSeparator(sharedTextPtr->undoStack); } - textPtr->lastEditMode = TK_TEXT_EDIT_INSERT; + sharedTextPtr->lastEditMode = TK_TEXT_EDIT_INSERT; - TkTextIndexForwBytes(indexPtr, length, &toIndex); + TkTextIndexForwBytes(textPtr, indexPtr, length, &toIndex); TextPushUndoAction(textPtr, stringPtr, 1, indexPtr, &toIndex); } - UpdateDirtyFlag(textPtr); - - if (resetView && !noViewUpdate) { - TkTextIndex newTop; - TkTextMakeByteIndex(textPtr->tree, lineIndex, 0, &newTop); - TkTextIndexForwBytes(&newTop, offset, &newTop); - TkTextSetYView(textPtr, &newTop, 0); + UpdateDirtyFlag(sharedTextPtr); + + resetViewCount = 0; + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; + tPtr = tPtr->next) { + if (lineAndByteIndex[resetViewCount] != -1) { + if ((tPtr != textPtr) || viewUpdate) { + TkTextIndex newTop; + TkTextMakeByteIndex(sharedTextPtr->tree, tPtr, + lineAndByteIndex[resetViewCount], + 0, &newTop); + TkTextIndexForwBytes(tPtr, &newTop, + lineAndByteIndex[resetViewCount+1], + &newTop); + TkTextSetYView(tPtr, &newTop, 0); + } + } + resetViewCount += 2; } - + if (sharedTextPtr->refCount > PIXEL_CLIENTS) { + ckfree((char*)lineAndByteIndex); + } + /* * Invalidate any selection retrievals in progress. */ - textPtr->abortSelections = 1; - + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; + tPtr = tPtr->next) { + tPtr->abortSelections = 1; + } + /* For convenience, return the length of the string */ return length; } @@ -2036,35 +2594,31 @@ TextPushUndoAction (textPtr, undoString, insert, index1Ptr, index2Ptr) CONST TkTextIndex *index1Ptr;/* Index describing first location */ CONST TkTextIndex *index2Ptr;/* Index describing second location */ { + TkUndoSubAtom *iAtom, *dAtom; + /* Create the helpers */ - Tcl_Obj *cmdNameObj = Tcl_NewObj(); Tcl_Obj *seeInsertObj = Tcl_NewObj(); Tcl_Obj *markSet1InsertObj = Tcl_NewObj(); Tcl_Obj *markSet2InsertObj = Tcl_NewObj(); Tcl_Obj *insertCmdObj = Tcl_NewObj(); Tcl_Obj *deleteCmdObj = Tcl_NewObj(); - Tcl_Obj *insertCmd = Tcl_NewObj(); - Tcl_Obj *deleteCmd = Tcl_NewObj(); - /* Get the index positions */ - Tcl_Obj *index1Obj = TkTextNewIndexObj(textPtr, index1Ptr); - Tcl_Obj *index2Obj = TkTextNewIndexObj(textPtr, index2Ptr); - - /* Get the fully qualified name */ - Tcl_GetCommandFullName(textPtr->interp, textPtr->widgetCmd, cmdNameObj); + Tcl_Obj *index1Obj = TkTextNewIndexObj(NULL, index1Ptr); + Tcl_Obj *index2Obj = TkTextNewIndexObj(NULL, index2Ptr); /* These need refCounts, because they are used more than once below */ - Tcl_IncrRefCount(cmdNameObj); Tcl_IncrRefCount(seeInsertObj); Tcl_IncrRefCount(index1Obj); Tcl_IncrRefCount(index2Obj); - Tcl_ListObjAppendElement(NULL, seeInsertObj, cmdNameObj); + Tcl_ListObjAppendElement(NULL, seeInsertObj, + Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1)); Tcl_ListObjAppendElement(NULL, seeInsertObj, Tcl_NewStringObj("see",3)); Tcl_ListObjAppendElement(NULL, seeInsertObj, Tcl_NewStringObj("insert",6)); - Tcl_ListObjAppendElement(NULL, markSet1InsertObj, cmdNameObj); + Tcl_ListObjAppendElement(NULL, markSet1InsertObj, + Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1)); Tcl_ListObjAppendElement(NULL, markSet1InsertObj, Tcl_NewStringObj("mark",4)); Tcl_ListObjAppendElement(NULL, markSet1InsertObj, @@ -2075,25 +2629,40 @@ TextPushUndoAction (textPtr, undoString, insert, index1Ptr, index2Ptr) Tcl_ListObjAppendElement(NULL, markSet1InsertObj, index1Obj); Tcl_ListObjAppendElement(NULL, markSet2InsertObj, index2Obj); - Tcl_ListObjAppendElement(NULL, insertCmdObj, cmdNameObj); Tcl_ListObjAppendElement(NULL, insertCmdObj, Tcl_NewStringObj("insert",6)); Tcl_ListObjAppendElement(NULL, insertCmdObj, index1Obj); - /* Only use of 'undoString' */ + /* Only use of 'undoString' is here */ Tcl_ListObjAppendElement(NULL, insertCmdObj, undoString); - Tcl_ListObjAppendElement(NULL, deleteCmdObj, cmdNameObj); Tcl_ListObjAppendElement(NULL, deleteCmdObj, Tcl_NewStringObj("delete",6)); Tcl_ListObjAppendElement(NULL, deleteCmdObj, index1Obj); Tcl_ListObjAppendElement(NULL, deleteCmdObj, index2Obj); - Tcl_ListObjAppendElement(NULL, insertCmd, insertCmdObj); - Tcl_ListObjAppendElement(NULL, insertCmd, markSet2InsertObj); - Tcl_ListObjAppendElement(NULL, insertCmd, seeInsertObj); - Tcl_ListObjAppendElement(NULL, deleteCmd, deleteCmdObj); - Tcl_ListObjAppendElement(NULL, deleteCmd, markSet1InsertObj); - Tcl_ListObjAppendElement(NULL, deleteCmd, seeInsertObj); + /* + * Note: we don't wish to use textPtr->widgetCmd in these callbacks + * because if we delete the textPtr, but peers still exist, we will + * then have references to a non-existent Tcl_Command in the undo + * stack, which will lead to crashes later. Also, the behaviour of + * the widget wrt bindings (%W substitutions) always uses the widget + * path name, so there is no good reason the undo stack should do + * otherwise. + * + * For the 'insert' and 'delete' actions, we have to register a + * functional callback, because these actions are defined to + * operate on the underlying data shared by all peers. + */ + iAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, + (ClientData)textPtr->sharedTextPtr, + insertCmdObj, NULL); + TkUndoMakeCmdSubAtom(NULL, markSet2InsertObj, iAtom); + TkUndoMakeCmdSubAtom(NULL, seeInsertObj, iAtom); + + dAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, + (ClientData)textPtr->sharedTextPtr, + deleteCmdObj, NULL); + TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom); - Tcl_DecrRefCount(cmdNameObj); Tcl_DecrRefCount(seeInsertObj); Tcl_DecrRefCount(index1Obj); Tcl_DecrRefCount(index2Obj); @@ -2102,13 +2671,14 @@ TextPushUndoAction (textPtr, undoString, insert, index1Ptr, index2Ptr) * Depending whether the action is to insert or delete, we provide * the appropriate second and third arguments to TkUndoPushAction. * (The first is the 'actionCommand', and the second the - * 'revertCommand'). The final '1' says we are providing a list - * of scripts to execute rather than a single script. + * 'revertCommand'). */ if (insert) { - TkUndoPushAction(textPtr->undoStack, insertCmd, deleteCmd, 1); + TkUndoPushAction(textPtr->sharedTextPtr->undoStack, + iAtom, dAtom); } else { - TkUndoPushAction(textPtr->undoStack, deleteCmd, insertCmd, 1); + TkUndoPushAction(textPtr->sharedTextPtr->undoStack, + dAtom, iAtom); } } @@ -2116,6 +2686,104 @@ TextPushUndoAction (textPtr, undoString, insert, index1Ptr, index2Ptr) /* *---------------------------------------------------------------------- * + * TextUndoRedoCallback -- + * + * This procedure is registered with the generic undo/redo code + * to handle 'insert' and 'delete' actions on all text widgets. + * We cannot perform those actions on any particular text widget, + * because that text widget might have been deleted by the time + * we get here. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Will insert or delete text, depending on the first word + * contained in objPtr. + * + *---------------------------------------------------------------------- + */ + +int +TextUndoRedoCallback(interp, clientData, objPtr) + Tcl_Interp *interp; /* Current interpreter. */ + ClientData clientData; /* Passed from undo code, but + * contains our shared text + * data structure. */ + Tcl_Obj *objPtr; /* Arguments of a command to + * be handled by the shared + * text data structure. */ +{ + TkSharedText *sharedPtr = (TkSharedText*)clientData; + int objc; + Tcl_Obj **objv; + int res; + TkText *textPtr; + + res = Tcl_ListObjGetElements(interp, objPtr, &objc, &objv); + if (res != TCL_OK) { + return res; + } + + /* + * If possible, use a real text widget to perform the undo/redo + * action (i.e. insertion or deletion of text). This provides + * maximum compatibility with older versions of Tk, in which the + * user may rename the text widget to allow capture of undo or + * redo actions. + * + * In particular, this sorting of capture is useful in text editors + * based on the Tk text widget, which need to know which new text + * needs re-coloring. + * + * It would be better if the text widget provided some other + * mechanism to allow capture of this information ("What has + * just changed in the text widget?"). What we have here is + * not entirely satisfactory under all circumstances. + */ + textPtr = sharedPtr->peers; + while (textPtr != NULL) { + if (textPtr->start == NULL && textPtr->end == NULL) { + Tcl_Obj *cmdNameObj, *evalObj; + + evalObj = Tcl_NewObj(); + Tcl_IncrRefCount(evalObj); + /* + * We might wish to use the real, current command-name + * for the widget, but this will break any code that has + * over-ridden the widget, and is expecting to observe + * the insert/delete actions which are caused by undo/redo + * operations. + * + * cmdNameObj = Tcl_NewObj(); + * Tcl_GetCommandFullName(interp, textPtr->widgetCmd, + * cmdNameObj); + * + * While such interception is not explicitly documented as + * supported, it does occur, and so until we can provide + * some alternative mechanism for such code to do what + * it needs, we allow it to take place here. + */ + cmdNameObj = Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1); + Tcl_ListObjAppendElement(NULL, evalObj, cmdNameObj); + Tcl_ListObjAppendList(NULL, evalObj, objPtr); + res = Tcl_EvalObjEx(interp, evalObj, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(evalObj); + return res; + } + textPtr = textPtr->next; + } + /* + * If there's no current text widget which shows everything, then + * we fall back on acting directly. This means there is no way to + * intercept from the Tcl level. + */ + return SharedTextObjCmd((ClientData)sharedPtr, interp, objc+1, objv-1); +} + +/* + *---------------------------------------------------------------------- + * * CountIndices -- * * This procedure implements most of the functionality of the @@ -2174,14 +2842,22 @@ CountIndices(textPtr, indexPtr1, indexPtr2, type) * Side effects: * Characters get deleted from the text. * - * Unless 'noViewUpdate' is set, we may adjust the window + * If 'viewUpdate' is true, we may adjust the window * contents' y-position, and scrollbar setting. + * + * If 'viewUpdate' is false, true we can guarantee that + * textPtr->topIndex points to a valid TkTextLine after this + * procedure returns. However, if 'viewUpdate' is false, then + * there is no such guarantee (topIndex.linePtr can be garbage). + * The caller is expected to take actions to ensure the topIndex + * is validated before laying out the window again. * *---------------------------------------------------------------------- */ static int -DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate) +DeleteChars(sharedTextPtr, textPtr, indexPtr1, indexPtr2, viewUpdate) + TkSharedText *sharedTextPtr; /* Shared portion of peer widgets. */ TkText *textPtr; /* Overall information about text widget. */ CONST TkTextIndex *indexPtr1;/* Index describing location of first * character to delete. */ @@ -2189,11 +2865,19 @@ DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate) * character to delete. NULL means just * delete the one character given by * indexPtr1. */ - int noViewUpdate; /* Don't update the view if set */ + int viewUpdate; /* Update vertical view if set. */ { - int line1, line2, line, byteIndex, resetView; + int line1, line2; TkTextIndex index1, index2; + TkText *tPtr; + int *lineAndByteIndex; + int resetViewCount; + int pixels[2*PIXEL_CLIENTS]; + if (sharedTextPtr == NULL) { + sharedTextPtr = textPtr->sharedTextPtr; + } + /* * Prepare the starting and stopping indices. */ @@ -2226,9 +2910,9 @@ DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate) * deleting the newline and then adding a "clean" one back again). */ - line1 = TkBTreeLineIndex(index1.linePtr); - line2 = TkBTreeLineIndex(index2.linePtr); - if (line2 == TkBTreeNumLines(textPtr->tree)) { + line1 = TkBTreeLinesTo(textPtr, index1.linePtr); + line2 = TkBTreeLinesTo(textPtr, index2.linePtr); + if (line2 == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) { TkTextTag **arrayPtr; int arraySize, i; TkTextIndex oldIndex2; @@ -2240,7 +2924,7 @@ DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate) TkTextIndexBackChars(NULL, &index1, 1, &index1, COUNT_INDICES); line1--; } - arrayPtr = TkBTreeGetTags(&index2, &arraySize); + arrayPtr = TkBTreeGetTags(&index2, NULL, &arraySize); if (arrayPtr != NULL) { for (i = 0; i < arraySize; i++) { TkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0); @@ -2260,39 +2944,30 @@ DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate) Tcl_HashEntry *hPtr; int i; - for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); + for (i = 0, hPtr = Tcl_FirstHashEntry(&sharedTextPtr->tagTable, + &search); hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) { TkTextTag *tagPtr; tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); - if (TkBTreeTag(&index1, &index2, tagPtr, 0)) { + TkBTreeTag(&index1, &index2, tagPtr, 0); + } + /* + * Special case for the sel tag which is not in the hash table. + * We need to do this once for each peer text widget. + */ + + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; + tPtr = tPtr->next) { + if (TkBTreeTag(&index1, &index2, tPtr->selTagPtr, 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' + * Send an event that the selection changed. + * This is equivalent to + * "event generate $textWidget <<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); + TkTextSelectionEvent(textPtr); - textPtr->abortSelections = 1; - } + tPtr->abortSelections = 1; } } } @@ -2306,80 +2981,125 @@ DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate) * will be, then do the deletion, then reset the view. */ - TkTextChanged(textPtr, &index1, &index2); - resetView = 0; - line = 0; - byteIndex = 0; - if (TkTextIndexCmp(&index2, &textPtr->topIndex) >= 0) { - if (TkTextIndexCmp(&index1, &textPtr->topIndex) <= 0) { - /* - * Deletion range straddles topIndex: use the beginning - * of the range as the new topIndex. - */ + TkTextChanged(sharedTextPtr, NULL, &index1, &index2); + + resetViewCount = 0; + if (sharedTextPtr->refCount > PIXEL_CLIENTS) { + lineAndByteIndex = (int*)ckalloc(sizeof(int)* + 2*sharedTextPtr->refCount); + } else { + lineAndByteIndex = pixels; + } + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; + tPtr = tPtr->next) { + int line = 0; + int byteIndex = 0; + int resetView = 0; + if (TkTextIndexCmp(&index2, &tPtr->topIndex) >= 0) { + if (TkTextIndexCmp(&index1, &tPtr->topIndex) <= 0) { + /* + * Deletion range straddles topIndex: use the beginning + * of the range as the new topIndex. + */ - resetView = 1; - line = line1; - byteIndex = index1.byteIndex; - } else if (index1.linePtr == textPtr->topIndex.linePtr) { + resetView = 1; + line = line1; + byteIndex = index1.byteIndex; + } else if (index1.linePtr == tPtr->topIndex.linePtr) { + /* + * Deletion range starts on top line but after topIndex. + * Use the current topIndex as the new one. + */ + + resetView = 1; + line = line1; + byteIndex = tPtr->topIndex.byteIndex; + } + } else if (index2.linePtr == tPtr->topIndex.linePtr) { /* - * Deletion range starts on top line but after topIndex. - * Use the current topIndex as the new one. + * Deletion range ends on top line but before topIndex. + * Figure out what will be the new character index for + * the character currently pointed to by topIndex. */ resetView = 1; - line = line1; - byteIndex = textPtr->topIndex.byteIndex; + line = line2; + byteIndex = tPtr->topIndex.byteIndex; + if (index1.linePtr != index2.linePtr) { + byteIndex -= index2.byteIndex; + } else { + byteIndex -= (index2.byteIndex - index1.byteIndex); + } } - } else if (index2.linePtr == textPtr->topIndex.linePtr) { - /* - * Deletion range ends on top line but before topIndex. - * Figure out what will be the new character index for - * the character currently pointed to by topIndex. - */ - - resetView = 1; - line = line2; - byteIndex = textPtr->topIndex.byteIndex; - if (index1.linePtr != index2.linePtr) { - byteIndex -= index2.byteIndex; + if (resetView) { + lineAndByteIndex[resetViewCount] = line; + lineAndByteIndex[resetViewCount+1] = byteIndex; } else { - byteIndex -= (index2.byteIndex - index1.byteIndex); + lineAndByteIndex[resetViewCount] = -1; } + resetViewCount+=2; } - + /* * Push the deletion on the undo stack */ - if (textPtr->undo) { + if (sharedTextPtr->undo) { Tcl_Obj *get; - if (textPtr->autoSeparators - && (textPtr->lastEditMode != TK_TEXT_EDIT_DELETE)) { - TkUndoInsertUndoSeparator(textPtr->undoStack); + if (sharedTextPtr->autoSeparators + && (sharedTextPtr->lastEditMode != TK_TEXT_EDIT_DELETE)) { + TkUndoInsertUndoSeparator(sharedTextPtr->undoStack); } - textPtr->lastEditMode = TK_TEXT_EDIT_DELETE; + sharedTextPtr->lastEditMode = TK_TEXT_EDIT_DELETE; get = TextGetText(textPtr, &index1, &index2, 0); TextPushUndoAction(textPtr, get, 0, &index1, &index2); } - UpdateDirtyFlag(textPtr); - - textPtr->stateEpoch ++; - TkBTreeDeleteChars(&index1, &index2); - - if (resetView && !noViewUpdate) { - TkTextMakeByteIndex(textPtr->tree, line, byteIndex, &index1); - TkTextSetYView(textPtr, &index1, 0); + UpdateDirtyFlag(sharedTextPtr); + + sharedTextPtr->stateEpoch++; + + TkBTreeDeleteChars(sharedTextPtr->tree, &index1, &index2); + + resetViewCount = 0; + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; + tPtr = tPtr->next) { + int line = lineAndByteIndex[resetViewCount]; + if (line != -1) { + int byteIndex = lineAndByteIndex[resetViewCount+1]; + if (tPtr == textPtr) { + if (viewUpdate) { + TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, + line, byteIndex, &index1); + TkTextSetYView(tPtr, &index1, 0); + } + } else { + TkTextMakeByteIndex(sharedTextPtr->tree, NULL, + line, byteIndex, &index1); + TkTextSetYView(tPtr, &index1, 0); + } + } + resetViewCount += 2; } + if (sharedTextPtr->refCount > PIXEL_CLIENTS) { + ckfree((char*)lineAndByteIndex); + } + + if (line1 >= line2) { + + /* + * Invalidate any selection retrievals in progress, assuming + * we didn't check for this case above. + */ - /* - * Invalidate any selection retrievals in progress. - */ - - textPtr->abortSelections = 1; - + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; + tPtr = tPtr->next) { + tPtr->abortSelections = 1; + } + } + return TCL_OK; } @@ -2434,12 +3154,15 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) */ if (offset == 0) { - TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->selIndex); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + 0, 0, &textPtr->selIndex); textPtr->abortSelections = 0; } else if (textPtr->abortSelections) { return 0; } - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &eof); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), + 0, &eof); TkBTreeStartSearch(&textPtr->selIndex, &eof, textPtr->selTagPtr, &search); if (!TkBTreeCharTagged(&textPtr->selIndex, textPtr->selTagPtr)) { if (!TkBTreeNextTag(&search)) { @@ -2503,7 +3226,7 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) maxBytes -= chunkSize; count += chunkSize; } - TkTextIndexForwBytes(&textPtr->selIndex, chunkSize, + TkTextIndexForwBytes(textPtr, &textPtr->selIndex, chunkSize, &textPtr->selIndex); } @@ -2547,7 +3270,6 @@ TkTextLostSelection(clientData) ClientData clientData; /* Information about text widget. */ { register TkText *textPtr = (TkText *) clientData; - XEvent event; #ifdef ALWAYS_SHOW_SELECTION TkTextIndex start, end; @@ -2561,17 +3283,49 @@ TkTextLostSelection(clientData) * just remove the "sel" tag from everything in the widget. */ - TkTextMakeByteIndex(textPtr->tree, 0, 0, &start); - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &end); - TkTextRedrawTag(textPtr, &start, &end, textPtr->selTagPtr, 1); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, &start); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), + 0, &end); + TkTextRedrawTag(NULL, textPtr, &start, &end, textPtr->selTagPtr, 1); TkBTreeTag(&start, &end, textPtr->selTagPtr, 0); #endif - + /* * Send an event that the selection changed. This is equivalent to * "event generate $textWidget <<Selection>>" */ + TkTextSelectionEvent(textPtr); + textPtr->flags &= ~GOT_SELECTION; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextSelectionEvent -- + * + * When anything relevant to the "sel" tag has been changed, + * call this procedure to generate a <<Selection>> event. + * + * Results: + * None. + * + * Side effects: + * If <<Selection>> bindings are present, they will trigger. + * + *---------------------------------------------------------------------- + */ + +void +TkTextSelectionEvent(textPtr) + TkText *textPtr; +{ + /* + * Send an event that the selection changed. This is equivalent to + * "event generate $textWidget <<Selection>>" + */ + XEvent event; memset((VOID *) &event, 0, sizeof(event)); event.xany.type = VirtualEvent; event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); @@ -2580,8 +3334,6 @@ TkTextLostSelection(clientData) event.xany.display = Tk_Display(textPtr->tkwin); ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); Tk_HandleEvent(&event); - - textPtr->flags &= ~GOT_SELECTION; } /* @@ -2651,24 +3403,29 @@ TextBlinkProc(clientData) * Side effects: * See the user documentation. * - * Unless 'noViewUpdate' is set, we may adjust the window + * If 'viewUpdate' is true, 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 */ +TextInsertCmd(sharedTextPtr, textPtr, interp, objc, objv, indexPtr, viewUpdate) + TkSharedText *sharedTextPtr; /* Shared portion of peer widgets. */ + TkText *textPtr; /* Information about text widget. */ + Tcl_Interp *interp; /* Current interpreter. */ + int objc; /* Number of arguments. */ + Tcl_Obj *CONST objv[]; /* Argument objects. */ + CONST TkTextIndex *indexPtr; /* Index at which to insert */ + int viewUpdate; /* Update the view if set */ { TkTextIndex index1, index2; int j; + if (sharedTextPtr == NULL) { + sharedTextPtr = textPtr->sharedTextPtr; + } + index1 = *indexPtr; for (j = 0; j < objc; j += 2) { /* @@ -2678,14 +3435,15 @@ TextInsertCmd(textPtr, interp, objc, objv, indexPtr, noViewUpdate) * allowable index for insertion, otherwise * subsequent tag insertions will fail. */ - int length = InsertChars(textPtr, &index1, objv[j], noViewUpdate); + int length = InsertChars(sharedTextPtr, textPtr, &index1, + objv[j], viewUpdate); if (objc > (j+1)) { Tcl_Obj **tagNamePtrs; TkTextTag **oldTagArrayPtr; int numTags; - TkTextIndexForwBytes(&index1, length, &index2); - oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags); + TkTextIndexForwBytes(textPtr, &index1, length, &index2); + oldTagArrayPtr = TkBTreeGetTags(&index1, NULL, &numTags); if (oldTagArrayPtr != NULL) { int i; for (i = 0; i < numTags; i++) { @@ -2702,10 +3460,9 @@ TextInsertCmd(textPtr, interp, objc, objv, indexPtr, noViewUpdate) int i; for (i = 0; i < numTags; i++) { - TkBTreeTag(&index1, &index2, - TkTextCreateTag(textPtr, - Tcl_GetString(tagNamePtrs[i]), NULL), - 1); + CONST char *strTag = Tcl_GetString(tagNamePtrs[i]); + TkBTreeTag(&index1, &index2, + TkTextCreateTag(textPtr, strTag, NULL), 1); } index1 = index2; } @@ -2769,7 +3526,7 @@ TextSearchCmd(textPtr, interp, objc, objv) searchSpec.noLineStop = 0; searchSpec.overlap = 0; searchSpec.strictLimits = 0; - searchSpec.numLines = TkBTreeNumLines(textPtr->tree); + searchSpec.numLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); searchSpec.clientData = (ClientData)textPtr; searchSpec.addLineProc = &TextSearchAddNextLine; searchSpec.foundMatchProc = &TextSearchFoundMatch; @@ -2961,13 +3718,20 @@ TextSearchGetLineIndex(interp, objPtr, searchSpecPtr, linePosPtr, offsetPosPtr) return TCL_ERROR; } - line = TkBTreeLineIndex(indexPtr->linePtr); + line = TkBTreeLinesTo(textPtr, indexPtr->linePtr); if (line >= searchSpecPtr->numLines) { TkTextLine *linePtr; + int count = 0; + TkTextSegment *segPtr; + line = searchSpecPtr->numLines-1; - linePtr = TkBTreeFindLine(textPtr->tree, line); + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, line); + /* Count the number of bytes in this line */ + for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { + count += segPtr->size; + } *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, linePtr, - TkBTreeBytesInLine(linePtr)); + count); } else { *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, indexPtr->linePtr, indexPtr->byteIndex); @@ -3011,7 +3775,7 @@ TextSearchIndexInLine(searchSpecPtr, linePtr, byteIndex) TkText *textPtr = (TkText*)(searchSpecPtr->clientData); index = 0; - curIndex.tree = textPtr->tree; + curIndex.tree = textPtr->sharedTextPtr->tree; curIndex.linePtr = linePtr; curIndex.byteIndex = 0; for (segPtr = linePtr->segPtr, leftToScan = byteIndex; leftToScan > 0; @@ -3076,11 +3840,11 @@ TextSearchAddNextLine(lineNum, searchSpecPtr, theLine, lenPtr) * Extract the text from the line. */ - linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum); if (linePtr == NULL) { return NULL; } - curIndex.tree = textPtr->tree; + curIndex.tree = textPtr->sharedTextPtr->tree; curIndex.linePtr = linePtr; curIndex.byteIndex = 0; for (segPtr = linePtr->segPtr; segPtr != NULL; curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) { @@ -3198,10 +3962,10 @@ TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine, linePtr = (TkTextLine *)clientData; if (linePtr == NULL) { - linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum); } - curIndex.tree = textPtr->tree; + curIndex.tree = textPtr->sharedTextPtr->tree; curIndex.linePtr = linePtr; curIndex.byteIndex = 0; /* Find the starting point */ for (segPtr = linePtr->segPtr, leftToScan = matchOffset; @@ -3222,10 +3986,10 @@ TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine, } /* Calculate and store the found index in the result */ if (searchSpecPtr->exact) { - TkTextMakeByteIndex(textPtr->tree, lineNum, + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, matchOffset, &foundIndex); } else { - TkTextMakeCharIndex(textPtr->tree, lineNum, + TkTextMakeCharIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, matchOffset, &foundIndex); } if (searchSpecPtr->all) { @@ -3252,7 +4016,7 @@ TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine, * ever happen with searches which have matched across * multiple lines */ - linePtr = TkBTreeNextLine(linePtr); + linePtr = TkBTreeNextLine(textPtr, linePtr); segPtr = linePtr->segPtr; curIndex.linePtr = linePtr; curIndex.byteIndex = 0; } @@ -3559,7 +4323,7 @@ TextDumpCmd(textPtr, interp, objc, objv) if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index1) != TCL_OK) { return TCL_ERROR; } - lineno = TkBTreeLineIndex(index1.linePtr); + lineno = TkBTreeLinesTo(textPtr, index1.linePtr); arg++; atEnd = 0; if (objc == arg) { @@ -3585,7 +4349,7 @@ TextDumpCmd(textPtr, interp, objc, objv) DumpLine(interp, textPtr, what, index1.linePtr, index1.byteIndex, 32000000, lineno, command); linePtr = index1.linePtr; - while ((linePtr = TkBTreeNextLine(linePtr)) != (TkTextLine *)NULL) { + while ((linePtr = TkBTreeNextLine(textPtr, linePtr)) != NULL) { lineno++; if (linePtr == index2.linePtr) { break; @@ -3661,35 +4425,47 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command) savedChar = segPtr->body.chars[last]; segPtr->body.chars[last] = '\0'; - TkTextMakeByteIndex(textPtr->tree, lineno, offset + first, &index); - DumpSegment(interp, "text", segPtr->body.chars + first, + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineno, + offset + first, &index); + DumpSegment(textPtr, interp, "text", segPtr->body.chars + first, command, &index, what); segPtr->body.chars[last] = savedChar; } else if ((offset >= startByte)) { if ((what & TK_DUMP_MARK) && (segPtr->typePtr->name[0] == 'm')) { + char *name; TkTextMark *markPtr = (TkTextMark *)&segPtr->body; - char *name = Tcl_GetHashKey(&textPtr->markTable, markPtr->hPtr); - - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "mark", name, command, &index, what); + if (segPtr == textPtr->insertMarkPtr) { + name = "insert"; + } else if (segPtr == textPtr->currentMarkPtr) { + name = "current"; + } else { + name = Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, + markPtr->hPtr); + } + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + DumpSegment(textPtr, interp, "mark", name, command, &index, what); } else if ((what & TK_DUMP_TAG) && (segPtr->typePtr == &tkTextToggleOnType)) { - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "tagon", + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + DumpSegment(textPtr, interp, "tagon", segPtr->body.toggle.tagPtr->name, command, &index, what); } else if ((what & TK_DUMP_TAG) && (segPtr->typePtr == &tkTextToggleOffType)) { - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "tagoff", + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + DumpSegment(textPtr, interp, "tagoff", segPtr->body.toggle.tagPtr->name, command, &index, what); } else if ((what & TK_DUMP_IMG) && (segPtr->typePtr->name[0] == 'i')) { TkTextEmbImage *eiPtr = (TkTextEmbImage *)&segPtr->body; char *name = (eiPtr->name == NULL) ? "" : eiPtr->name; - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "image", name, + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + DumpSegment(textPtr, interp, "image", name, command, &index, what); } else if ((what & TK_DUMP_WIN) && (segPtr->typePtr->name[0] == 'w')) { @@ -3700,8 +4476,9 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command) } else { pathname = Tk_PathName(ewPtr->tkwin); } - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "window", pathname, + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + DumpSegment(textPtr, interp, "window", pathname, command, &index, what); } } @@ -3725,7 +4502,8 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command) *---------------------------------------------------------------------- */ static int -DumpSegment(interp, key, value, command, index, what) +DumpSegment(textPtr, interp, key, value, command, index, what) + TkText *textPtr; Tcl_Interp *interp; CONST char *key; /* Segment type key */ CONST char *value; /* Segment value */ @@ -3734,7 +4512,7 @@ DumpSegment(interp, key, value, command, index, what) int what; /* Look for TK_DUMP_INDEX bit */ { char buffer[TK_POS_CHARS]; - TkTextPrintIndex(index, buffer); + TkTextPrintIndex(textPtr, index, buffer); if (command == NULL) { Tcl_AppendElement(interp, key); Tcl_AppendElement(interp, value); @@ -3777,24 +4555,24 @@ TextEditUndo(textPtr) { int status; - if (!textPtr->undo) { + if (!textPtr->sharedTextPtr->undo) { return TCL_OK; } /* Turn off the undo feature */ - textPtr->undo = 0; + textPtr->sharedTextPtr->undo = 0; /* The dirty counter should count downwards as we are undoing things */ - textPtr->isDirtyIncrement = -1; + textPtr->sharedTextPtr->isDirtyIncrement = -1; /* revert one compound action */ - status = TkUndoRevert(textPtr->undoStack); + status = TkUndoRevert(textPtr->sharedTextPtr->undoStack); /* Restore the isdirty increment */ - textPtr->isDirtyIncrement = 1; + textPtr->sharedTextPtr->isDirtyIncrement = 1; /* Turn back on the undo feature */ - textPtr->undo = 1; + textPtr->sharedTextPtr->undo = 1; return status; } @@ -3821,18 +4599,18 @@ TextEditRedo(textPtr) { int status; - if (!textPtr->undo) { + if (!textPtr->sharedTextPtr->undo) { return TCL_OK; } /* Turn off the undo feature temporarily */ - textPtr->undo = 0; + textPtr->sharedTextPtr->undo = 0; /* reapply one compound action */ - status = TkUndoApply(textPtr->undoStack); + status = TkUndoApply(textPtr->sharedTextPtr->undoStack); /* Turn back on the undo feature */ - textPtr->undo = 1; + textPtr->sharedTextPtr->undo = 1; return status; } @@ -3883,7 +4661,8 @@ TextEditCmd(textPtr, interp, objc, objv) switch ((enum editOptions)index) { case EDIT_MODIFIED: { if (objc == 3) { - Tcl_SetObjResult(interp, Tcl_NewBooleanObj(textPtr->isDirty)); + Tcl_SetObjResult(interp, + Tcl_NewBooleanObj(textPtr->sharedTextPtr->isDirty)); } else if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "?boolean?"); return TCL_ERROR; @@ -3899,11 +4678,11 @@ TextEditCmd(textPtr, interp, objc, objv) */ if (setModified) { - textPtr->isDirty = 1; - textPtr->modifiedSet = 1; + textPtr->sharedTextPtr->isDirty = 1; + textPtr->sharedTextPtr->modifiedSet = 1; } else { - textPtr->isDirty = 0; - textPtr->modifiedSet = 0; + textPtr->sharedTextPtr->isDirty = 0; + textPtr->sharedTextPtr->modifiedSet = 0; } /* @@ -3938,7 +4717,7 @@ TextEditCmd(textPtr, interp, objc, objv) Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - TkUndoClearStacks(textPtr->undoStack); + TkUndoClearStacks(textPtr->sharedTextPtr->undoStack); break; } case EDIT_SEPARATOR: { @@ -3946,7 +4725,7 @@ TextEditCmd(textPtr, interp, objc, objv) Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - TkUndoInsertUndoSeparator(textPtr->undoStack); + TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack); break; } case EDIT_UNDO: { @@ -4009,8 +4788,9 @@ TextGetText(textPtr, indexPtr1,indexPtr2, visibleOnly) TkTextIndex tmpIndex; Tcl_Obj *resultPtr = Tcl_NewObj(); - TkTextMakeByteIndex(indexPtr1->tree, TkBTreeLineIndex(indexPtr1->linePtr), - indexPtr1->byteIndex, &tmpIndex); + TkTextMakeByteIndex(indexPtr1->tree, textPtr, + TkBTreeLinesTo(textPtr, indexPtr1->linePtr), + indexPtr1->byteIndex, &tmpIndex); if (TkTextIndexCmp(indexPtr1, indexPtr2) < 0) { while (1) { @@ -4041,7 +4821,7 @@ TextGetText(textPtr, indexPtr1,indexPtr2, visibleOnly) last - offset); } } - TkTextIndexForwBytes(&tmpIndex, last-offset, &tmpIndex); + TkTextIndexForwBytes(textPtr, &tmpIndex, last-offset, &tmpIndex); } } return resultPtr; @@ -4064,31 +4844,35 @@ TextGetText(textPtr, indexPtr1,indexPtr2, visibleOnly) */ static void -UpdateDirtyFlag (textPtr) - TkText *textPtr; /* Information about text widget. */ +UpdateDirtyFlag (sharedTextPtr) + TkSharedText *sharedTextPtr; /* Information about text widget. */ { int oldDirtyFlag; - if (textPtr->modifiedSet) { + if (sharedTextPtr->modifiedSet) { return; } - oldDirtyFlag = textPtr->isDirty; - textPtr->isDirty += textPtr->isDirtyIncrement; - if (textPtr->isDirty == 0 || oldDirtyFlag == 0) { - XEvent event; - /* - * Send an event that the text was modified. This is equivalent to - * "event generate $textWidget <<Modified>>" - */ + oldDirtyFlag = sharedTextPtr->isDirty; + sharedTextPtr->isDirty += sharedTextPtr->isDirtyIncrement; + if (sharedTextPtr->isDirty == 0 || oldDirtyFlag == 0) { + TkText *textPtr; + for (textPtr = sharedTextPtr->peers; textPtr != NULL; + textPtr = textPtr->next) { + XEvent event; + /* + * Send an event that the text was modified. This is equivalent to + * "event generate $textWidget <<Modified>>" + */ - 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("Modified"); - Tk_HandleEvent(&event); + 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("Modified"); + Tk_HandleEvent(&event); + } } } @@ -4321,6 +5105,15 @@ SearchCore(interp, searchSpecPtr, patObj) lineInfo = (*searchSpecPtr->addLineProc)(lineNum, searchSpecPtr, theLine, &lastOffset); + if (lineInfo == NULL) { + /* + * This should not happen, since 'lineNum' should be valid + * in the call above. However, let's try to be flexible and + * not cause a crash below. + */ + goto nextLine; + } + if (lineNum == searchSpecPtr->stopLine && searchSpecPtr->backwards) { firstOffset = searchSpecPtr->stopOffset; } else { @@ -5075,3 +5868,162 @@ SearchCore(interp, searchSpecPtr, patObj) return code; } + +/* + *---------------------------------------------------------------------- + * + * GetLineStartEnd - + * + * Converts an internal TkTextLine ptr into a Tcl string obj + * containing the line number. (Handler for the 'line' + * configuration option type) + * + * Results: + * Tcl_Obj containing the string representation of the line value. + * + * Side effects: + * Creates a new Tcl_Obj. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +GetLineStartEnd(clientData, tkwin, recordPtr, internalOffset) + ClientData clientData; + Tk_Window tkwin; + char *recordPtr; /* Pointer to widget record. */ + int internalOffset; /* Offset within *recordPtr containing the + * line value. */ +{ + TkTextLine *linePtr = *(TkTextLine **)(recordPtr + internalOffset); + + if (linePtr == NULL) { + return Tcl_NewObj(); + } else { + return Tcl_NewIntObj(1+TkBTreeLinesTo(NULL, linePtr)); + } +} + +/* + *---------------------------------------------------------------------- + * + * SetLineStartEnd -- + * + * Converts a Tcl_Obj representing a widget's (start or end) line + * into a TkTextLine* value. (Handler for the 'line' configuration + * option type) + * + * Results: + * Standard Tcl result. + * + * Side effects: + * May store the TkTextLine* value into the internal representation + * pointer. May change the pointer to the Tcl_Obj to NULL to indicate + * that the specified string was empty and that is acceptable. + * + *---------------------------------------------------------------------- + */ + +static int +SetLineStartEnd(clientData, interp, tkwin, value, recordPtr, internalOffset, + oldInternalPtr, flags) + ClientData clientData; + Tcl_Interp *interp; /* Current interp; may be used for errors. */ + Tk_Window tkwin; /* Window for which option is being set. */ + Tcl_Obj **value; /* Pointer to the pointer to the value object. + * We use a pointer to the pointer because + * we may need to return a value (NULL). */ + char *recordPtr; /* Pointer to storage for the widget record. */ + int internalOffset; /* Offset within *recordPtr at which the + internal value is to be stored. */ + char *oldInternalPtr; /* Pointer to storage for the old value. */ + int flags; /* Flags for the option, set Tk_SetOptions. */ +{ + TkTextLine *linePtr = NULL; + char *internalPtr; + TkText *textPtr = (TkText*)recordPtr; + + if (internalOffset >= 0) { + internalPtr = recordPtr + internalOffset; + } else { + internalPtr = NULL; + } + + if (flags & TK_OPTION_NULL_OK && ObjectIsEmpty(*value)) { + *value = NULL; + } else { + int line; + if (Tcl_GetIntFromObj(interp, *value, &line) != TCL_OK) { + return TCL_ERROR; + } + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, NULL, line-1); + } + + if (internalPtr != NULL) { + *((TkTextLine **) oldInternalPtr) = *((TkTextLine **) internalPtr); + *((TkTextLine **) internalPtr) = linePtr; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * RestoreLineStartEnd -- + * + * Restore a line option value from a saved value. (Handler for + * the 'line' configuration option type) + * + * Results: + * None. + * + * Side effects: + * Restores the old value. + * + *---------------------------------------------------------------------- + */ + +static void +RestoreLineStartEnd(clientData, tkwin, internalPtr, oldInternalPtr) + ClientData clientData; + Tk_Window tkwin; + char *internalPtr; /* Pointer to storage for value. */ + char *oldInternalPtr; /* Pointer to old value. */ +{ + *(TkTextLine **)internalPtr = *(TkTextLine **)oldInternalPtr; +} + +/* + *---------------------------------------------------------------------- + * + * ObjectIsEmpty -- + * + * This procedure tests whether the string value of an object is + * empty. + * + * Results: + * The return value is 1 if the string value of objPtr has length + * zero, and 0 otherwise. + * + * Side effects: + * May cause object shimmering, since this function can force a + * conversion to a string object. + * + *---------------------------------------------------------------------- + */ + +static int +ObjectIsEmpty(objPtr) + Tcl_Obj *objPtr; /* Object to test. May be NULL. */ +{ + int length; + + if (objPtr == NULL) { + return 1; + } + if (objPtr->bytes != NULL) { + return (objPtr->length == 0); + } + Tcl_GetStringFromObj(objPtr, &length); + return (length == 0); +} diff --git a/generic/tkText.h b/generic/tkText.h index 57560e8..14b2a71 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.24 2003/12/15 11:51:05 vincentdarley Exp $ + * RCS: @(#) $Id: tkText.h,v 1.25 2004/09/10 12:13:41 vincentdarley Exp $ */ #ifndef _TKTEXT @@ -50,15 +50,16 @@ 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. */ + int *pixels; /* Array containing two integers + * for each referring text widget. + * The first of these is the number + * of vertical pixels taken up by + * this line, whether currently + * displayed or not. This number + * is only updated asychronously. + * The second of these is the last + * epoch at which the pixel height + * was recalculated. */ } TkTextLine; /* @@ -94,7 +95,7 @@ typedef struct TkTextMark { TkTextLine *linePtr; /* Line structure that contains the * segment. */ Tcl_HashEntry *hPtr; /* Pointer to hash table entry for mark - * (in textPtr->markTable). */ + * (in sharedTextPtr->markTable). */ } TkTextMark; /* @@ -103,14 +104,33 @@ typedef struct TkTextMark { * file tkTextWind.c */ -typedef struct TkTextEmbWindow { +typedef struct TkTextEmbWindowClient { struct TkText *textPtr; /* Information about the overall text * widget. */ - TkTextLine *linePtr; /* Line structure that contains this - * window. */ Tk_Window tkwin; /* Window for this segment. NULL * means that the window hasn't * been created yet. */ + int chunkCount; /* Number of display chunks that + * refer to this window. */ + int displayed; /* Non-zero means that the window + * has been displayed on the screen + * recently. */ + struct TkTextSegment *parent; + struct TkTextEmbWindowClient *next; +} TkTextEmbWindowClient; + +typedef struct TkTextEmbWindow { + struct TkSharedText *sharedTextPtr; /* Information about the shared + * portion of the text widget. */ + Tk_Window tkwin; /* Window for this segment. + * This is just a temporary + * value, copied from + * 'clients', to make option + * table updating easier. NULL + * means that the window hasn't + * been created yet. */ + TkTextLine *linePtr; /* Line structure that contains this + * window. */ char *create; /* Script to create window on-demand. * NULL means no such script. * Malloc-ed. */ @@ -122,13 +142,11 @@ typedef struct TkTextEmbWindow { int stretch; /* Should window stretch to fill * vertical space of line (except for * pady)? 0 or 1. */ - int chunkCount; /* Number of display chunks that - * refer to this window. */ - int displayed; /* Non-zero means that the window - * has been displayed on the screen - * recently. */ Tk_OptionTable optionTable; /* Token representing the * configuration specifications. */ + TkTextEmbWindowClient *clients; /* Linked list of peer-widget + * specific information for + * this embedded window. */ } TkTextEmbWindow; /* @@ -138,8 +156,10 @@ typedef struct TkTextEmbWindow { */ typedef struct TkTextEmbImage { - struct TkText *textPtr; /* Information about the overall text - * widget. */ + struct TkSharedText *sharedTextPtr; /* Information about the shared + * portion of the text widget. + * This is used when the image + * changes or is deleted. */ TkTextLine *linePtr; /* Line structure that contains this * image. */ char *imageString; /* Name of the image for this segment */ @@ -215,6 +235,7 @@ typedef struct TkTextIndex { typedef struct TkTextDispChunk TkTextDispChunk; typedef void Tk_ChunkDisplayProc _ANSI_ARGS_(( + struct TkText *textPtr, TkTextDispChunk *chunkPtr, int x, int y, int height, int baseline, Display *display, Drawable dst, int screenY)); @@ -224,6 +245,7 @@ typedef void Tk_ChunkUndisplayProc _ANSI_ARGS_(( typedef int Tk_ChunkMeasureProc _ANSI_ARGS_(( TkTextDispChunk *chunkPtr, int x)); typedef void Tk_ChunkBboxProc _ANSI_ARGS_(( + struct TkText *textPtr, TkTextDispChunk *chunkPtr, int index, int y, int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr, int *heightPtr)); @@ -293,8 +315,8 @@ struct TkTextDispChunk { /* * One data structure of the following type is used for each tag in a - * text widget. These structures are kept in textPtr->tagTable and - * referred to in other structures. + * text widget. These structures are kept in sharedTextPtr->tagTable + * and referred to in other structures. */ typedef enum { TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, @@ -302,10 +324,17 @@ typedef enum { TEXT_WRAPMODE_CHAR, TEXT_WRAPMODE_NONE, } TkWrapMode; typedef struct TkTextTag { - 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. */ + CONST char *name; /* Name of this tag. This field is + * actually a pointer to the key from + * the entry in + * sharedTextPtr->tagTable, so it + * needn't be freed explicitly. For + * 'sel' tags this is just a static + * string, so again need not be freed. + * */ + CONST struct TkText *textPtr;/* If non-NULL, then this tag only + * applies to the given text widget + * (when there are peer widgets). */ int priority; /* Priority of this tag within widget. 0 * means lowest priority. Exactly one tag * has each integer value between 0 and @@ -504,30 +533,29 @@ typedef enum { } TkTextState; /* - * A data structure of the following type is kept for each text widget that - * currently exists for this process: + * A data structure of the following type is shared between each text widget + * that are peers. */ -typedef struct TkText { - Tk_Window tkwin; /* Window that embodies the text. NULL - * means that the window has been destroyed - * but the data structures haven't yet been - * cleaned up.*/ - Display *display; /* Display for widget. Needed, among other - * things, to allow resources to be freed - * even after tkwin has gone away. */ - Tcl_Interp *interp; /* Interpreter associated with widget. Used - * to delete widget command. */ - Tcl_Command widgetCmd; /* Token for text's widget command. */ +typedef struct TkSharedText { + int refCount; /* Reference count this shared object */ + TkTextBTree tree; /* B-tree representation of text and tags for * widget. */ Tcl_HashTable tagTable; /* Hash table that maps from tag names to - * pointers to TkTextTag structures. */ + * pointers to TkTextTag structures. + * The "sel" tag does not feature in + * this table, since there's one of + * those for each text peer. */ int numTags; /* Number of tags currently defined for * widget; needed to keep track of * priorities. */ Tcl_HashTable markTable; /* Hash table that maps from mark names to - * pointers to mark segments. */ + * pointers to mark segments. The + * special "insert" and "current" marks + * are not stored in this table, but + * directly accessed as fields of + * textPtr. */ Tcl_HashTable windowTable; /* Hash table that maps from window names * to pointers to window segments. If a * window segment doesn't yet have an @@ -538,6 +566,96 @@ typedef struct TkText { * image segment doesn't yet have an * associated image, there is no entry for * it here. */ + Tk_BindingTable bindingTable; + /* Table of all bindings currently defined + * for this widget. NULL means that no + * bindings exist, so the table hasn't been + * created. Each "object" used for this + * table is the name of a tag. */ + int stateEpoch; /* This is incremented each time the + * B-tree's contents change + * structurally, and means that any + * cached TkTextIndex objects are no + * longer valid. */ + + /* + * Information related to the undo/redo functonality + */ + + TkUndoRedoStack *undoStack; /* The undo/redo stack */ + + int undo; /* Non-zero means the undo/redo behaviour is + * enabled */ + + int maxUndo; /* The maximum depth of the undo stack + * expressed as the maximum number of + * compound statements */ + + int autoSeparators; /* Non-zero means the separators will be + * inserted automatically */ + + int modifiedSet; /* Flag indicating that the 'dirtynesss' of + * the text widget has been expplicitly set. + */ + + int isDirty; /* Flag indicating the 'dirtynesss' of the text + * widget. If the flag is not zero, unsaved + * modifications have been applied to the + * text widget */ + + int isDirtyIncrement; /* Amount with which the isDirty flag is + * incremented every edit action */ + + TkTextEditMode lastEditMode;/* Keeps track of what the last edit + * mode was */ + + /* + * Keep track of all the peers + */ + struct TkText *peers; + +} TkSharedText; + +/* + * A data structure of the following type is kept for each text widget that + * currently exists for this process: + */ + +typedef struct TkText { + /* + * Information related to and accessed by widget peers and the + * TkSharedText handling routines. + */ + + TkSharedText *sharedTextPtr;/* Shared section of all peers. */ + struct TkText *next; /* Next in list of linked peers. */ + TkTextLine *start; /* First B-tree line to show, or NULL + * to start at the beginning. */ + TkTextLine *end; /* Last B-tree line to show, or NULL + * for up to the end. */ + int pixelReference; /* Counter into the current tree + * reference index corresponding to + * this widget */ + + int abortSelections; /* Set to 1 whenever the text is modified + * in a way that interferes with selection + * retrieval: used to abort incremental + * selection retrievals. */ + + /* + * Standard Tk widget information and text-widget specific items + */ + + Tk_Window tkwin; /* Window that embodies the text. NULL + * means that the window has been destroyed + * but the data structures haven't yet been + * cleaned up.*/ + Display *display; /* Display for widget. Needed, among other + * things, to allow resources to be freed + * even after tkwin has gone away. */ + Tcl_Interp *interp; /* Interpreter associated with widget. Used + * to delete widget command. */ + Tcl_Command widgetCmd; /* Token for text's widget command. */ int state; /* Either STATE_NORMAL or STATE_DISABLED. A * text widget is read-only when disabled. */ /* @@ -603,27 +721,22 @@ typedef struct TkText { * a new selection has been made. */ Tk_3DBorder selBorder; /* Border and background for selected * characters. This is a copy of information - * in *cursorTagPtr, so it shouldn't be + * in *selTagPtr, so it shouldn't be * explicitly freed. */ + Tk_3DBorder inactiveSelBorder;/* Border and background for selected + * characters when they don't have the + * focus. */ int selBorderWidth; /* Width of border around selection. */ Tcl_Obj* selBorderWidthPtr; /* Width of border around selection. */ XColor *selFgColorPtr; /* Foreground color for selected text. * This is a copy of information in - * *cursorTagPtr, so it shouldn't be + * *selTagPtr, so it shouldn't be * explicitly freed. */ int exportSelection; /* Non-zero means tie "sel" tag to X * selection. */ TkTextIndex selIndex; /* Used during multi-pass selection retrievals. * This index identifies the next character * to be returned from the selection. */ - int abortSelections; /* Set to 1 whenever the text is modified - * in a way that interferes with selection - * retrieval: used to abort incremental - * selection retrievals. */ - int selOffset; /* Offset in selection corresponding to - * selLine and selCh. -1 means neither - * this information nor selIndex is of any - * use. */ /* * Information related to insertion cursor: @@ -647,12 +760,6 @@ typedef struct TkText { * Information used for event bindings associated with tags: */ - Tk_BindingTable bindingTable; - /* Table of all bindings currently defined - * for this widget. NULL means that no - * bindings exist, so the table hasn't been - * created. Each "object" used for this - * table is the address of a tag. */ TkTextSegment *currentMarkPtr; /* Pointer to segment for "current" mark, * or NULL if none. */ @@ -682,44 +789,24 @@ typedef struct TkText { Tk_OptionTable optionTable; /* Token representing the configuration * specifications. */ - int stateEpoch; /* This is incremented each time the widget's - * contents change, and means that any cached - * TkTextIndex objects are no longer valid. */ int refCount; /* Number of cached TkTextIndex objects * refering to us */ - + int insertCursorType; /* 0 = standard insertion cursor, + * 1 = block cursor. */ /* - * Information related to the undo/redo functonality + * Copies of information from the shared section relating to the + * undo/redo functonality */ - TkUndoRedoStack *undoStack; /* The undo/redo stack */ - int undo; /* Non-zero means the undo/redo behaviour is * enabled */ int maxUndo; /* The maximum depth of the undo stack - * expressed as the maximum number of - * compound statements */ + * expressed as the maximum number of + * compound statements */ int autoSeparators; /* Non-zero means the separators will be * inserted automatically */ - - int modifiedSet; /* Flag indicating that the 'dirtynesss' of - * the text widget has been expplicitly set. - */ - - int isDirty; /* Flag indicating the 'dirtynesss' of the text - * widget. If the flag is not zero, unsaved - * modifications have been applied to the - * text widget */ - - int isDirtyIncrement; /* Amount with which the isDirty flag is - * incremented every edit action */ - - TkTextEditMode lastEditMode;/* Keeps track of what the last edit - * mode was */ - int insertCursorType; /* 0 = standard insertion cursor, - * 1 = block cursor. */ } TkText; /* @@ -837,6 +924,7 @@ typedef struct TkTextElideInfo { int elide; /* Is the state currently elided */ int elidePriority; /* Tag priority controlling elide state */ TkTextSegment *segPtr; /* Segment to look at next */ + int segOffset; /* Offset of segment within line */ int deftagCnts[LOTSA_TAGS]; TkTextTag *deftagPtrs[LOTSA_TAGS]; int *tagCnts; /* 0 or 1 depending if the tag with @@ -864,6 +952,11 @@ typedef struct TkTextElideInfo { * calculations of individual lines displayed in the widget. */ #define TK_TEXT_LINE_GEOMETRY 1 +/* + * Mask used for those options which may impact the start and + * end lines used in the widget. + */ +#define TK_TEXT_LINE_RANGE 2 /* * Used as 'action' values in calls to TkTextInvalidateLineMetrics @@ -892,38 +985,59 @@ EXTERN Tk_SegType tkTextToggleOnType; EXTERN Tk_SegType tkTextToggleOffType; /* + * Convenience macros for use by B-tree clients which want to access + * pixel information on each line. Currently only used by TkTextDisp.c + */ + +#define TkBTreeLinePixelCount(text, line) \ + line->pixels[2*text->pixelReference] +#define TkBTreeLinePixelEpoch(text, line) \ + line->pixels[1+2*text->pixelReference] + +/* * Declarations for procedures that are used by the text-related files * but shouldn't be used anywhere else in Tk (or by Tk clients): */ - -EXTERN int TkBTreeAdjustPixelHeight _ANSI_ARGS_((TkTextLine *linePtr, - int newPixelHeight)); + +EXTERN int TkBTreeAdjustPixelHeight _ANSI_ARGS_((CONST TkText *textPtr, + TkTextLine *linePtr, int newPixelHeight)); EXTERN int TkBTreeCharTagged _ANSI_ARGS_((CONST TkTextIndex *indexPtr, TkTextTag *tagPtr)); EXTERN void TkBTreeCheck _ANSI_ARGS_((TkTextBTree tree)); -EXTERN int TkBTreeCharsInLine _ANSI_ARGS_((TkTextLine *linePtr)); -EXTERN int TkBTreeBytesInLine _ANSI_ARGS_((TkTextLine *linePtr)); -EXTERN TkTextBTree TkBTreeCreate _ANSI_ARGS_((TkText *textPtr)); +EXTERN TkTextBTree TkBTreeCreate _ANSI_ARGS_((TkSharedText *sharedTextPtr)); +EXTERN void TkBTreeAddClient _ANSI_ARGS_((TkTextBTree tree, + TkText *textPtr, + int defaultHeight)); +EXTERN void TkBTreeClientRangeChanged _ANSI_ARGS_((TkText *textPtr, + int defaultHeight)); +EXTERN void TkBTreeRemoveClient _ANSI_ARGS_((TkTextBTree tree, + TkText *textPtr)); EXTERN void TkBTreeDestroy _ANSI_ARGS_((TkTextBTree tree)); -EXTERN void TkBTreeDeleteChars _ANSI_ARGS_((TkTextIndex *index1Ptr, - TkTextIndex *index2Ptr)); -EXTERN TkTextLine * TkBTreeFindLine _ANSI_ARGS_((TkTextBTree tree, - int line)); +EXTERN void TkBTreeDeleteChars _ANSI_ARGS_((TkTextBTree tree, + TkTextIndex *index1Ptr, TkTextIndex *index2Ptr)); +EXTERN TkTextLine * TkBTreeFindLine _ANSI_ARGS_((TkTextBTree tree, + CONST TkText *textPtr, int line)); EXTERN TkTextLine * TkBTreeFindPixelLine _ANSI_ARGS_((TkTextBTree tree, - int pixels, int *pixelOffset)); + CONST TkText *textPtr, 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)); + CONST TkText *textPtr, int *numTagsPtr)); +EXTERN void TkBTreeInsertChars _ANSI_ARGS_((TkTextBTree tree, + TkTextIndex *indexPtr, CONST char *string)); +EXTERN int TkBTreeLinesTo _ANSI_ARGS_((CONST TkText *textPtr, + TkTextLine *linePtr)); +EXTERN int TkBTreePixelsTo _ANSI_ARGS_((CONST TkText *textPtr, + TkTextLine *linePtr)); EXTERN void TkBTreeLinkSegment _ANSI_ARGS_((TkTextSegment *segPtr, TkTextIndex *indexPtr)); -EXTERN TkTextLine * TkBTreeNextLine _ANSI_ARGS_((TkTextLine *linePtr)); +EXTERN TkTextLine * TkBTreeNextLine _ANSI_ARGS_((CONST TkText *textPtr, + 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 TkBTreeNumLines _ANSI_ARGS_((TkTextBTree tree, + CONST TkText *textPtr)); +EXTERN int TkBTreeNumPixels _ANSI_ARGS_((TkTextBTree tree, + CONST TkText *textPtr)); +EXTERN TkTextLine * TkBTreePreviousLine _ANSI_ARGS_((TkText *textPtr, + TkTextLine *linePtr)); EXTERN int TkBTreePrevTag _ANSI_ARGS_((TkTextSearch *searchPtr)); EXTERN void TkBTreeStartSearch _ANSI_ARGS_((TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, @@ -934,11 +1048,13 @@ EXTERN void TkBTreeStartSearchBack _ANSI_ARGS_((TkTextIndex *index1Ptr, EXTERN int TkBTreeTag _ANSI_ARGS_((TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, int add)); -EXTERN void TkBTreeUnlinkSegment _ANSI_ARGS_((TkTextBTree tree, +EXTERN void TkBTreeUnlinkSegment _ANSI_ARGS_(( TkTextSegment *segPtr, TkTextLine *linePtr)); EXTERN void TkTextBindProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); -EXTERN void TkTextChanged _ANSI_ARGS_((TkText *textPtr, +EXTERN void TkTextSelectionEvent _ANSI_ARGS_((TkText *textPtr)); +EXTERN void TkTextChanged _ANSI_ARGS_((TkSharedText *sharedTextPtr, + TkText *textPtr, CONST TkTextIndex *index1Ptr, CONST TkTextIndex *index2Ptr)); EXTERN int TkTextCharBbox _ANSI_ARGS_((TkText *textPtr, @@ -952,13 +1068,15 @@ 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_(( +EXTERN void TkTextEmbWinDisplayProc _ANSI_ARGS_((TkText *textPtr, 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, int *newTag)); EXTERN void TkTextFreeDInfo _ANSI_ARGS_((TkText *textPtr)); +EXTERN void TkTextDeleteTag _ANSI_ARGS_((TkText *textPtr, + TkTextTag *tagPtr)); EXTERN void TkTextFreeTag _ANSI_ARGS_((TkText *textPtr, TkTextTag *tagPtr)); EXTERN int TkTextGetIndex _ANSI_ARGS_((Tcl_Interp *interp, @@ -967,6 +1085,9 @@ EXTERN int TkTextGetIndex _ANSI_ARGS_((Tcl_Interp *interp, EXTERN int TkTextGetObjIndex _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *idxPtr, TkTextIndex *indexPtr)); +EXTERN int TkTextSharedGetObjIndex _ANSI_ARGS_((Tcl_Interp *interp, + TkSharedText *sharedTextPtr, Tcl_Obj *idxPtr, + TkTextIndex *indexPtr)); EXTERN CONST TkTextIndex* TkTextGetIndexFromObj _ANSI_ARGS_((Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *objPtr)); EXTERN TkTextTabArray * TkTextGetTabs _ANSI_ARGS_((Tcl_Interp *interp, @@ -974,7 +1095,7 @@ EXTERN TkTextTabArray * TkTextGetTabs _ANSI_ARGS_((Tcl_Interp *interp, EXTERN void TkTextFindDisplayLineEnd _ANSI_ARGS_(( TkText *textPtr, TkTextIndex *indexPtr, int end, int *xOffset)); -EXTERN void TkTextIndexBackBytes _ANSI_ARGS_(( +EXTERN void TkTextIndexBackBytes _ANSI_ARGS_((CONST TkText *textPtr, CONST TkTextIndex *srcPtr, int count, TkTextIndex *dstPtr)); EXTERN void TkTextIndexBackChars _ANSI_ARGS_(( @@ -988,7 +1109,7 @@ EXTERN int TkTextIndexCount _ANSI_ARGS_((CONST TkText *textPtr, CONST TkTextIndex *index1Ptr, CONST TkTextIndex *index2Ptr, TkTextCountType type)); -EXTERN int TkTextIndexForwBytes _ANSI_ARGS_(( +EXTERN int TkTextIndexForwBytes _ANSI_ARGS_((CONST TkText *textPtr, CONST TkTextIndex *srcPtr, int count, TkTextIndex *dstPtr)); EXTERN void TkTextIndexForwChars _ANSI_ARGS_(( @@ -1001,14 +1122,14 @@ 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_(( +EXTERN void TkTextInsertDisplayProc _ANSI_ARGS_((TkText *textPtr, TkTextDispChunk *chunkPtr, int x, int y, int height, int baseline, Display *display, Drawable dst, int screenY)); EXTERN void TkTextLostSelection _ANSI_ARGS_(( ClientData clientData)); EXTERN TkTextIndex * TkTextMakeCharIndex _ANSI_ARGS_((TkTextBTree tree, - int lineIndex, int charIndex, + TkText *textPtr, int lineIndex, int charIndex, TkTextIndex *indexPtr)); EXTERN int TkTextMeasureDown _ANSI_ARGS_((TkText *textPtr, TkTextIndex *srcPtr, int distance)); @@ -1018,11 +1139,12 @@ EXTERN int TkTextIsElided _ANSI_ARGS_((CONST TkText *textPtr, CONST TkTextIndex *indexPtr, TkTextElideInfo *infoPtr)); EXTERN TkTextIndex * TkTextMakeByteIndex _ANSI_ARGS_((TkTextBTree tree, - int lineIndex, int byteIndex, + CONST TkText *textPtr, int lineIndex, int byteIndex, TkTextIndex *indexPtr)); EXTERN int TkTextMakePixelIndex _ANSI_ARGS_((TkText *textPtr, int pixelIndex, TkTextIndex *indexPtr)); -EXTERN void TkTextInvalidateLineMetrics _ANSI_ARGS_((TkText *textPtr, +EXTERN void TkTextInvalidateLineMetrics _ANSI_ARGS_(( + TkSharedText *sharedTextPtr, TkText *textPtr, TkTextLine *linePtr, int lineCount, int action)); EXTERN int TkTextUpdateLineMetrics _ANSI_ARGS_((TkText *textPtr, int lineNum, int endLine, int doThisMuch)); @@ -1040,13 +1162,14 @@ EXTERN void TkTextPickCurrent _ANSI_ARGS_((TkText *textPtr, XEvent *eventPtr)); EXTERN void TkTextPixelIndex _ANSI_ARGS_((TkText *textPtr, int x, int y, TkTextIndex *indexPtr, int *nearest)); -EXTERN int TkTextPrintIndex _ANSI_ARGS_(( +EXTERN int TkTextPrintIndex _ANSI_ARGS_((CONST TkText *textPtr, CONST TkTextIndex *indexPtr, char *string)); EXTERN Tcl_Obj* TkTextNewIndexObj _ANSI_ARGS_((TkText *textPtr, CONST TkTextIndex *indexPtr)); EXTERN void TkTextRedrawRegion _ANSI_ARGS_((TkText *textPtr, int x, int y, int width, int height)); -EXTERN void TkTextRedrawTag _ANSI_ARGS_((TkText *textPtr, +EXTERN void TkTextRedrawTag _ANSI_ARGS_((TkSharedText *sharedTextPtr, + TkText *textPtr, TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, TkTextTag *tagPtr, int withTag)); EXTERN void TkTextRelayoutWindow _ANSI_ARGS_((TkText *textPtr, @@ -1076,6 +1199,8 @@ 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[])); +EXTERN void TkTextWinFreeClient _ANSI_ARGS_((Tcl_HashEntry *hPtr, + TkTextEmbWindowClient *client)); # undef TCL_STORAGE_CLASS # define TCL_STORAGE_CLASS DLLIMPORT diff --git a/generic/tkTextBTree.c b/generic/tkTextBTree.c index 9f3b8e5..964dfcd 100644 --- a/generic/tkTextBTree.c +++ b/generic/tkTextBTree.c @@ -4,14 +4,14 @@ * This file contains code that manages the B-tree representation * of text for Tk's text widget and implements character and * toggle segment types. - * + * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * * 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.14 2004/06/07 16:23:52 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextBTree.c,v 1.15 2004/09/10 12:13:41 vincentdarley Exp $ */ #include "tkInt.h" @@ -19,6 +19,29 @@ #include "tkText.h" /* + * Implementation notes: + * + * Most of this file is independent of the text widget implementation + * and representation now. Without much effort this could be developed + * further into a new Tcl object type of which the Tk text widget is one + * example of a client. + * + * The B-tree is set up with a dummy last line of text which must not be + * displayed, and must _never_ have a non-zero pixel count. This dummy + * line is a historical convenience to avoid other code having to deal + * with NULL TkTextLines. Since Tk 8.5, with pixel line height + * calculations and peer widgets, this dummy line is becoming somewhat + * of a liability, and special case code has been required to deal with + * it. It is probably a good idea to investigate removing the dummy + * line completely. This could result in an overall simplification + * (although it would require new special case code to deal with the + * fact that '.text index end' would then not really point to a valid + * line, rather it would point to the beginning of a non-existent line + * one beyond all current lines - we could perhaps define that as a + * TkTextIndex with a NULL TkTextLine ptr). + */ + +/* * The data structure below keeps summary information about one tag as part * of the tag information in a node. */ @@ -55,11 +78,19 @@ 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; /* Total number of vertical - * display pixels in the - * subtree rooted here. */ + int* numPixels; /* Array containing total number + * of vertical display pixels in + * the subtree rooted here, one + * entry for each peer widget. */ } Node; +/* + * Used to avoid having to allocate and deallocate arrays on the + * fly for commonly used procedures. Must be > 0. + */ + +#define PIXEL_CLIENTS 5 + /* * Upper and lower bounds on how many children a node may have: * rebalance when either of these limits is exceeded. MAX_CHILDREN @@ -70,13 +101,24 @@ typedef struct Node { #define MIN_CHILDREN 6 /* - * The data structure below defines an entire B-tree. + * The data structure below defines an entire B-tree. Since text + * widgets are the only current B-tree clients, 'clients' and + * 'pixelReferences' are identical. */ typedef struct BTree { Node *rootPtr; /* Pointer to root of B-tree. */ - TkText *textPtr; /* Used to find tagTable in consistency - * checking code */ + int clients; /* Number of clients of this + * B-tree */ + int pixelReferences; /* Number of clients of this + * B-tree which care about + * pixel heights */ + TkSharedText *sharedTextPtr; /* Used to find tagTable in consistency + * checking code, and to access + * list of all B-tree clients */ + int startEndCount; + TkTextLine **startEnd; + TkText **startEndRef; } BTree; /* @@ -116,6 +158,10 @@ int tkBTreeDebug = 0; * Forward declarations for procedures defined in this file: */ +static int AdjustPixelClient _ANSI_ARGS_((BTree *treePtr, + int defaultHeight, Node *nodePtr, TkTextLine *start, + TkTextLine *end, int useReference, + int newPixelReferences, int *counting)); static void ChangeNodeToggleCount _ANSI_ARGS_((Node *nodePtr, TkTextTag *tagPtr, int delta)); static void CharCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, @@ -126,7 +172,8 @@ static TkTextSegment * CharCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static TkTextSegment * CharSplitProc _ANSI_ARGS_((TkTextSegment *segPtr, int index)); -static void CheckNodeConsistency _ANSI_ARGS_((Node *nodePtr)); +static void CheckNodeConsistency _ANSI_ARGS_((Node *nodePtr, + int references)); static void CleanupLine _ANSI_ARGS_((TkTextLine *linePtr)); static void DeleteSummaries _ANSI_ARGS_((Summary *tagPtr)); static void DestroyNode _ANSI_ARGS_((Node *nodePtr)); @@ -135,7 +182,10 @@ static TkTextSegment * FindTagEnd _ANSI_ARGS_((TkTextBTree tree, static void IncCount _ANSI_ARGS_((TkTextTag *tagPtr, int inc, TagInfo *tagInfoPtr)); static void Rebalance _ANSI_ARGS_((BTree *treePtr, Node *nodePtr)); -static void RecomputeNodeCounts _ANSI_ARGS_((Node *nodePtr)); +static void RecomputeNodeCounts _ANSI_ARGS_((BTree *treePtr, + Node *nodePtr)); +static void RemovePixelClient _ANSI_ARGS_((BTree *treePtr, + Node *nodePtr, int overwriteWithLast)); static TkTextSegment * SplitSeg _ANSI_ARGS_((TkTextIndex *indexPtr)); static void ToggleCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); @@ -147,6 +197,14 @@ static void ToggleLineChangeProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static TkTextSegment * FindTagStart _ANSI_ARGS_((TkTextBTree tree, TkTextTag *tagPtr, TkTextIndex *indexPtr)); +static void AdjustStartEndRefs _ANSI_ARGS_((BTree *treePtr, + TkText *textPtr, int action)); + +/* + * Actions for use by AdjustStartEndRefs + */ +#define TEXT_ADD_REFS 1 +#define TEXT_REMOVE_REFS 2 /* * Type record for character segments: @@ -213,8 +271,8 @@ Tk_SegType tkTextToggleOffType = { */ TkTextBTree -TkBTreeCreate(textPtr) - TkText *textPtr; +TkBTreeCreate(sharedTextPtr) + TkSharedText *sharedTextPtr; { register BTree *treePtr; register Node *rootPtr; @@ -231,6 +289,7 @@ TkBTreeCreate(textPtr) rootPtr = (Node *) ckalloc(sizeof(Node)); linePtr = (TkTextLine *) ckalloc(sizeof(TkTextLine)); linePtr2 = (TkTextLine *) ckalloc(sizeof(TkTextLine)); + rootPtr->parentPtr = NULL; rootPtr->nextPtr = NULL; rootPtr->summaryPtr = NULL; @@ -238,12 +297,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; + /* + * The tree currently has no registered clients, so all pixel + * count pointers are simply NULL + */ + rootPtr->numPixels = NULL; + linePtr->pixels = NULL; + linePtr2->pixels = NULL; linePtr->parentPtr = rootPtr; linePtr->nextPtr = linePtr2; @@ -266,21 +326,152 @@ TkBTreeCreate(textPtr) segPtr->body.chars[1] = 0; treePtr = (BTree *) ckalloc(sizeof(BTree)); + treePtr->sharedTextPtr = sharedTextPtr; treePtr->rootPtr = rootPtr; - treePtr->textPtr = textPtr; - + treePtr->clients = 0; + treePtr->pixelReferences = 0; + treePtr->startEndCount = 0; + treePtr->startEnd = NULL; + treePtr->startEndRef = NULL; + return (TkTextBTree) treePtr; } /* *---------------------------------------------------------------------- * + * TkBTreeAddClient -- + * + * This procedure is called to provide a client with access to + * a given B-tree. If the client wishes to make use of the + * B-tree's pixel height storage, caching and calculation + * mechanisms, then a non-negative 'defaultHeight' must be + * provided. In this case the return value is a pixel tree + * reference which must be provided in all of the B-tree API which + * refers to or modifies pixel heights: + * + * TkBTreeAdjustPixelHeight, + * TkBTreeFindPixelLine, + * TkBTreeNumPixels, + * TkBTreePixelsTo, + * (and two private functions AdjustPixelClient, RemovePixelClient). + * + * If this is not provided, then the above functions must + * never be called for this client. + * + * Results: + * The return value is the pixelReference used by the B-tree to + * refer to pixel counts for the new client. It should be stored + * by the caller. If defaultHeight was negative, then the return + * value will be -1. + * + * Side effects: + * Memory may be allocated and initialized. + * + *---------------------------------------------------------------------- + */ + +void +TkBTreeAddClient(tree, textPtr, defaultHeight) + TkTextBTree tree; /* B-tree to add a client to */ + TkText *textPtr; /* Client to add */ + int defaultHeight; /* Default line height for the new + * client, or -1 if no pixel + * heights are to be kept. */ +{ + register BTree *treePtr = (BTree*) tree; + + if (treePtr == NULL) { + Tcl_Panic("NULL treePtr in TkBTreeAddClient"); + } + + if (textPtr->start != NULL || textPtr->end != NULL) { + AdjustStartEndRefs(treePtr, textPtr, TEXT_ADD_REFS); + } + + if (defaultHeight >= 0) { + TkTextLine *end; + int counting = (textPtr->start == NULL ? 1 : 0); + int useReference = treePtr->pixelReferences; + /* + * We must set the 'end' value in AdjustPixelClient so that + * the last dummy line in the B-tree doesn't contain + * a pixel height. + */ + end = textPtr->end; + if (end == NULL) { + end = TkBTreeFindLine(tree, NULL, TkBTreeNumLines(tree, NULL)); + } + AdjustPixelClient(treePtr, defaultHeight, treePtr->rootPtr, + textPtr->start, end, useReference, + 1 + useReference, &counting); + + textPtr->pixelReference = useReference; + treePtr->pixelReferences++; + } else { + textPtr->pixelReference = -1; + } + treePtr->clients++; +} + +/* + *---------------------------------------------------------------------- + * + * TkBTreeClientRangeChanged -- + * + * Called when the -startline or -endline options of a text + * widget client of the B-tree have changed. + * + * Results: + * None. + * + * Side effects: + * Lots of processing of the B-tree is done, with potential + * for memory to be allocated and initialized for the pixel + * heights of the widget. + * + *---------------------------------------------------------------------- + */ + +void +TkBTreeClientRangeChanged(textPtr, defaultHeight) + TkText *textPtr; /* Client whose start, end have + * changed. */ + int defaultHeight; /* Default line height for the new + * client, or -1 if no pixel + * heights are to be kept. */ +{ + TkTextLine *end; + BTree *treePtr = (BTree*) textPtr->sharedTextPtr->tree; + + int counting = (textPtr->start == NULL ? 1 : 0); + int useReference = textPtr->pixelReference; + + AdjustStartEndRefs(treePtr, textPtr, TEXT_ADD_REFS | TEXT_REMOVE_REFS); + /* + * We must set the 'end' value in AdjustPixelClient so that + * the last dummy line in the B-tree doesn't contain + * a pixel height. + */ + end = textPtr->end; + if (end == NULL) { + end = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + NULL, TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL)); + } + AdjustPixelClient(treePtr, defaultHeight, treePtr->rootPtr, + textPtr->start, end, useReference, + treePtr->pixelReferences, &counting); +} + +/* + *---------------------------------------------------------------------- + * * TkBTreeDestroy -- * * Delete a B-tree, recycling all of the storage it contains. * * Results: - * The tree given by treePtr is deleted. TreePtr should never + * The tree is deleted, so 'tree' should never * again be used. * * Side effects: @@ -291,17 +482,347 @@ TkBTreeCreate(textPtr) void TkBTreeDestroy(tree) - TkTextBTree tree; /* Pointer to tree to delete. */ + TkTextBTree tree; /* Tree to clean up */ { BTree *treePtr = (BTree *) tree; + /* + * There's no need to loop over each client of the tree, calling + * 'TkBTreeRemoveClient', since the 'DestroyNode' will clean + * everything up itself. + */ + DestroyNode(treePtr->rootPtr); + if (treePtr->startEnd != NULL) { + ckfree((char *) treePtr->startEnd); + ckfree((char *) treePtr->startEndRef); + } ckfree((char *) treePtr); } /* *---------------------------------------------------------------------- * + * TkBTreeRemoveClient -- + * + * Remove a client widget from its B-tree, cleaning up the pixel + * arrays which it uses if necessary. If this is the last such + * widget, we also destroy the whole tree. + * + * Results: + * All tree-specific aspects of the given client are deleted. + * If no more references exist, then the given tree is also + * deleted (in which case 'tree' must not be used again). + * + * Side effects: + * Memory may be freed. + * + *---------------------------------------------------------------------- + */ + +void +TkBTreeRemoveClient(tree, textPtr) + TkTextBTree tree; /* Tree to remove client from */ + TkText *textPtr; /* Client to remove. */ +{ + BTree *treePtr = (BTree *) tree; + int pixelReference = textPtr->pixelReference; + + if (treePtr->clients == 1) { + /* The last reference to the tree */ + DestroyNode(treePtr->rootPtr); + ckfree((char *) treePtr); + return; + } else if (pixelReference == -1) { + /* A client which doesn't care about pixels */ + treePtr->clients--; + } else { + /* Clean up pixel data for the given reference */ + + if (pixelReference == (treePtr->pixelReferences-1)) { + /* + * The widget we're removing has the last index, + * so deletion is easier. + */ + RemovePixelClient(treePtr, treePtr->rootPtr, -1); + } else { + TkText *adjustPtr; + + RemovePixelClient(treePtr, treePtr->rootPtr, pixelReference); + + /* + * Now we need to adjust the 'pixelReference' of the + * peer widget whose storage we've just moved. + */ + adjustPtr = treePtr->sharedTextPtr->peers; + while (adjustPtr != NULL) { + if (adjustPtr->pixelReference == (treePtr->pixelReferences-1)) { + adjustPtr->pixelReference = pixelReference; + break; + } + adjustPtr = adjustPtr->next; + } + if (adjustPtr == NULL) { + Tcl_Panic("Couldn't find text widget with correct reference"); + } + } + treePtr->pixelReferences--; + treePtr->clients--; + } + + if (textPtr->start != NULL || textPtr->end != NULL) { + AdjustStartEndRefs(treePtr, textPtr, TEXT_REMOVE_REFS); + } +} + +/* + *---------------------------------------------------------------------- + * + * AdjustStartEndRefs -- + * + * Modify B-tree's cache of start, end lines for the given text + * widget. + * + * Results: + * None. + * + * Side effects: + * The number of cached items may change + * (treePtr->startEndCount). + * + *---------------------------------------------------------------------- + */ + +static void +AdjustStartEndRefs(treePtr, textPtr, action) + BTree *treePtr; /* The entire B-tree */ + TkText *textPtr; /* The text widget for which we want to + * adjust it's start and end cache. */ + int action; /* Action to perform */ +{ + if (action & TEXT_REMOVE_REFS) { + int i = 0; + int count = 0; + + while (i < treePtr->startEndCount) { + if (i != count) { + treePtr->startEnd[count] = treePtr->startEnd[i]; + treePtr->startEndRef[count] = treePtr->startEndRef[i]; + } + if (treePtr->startEndRef[i] != textPtr) { + count++; + } + i++; + } + treePtr->startEndCount = count; + treePtr->startEnd = (TkTextLine**)ckrealloc((char*)treePtr->startEnd, + sizeof(TkTextLine*)*count); + treePtr->startEndRef = (TkText**)ckrealloc((char*)treePtr->startEndRef, + sizeof(TkText*)*count); + } + if ((action & TEXT_ADD_REFS) + && (textPtr->start != NULL || textPtr->end != NULL)) { + int count; + + if (textPtr->start != NULL) treePtr->startEndCount++; + if (textPtr->end != NULL) treePtr->startEndCount++; + + count = treePtr->startEndCount; + + treePtr->startEnd = (TkTextLine**)ckrealloc((char*)treePtr->startEnd, + sizeof(TkTextLine*)*count); + treePtr->startEndRef = (TkText**)ckrealloc((char*)treePtr->startEndRef, + sizeof(TkText*)*count); + + if (textPtr->start != NULL) { + count--; + treePtr->startEnd[count] = textPtr->start; + treePtr->startEndRef[count] = treePtr->sharedTextPtr->peers; + } + if (textPtr->end != NULL) { + count--; + treePtr->startEnd[count] = textPtr->end; + treePtr->startEndRef[count] = treePtr->sharedTextPtr->peers; + } + } +} + +/* + *---------------------------------------------------------------------- + * + * AdjustPixelClient -- + * + * Utility procedure used to update all data structures for the + * existence of a new peer widget based on this B-tree, or for + * the modification of the start, end lines of an existing peer + * widget. + * + * Immediately _after_ calling this, treePtr->clients and + * treePtr->pixelReferences should be adjusted if needed (i.e. + * if this is a new peer). + * + * Results: + * None. + * + * Side effects: + * All the storage for Nodes and TkTextLines in the tree may + * be adjusted. + * + *---------------------------------------------------------------------- + */ + +static int +AdjustPixelClient(treePtr, defaultHeight, nodePtr, start, end, + useReference, newPixelReferences, counting) + BTree *treePtr; /* Pointer to tree */ + int defaultHeight; /* Default pixel line + * height, which can be zero. */ + Node *nodePtr; /* Adjust from this node + * downwards */ + TkTextLine *start; /* First line for this pixel + * client */ + TkTextLine *end; /* Last line for this pixel + * client */ + int useReference; /* pixel reference for the + * client we are adding or + * changing */ + int newPixelReferences; /* New number of pixel + * references to this B-tree */ + int *counting; /* References an integer which + * is zero if we're outside the + * relevant range for this + * client, and 1 if we're + * inside. */ +{ + int pixelCount = 0; + + /* + * Traverse entire tree down from nodePtr, reallocating pixel + * structures for each Node and TkTextLine, adding room for the new + * peer's pixel information (1 extra int per Node, 2 extra ints per + * TkTextLine). Also copy the information from the last peer into + * the new space (so it contains something sensible). + */ + + if (nodePtr->level != 0) { + Node *loopPtr = nodePtr->children.nodePtr; + while (loopPtr != NULL) { + pixelCount += AdjustPixelClient(treePtr, defaultHeight, loopPtr, + start, end, useReference, + newPixelReferences, counting); + loopPtr = loopPtr->nextPtr; + } + } else { + register TkTextLine *linePtr = nodePtr->children.linePtr; + + while (linePtr != NULL) { + if (!*counting && (linePtr == start)) { + *counting = 1; + } + if (*counting && (linePtr == end)) { + *counting = 0; + } + if (newPixelReferences != treePtr->pixelReferences) { + linePtr->pixels = (int*)ckrealloc((char*)linePtr->pixels, + sizeof(int)*2*newPixelReferences); + } + /* + * Notice that for the very last line, we are never counting + * and therefore this always has a height of 0 and an epoch + * of 1. + */ + linePtr->pixels[2*useReference] = (*counting ? defaultHeight : 0); + linePtr->pixels[1+2*useReference] = (*counting ? 0 : 1); + pixelCount += linePtr->pixels[2*useReference]; + + linePtr = linePtr->nextPtr; + } + } + if (newPixelReferences != treePtr->pixelReferences) { + nodePtr->numPixels = (int*)ckrealloc((char*)nodePtr->numPixels, + sizeof(int)*newPixelReferences); + } + nodePtr->numPixels[useReference] = pixelCount; + return pixelCount; +} + +/* + *---------------------------------------------------------------------- + * + * RemovePixelClient -- + * + * Utility procedure used to update all data structures for the + * removal of a peer widget which used to be based on this B-tree. + * + * Immediately _after_ calling this, treePtr->clients should + * be decremented. + * + * Results: + * None. + * + * Side effects: + * All the storage for Nodes and TkTextLines in the tree may + * be adjusted. + * + *---------------------------------------------------------------------- + */ + +static void +RemovePixelClient(treePtr, nodePtr, overwriteWithLast) + BTree *treePtr; /* Pointer to tree */ + Node *nodePtr; /* Adjust from this node + * downwards */ + int overwriteWithLast; /* Over-write this peer widget's + * information with the last one + */ +{ + /* + * Traverse entire tree down from nodePtr, reallocating pixel + * structures for each Node and TkTextLine, removing space allocated + * for one peer. If 'overwriteWithLast' is not -1, then copy the + * information which was in the last slot on top of one of the + * others (i.e. it's not the last one we're deleting). + */ + + if (overwriteWithLast != -1) { + nodePtr->numPixels[overwriteWithLast] + = nodePtr->numPixels[treePtr->pixelReferences-1]; + } + if (treePtr->pixelReferences == 1) { + nodePtr->numPixels = NULL; + } else { + nodePtr->numPixels = (int*)ckrealloc((char*)nodePtr->numPixels, + sizeof(int)*(treePtr->pixelReferences-1)); + } + if (nodePtr->level != 0) { + nodePtr = nodePtr->children.nodePtr; + while (nodePtr != NULL) { + RemovePixelClient(treePtr, nodePtr, overwriteWithLast); + nodePtr = nodePtr->nextPtr; + } + } else { + register TkTextLine *linePtr = nodePtr->children.linePtr; + while (linePtr != NULL) { + if (overwriteWithLast != -1) { + linePtr->pixels[2*overwriteWithLast] + = linePtr->pixels[2*(treePtr->pixelReferences-1)]; + linePtr->pixels[1+2*overwriteWithLast] + = linePtr->pixels[1+2*(treePtr->pixelReferences-1)]; + } + if (treePtr->pixelReferences == 1) { + linePtr->pixels = NULL; + } else { + linePtr->pixels = (int*)ckrealloc((char*)linePtr->pixels, + sizeof(int)*2*(treePtr->pixelReferences-1)); + } + linePtr = linePtr->nextPtr; + } + } +} + +/* + *---------------------------------------------------------------------- + * * DestroyNode -- * * This is a recursive utility procedure used during the deletion @@ -318,7 +839,7 @@ TkBTreeDestroy(tree) static void DestroyNode(nodePtr) - register Node *nodePtr; + register Node *nodePtr; /* Destroy from this node downwards */ { if (nodePtr->level == 0) { TkTextLine *linePtr; @@ -332,6 +853,7 @@ DestroyNode(nodePtr) linePtr->segPtr = segPtr->nextPtr; (*segPtr->typePtr->deleteProc)(segPtr, linePtr, 1); } + ckfree((char *) linePtr->pixels); ckfree((char *) linePtr); } } else { @@ -344,6 +866,7 @@ DestroyNode(nodePtr) } } DeleteSummaries(nodePtr->summaryPtr); + ckfree((char *) nodePtr->numPixels); ckfree((char *) nodePtr); } @@ -396,7 +919,8 @@ DeleteSummaries(summaryPtr) */ int -TkBTreeAdjustPixelHeight(linePtr, newPixelHeight) +TkBTreeAdjustPixelHeight(textPtr, linePtr, newPixelHeight) + CONST TkText *textPtr; /* Client of the B-tree */ register TkTextLine *linePtr; /* The logical line to update */ int newPixelHeight; /* The line's known height * in pixels */ @@ -404,8 +928,9 @@ TkBTreeAdjustPixelHeight(linePtr, newPixelHeight) register Node *nodePtr; int changeToPixelCount; /* Counts change to total number of * pixels in file. */ - - changeToPixelCount = newPixelHeight - linePtr->pixelHeight; + int pixelReference = textPtr->pixelReference; + + changeToPixelCount = newPixelHeight - linePtr->pixels[2*pixelReference]; /* * Increment the pixel counts in all the parent nodes of the @@ -413,16 +938,16 @@ TkBTreeAdjustPixelHeight(linePtr, newPixelHeight) */ nodePtr = linePtr->parentPtr; - nodePtr->numPixels += changeToPixelCount; + nodePtr->numPixels[pixelReference] += changeToPixelCount; while (nodePtr->parentPtr != NULL) { nodePtr = nodePtr->parentPtr; - nodePtr->numPixels += changeToPixelCount; + nodePtr->numPixels[pixelReference] += changeToPixelCount; } - linePtr->pixelHeight = newPixelHeight; + linePtr->pixels[2*pixelReference] = newPixelHeight; - return nodePtr->numPixels; + return nodePtr->numPixels[pixelReference]; } /* @@ -444,7 +969,8 @@ TkBTreeAdjustPixelHeight(linePtr, newPixelHeight) */ void -TkBTreeInsertChars(indexPtr, string) +TkBTreeInsertChars(tree, indexPtr, string) + TkTextBTree tree; /* Tree to insert into */ register TkTextIndex *indexPtr; /* Indicates where to insert text. * When the procedure returns, this * index is no longer valid because @@ -471,9 +997,12 @@ 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 + int *changeToPixelCount; /* Counts change to total number of * pixels in file. */ - + int ref; + int pixels[PIXEL_CLIENTS]; + + BTree *treePtr = (BTree*)tree; prevPtr = SplitSeg(indexPtr); linePtr = indexPtr->linePtr; curPtr = prevPtr; @@ -485,7 +1014,16 @@ TkBTreeInsertChars(indexPtr, string) */ changeToLineCount = 0; - changeToPixelCount = 0; + if (treePtr->pixelReferences > PIXEL_CLIENTS) { + changeToPixelCount = (int*) ckalloc(sizeof(int) * + treePtr->pixelReferences); + } else { + changeToPixelCount = pixels; + } + for (ref = 0; ref < treePtr->pixelReferences; ref++) { + changeToPixelCount[ref] = 0; + } + while (*string != 0) { for (eol = string; *eol != 0; eol++) { if (*eol == '\n') { @@ -517,22 +1055,27 @@ TkBTreeInsertChars(indexPtr, string) */ newLinePtr = (TkTextLine *) ckalloc(sizeof(TkTextLine)); + newLinePtr->pixels = + (int*) ckalloc(sizeof(int)*2*treePtr->pixelReferences); + newLinePtr->parentPtr = linePtr->parentPtr; newLinePtr->nextPtr = linePtr->nextPtr; linePtr->nextPtr = newLinePtr; newLinePtr->segPtr = segPtr->nextPtr; /* * Set up a starting default height, which will be re-adjusted - * later + * later. We need to do this for each referenced widget */ - newLinePtr->pixelHeight = linePtr->pixelHeight; - newLinePtr->pixelCalculationEpoch = 0; + for (ref = 0; ref < treePtr->pixelReferences; ref++) { + newLinePtr->pixels[2*ref] = linePtr->pixels[2*ref]; + newLinePtr->pixels[1+2*ref] = 0; + changeToPixelCount[ref] += newLinePtr->pixels[2*ref]; + } segPtr->nextPtr = NULL; linePtr = newLinePtr; curPtr = NULL; changeToLineCount++; - changeToPixelCount += newLinePtr->pixelHeight; string = eol; } @@ -543,7 +1086,7 @@ TkBTreeInsertChars(indexPtr, string) * 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, + TkTextInvalidateLineMetrics(treePtr->sharedTextPtr, NULL, indexPtr->linePtr, changeToLineCount, TK_TEXT_INVALIDATE_INSERT); @@ -565,12 +1108,18 @@ TkBTreeInsertChars(indexPtr, string) for (nodePtr = linePtr->parentPtr ; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines += changeToLineCount; - nodePtr->numPixels += changeToPixelCount; + for (ref = 0; ref < treePtr->pixelReferences; ref++) { + nodePtr->numPixels[ref] += changeToPixelCount[ref]; + } + } + if (treePtr->pixelReferences > PIXEL_CLIENTS) { + ckfree((char*)changeToPixelCount); } + nodePtr = linePtr->parentPtr; nodePtr->numChildren += changeToLineCount; if (nodePtr->numChildren > MAX_CHILDREN) { - Rebalance((BTree *) indexPtr->tree, nodePtr); + Rebalance(treePtr, nodePtr); } if (tkBTreeDebug) { @@ -715,7 +1264,8 @@ CleanupLine(linePtr) */ void -TkBTreeDeleteChars(index1Ptr, index2Ptr) +TkBTreeDeleteChars(tree, index1Ptr, index2Ptr) + TkTextBTree tree; /* Tree to delete from */ register TkTextIndex *index1Ptr; /* Indicates first character that is * to be deleted. */ register TkTextIndex *index2Ptr; /* Indicates character just after the @@ -729,6 +1279,8 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) TkTextLine *curLinePtr; Node *curNodePtr, *nodePtr; int changeToLineCount = 0; + int ref; + BTree *treePtr = (BTree*)tree; /* * Tricky point: split at index2Ptr first; otherwise the split @@ -767,7 +1319,7 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) * (unless it's the starting line for the range). */ - nextLinePtr = TkBTreeNextLine(curLinePtr); + nextLinePtr = TkBTreeNextLine(NULL, curLinePtr); if (curLinePtr != index1Ptr->linePtr) { if (curNodePtr == index1Ptr->linePtr->parentPtr) { index1Ptr->linePtr->nextPtr = curLinePtr->nextPtr; @@ -777,10 +1329,35 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) for (nodePtr = curNodePtr; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines--; - nodePtr->numPixels -= curLinePtr->pixelHeight; + for (ref = 0; ref < treePtr->pixelReferences; ref++) { + nodePtr->numPixels[ref] -= curLinePtr->pixels[2*ref]; + } } changeToLineCount++; curNodePtr->numChildren--; + /* Check if we need to adjust any partial clients */ + if (treePtr->startEnd != NULL) { + int checkCount = 0; + while (checkCount < treePtr->startEndCount) { + if (treePtr->startEnd[checkCount] == curLinePtr) { + TkText *peer = treePtr->startEndRef[checkCount]; + /* + * We're deleting a line which is the start + * or end of a current client. This means + * we need to adjust that client. + */ + treePtr->startEnd[checkCount] = nextLinePtr; + if (peer->start == curLinePtr) { + peer->start = nextLinePtr; + } + if (peer->end == curLinePtr) { + peer->end = nextLinePtr; + } + } + checkCount++; + } + } + ckfree((char *) curLinePtr->pixels); ckfree((char *) curLinePtr); } curLinePtr = nextLinePtr; @@ -851,7 +1428,9 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) for (nodePtr = curNodePtr; nodePtr != NULL; nodePtr = nodePtr->parentPtr) { nodePtr->numLines--; - nodePtr->numPixels -= index2Ptr->linePtr->pixelHeight; + for (ref = 0; ref < treePtr->pixelReferences; ref++) { + nodePtr->numPixels[ref] -= index2Ptr->linePtr->pixels[2*ref]; + } } changeToLineCount++; curNodePtr->numChildren--; @@ -864,6 +1443,36 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) } prevLinePtr->nextPtr = index2Ptr->linePtr->nextPtr; } + /* + * Check if we need to adjust any partial clients. In this case + * if we're deleting the line, we actually move back to the + * previous line for our (start,end) storage. We do this + * because we still want the portion of the second line that + * still exists to be in the start,end range. + */ + if (treePtr->startEnd != NULL) { + int checkCount = 0; + + while (treePtr->startEnd[checkCount] != NULL) { + if (treePtr->startEnd[checkCount] == index2Ptr->linePtr) { + TkText *peer = treePtr->startEndRef[checkCount]; + /* + * We're deleting a line which is the start + * or end of a current client. This means + * we need to adjust that client. + */ + treePtr->startEnd[checkCount] = index1Ptr->linePtr; + if (peer->start == index2Ptr->linePtr) { + peer->start = index1Ptr->linePtr; + } + if (peer->end == index2Ptr->linePtr) { + peer->end = index1Ptr->linePtr; + } + } + checkCount++; + } + } + ckfree((char *) index2Ptr->linePtr->pixels); ckfree((char *) index2Ptr->linePtr); Rebalance((BTree *) index2Ptr->tree, curNodePtr); @@ -881,8 +1490,8 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) * 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, + if (TkBTreeNextLine(NULL, index1Ptr->linePtr) != NULL) { + TkTextInvalidateLineMetrics(treePtr->sharedTextPtr, NULL, index1Ptr->linePtr, changeToLineCount, TK_TEXT_INVALIDATE_DELETE); } @@ -915,21 +1524,42 @@ TkBTreeDeleteChars(index1Ptr, index2Ptr) */ TkTextLine * -TkBTreeFindLine(tree, line) +TkBTreeFindLine(tree, textPtr, line) TkTextBTree tree; /* B-tree in which to find line. */ + CONST TkText *textPtr; /* Relative to this client of the + * B-tree */ int line; /* Index of desired line. */ { BTree *treePtr = (BTree *) tree; register Node *nodePtr; register TkTextLine *linePtr; - int linesLeft; + if (treePtr == NULL) { + treePtr = (BTree *) textPtr->sharedTextPtr->tree; + } + nodePtr = treePtr->rootPtr; - linesLeft = line; if ((line < 0) || (line >= nodePtr->numLines)) { return NULL; } - + + /* + * Check for the any start/end offset for this text widget + */ + if (textPtr != NULL) { + if (textPtr->start != NULL) { + line += TkBTreeLinesTo(NULL, textPtr->start); + if (line >= nodePtr->numLines) { + return NULL; + } + } + if (textPtr->end != NULL) { + if (line > TkBTreeLinesTo(NULL, textPtr->end)) { + return NULL; + } + } + } + /* * Work down through levels of the tree until a node is found at * level 0. @@ -937,12 +1567,12 @@ TkBTreeFindLine(tree, line) while (nodePtr->level != 0) { for (nodePtr = nodePtr->children.nodePtr; - nodePtr->numLines <= linesLeft; + nodePtr->numLines <= line; nodePtr = nodePtr->nextPtr) { if (nodePtr == NULL) { Tcl_Panic("TkBTreeFindLine ran out of nodes"); } - linesLeft -= nodePtr->numLines; + line -= nodePtr->numLines; } } @@ -950,12 +1580,12 @@ TkBTreeFindLine(tree, line) * Work through the lines attached to the level-0 node. */ - for (linePtr = nodePtr->children.linePtr; linesLeft > 0; + for (linePtr = nodePtr->children.linePtr; line > 0; linePtr = linePtr->nextPtr) { if (linePtr == NULL) { Tcl_Panic("TkBTreeFindLine ran out of lines"); } - linesLeft -= 1; + line -= 1; } return linePtr; } @@ -985,24 +1615,25 @@ TkBTreeFindLine(tree, line) */ TkTextLine * -TkBTreeFindPixelLine(tree, pixels, pixelOffset) - TkTextBTree tree; /* B-tree in which to find line. */ - int pixels; /* Index of desired line. */ +TkBTreeFindPixelLine(tree, textPtr, pixels, pixelOffset) + TkTextBTree tree; /* B-tree to use. */ + CONST TkText *textPtr; /* Relative to this client of the + * B-tree */ + int pixels; /* Pixel index of desired line. */ int *pixelOffset; /* Used to return offset */ { BTree *treePtr = (BTree *) tree; register Node *nodePtr; register TkTextLine *linePtr; - int pixelsLeft; - + int pixelReference = textPtr->pixelReference; + nodePtr = treePtr->rootPtr; - pixelsLeft = pixels; - if ((pixels < 0) || (pixels > nodePtr->numPixels)) { + if ((pixels < 0) || (pixels > nodePtr->numPixels[pixelReference])) { return NULL; } - if (nodePtr->numPixels == 0) { + if (nodePtr->numPixels[pixelReference] == 0) { Tcl_Panic("TkBTreeFindPixelLine called with empty window"); } @@ -1013,12 +1644,12 @@ TkBTreeFindPixelLine(tree, pixels, pixelOffset) while (nodePtr->level != 0) { for (nodePtr = nodePtr->children.nodePtr; - nodePtr->numPixels <= pixelsLeft; + nodePtr->numPixels[pixelReference] <= pixels; nodePtr = nodePtr->nextPtr) { if (nodePtr == NULL) { Tcl_Panic("TkBTreeFindPixelLine ran out of nodes"); } - pixelsLeft -= nodePtr->numPixels; + pixels -= nodePtr->numPixels[pixelReference]; } } @@ -1027,15 +1658,15 @@ TkBTreeFindPixelLine(tree, pixels, pixelOffset) */ for (linePtr = nodePtr->children.linePtr; - linePtr->pixelHeight < pixelsLeft; + linePtr->pixels[2*pixelReference] < pixels; linePtr = linePtr->nextPtr) { if (linePtr == NULL) { Tcl_Panic("TkBTreeFindPixelLine ran out of lines"); } - pixelsLeft -= linePtr->pixelHeight; + pixels -= linePtr->pixels[2*pixelReference]; } if (pixelOffset != NULL && linePtr != NULL) { - *pixelOffset = pixelsLeft; + *pixelOffset = pixels; } return linePtr; } @@ -1060,16 +1691,22 @@ TkBTreeFindPixelLine(tree, pixels, pixelOffset) */ TkTextLine * -TkBTreeNextLine(linePtr) +TkBTreeNextLine(textPtr, linePtr) + CONST TkText *textPtr; /* Next line in the context of + * this client */ register TkTextLine *linePtr; /* Pointer to existing line in * B-tree. */ { register Node *nodePtr; if (linePtr->nextPtr != NULL) { - return linePtr->nextPtr; + if (textPtr != NULL && (linePtr == textPtr->end)) { + return NULL; + } else { + return linePtr->nextPtr; + } } - + /* * This was the last line associated with the particular parent node. * Search up the tree for the next node, then search down from that @@ -1111,7 +1748,9 @@ TkBTreeNextLine(linePtr) */ TkTextLine * -TkBTreePreviousLine(linePtr) +TkBTreePreviousLine(textPtr, linePtr) + TkText *textPtr; /* Relative to this client of the + * B-tree */ register TkTextLine *linePtr; /* Pointer to existing line in * B-tree. */ { @@ -1119,6 +1758,10 @@ TkBTreePreviousLine(linePtr) register Node *node2Ptr; register TkTextLine *prevPtr; + if (textPtr != NULL && textPtr->start == linePtr) { + return NULL; + } + /* * Find the line under this node just before the starting line. */ @@ -1166,7 +1809,7 @@ TkBTreePreviousLine(linePtr) /* *---------------------------------------------------------------------- * - * TkBTreePixels -- + * TkBTreePixelsTo -- * * 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 @@ -1187,16 +1830,19 @@ TkBTreePreviousLine(linePtr) */ int -TkBTreePixels(linePtr) +TkBTreePixelsTo(textPtr, linePtr) + CONST TkText *textPtr; /* Relative to this client of the + * B-tree */ TkTextLine *linePtr; /* Pointer to existing line in * B-tree. */ { register TkTextLine *linePtr2; - register Node *nodePtr, *parentPtr, *nodePtr2; + register Node *nodePtr, *parentPtr; int index; + int pixelReference = textPtr->pixelReference; /* - * First count how many lines precede this one in its level-0 + * First count how many pixels precede this line in its level-0 * node. */ @@ -1205,25 +1851,26 @@ TkBTreePixels(linePtr) for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr; linePtr2 = linePtr2->nextPtr) { if (linePtr2 == NULL) { - Tcl_Panic("TkBTreePixels couldn't find line"); + Tcl_Panic("TkBTreePixelsTo couldn't find line"); } - index += linePtr2->pixelHeight; + index += linePtr2->pixels[2*pixelReference]; } /* * Now work up through the levels of the tree one at a time, - * counting how many lines are in nodes preceding the current + * counting how many pixels are in nodes preceding the current * node. */ for (parentPtr = nodePtr->parentPtr ; parentPtr != NULL; nodePtr = parentPtr, parentPtr = parentPtr->parentPtr) { + register Node *nodePtr2; for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr; nodePtr2 = nodePtr2->nextPtr) { if (nodePtr2 == NULL) { - Tcl_Panic("TkBTreePixels couldn't find node"); + Tcl_Panic("TkBTreePixelsTo couldn't find node"); } - index += nodePtr2->numPixels; + index += nodePtr2->numPixels[pixelReference]; } } return index; @@ -1232,7 +1879,7 @@ TkBTreePixels(linePtr) /* *---------------------------------------------------------------------- * - * TkBTreeLineIndex -- + * TkBTreeLinesTo -- * * Given a pointer to a line in a B-tree, return the numerical * index of that line. @@ -1248,7 +1895,9 @@ TkBTreePixels(linePtr) */ int -TkBTreeLineIndex(linePtr) +TkBTreeLinesTo(textPtr, linePtr) + CONST TkText *textPtr; /* Relative to this client of the + * B-tree */ TkTextLine *linePtr; /* Pointer to existing line in * B-tree. */ { @@ -1266,7 +1915,7 @@ TkBTreeLineIndex(linePtr) for (linePtr2 = nodePtr->children.linePtr; linePtr2 != linePtr; linePtr2 = linePtr2->nextPtr) { if (linePtr2 == NULL) { - Tcl_Panic("TkBTreeLineIndex couldn't find line"); + Tcl_Panic("TkBTreeLinesTo couldn't find line"); } index += 1; } @@ -1282,11 +1931,14 @@ TkBTreeLineIndex(linePtr) for (nodePtr2 = parentPtr->children.nodePtr; nodePtr2 != nodePtr; nodePtr2 = nodePtr2->nextPtr) { if (nodePtr2 == NULL) { - Tcl_Panic("TkBTreeLineIndex couldn't find node"); + Tcl_Panic("TkBTreeLinesTo couldn't find node"); } index += nodePtr2->numLines; } } + if (textPtr != NULL && textPtr->start != NULL) { + index -= TkBTreeLinesTo(NULL, textPtr->start); + } return index; } @@ -1352,8 +2004,7 @@ TkBTreeLinkSegment(segPtr, indexPtr) /* ARGSUSED */ void -TkBTreeUnlinkSegment(tree, segPtr, linePtr) - TkTextBTree tree; /* Tree containing segment. */ +TkBTreeUnlinkSegment(segPtr, linePtr) TkTextSegment *segPtr; /* Segment to be unlinked. */ TkTextLine *linePtr; /* Line that currently contains * segment. */ @@ -1951,8 +2602,8 @@ TkBTreeStartSearch(index1Ptr, index2Ptr, tagPtr, searchPtr) } searchPtr->lastPtr = TkTextIndexToSeg(index2Ptr, (int *) NULL); searchPtr->tagPtr = tagPtr; - searchPtr->linesLeft = TkBTreeLineIndex(index2Ptr->linePtr) + 1 - - TkBTreeLineIndex(index1Ptr->linePtr); + searchPtr->linesLeft = TkBTreeLinesTo(NULL, index2Ptr->linePtr) + 1 + - TkBTreeLinesTo(NULL, index1Ptr->linePtr); searchPtr->allTags = (tagPtr == NULL); if (searchPtr->linesLeft == 1) { /* @@ -2042,7 +2693,7 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr) searchPtr->curIndex = index0; index1Ptr = &index0; } else { - TkTextIndexBackChars(NULL,index1Ptr, 1, &searchPtr->curIndex, + TkTextIndexBackChars(NULL, index1Ptr, 1, &searchPtr->curIndex, COUNT_INDICES); } searchPtr->segPtr = NULL; @@ -2054,7 +2705,7 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr) * at the second index specified by the user. */ - if ((TkBTreeLineIndex(index2Ptr->linePtr) == 0) && + if ((TkBTreeLinesTo(NULL, index2Ptr->linePtr) == 0) && (index2Ptr->byteIndex == 0)) { backOne = *index2Ptr; searchPtr->lastPtr = NULL; /* Signals special case for 1.0 */ @@ -2063,8 +2714,8 @@ TkBTreeStartSearchBack(index1Ptr, index2Ptr, tagPtr, searchPtr) searchPtr->lastPtr = TkTextIndexToSeg(&backOne, (int *) NULL); } searchPtr->tagPtr = tagPtr; - searchPtr->linesLeft = TkBTreeLineIndex(index1Ptr->linePtr) + 1 - - TkBTreeLineIndex(backOne.linePtr); + searchPtr->linesLeft = TkBTreeLinesTo(NULL, index1Ptr->linePtr) + 1 + - TkBTreeLinesTo(NULL, backOne.linePtr); searchPtr->allTags = (tagPtr == NULL); if (searchPtr->linesLeft == 1) { /* @@ -2588,9 +3239,12 @@ TkBTreeCharTagged(indexPtr, tagPtr) /* ARGSUSED */ TkTextTag ** -TkBTreeGetTags(indexPtr, numTagsPtr) +TkBTreeGetTags(indexPtr, textPtr, numTagsPtr) CONST TkTextIndex *indexPtr;/* Indicates a particular position in * the B-tree. */ + CONST TkText *textPtr; /* If non-NULL, then only return tags + * for this text widget (when there are + * peer widgets). */ int *numTagsPtr; /* Store number of tags found at this * location. */ { @@ -2664,13 +3318,17 @@ TkBTreeGetTags(indexPtr, numTagsPtr) /* * Go through the tag information and squash out all of the tags * that have even toggle counts (these tags exist before the point - * of interest, but not at the desired character itself). + * of interest, but not at the desired character itself). Also + * squash out all tags that don't belong to the requested widget. */ for (src = 0, dst = 0; src < tagInfo.numTags; src++) { if (tagInfo.counts[src] & 1) { - tagInfo.tagPtrs[dst] = tagInfo.tagPtrs[src]; - dst++; + CONST TkText *tagTextPtr = tagInfo.tagPtrs[src]->textPtr; + if (tagTextPtr == NULL || textPtr == NULL || tagTextPtr == textPtr) { + tagInfo.tagPtrs[dst] = tagInfo.tagPtrs[src]; + dst++; + } } } *numTagsPtr = dst; @@ -2688,17 +3346,21 @@ TkBTreeGetTags(indexPtr, numTagsPtr) * TkTextIsElided -- * * Special case to just return information about elided attribute. - * Specialized from TkBTreeGetTags(indexPtr, numTagsPtr) and + * Specialized from TkBTreeGetTags(indexPtr, textPtr, numTagsPtr) and * GetStyle(textPtr, indexPtr). Just need to keep track of * invisibility settings for each priority, pick highest one active * at end. * * Note that this returns all elide information up to and including * the given index (quite obviously). However, this does mean that - * indexPtr is a line-start and one then iterates from the beginning + * if indexPtr is a line-start and one then iterates from the beginning * of that line forwards, one will actually revisit the segPtrs of * size zero (for tag toggling, for example) which have already been * seen here. + * + * For this reason we fill in the fields 'segPtr' and 'segOffset' + * of elideInfo, enabling our caller easily to calculate + * incremental changes from where we left off. * * Results: * Returns whether this text should be elided or not. @@ -2738,7 +3400,7 @@ TkTextIsElided(textPtr, indexPtr, elideInfo) infoPtr->elide = 0; /* if nobody says otherwise, it's visible */ infoPtr->tagCnts = infoPtr->deftagCnts; infoPtr->tagPtrs = infoPtr->deftagPtrs; - infoPtr->numTags = textPtr->numTags; + infoPtr->numTags = textPtr->sharedTextPtr->numTags; /* Almost always avoid malloc, so stay out of system calls */ if (LOTSA_TAGS < infoPtr->numTags) { @@ -2774,7 +3436,8 @@ TkTextIsElided(textPtr, indexPtr, elideInfo) * so that our caller knows where to start. */ infoPtr->segPtr = segPtr; - + infoPtr->segOffset = index; + /* * Record toggles for tags in lines that are predecessors of * indexPtr->linePtr but under the same level-0 node. @@ -2829,13 +3492,12 @@ TkTextIsElided(textPtr, indexPtr, elideInfo) infoPtr->elidePriority = -1; for (i = infoPtr->numTags-1; i >=0; i--) { if (infoPtr->tagCnts[i] & 1) { -#ifndef ALWAYS_SHOW_SELECTION - /* who would make the selection elided? */ + /* Who would make the selection elided? */ if ((tagPtr == textPtr->selTagPtr) - && !(textPtr->flags & GOT_FOCUS)) { + && !(textPtr->flags & GOT_FOCUS) + && (textPtr->inactiveSelBorder == NULL)) { continue; } -#endif infoPtr->elide = infoPtr->tagPtrs[i]->elide; /* Note: i == infoPtr->tagPtrs[i]->priority */ infoPtr->elidePriority = i; @@ -2987,7 +3649,7 @@ TkBTreeCheck(tree) /* * Make sure that the tag toggle counts and the tag root pointers are OK. */ - for (entryPtr = Tcl_FirstHashEntry(&treePtr->textPtr->tagTable, &search); + for (entryPtr = Tcl_FirstHashEntry(&treePtr->sharedTextPtr->tagTable, &search); entryPtr != NULL ; entryPtr = Tcl_NextHashEntry(&search)) { tagPtr = (TkTextTag *) Tcl_GetHashValue(entryPtr); nodePtr = tagPtr->tagRootPtr; @@ -3045,7 +3707,7 @@ TkBTreeCheck(tree) */ nodePtr = treePtr->rootPtr; - CheckNodeConsistency(treePtr->rootPtr); + CheckNodeConsistency(treePtr->rootPtr, treePtr->pixelReferences); /* * Make sure that there are at least two lines in the text and @@ -3113,15 +3775,19 @@ TkBTreeCheck(tree) */ static void -CheckNodeConsistency(nodePtr) +CheckNodeConsistency(nodePtr, references) register Node *nodePtr; /* Node whose subtree should be * checked. */ + int references; /* Number of referring widgets + * which have pixel counts. */ { register Node *childNodePtr; register Summary *summaryPtr, *summaryPtr2; register TkTextLine *linePtr; register TkTextSegment *segPtr; - int numChildren, numLines, numPixels, toggleCount, minChildren; + int numChildren, numLines, toggleCount, minChildren, i; + int *numPixels; + int pixels[PIXEL_CLIENTS]; if (nodePtr->parentPtr != NULL) { minChildren = MIN_CHILDREN; @@ -3138,7 +3804,15 @@ CheckNodeConsistency(nodePtr) numChildren = 0; numLines = 0; - numPixels = 0; + if (references > PIXEL_CLIENTS) { + numPixels = (int*)ckalloc(sizeof(int)*references); + } else { + numPixels = pixels; + } + for (i = 0; i<references; i++) { + numPixels[i] = 0; + } + if (nodePtr->level == 0) { for (linePtr = nodePtr->children.linePtr; linePtr != NULL; linePtr = linePtr->nextPtr) { @@ -3166,7 +3840,9 @@ CheckNodeConsistency(nodePtr) } numChildren++; numLines++; - numPixels += linePtr->pixelHeight; + for (i = 0; i<references; i++) { + numPixels[i] += linePtr->pixels[2*i]; + } } } else { for (childNodePtr = nodePtr->children.nodePtr; childNodePtr != NULL; @@ -3178,7 +3854,7 @@ CheckNodeConsistency(nodePtr) Tcl_Panic("CheckNodeConsistency: level mismatch (%d %d)", nodePtr->level, childNodePtr->level); } - CheckNodeConsistency(childNodePtr); + CheckNodeConsistency(childNodePtr, references); for (summaryPtr = childNodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { for (summaryPtr2 = nodePtr->summaryPtr; ; @@ -3198,7 +3874,9 @@ CheckNodeConsistency(nodePtr) } numChildren++; numLines += childNodePtr->numLines; - numPixels += childNodePtr->numPixels; + for (i = 0; i<references; i++) { + numPixels[i] += childNodePtr->numPixels[i]; + } } } if (numChildren != nodePtr->numChildren) { @@ -3209,11 +3887,16 @@ CheckNodeConsistency(nodePtr) Tcl_Panic("CheckNodeConsistency: mismatch in numLines (%d %d)", numLines, nodePtr->numLines); } - if (numPixels != nodePtr->numPixels) { - Tcl_Panic("CheckNodeConsistency: mismatch in numPixels (%d %d)", - numPixels, nodePtr->numPixels); + for (i = 0; i<references; i++) { + if (numPixels[i] != nodePtr->numPixels[i]) { + Tcl_Panic("CheckNodeConsistency: mismatch in numPixels (%d %d) for widget (%d)", + numPixels[i], nodePtr->numPixels[i], i); + } } - + if (references > PIXEL_CLIENTS) { + ckfree((char*)numPixels); + } + for (summaryPtr = nodePtr->summaryPtr; summaryPtr != NULL; summaryPtr = summaryPtr->nextPtr) { if (summaryPtr->tagPtr->toggleCount == summaryPtr->toggleCount) { @@ -3319,11 +4002,20 @@ Rebalance(treePtr, nodePtr) newPtr->children.nodePtr = nodePtr; newPtr->numChildren = 1; newPtr->numLines = nodePtr->numLines; - newPtr->numPixels = nodePtr->numPixels; - RecomputeNodeCounts(newPtr); + newPtr->numPixels = (int*) ckalloc(sizeof(int)* + treePtr->pixelReferences); + for (i=0; i<treePtr->pixelReferences; i++) { + newPtr->numPixels[i] = nodePtr->numPixels[i]; + } + RecomputeNodeCounts(treePtr, newPtr); treePtr->rootPtr = newPtr; } newPtr = (Node *) ckalloc(sizeof(Node)); + newPtr->numPixels = (int*) ckalloc(sizeof(int)* + treePtr->pixelReferences); + for (i=0; i<treePtr->pixelReferences; i++) { + newPtr->numPixels[i] = 0; + } newPtr->parentPtr = nodePtr->parentPtr; newPtr->nextPtr = nodePtr->nextPtr; nodePtr->nextPtr = newPtr; @@ -3347,11 +4039,11 @@ Rebalance(treePtr, nodePtr) newPtr->children.nodePtr = childPtr->nextPtr; childPtr->nextPtr = NULL; } - RecomputeNodeCounts(nodePtr); + RecomputeNodeCounts(treePtr, nodePtr); nodePtr->parentPtr->numChildren++; nodePtr = newPtr; if (nodePtr->numChildren <= MAX_CHILDREN) { - RecomputeNodeCounts(nodePtr); + RecomputeNodeCounts(treePtr, nodePtr); break; } } @@ -3462,7 +4154,7 @@ Rebalance(treePtr, nodePtr) */ if (totalChildren <= MAX_CHILDREN) { - RecomputeNodeCounts(nodePtr); + RecomputeNodeCounts(treePtr, nodePtr); nodePtr->nextPtr = otherPtr->nextPtr; nodePtr->parentPtr->numChildren--; DeleteSummaries(otherPtr->summaryPtr); @@ -3482,8 +4174,8 @@ Rebalance(treePtr, nodePtr) otherPtr->children.nodePtr = halfwayNodePtr->nextPtr; halfwayNodePtr->nextPtr = NULL; } - RecomputeNodeCounts(nodePtr); - RecomputeNodeCounts(otherPtr); + RecomputeNodeCounts(treePtr, nodePtr); + RecomputeNodeCounts(treePtr, otherPtr); } } } @@ -3511,7 +4203,8 @@ Rebalance(treePtr, nodePtr) */ static void -RecomputeNodeCounts(nodePtr) +RecomputeNodeCounts(treePtr, nodePtr) + register BTree *treePtr; /* The whole B-tree */ register Node *nodePtr; /* Node whose tag summary information * must be recomputed. */ { @@ -3520,7 +4213,8 @@ RecomputeNodeCounts(nodePtr) register TkTextLine *linePtr; register TkTextSegment *segPtr; TkTextTag *tagPtr; - + int ref; + /* * Zero out all the existing counts for the node, but don't delete * the existing Summary records (most of them will probably be reused). @@ -3532,7 +4226,9 @@ RecomputeNodeCounts(nodePtr) } nodePtr->numChildren = 0; nodePtr->numLines = 0; - nodePtr->numPixels = 0; + for (ref = 0; ref<treePtr->pixelReferences; ref++) { + nodePtr->numPixels[ref] = 0; + } /* * Scan through the children, adding the childrens' tag counts into @@ -3545,7 +4241,9 @@ RecomputeNodeCounts(nodePtr) linePtr = linePtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines++; - nodePtr->numPixels += linePtr->pixelHeight; + for (ref = 0; ref<treePtr->pixelReferences; ref++) { + nodePtr->numPixels[ref] += linePtr->pixels[2*ref]; + } linePtr->parentPtr = nodePtr; for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { @@ -3577,7 +4275,9 @@ RecomputeNodeCounts(nodePtr) childPtr = childPtr->nextPtr) { nodePtr->numChildren++; nodePtr->numLines += childPtr->numLines; - nodePtr->numPixels += childPtr->numPixels; + for (ref = 0; ref<treePtr->pixelReferences; ref++) { + nodePtr->numPixels[ref] += childPtr->numPixels[ref]; + } childPtr->parentPtr = nodePtr; for (summaryPtr2 = childPtr->summaryPtr; summaryPtr2 != NULL; summaryPtr2 = summaryPtr2->nextPtr) { @@ -3646,7 +4346,7 @@ RecomputeNodeCounts(nodePtr) * * TkBTreeNumLines -- * - * This procedure returns a count of the number of lines of + * This procedure returns a count of the number of logical lines of * text present in a given B-tree. * * Results: @@ -3661,11 +4361,24 @@ RecomputeNodeCounts(nodePtr) */ int -TkBTreeNumLines(tree) +TkBTreeNumLines(tree, textPtr) TkTextBTree tree; /* Information about tree. */ + CONST TkText *textPtr; /* Relative to this client of the + * B-tree */ { BTree *treePtr = (BTree *) tree; - return treePtr->rootPtr->numLines - 1; + int count; + + if (textPtr != NULL && textPtr->end != NULL) { + count = TkBTreeLinesTo(NULL, textPtr->end); + } else { + count = treePtr->rootPtr->numLines - 1; + } + if (textPtr != NULL && textPtr->start != NULL) { + count -= TkBTreeLinesTo(NULL, textPtr->start); + } + + return count; } /* @@ -3674,13 +4387,14 @@ TkBTreeNumLines(tree) * TkBTreeNumPixels -- * * This procedure returns a count of the number of pixels of - * text present in a given B-tree. + * text present in a given widget's B-tree representation. * * 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). + * tree (since the dummy line used to mark the end of the B-tree is + * maintained with zero height, as are any lines that are before or + * after the '-start -end' range of the text widget in question, + * the number stored at the root is the number we want). * * Side effects: * None. @@ -3689,11 +4403,13 @@ TkBTreeNumLines(tree) */ int -TkBTreeNumPixels(tree) - TkTextBTree tree; /* Information about tree. */ +TkBTreeNumPixels(tree, textPtr) + TkTextBTree tree; /* The B-tree */ + CONST TkText *textPtr; /* Relative to this client of the + * B-tree */ { BTree *treePtr = (BTree *) tree; - return treePtr->rootPtr->numPixels; + return treePtr->rootPtr->numPixels[textPtr->pixelReference]; } /* @@ -4056,54 +4772,3 @@ ToggleCheckProc(segPtr, linePtr) } } } - -/* - *---------------------------------------------------------------------- - * - * TkBTreeCharsInLine -- - * - * This procedure returns a count of the number of characters - * in a given line. - * - * Results: - * The return value is the character count for linePtr. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -int -TkBTreeCharsInLine(linePtr) - TkTextLine *linePtr; /* Line whose characters should be - * counted. */ -{ - TkTextSegment *segPtr; - int count; - - count = 0; - for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { - if (segPtr->typePtr == &tkTextCharType) { - count += Tcl_NumUtfChars(segPtr->body.chars, segPtr->size); - } else { - count += segPtr->size; - } - } - return count; -} - -int -TkBTreeBytesInLine(linePtr) - TkTextLine *linePtr; /* Line whose characters should be - * counted. */ -{ - TkTextSegment *segPtr; - int count; - - count = 0; - for (segPtr = linePtr->segPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { - count += segPtr->size; - } - return count; -} diff --git a/generic/tkTextDisp.c b/generic/tkTextDisp.c index ef977bb..63b8da6 100644 --- a/generic/tkTextDisp.c +++ b/generic/tkTextDisp.c @@ -3,9 +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. (Well, strictly, each TkTextLine caches its - * last observed pixel height, but that information is - * calculated here). + * of text widgets. (Well, strictly, each TkTextLine and B-tree + * node caches its last observed pixel height, but that information + * originates here). * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. @@ -13,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.43 2004/06/08 20:28:19 dgp Exp $ + * RCS: @(#) $Id: tkTextDisp.c,v 1.44 2004/09/10 12:13:41 vincentdarley Exp $ */ #include "tkPort.h" @@ -425,11 +425,13 @@ static int lineHeightsRecalculated; /* Number of line layouts purely static void AdjustForTab _ANSI_ARGS_((TkText *textPtr, TkTextTabArray *tabArrayPtr, int index, TkTextDispChunk *chunkPtr)); -static void CharBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, +static void CharBboxProc _ANSI_ARGS_((TkText *textPtr, + TkTextDispChunk *chunkPtr, int index, int y, int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr, int *heightPtr)); -static void CharDisplayProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, +static void CharDisplayProc _ANSI_ARGS_((TkText *textPtr, + TkTextDispChunk *chunkPtr, int x, int y, int height, int baseline, Display *display, Drawable dst, int screenY)); static int CharMeasureProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, @@ -444,7 +446,8 @@ static void CharUndisplayProc _ANSI_ARGS_((TkText *textPtr, as potentially many elided DLine chunks if large, tag toggle-filled elided region. */ -static void ElideBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, +static void ElideBboxProc _ANSI_ARGS_((TkText *textPtr, + TkTextDispChunk *chunkPtr, int index, int y, int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr, int *heightPtr)); @@ -488,8 +491,16 @@ static void YScrollByPixels _ANSI_ARGS_((TkText *textPtr, static int SizeOfTab _ANSI_ARGS_((TkText *textPtr, TkTextTabArray *tabArrayPtr, int *indexPtr, int x, int maxX)); +static void TextChanged _ANSI_ARGS_((TkText *textPtr, + CONST TkTextIndex *index1Ptr, + CONST TkTextIndex *index2Ptr)); static void TextInvalidateRegion _ANSI_ARGS_((TkText *textPtr, TkRegion region)); +static void TextRedrawTag _ANSI_ARGS_((TkText *textPtr, + TkTextIndex *index1Ptr, TkTextIndex *index2Ptr, + TkTextTag *tagPtr, int withTag)); +static void TextInvalidateLineMetrics _ANSI_ARGS_((TkText *textPtr, + TkTextLine *linePtr, int lineCount, int action)); static int CalculateDisplayLineHeight _ANSI_ARGS_(( TkText *textPtr, CONST TkTextIndex *indexPtr, int *byteCountPtr)); @@ -507,7 +518,7 @@ static void AsyncUpdateYScrollbar _ANSI_ARGS_((ClientData clientData)); /* - * Result values returned by TextGetScrollInfo: + * Result values returned by TextGetScrollInfoObj: */ #define TKTEXT_SCROLL_MOVETO 1 @@ -687,7 +698,7 @@ GetStyle(textPtr, indexPtr) * priority tag). */ - tagPtrs = TkBTreeGetTags(indexPtr, &numTags); + tagPtrs = TkBTreeGetTags(indexPtr, textPtr, &numTags); borderPrio = borderWidthPrio = reliefPrio = bgStipplePrio = -1; fgPrio = fontPrio = fgStipplePrio = -1; underlinePrio = elidePrio = justifyPrio = offsetPrio = -1; @@ -706,21 +717,25 @@ GetStyle(textPtr, indexPtr) styleValues.wrapMode = textPtr->wrapMode; styleValues.elide = 0; for (i = 0 ; i < numTags; i++) { + Tk_3DBorder border; tagPtr = tagPtrs[i]; - + border = tagPtr->border; + /* - * On Windows and Mac, we need to skip the selection tag if + * If this is the selection tag, and inactiveSelBorder is NULL + * (the default on Windows and Mac), then we need to skip it if * we don't have focus. */ -#ifndef ALWAYS_SHOW_SELECTION if ((tagPtr == textPtr->selTagPtr) && !(textPtr->flags & GOT_FOCUS)) { - continue; + if (textPtr->inactiveSelBorder == NULL) { + continue; + } + border = textPtr->inactiveSelBorder; } -#endif - if ((tagPtr->border != NULL) && (tagPtr->priority > borderPrio)) { - styleValues.border = tagPtr->border; + if ((border != NULL) && (tagPtr->priority > borderPrio)) { + styleValues.border = border; borderPrio = tagPtr->priority; } if ((tagPtr->borderWidthPtr != NULL) @@ -1090,11 +1105,12 @@ LayoutDLine(textPtr, indexPtr) * update the line's pixel height, and bring * its pixel calculation up to date. */ - dlPtr->index.linePtr->pixelCalculationEpoch + TkBTreeLinePixelEpoch(textPtr, dlPtr->index.linePtr) = textPtr->dInfoPtr->lineMetricUpdateEpoch; - if (dlPtr->index.linePtr->pixelHeight != 0) { - TkBTreeAdjustPixelHeight(dlPtr->index.linePtr, 0); + if (TkBTreeLinePixelCount(textPtr, dlPtr->index.linePtr) != 0) { + TkBTreeAdjustPixelHeight(textPtr, + dlPtr->index.linePtr, 0); } } TkTextFreeElideInfo(&info); @@ -1532,8 +1548,8 @@ UpdateDisplayInfo(textPtr) *-------------------------------------------------------------- */ - lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); + lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); dlPtr = dInfoPtr->dLinePtr; prevPtr = NULL; y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; @@ -1583,7 +1599,7 @@ UpdateDisplayInfo(textPtr) * information. */ - TkTextPrintIndex(&index, string); + TkTextPrintIndex(textPtr, &index, string); Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, string, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); @@ -1646,7 +1662,7 @@ UpdateDisplayInfo(textPtr) if (lineHeight != -1) { lineHeight += dlPtr->height; } - TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); + TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index); prevPtr = dlPtr; dlPtr = dlPtr->nextPtr; @@ -1670,7 +1686,8 @@ UpdateDisplayInfo(textPtr) } if ((lineHeight != -1) - && (lineHeight > prevPtr->index.linePtr->pixelHeight)) { + && (lineHeight > TkBTreeLinePixelCount(textPtr, + prevPtr->index.linePtr))) { /* * The logical line height we just calculated is actually * larger than the currently cached height of the @@ -1680,7 +1697,9 @@ UpdateDisplayInfo(textPtr) * with DLine pointers do not exceed counts made * through the BTree. */ - TkBTreeAdjustPixelHeight(prevPtr->index.linePtr, lineHeight); + TkBTreeAdjustPixelHeight(textPtr, + 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 @@ -1689,7 +1708,7 @@ UpdateDisplayInfo(textPtr) * have got this right for the first line in the * re-display. */ - prevPtr->index.linePtr->pixelCalculationEpoch = + TkBTreeLinePixelEpoch(textPtr, prevPtr->index.linePtr) = dInfoPtr->lineMetricUpdateEpoch; } lineHeight = 0; @@ -1757,16 +1776,25 @@ UpdateDisplayInfo(textPtr) */ spaceLeft = maxY - y; - lineNum = TkBTreeLineIndex(dInfoPtr->dLinePtr->index.linePtr); - bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; - if (bytesToCount == 0) { - bytesToCount = INT_MAX; - lineNum--; + if (dInfoPtr->dLinePtr == NULL) { + /* + * No lines have been laid out. This must be an empty + * peer widget. + */ + lineNum = -1; + } else { + lineNum = TkBTreeLinesTo(textPtr, dInfoPtr->dLinePtr->index.linePtr); + bytesToCount = dInfoPtr->dLinePtr->index.byteIndex; + if (bytesToCount == 0) { + bytesToCount = INT_MAX; + lineNum--; + } } for ( ; (lineNum >= 0) && (spaceLeft > 0); lineNum--) { int pixelHeight = 0; - index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineNum); index.byteIndex = 0; lowestPtr = NULL; @@ -1778,7 +1806,7 @@ UpdateDisplayInfo(textPtr) if (dlPtr->length == 0 && dlPtr->height == 0) { bytesToCount--; break; } /* elide */ - TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); + TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index); bytesToCount -= dlPtr->byteCount; } while ((bytesToCount > 0) && (index.linePtr == lowestPtr->index.linePtr)); @@ -1788,15 +1816,17 @@ UpdateDisplayInfo(textPtr) * 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 (pixelHeight > TkBTreeLinePixelCount(textPtr, + lowestPtr->index.linePtr)) { + TkBTreeAdjustPixelHeight(textPtr, + lowestPtr->index.linePtr, + pixelHeight); if (index.linePtr != lowestPtr->index.linePtr) { /* * We examined the entire line, so can update * the epoch. */ - lowestPtr->index.linePtr->pixelCalculationEpoch = + TkBTreeLinePixelEpoch(textPtr, lowestPtr->index.linePtr) = dInfoPtr->lineMetricUpdateEpoch; } } @@ -1815,7 +1845,7 @@ UpdateDisplayInfo(textPtr) if (tkTextDebug) { char string[TK_POS_CHARS]; - TkTextPrintIndex(&dlPtr->index, string); + TkTextPrintIndex(textPtr, &dlPtr->index, string); Tcl_SetVar2(textPtr->interp, "tk_textRelayout", (char *) NULL, string, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); @@ -1835,7 +1865,7 @@ UpdateDisplayInfo(textPtr) if (lineNum >= 0) { dInfoPtr->newTopPixelOffset = -spaceLeft; if (spaceLeft > 0 || - dInfoPtr->newTopPixelOffset >= dInfoPtr->dLinePtr->height) { + dInfoPtr->newTopPixelOffset >= dInfoPtr->dLinePtr->height) { /* Bad situation */ Tcl_Panic("Pixel height problem while laying out text widget"); } @@ -1848,15 +1878,17 @@ UpdateDisplayInfo(textPtr) * Update them. */ - textPtr->topIndex = dInfoPtr->dLinePtr->index; - y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; - for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; - dlPtr = dlPtr->nextPtr) { - if (y > dInfoPtr->maxY) { - Tcl_Panic("Added too many new lines in UpdateDisplayInfo"); + if (dInfoPtr->dLinePtr != NULL) { + textPtr->topIndex = dInfoPtr->dLinePtr->index; + y = dInfoPtr->y - dInfoPtr->newTopPixelOffset; + for (dlPtr = dInfoPtr->dLinePtr; dlPtr != NULL; + dlPtr = dlPtr->nextPtr) { + if (y > dInfoPtr->maxY) { + Tcl_Panic("Added too many new lines in UpdateDisplayInfo"); + } + dlPtr->y = y; + y += dlPtr->height; } - dlPtr->y = y; - y += dlPtr->height; } } @@ -1873,42 +1905,44 @@ UpdateDisplayInfo(textPtr) */ dlPtr = dInfoPtr->dLinePtr; - if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { - dlPtr->flags |= OLD_Y_INVALID; - } - while (1) { - if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) - && (dlPtr->flags & HAS_3D_BORDER)) { - dlPtr->flags |= OLD_Y_INVALID; - } - /* - * If the old top-line was not completely showing (i.e. the - * pixelOffset is non-zero) and is no longer the top-line, then - * we must re-draw it. - */ - if ((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)) { + if (dlPtr != NULL) { + if ((dlPtr->flags & HAS_3D_BORDER) && !(dlPtr->flags & TOP_LINE)) { dlPtr->flags |= OLD_Y_INVALID; } - if (dlPtr->nextPtr == NULL) { - if ((dlPtr->flags & HAS_3D_BORDER) - && !(dlPtr->flags & BOTTOM_LINE)) { + while (1) { + if ((dlPtr->flags & TOP_LINE) && (dlPtr != dInfoPtr->dLinePtr) + && (dlPtr->flags & HAS_3D_BORDER)) { dlPtr->flags |= OLD_Y_INVALID; } - dlPtr->flags &= ~TOP_LINE; - dlPtr->flags |= BOTTOM_LINE; - break; + /* + * If the old top-line was not completely showing (i.e. the + * pixelOffset is non-zero) and is no longer the top-line, then + * we must re-draw it. + */ + if ((dlPtr->flags & TOP_LINE) + && dInfoPtr->topPixelOffset!=0 && dlPtr!=dInfoPtr->dLinePtr) { + dlPtr->flags |= OLD_Y_INVALID; + } + if ((dlPtr->flags & BOTTOM_LINE) && (dlPtr->nextPtr != NULL) + && (dlPtr->flags & HAS_3D_BORDER)) { + dlPtr->flags |= OLD_Y_INVALID; + } + if (dlPtr->nextPtr == NULL) { + if ((dlPtr->flags & HAS_3D_BORDER) + && !(dlPtr->flags & BOTTOM_LINE)) { + dlPtr->flags |= OLD_Y_INVALID; + } + dlPtr->flags &= ~TOP_LINE; + dlPtr->flags |= BOTTOM_LINE; + break; + } + dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE); + dlPtr = dlPtr->nextPtr; } - dlPtr->flags &= ~(TOP_LINE|BOTTOM_LINE); - dlPtr = dlPtr->nextPtr; + dInfoPtr->dLinePtr->flags |= TOP_LINE; + dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset; } - dInfoPtr->dLinePtr->flags |= TOP_LINE; - dInfoPtr->topPixelOffset = dInfoPtr->newTopPixelOffset; - + /* * Arrange for scrollbars to be updated. */ @@ -2017,7 +2051,7 @@ FreeDLines(textPtr, firstPtr, lastPtr, action) * information. */ - TkTextPrintIndex(&firstPtr->index, string); + TkTextPrintIndex(textPtr, &firstPtr->index, string); Tcl_SetVar2(textPtr->interp, "tk_textHeightCalc", (char *) NULL, string, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); } @@ -2116,7 +2150,7 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) chunkPtr = chunkPtr->nextPtr) { if (chunkPtr->displayProc == TkTextInsertDisplayProc) { int x = chunkPtr->x + dInfoPtr->x - dInfoPtr->curXPixelOffset; - (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, + (*chunkPtr->displayProc)(textPtr, chunkPtr, x, dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, dlPtr->y + dlPtr->spaceAbove); @@ -2161,7 +2195,7 @@ DisplayDLine(textPtr, dlPtr, prevPtr, pixmap) */ x = -chunkPtr->width; } - (*chunkPtr->displayProc)(chunkPtr, x, dlPtr->spaceAbove, + (*chunkPtr->displayProc)(textPtr, chunkPtr, x, dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, display, pixmap, dlPtr->y + dlPtr->spaceAbove); @@ -2698,22 +2732,29 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) { TkTextLine *linePtr = NULL; int count = 0; - int totalLines = TkBTreeNumLines(textPtr->tree); + int totalLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); + + if (totalLines == 0) { + /* Empty peer widget */ + return endLine; + } while (1) { /* Get a suitable line */ if (lineNum == -1 && linePtr == NULL) { lineNum = 0; - linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineNum); } else { if (lineNum == -1 || linePtr == NULL) { if (lineNum == -1) { lineNum = 0; } - linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineNum); } else { lineNum++; - linePtr = TkBTreeNextLine(linePtr); + linePtr = TkBTreeNextLine(textPtr, linePtr); } if (lineNum == endLine) { /* @@ -2735,7 +2776,7 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) } /* Now update the line's metrics if necessary */ - if (linePtr->pixelCalculationEpoch + if (TkBTreeLinePixelEpoch(textPtr, linePtr) != textPtr->dInfoPtr->lineMetricUpdateEpoch) { if (doThisMuch == -1) { count += 8 * TkTextUpdateOneLine(textPtr, linePtr, @@ -2753,7 +2794,8 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) * we are looking at a long line wrapped many * times, which we will examine in pieces. */ - if (textPtr->dInfoPtr->metricEpoch == textPtr->stateEpoch + if (textPtr->dInfoPtr->metricEpoch == + textPtr->sharedTextPtr->stateEpoch && textPtr->dInfoPtr->metricIndex.linePtr == linePtr) { indexPtr = &textPtr->dInfoPtr->metricIndex; pixelHeight = textPtr->dInfoPtr->metricPixelHeight; @@ -2764,7 +2806,7 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) * it when it is out of date. */ textPtr->dInfoPtr->metricEpoch = -1; - index.tree = textPtr->tree; + index.tree = textPtr->sharedTextPtr->tree; index.linePtr = linePtr; index.byteIndex = 0; index.textPtr = NULL; @@ -2787,9 +2829,11 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) */ if (pixelHeight == 0) { textPtr->dInfoPtr->metricIndex = index; - textPtr->dInfoPtr->metricEpoch = textPtr->stateEpoch; + textPtr->dInfoPtr->metricEpoch = + textPtr->sharedTextPtr->stateEpoch; } - textPtr->dInfoPtr->metricPixelHeight = linePtr->pixelHeight; + textPtr->dInfoPtr->metricPixelHeight = + TkBTreeLinePixelCount(textPtr, linePtr); break; } } @@ -2832,7 +2876,7 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) /* *---------------------------------------------------------------------- * - * TkTextInvalidateLineMetrics -- + * TkTextInvalidateLineMetrics, TextInvalidateLineMetrics -- * * Mark a number of text lines as having invalid line metric * calculations. Never call this with linePtr as the last @@ -2854,7 +2898,9 @@ TkTextUpdateLineMetrics(textPtr, lineNum, endLine, doThisMuch) */ void -TkTextInvalidateLineMetrics(textPtr, linePtr, lineCount, action) +TkTextInvalidateLineMetrics(sharedTextPtr, textPtr, linePtr, lineCount, action) + TkSharedText *sharedTextPtr;/* Shared widget section for all peers, + * or NULL. */ TkText *textPtr; /* Widget record for text widget. */ TkTextLine *linePtr; /* Invalidation starts from this line. */ int lineCount; /* And includes this many following @@ -2863,23 +2909,44 @@ TkTextInvalidateLineMetrics(textPtr, linePtr, lineCount, action) * occurred (insert, delete, or * simple). */ { + if (sharedTextPtr == NULL) { + TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action); + } else { + textPtr = sharedTextPtr->peers; + while (textPtr != NULL) { + TextInvalidateLineMetrics(textPtr, linePtr, lineCount, action); + textPtr = textPtr->next; + } + } +} + +static void +TextInvalidateLineMetrics(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); + fromLine = TkBTreeLinesTo(textPtr, linePtr); /* * Invalidate the height calculations of each line in the * given range. */ - linePtr->pixelCalculationEpoch = 0; + TkBTreeLinePixelEpoch(textPtr, linePtr) = 0; while (counter > 0 && linePtr != 0) { - linePtr = TkBTreeNextLine(linePtr); + linePtr = TkBTreeNextLine(textPtr, linePtr); if (linePtr != NULL) { - linePtr->pixelCalculationEpoch = 0; + TkBTreeLinePixelEpoch(textPtr, linePtr) = 0; } counter--; } @@ -3071,7 +3138,7 @@ TkTextFindDisplayLineEnd(textPtr, indexPtr, end, xOffset) return; } FreeDLines(textPtr, dlPtr, NULL, DLINE_FREE_TEMP); - TkTextIndexForwBytes(&index, byteCount, &index); + TkTextIndexForwBytes(textPtr, &index, byteCount, &index); } } } @@ -3169,7 +3236,7 @@ TkTextIndexYPixels(textPtr, indexPtr) int pixelHeight; TkTextIndex index; - pixelHeight = TkBTreePixels(indexPtr->linePtr); + pixelHeight = TkBTreePixelsTo(textPtr, indexPtr->linePtr); /* * Iterate through all display-lines corresponding to the single @@ -3182,7 +3249,7 @@ TkTextIndexYPixels(textPtr, indexPtr) return pixelHeight; } - index.tree = textPtr->tree; + index.tree = textPtr->sharedTextPtr->tree; index.linePtr = indexPtr->linePtr; index.byteIndex = 0; index.textPtr = NULL; @@ -3260,7 +3327,7 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) int displayLines, partialCalc; if (indexPtr == NULL) { - index.tree = textPtr->tree; + index.tree = textPtr->sharedTextPtr->tree; index.linePtr = linePtr; index.byteIndex = 0; index.textPtr = NULL; @@ -3298,7 +3365,7 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) displayLines++; } - if (TkTextIndexForwBytes(indexPtr, bytes, indexPtr)) { + if (TkTextIndexForwBytes(textPtr, indexPtr, bytes, indexPtr)) { break; } @@ -3326,13 +3393,14 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) * yet up to date, that will happen in TkBTreeAdjustPixelHeight * just below). */ - linePtr->pixelCalculationEpoch = textPtr->dInfoPtr->lineMetricUpdateEpoch; + TkBTreeLinePixelEpoch(textPtr, linePtr) + = textPtr->dInfoPtr->lineMetricUpdateEpoch; /* * Also cancel any partial line height calculation state. */ textPtr->dInfoPtr->metricEpoch = -1; - if (linePtr->pixelHeight == pixelHeight) { + if (TkBTreeLinePixelCount(textPtr, linePtr) == pixelHeight) { return displayLines; } } @@ -3342,16 +3410,17 @@ TkTextUpdateOneLine(textPtr, linePtr, pixelHeight, indexPtr) * of the entire widget, which may be used just below for * reporting/debugging purposes. */ - pixelHeight = TkBTreeAdjustPixelHeight(linePtr, pixelHeight); + pixelHeight = TkBTreeAdjustPixelHeight(textPtr, + linePtr, pixelHeight); if (tkTextDebug) { char buffer[2 * TCL_INTEGER_SPACE + 1]; - if (TkBTreeNextLine(linePtr) == NULL) { + if (TkBTreeNextLine(textPtr, linePtr) == NULL) { Tcl_Panic("Mustn't ever update line height of last artificial line"); } - sprintf(buffer, "%d %d", TkBTreeLineIndex(linePtr), pixelHeight); + sprintf(buffer, "%d %d", TkBTreeLinesTo(textPtr, linePtr), pixelHeight); Tcl_SetVar2(textPtr->interp, "tk_textNumPixels", (char *) NULL, buffer, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); } @@ -3713,7 +3782,7 @@ DisplayText(clientData) if ((dlPtr->flags & OLD_Y_INVALID) || dlPtr->oldY != dlPtr->y) { if (tkTextDebug) { char string[TK_POS_CHARS]; - TkTextPrintIndex(&dlPtr->index, string); + TkTextPrintIndex(textPtr, &dlPtr->index, string); Tcl_SetVar2(textPtr->interp, "tk_textRedraw", (char *) NULL, string, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); @@ -3764,7 +3833,8 @@ DisplayText(clientData) */ x = -chunkPtr->width; } - TkTextEmbWinDisplayProc(chunkPtr, x, dlPtr->spaceAbove, + TkTextEmbWinDisplayProc(textPtr, chunkPtr, + x, dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, (Display *) NULL, (Drawable) None, dlPtr->y + dlPtr->spaceAbove); @@ -3986,7 +4056,7 @@ TextInvalidateRegion(textPtr, region) /* *---------------------------------------------------------------------- * - * TkTextChanged -- + * TkTextChanged, TextChanged -- * * This procedure is invoked when info in a text widget is about * to be modified in a way that changes how it is displayed (e.g. @@ -4011,13 +4081,35 @@ TextInvalidateRegion(textPtr, region) */ void -TkTextChanged(textPtr, index1Ptr, index2Ptr) - TkText *textPtr; /* Widget record for text widget. */ +TkTextChanged(sharedTextPtr, textPtr, index1Ptr, index2Ptr) + TkSharedText *sharedTextPtr; /* Shared widget section, or NULL */ + TkText *textPtr; /* Widget record for text widget, + * or NULL. */ CONST TkTextIndex *index1Ptr; /* Index of first character to * redisplay. */ CONST TkTextIndex *index2Ptr; /* Index of character just after last one * to redisplay. */ { + if (sharedTextPtr == NULL) { + TextChanged(textPtr, index1Ptr, index2Ptr); + } else { + textPtr = sharedTextPtr->peers; + while (textPtr != NULL) { + TextChanged(textPtr, index1Ptr, index2Ptr); + textPtr = textPtr->next; + } + } +} + +static void +TextChanged(textPtr, index1Ptr, index2Ptr) + TkText *textPtr; /* Widget record for text widget, + * or NULL. */ + CONST TkTextIndex *index1Ptr; /* Index of first character to + * redisplay. */ + CONST TkTextIndex *index2Ptr; /* Index of character just after last one + * to redisplay. */ +{ TextDInfo *dInfoPtr = textPtr->dInfoPtr; DLine *firstPtr, *lastPtr; TkTextIndex rounded; @@ -4075,7 +4167,7 @@ TkTextChanged(textPtr, index1Ptr, index2Ptr) /* *---------------------------------------------------------------------- * - * TkTextRedrawTag -- + * TkTextRedrawTag, TextRedrawTag -- * * This procedure is invoked to request a redraw of all characters * in a given range that have a particular tag on or off. It's @@ -4092,7 +4184,32 @@ TkTextChanged(textPtr, index1Ptr, index2Ptr) */ void -TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) +TkTextRedrawTag(sharedTextPtr, textPtr, index1Ptr, index2Ptr, tagPtr, withTag) + TkSharedText *sharedTextPtr; /* Shared widget section, or NULL */ + TkText *textPtr; /* Widget record for text widget. */ + TkTextIndex *index1Ptr; /* First character in range to consider + * for redisplay. NULL means start at + * beginning of text. */ + TkTextIndex *index2Ptr; /* Character just after last one to consider + * for redisplay. NULL means process all + * the characters in the text. */ + TkTextTag *tagPtr; /* Information about tag. */ + int withTag; /* 1 means redraw characters that have the + * tag, 0 means redraw those without. */ +{ + if (sharedTextPtr == NULL) { + TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag); + } else { + textPtr = sharedTextPtr->peers; + while (textPtr != NULL) { + TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag); + textPtr = textPtr->next; + } + } +} + +static void +TextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) TkText *textPtr; /* Widget record for text widget. */ TkTextIndex *index1Ptr; /* First character in range to consider * for redisplay. NULL means start at @@ -4126,18 +4243,18 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) int lineCount; if (index2Ptr == NULL) { endLine = NULL; - lineCount = TkBTreeNumLines(textPtr->tree); + lineCount = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); } else { endLine = index2Ptr->linePtr; - lineCount = TkBTreeLineIndex(endLine); + lineCount = TkBTreeLinesTo(textPtr, endLine); } if (index1Ptr == NULL) { startLine = NULL; } else { startLine = index1Ptr->linePtr; - lineCount -= TkBTreeLineIndex(startLine); + lineCount -= TkBTreeLinesTo(textPtr, startLine); } - TkTextInvalidateLineMetrics(textPtr, startLine, lineCount, + TkTextInvalidateLineMetrics(NULL, textPtr, startLine, lineCount, TK_TEXT_INVALIDATE_ONLY); } @@ -4159,8 +4276,9 @@ TkTextRedrawTag(textPtr, index1Ptr, index2Ptr, tagPtr, withTag) */ if (index2Ptr == NULL) { - index2Ptr = TkTextMakeByteIndex(textPtr->tree, - TkBTreeNumLines(textPtr->tree), 0, &endOfText); + int lastLine = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); + index2Ptr = TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lastLine, 0, &endOfText); } /* @@ -4380,6 +4498,12 @@ TkTextRelayoutWindow(textPtr, mask) dInfoPtr->currentMetricUpdateLine = -1; + /* + * Also cancel any partial line-height calculations (for + * long-wrapped lines) in progress + */ + dInfoPtr->metricEpoch = -1; + if (dInfoPtr->lineUpdateTimer == NULL) { textPtr->refCount++; dInfoPtr->lineUpdateTimer = Tcl_CreateTimerHandler(1, @@ -4438,9 +4562,9 @@ TkTextSetYView(textPtr, indexPtr, pickPlace) * text, round it back to the last real line. */ - lineIndex = TkBTreeLineIndex(indexPtr->linePtr); - if (lineIndex == TkBTreeNumLines(indexPtr->tree)) { - TkTextIndexBackChars(NULL,indexPtr, 1, &rounded, COUNT_INDICES); + lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr); + if (lineIndex == TkBTreeNumLines(indexPtr->tree, textPtr)) { + TkTextIndexBackChars(textPtr, indexPtr, 1, &rounded, COUNT_INDICES); indexPtr = &rounded; } @@ -4608,8 +4732,8 @@ TkTextMeasureDown(textPtr, srcPtr, distance) DLine *dlPtr; TkTextIndex loop; - lastLinePtr = - TkBTreeFindLine(textPtr->tree, TkBTreeNumLines(textPtr->tree)); + lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); do { dlPtr = LayoutDLine(textPtr, srcPtr); @@ -4620,7 +4744,7 @@ TkTextMeasureDown(textPtr, srcPtr, distance) break; } distance -= dlPtr->height; - TkTextIndexForwBytes(srcPtr, dlPtr->byteCount, &loop); + TkTextIndexForwBytes(textPtr, srcPtr, dlPtr->byteCount, &loop); FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE_TEMP); if (loop.linePtr == lastLinePtr) { break; @@ -4681,7 +4805,7 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr, overlap) bytesToCount = srcPtr->byteIndex + 1; index.tree = srcPtr->tree; - for (lineNum = TkBTreeLineIndex(srcPtr->linePtr); lineNum >= 0; + for (lineNum = TkBTreeLinesTo(textPtr, srcPtr->linePtr); lineNum >= 0; lineNum--) { /* * Layout an entire text line (potentially > 1 display line). @@ -4693,14 +4817,14 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr, overlap) * in the list). */ - index.linePtr = TkBTreeFindLine(srcPtr->tree, lineNum); + index.linePtr = TkBTreeFindLine(srcPtr->tree, textPtr, lineNum); index.byteIndex = 0; lowestPtr = NULL; do { dlPtr = LayoutDLine(textPtr, &index); dlPtr->nextPtr = lowestPtr; lowestPtr = dlPtr; - TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); + TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index); bytesToCount -= dlPtr->byteCount; } while (bytesToCount>0 && index.linePtr==dlPtr->index.linePtr); @@ -4740,7 +4864,7 @@ MeasureUp(textPtr, srcPtr, distance, dstPtr, overlap) * in the text. */ - TkTextMakeByteIndex(textPtr->tree, 0, 0, dstPtr); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, dstPtr); if (overlap != NULL) { *overlap = 0; } @@ -4792,8 +4916,9 @@ TkTextSeeCmd(textPtr, interp, objc, objv) * text, round it back to the last real line. */ - if (TkBTreeLineIndex(index.linePtr) == TkBTreeNumLines(index.tree)) { - TkTextIndexBackChars(NULL,&index, 1, &index, COUNT_INDICES); + if (TkBTreeLinesTo(textPtr, index.linePtr) + == TkBTreeNumLines(index.tree, textPtr)) { + TkTextIndexBackChars(textPtr, &index, 1, &index, COUNT_INDICES); } /* @@ -4840,7 +4965,7 @@ TkTextSeeCmd(textPtr, interp, objc, objv) */ if (chunkPtr != NULL) { - (*chunkPtr->bboxProc)(chunkPtr, byteCount, + (*chunkPtr->bboxProc)(textPtr, chunkPtr, byteCount, dlPtr->y + dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, @@ -5004,14 +5129,15 @@ YScrollByPixels(textPtr, offset) * distance. */ - lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); + lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); offset += dInfoPtr->topPixelOffset; dInfoPtr->newTopPixelOffset = 0; while (offset > 0) { dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); dlPtr->nextPtr = NULL; - TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new); + TkTextIndexForwBytes(textPtr, &textPtr->topIndex, + dlPtr->byteCount, &new); if (offset <= dlPtr->height) { /* Adjust the top overlap accordingly */ dInfoPtr->newTopPixelOffset = offset; @@ -5074,18 +5200,19 @@ YScrollByLines(textPtr, offset) */ bytesToCount = textPtr->topIndex.byteIndex + 1; - index.tree = textPtr->tree; + index.tree = textPtr->sharedTextPtr->tree; offset--; /* Skip line containing topIndex. */ - for (lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr); + for (lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr); lineNum >= 0; lineNum--) { - index.linePtr = TkBTreeFindLine(textPtr->tree, lineNum); + index.linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineNum); index.byteIndex = 0; lowestPtr = NULL; do { dlPtr = LayoutDLine(textPtr, &index); dlPtr->nextPtr = lowestPtr; lowestPtr = dlPtr; - TkTextIndexForwBytes(&index, dlPtr->byteCount, &index); + TkTextIndexForwBytes(textPtr, &index, dlPtr->byteCount, &index); bytesToCount -= dlPtr->byteCount; } while ((bytesToCount > 0) && (index.linePtr == dlPtr->index.linePtr)); @@ -5116,7 +5243,8 @@ YScrollByLines(textPtr, offset) * overlapping the top window border. */ - TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->topIndex); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + 0, 0, &textPtr->topIndex); dInfoPtr->newTopPixelOffset = 0; } else { /* @@ -5124,13 +5252,14 @@ YScrollByLines(textPtr, offset) * Just count lines from the current top of the window. */ - lastLinePtr = TkBTreeFindLine(textPtr->tree, - TkBTreeNumLines(textPtr->tree)); + lastLinePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)); for (i = 0; i < offset; i++) { dlPtr = LayoutDLine(textPtr, &textPtr->topIndex); if (dlPtr->length == 0 && dlPtr->height == 0) offset++; dlPtr->nextPtr = NULL; - TkTextIndexForwBytes(&textPtr->topIndex, dlPtr->byteCount, &new); + TkTextIndexForwBytes(textPtr, &textPtr->topIndex, + dlPtr->byteCount, &new); FreeDLines(textPtr, dlPtr, (DLine *) NULL, DLINE_FREE); if (new.linePtr == lastLinePtr) { break; @@ -5209,7 +5338,8 @@ TkTextYviewCmd(textPtr, interp, objc, objv) if ((objc == 3) || pickPlace) { int lineNum; if (Tcl_GetIntFromObj(interp, objv[2+pickPlace], &lineNum) == TCL_OK) { - TkTextMakeByteIndex(textPtr->tree, lineNum, 0, &index); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineNum, 0, &index); TkTextSetYView(textPtr, &index, 0); return TCL_OK; } @@ -5236,7 +5366,8 @@ TkTextYviewCmd(textPtr, interp, objc, objv) case TKTEXT_SCROLL_ERROR: return TCL_ERROR; case TKTEXT_SCROLL_MOVETO: { - int numPixels = TkBTreeNumPixels(textPtr->tree); + int numPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, + textPtr); int topMostPixel; if (numPixels == 0) { /* @@ -5549,7 +5680,7 @@ GetYPixelCount(textPtr, dlPtr) * for any difference between the top of the logical line and * the display line. */ - int count = TkBTreePixels(linePtr); + int count = TkBTreePixelsTo(textPtr, linePtr); /* * For the common case where this dlPtr is also the start of the @@ -5574,7 +5705,7 @@ GetYPixelCount(textPtr, dlPtr) * of 1000 wrapped lines all from a single logical line -- but that * sort of optimization is left for the future). */ - count += linePtr->pixelHeight; + count += TkBTreeLinePixelCount(textPtr, linePtr); do { count -= dlPtr->height; @@ -5590,7 +5721,8 @@ GetYPixelCount(textPtr, dlPtr) TkTextIndex index; int notFirst = 0; while (1) { - TkTextIndexForwBytes(&dlPtr->index, dlPtr->byteCount, &index); + TkTextIndexForwBytes(textPtr, &dlPtr->index, + dlPtr->byteCount, &index); if (notFirst) { FreeDLines(textPtr, dlPtr, (DLine *)NULL, DLINE_FREE_TEMP); } @@ -5608,7 +5740,7 @@ GetYPixelCount(textPtr, dlPtr) * suite uses this information. */ - TkTextPrintIndex(&index, string); + TkTextPrintIndex(textPtr, &index, string); Tcl_SetVar2(textPtr->interp, "tk_textHeightCalc", (char *) NULL, string, TCL_GLOBAL_ONLY|TCL_APPEND_VALUE|TCL_LIST_ELEMENT); @@ -5671,7 +5803,8 @@ GetYView(interp, textPtr, report) return; } - totalPixels = TkBTreeNumPixels(textPtr->tree); + totalPixels = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, + textPtr); if (totalPixels == 0) { first = 0.0; @@ -5846,8 +5979,8 @@ FindDLine(dlPtr, indexPtr) if (dlPtr == NULL) { return NULL; } - if (TkBTreeLineIndex(indexPtr->linePtr) - < TkBTreeLineIndex(dlPtr->index.linePtr)) { + if (TkBTreeLinesTo(NULL, indexPtr->linePtr) + < TkBTreeLinesTo(NULL, dlPtr->index.linePtr)) { /* * The first display line is already past the desired line. */ @@ -5866,7 +5999,12 @@ FindDLine(dlPtr, indexPtr) return NULL; } } - linePtr = TkBTreeNextLine(linePtr); + /* + * VMD: some concern here as to whether this logic, + * or the caller's logic will work well with + * partial peer widgets. + */ + linePtr = TkBTreeNextLine(NULL, linePtr); if (linePtr == NULL) { Tcl_Panic("FindDLine reached end of text"); } @@ -5956,23 +6094,31 @@ TkTextPixelIndex(textPtr, x, y, indexPtr, nearest) * Find the display line containing the desired y-coordinate. */ - for (dlPtr = validDlPtr = dInfoPtr->dLinePtr; + if (dInfoPtr->dLinePtr == NULL) { + if (nearest != NULL) { + *nearest = 1; + } + *indexPtr = textPtr->topIndex; + return; + } else { + for (dlPtr = validDlPtr = dInfoPtr->dLinePtr; y >= (dlPtr->y + dlPtr->height); dlPtr = dlPtr->nextPtr) { - if (dlPtr->chunkPtr !=NULL) validDlPtr = dlPtr; - if (dlPtr->nextPtr == NULL) { - /* - * Y-coordinate is off the bottom of the displayed text. - * Use the last character on the last line. - */ + if (dlPtr->chunkPtr !=NULL) validDlPtr = dlPtr; + if (dlPtr->nextPtr == NULL) { + /* + * Y-coordinate is off the bottom of the displayed text. + * Use the last character on the last line. + */ - x = dInfoPtr->maxX - 1; - nearby = 1; - break; + x = dInfoPtr->maxX - 1; + nearby = 1; + break; + } } + if (dlPtr->chunkPtr == NULL) dlPtr = validDlPtr; } - if (dlPtr->chunkPtr == NULL) dlPtr = validDlPtr; - + if (nearest != NULL) { *nearest = nearby; } @@ -6133,7 +6279,7 @@ DlineXOfIndex(textPtr, dlPtr, byteIndex) while (byteIndex > 0) { if (byteIndex < chunkPtr->numBytes) { int y, width, height; - (*chunkPtr->bboxProc)(chunkPtr, byteIndex, + (*chunkPtr->bboxProc)(textPtr, chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, &x, &y, &width, @@ -6233,7 +6379,8 @@ TkTextCharBbox(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, charWidthPtr) * horizontal scrolling. */ - (*chunkPtr->bboxProc)(chunkPtr, byteIndex, dlPtr->y + dlPtr->spaceAbove, + (*chunkPtr->bboxProc)(textPtr, chunkPtr, byteIndex, + dlPtr->y + dlPtr->spaceAbove, dlPtr->height - dlPtr->spaceAbove - dlPtr->spaceBelow, dlPtr->baseline - dlPtr->spaceAbove, xPtr, yPtr, widthPtr, heightPtr); @@ -6343,8 +6490,9 @@ TkTextDLineInfo(textPtr, indexPtr, xPtr, yPtr, widthPtr, heightPtr, basePtr) * Get bounding-box information about an elided chunk */ static void -ElideBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, +ElideBboxProc(textPtr, chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, widthPtr, heightPtr) + TkText *textPtr; TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ int index; /* Index of desired character within * the chunk. */ @@ -6556,7 +6704,8 @@ TkTextCharLayoutProc(textPtr, indexPtr, segPtr, byteOffset, maxX, maxBytes, */ static void -CharDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) +CharDisplayProc(textPtr, chunkPtr, x, y, height, baseline, display, dst, screenY) + TkText *textPtr; TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ int x; /* X-position in dst at which to * draw this chunk (may differ from @@ -6721,8 +6870,9 @@ CharMeasureProc(chunkPtr, x) */ static void -CharBboxProc(chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr, +CharBboxProc(textPtr, chunkPtr, byteIndex, y, lineHeight, baseline, xPtr, yPtr, widthPtr, heightPtr) + TkText *textPtr; TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ int byteIndex; /* Byte offset of desired character * within the chunk. */ @@ -7317,3 +7467,4 @@ TextGetScrollInfoObj(interp, textPtr, objc, objv, dblPtr, intPtr) "\": must be moveto or scroll", (char *) NULL); return TKTEXT_SCROLL_ERROR; } + diff --git a/generic/tkTextImage.c b/generic/tkTextImage.c index 87cdb2e..fbbec00 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.13 2004/03/16 19:53:09 hobbs Exp $ + * RCS: @(#) $Id: tkTextImage.c,v 1.14 2004/09/10 12:13:41 vincentdarley Exp $ */ #include "tk.h" @@ -32,7 +32,8 @@ static TkTextSegment * EmbImageCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static void EmbImageCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); -static void EmbImageBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, +static void EmbImageBboxProc _ANSI_ARGS_((TkText *textPtr, + TkTextDispChunk *chunkPtr, int index, int y, int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr, int *heightPtr)); @@ -40,7 +41,7 @@ static int EmbImageConfigure _ANSI_ARGS_((TkText *textPtr, TkTextSegment *eiPtr, int objc, Tcl_Obj *CONST objv[])); static int EmbImageDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr, int treeGone)); -static void EmbImageDisplayProc _ANSI_ARGS_(( +static void EmbImageDisplayProc _ANSI_ARGS_((TkText *textPtr, TkTextDispChunk *chunkPtr, int x, int y, int lineHeight, int baseline, Display *display, Drawable dst, int screenY)); @@ -200,13 +201,14 @@ TkTextImageCmd(textPtr, interp, objc, objv) return TCL_OK; } } else { - TkTextChanged(textPtr, &index, &index); + TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index); /* * It's probably not true that all window configuration * can change the line height, so we could be more * efficient here and only call this when necessary. */ - TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); return EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4); } @@ -231,10 +233,11 @@ TkTextImageCmd(textPtr, interp, objc, objv) * Don't allow insertions on the last (dummy) line of the text. */ - lineIndex = TkBTreeLineIndex(index.linePtr); - if (lineIndex == TkBTreeNumLines(textPtr->tree)) { + lineIndex = TkBTreeLinesTo(textPtr, index.linePtr); + if (lineIndex == TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)) { lineIndex--; - TkTextMakeByteIndex(textPtr->tree, lineIndex, 1000000, &index); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineIndex, 1000000, &index); } /* @@ -244,7 +247,7 @@ TkTextImageCmd(textPtr, interp, objc, objv) eiPtr = (TkTextSegment *) ckalloc(EI_SEG_SIZE); eiPtr->typePtr = &tkTextEmbImageType; eiPtr->size = 1; - eiPtr->body.ei.textPtr = textPtr; + eiPtr->body.ei.sharedTextPtr = textPtr->sharedTextPtr; eiPtr->body.ei.linePtr = NULL; eiPtr->body.ei.imageName = NULL; eiPtr->body.ei.imageString = NULL; @@ -260,16 +263,17 @@ TkTextImageCmd(textPtr, interp, objc, objv) * it again if the configuration fails). */ - TkTextChanged(textPtr, &index, &index); + TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index); TkBTreeLinkSegment(eiPtr, &index); if (EmbImageConfigure(textPtr, eiPtr, objc-4, objv+4) != TCL_OK) { TkTextIndex index2; TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES); - TkBTreeDeleteChars(&index, &index2); + TkBTreeDeleteChars(textPtr->sharedTextPtr->tree, &index, &index2); return TCL_ERROR; } - TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); return TCL_OK; } @@ -281,10 +285,10 @@ TkTextImageCmd(textPtr, interp, objc, objv) Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - for (hPtr = Tcl_FirstHashEntry(&textPtr->imageTable, &search); + for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->imageTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Tcl_AppendElement(interp, - Tcl_GetHashKey(&textPtr->markTable, hPtr)); + Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr)); } return TCL_OK; } @@ -382,12 +386,12 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) return TCL_ERROR; } len = strlen(name); - for (hPtr = Tcl_FirstHashEntry(&textPtr->imageTable, &search); + for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->imageTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { - char *haveName = Tcl_GetHashKey(&textPtr->imageTable, hPtr); + char *haveName = Tcl_GetHashKey(&textPtr->sharedTextPtr->imageTable, hPtr); if (strncmp(name, haveName, len) == 0) { new = 0; - sscanf(haveName+len,"#%d",&new); + sscanf(haveName+len, "#%d", &new); if (new > count) { count = new; } @@ -402,11 +406,11 @@ EmbImageConfigure(textPtr, eiPtr, objc, objv) if (conflict) { char buf[4 + TCL_INTEGER_SPACE]; - sprintf(buf, "#%d",count+1); - Tcl_DStringAppend(&newName,buf, -1); + sprintf(buf, "#%d", count+1); + Tcl_DStringAppend(&newName, buf, -1); } name = Tcl_DStringValue(&newName); - hPtr = Tcl_CreateHashEntry(&textPtr->imageTable, name, &new); + hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->imageTable, name, &new); Tcl_SetHashValue(hPtr, eiPtr); Tcl_AppendResult(textPtr->interp, name , (char *) NULL); eiPtr->body.ei.name = ckalloc((unsigned) Tcl_DStringLength(&newName)+1); @@ -446,7 +450,7 @@ EmbImageDeleteProc(eiPtr, linePtr, treeGone) Tcl_HashEntry *hPtr; if (eiPtr->body.ei.image != NULL) { - hPtr = Tcl_FindHashEntry(&eiPtr->body.ei.textPtr->imageTable, + hPtr = Tcl_FindHashEntry(&eiPtr->body.ei.sharedTextPtr->imageTable, eiPtr->body.ei.name); if (hPtr != NULL) { /* @@ -459,8 +463,12 @@ EmbImageDeleteProc(eiPtr, linePtr, treeGone) } Tk_FreeImage(eiPtr->body.ei.image); } + /* + * No need to supply a tkwin argument, since we have no + * window-specific options. + */ Tk_FreeConfigOptions((char *) &eiPtr->body.ei, eiPtr->body.ei.optionTable, - eiPtr->body.ei.textPtr->tkwin); + NULL); ckfree((char *) eiPtr); return 0; } @@ -632,7 +640,9 @@ EmbImageCheckProc(eiPtr, linePtr) */ static void -EmbImageDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY) +EmbImageDisplayProc(textPtr, chunkPtr, x, y, lineHeight, baseline, display, + dst, screenY) + TkText *textPtr; TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ int x; /* X-position in dst at which to * draw this chunk (differs from @@ -666,7 +676,7 @@ EmbImageDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY) * into account the align value for the image. */ - EmbImageBboxProc(chunkPtr, 0, y, lineHeight, baseline, &lineX, + EmbImageBboxProc(textPtr, chunkPtr, 0, y, lineHeight, baseline, &lineX, &imageY, &width, &height); imageX = lineX - chunkPtr->x + x; @@ -698,8 +708,9 @@ EmbImageDisplayProc(chunkPtr, x, y, lineHeight, baseline, display, dst, screenY) */ static void -EmbImageBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, +EmbImageBboxProc(textPtr, chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, widthPtr, heightPtr) + TkText *textPtr; TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ int index; /* Index of desired character within * the chunk. */ @@ -771,12 +782,12 @@ TkTextImageIndex(textPtr, name, indexPtr) Tcl_HashEntry *hPtr; TkTextSegment *eiPtr; - hPtr = Tcl_FindHashEntry(&textPtr->imageTable, name); + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->imageTable, name); if (hPtr == NULL) { return 0; } eiPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); - indexPtr->tree = textPtr->tree; + indexPtr->tree = textPtr->sharedTextPtr->tree; indexPtr->linePtr = eiPtr->body.ei.linePtr; indexPtr->byteIndex = TkTextSegToOffset(eiPtr, indexPtr->linePtr); return 1; @@ -812,15 +823,15 @@ EmbImageProc(clientData, x, y, width, height, imgWidth, imgHeight) TkTextSegment *eiPtr = (TkTextSegment *) clientData; TkTextIndex index; - index.tree = eiPtr->body.ei.textPtr->tree; + index.tree = eiPtr->body.ei.sharedTextPtr->tree; index.linePtr = eiPtr->body.ei.linePtr; index.byteIndex = TkTextSegToOffset(eiPtr, eiPtr->body.ei.linePtr); - TkTextChanged(eiPtr->body.ei.textPtr, &index, &index); + TkTextChanged(eiPtr->body.ei.sharedTextPtr, NULL, &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, + TkTextInvalidateLineMetrics(eiPtr->body.ei.sharedTextPtr, NULL, index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } diff --git a/generic/tkTextIndex.c b/generic/tkTextIndex.c index 44deddd..b405880 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.18 2004/06/04 10:51:18 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextIndex.c,v 1.19 2004/09/10 12:13:42 vincentdarley Exp $ */ #include "default.h" @@ -40,7 +40,8 @@ static CONST char * ForwBack _ANSI_ARGS_((TkText *textPtr, 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, + TkSharedText *sharedPtr, TkText *textPtr, + CONST char *string, TkTextIndex *indexPtr, int *canCachePtr)); /* @@ -120,7 +121,7 @@ UpdateStringOfTextIndex(objPtr) CONST TkTextIndex *indexPtr = GET_TEXTINDEX(objPtr); - len = TkTextPrintIndex(indexPtr, buffer); + len = TkTextPrintIndex(indexPtr->textPtr, indexPtr, buffer); objPtr->bytes = ckalloc((unsigned) len + 1); strcpy(objPtr->bytes, buffer); @@ -178,7 +179,7 @@ MakeObjIndex(textPtr, objPtr, origPtr) if (textPtr != NULL) { textPtr->refCount++; - SET_INDEXEPOCH(objPtr, textPtr->stateEpoch); + SET_INDEXEPOCH(objPtr, textPtr->sharedTextPtr->stateEpoch); } else { SET_INDEXEPOCH(objPtr, 0); } @@ -201,7 +202,7 @@ TkTextGetIndexFromObj(interp, textPtr, objPtr) indexPtr = GET_TEXTINDEX(objPtr); epoch = GET_INDEXEPOCH(objPtr); - if (epoch == textPtr->stateEpoch) { + if (epoch == textPtr->sharedTextPtr->stateEpoch) { if (indexPtr->textPtr == textPtr) { return indexPtr; } @@ -214,7 +215,7 @@ TkTextGetIndexFromObj(interp, textPtr, objPtr) * date (text has been added/deleted since). */ - if (GetIndex(interp, textPtr, Tcl_GetString(objPtr), + if (GetIndex(interp, NULL, textPtr, Tcl_GetString(objPtr), &index, &cache) != TCL_OK) { return NULL; } @@ -317,21 +318,26 @@ TkTextMakePixelIndex(textPtr, pixelIndex, indexPtr) { int pixelOffset = 0; - indexPtr->tree = textPtr->tree; + indexPtr->tree = textPtr->sharedTextPtr->tree; indexPtr->textPtr = textPtr; if (pixelIndex < 0) { pixelIndex = 0; } - indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->tree, pixelIndex, + indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree, + textPtr, + pixelIndex, &pixelOffset); /* * 'pixedlIndex' was too large, so we try again, just to find * the last pixel in the window */ if (indexPtr->linePtr == NULL) { - indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->tree, - TkBTreeNumPixels(textPtr->tree)-1, &pixelOffset); + int lastMinusOne = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, + textPtr)-1; + indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree, + textPtr, + lastMinusOne, &pixelOffset); indexPtr->byteIndex = 0; return pixelOffset; } @@ -364,9 +370,10 @@ TkTextMakePixelIndex(textPtr, pixelIndex, indexPtr) */ TkTextIndex * -TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) +TkTextMakeByteIndex(tree, textPtr, lineIndex, byteIndex, indexPtr) TkTextBTree tree; /* Tree that lineIndex and byteIndex refer * to. */ + CONST TkText *textPtr; int lineIndex; /* Index of desired line (0 means first * line of text). */ int byteIndex; /* Byte index of desired character. */ @@ -385,9 +392,10 @@ TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) if (byteIndex < 0) { byteIndex = 0; } - indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex); + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex); if (indexPtr->linePtr == NULL) { - indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree)); + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, + TkBTreeNumLines(tree, textPtr)); byteIndex = 0; } if (byteIndex == 0) { @@ -455,9 +463,10 @@ TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) */ TkTextIndex * -TkTextMakeCharIndex(tree, lineIndex, charIndex, indexPtr) +TkTextMakeCharIndex(tree, textPtr, lineIndex, charIndex, indexPtr) TkTextBTree tree; /* Tree that lineIndex and charIndex refer * to. */ + TkText *textPtr; int lineIndex; /* Index of desired line (0 means first * line of text). */ int charIndex; /* Index of desired character. */ @@ -476,9 +485,10 @@ TkTextMakeCharIndex(tree, lineIndex, charIndex, indexPtr) if (charIndex < 0) { charIndex = 0; } - indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex); + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex); if (indexPtr->linePtr == NULL) { - indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree)); + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, + TkBTreeNumLines(tree, textPtr)); charIndex = 0; } @@ -626,7 +636,36 @@ TkTextGetObjIndex(interp, textPtr, idxObj, indexPtr) * of position. */ TkTextIndex *indexPtr; /* Index structure to fill in. */ { - return GetIndex(interp, textPtr, Tcl_GetString(idxObj), indexPtr, NULL); + return GetIndex(interp, NULL, textPtr, Tcl_GetString(idxObj), indexPtr, NULL); +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextSharedGetObjIndex -- + * + * Simpler wrapper around the string based function, but could be + * enhanced with a new object type in the future. + * + * Results: + * see TkTextGetIndex + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextSharedGetObjIndex(interp, sharedTextPtr, idxObj, indexPtr) + Tcl_Interp *interp; /* Use this for error reporting. */ + TkSharedText *sharedTextPtr;/* Information about text widget. */ + Tcl_Obj *idxObj; /* Object containing textual description + * of position. */ + TkTextIndex *indexPtr; /* Index structure to fill in. */ +{ + return GetIndex(interp, sharedTextPtr, NULL, + Tcl_GetString(idxObj), indexPtr, NULL); } /* @@ -655,7 +694,7 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) CONST char *string; /* Textual description of position. */ TkTextIndex *indexPtr; /* Index structure to fill in. */ { - return GetIndex(interp, textPtr, string, indexPtr, NULL); + return GetIndex(interp, NULL, textPtr, string, indexPtr, NULL); } /* @@ -682,8 +721,9 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) */ static int -GetIndex(interp, textPtr, string, indexPtr, canCachePtr) +GetIndex(interp, sharedPtr, textPtr, string, indexPtr, canCachePtr) Tcl_Interp *interp; /* Use this for error reporting. */ + TkSharedText *sharedPtr; TkText *textPtr; /* Information about text widget. */ CONST char *string; /* Textual description of position. */ TkTextIndex *indexPtr; /* Index structure to fill in. */ @@ -692,8 +732,6 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) { char *p, *end, *endOfBase; Tcl_HashEntry *hPtr; - TkTextTag *tagPtr; - TkTextSearch search; TkTextIndex first, last; int wantLast, result; char c; @@ -701,6 +739,10 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) Tcl_DString copy; int canCache = 0; + if (sharedPtr == NULL) { + sharedPtr = textPtr->sharedTextPtr; + } + /* *--------------------------------------------------------------------- * Stage 1: check to see if the index consists of nothing but a mark @@ -720,7 +762,7 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) *------------------------------------------------ */ - indexPtr->tree = textPtr->tree; + indexPtr->tree = sharedPtr->tree; /* * First look for the form "tag.first" or "tag.last" where "tag" @@ -733,6 +775,10 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) Tcl_DStringInit(©); p = strrchr(Tcl_DStringAppend(©, string, -1), '.'); if (p != NULL) { + TkTextSearch search; + TkTextTag *tagPtr; + CONST char *tagName; + if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) { wantLast = 0; endOfBase = p+6; @@ -742,23 +788,40 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) } else { goto tryxy; } - *p = 0; - hPtr = Tcl_FindHashEntry(&textPtr->tagTable, Tcl_DStringValue(©)); - *p = '.'; - if (hPtr == NULL) { + tagPtr = NULL; + tagName = Tcl_DStringValue(©); + if (((p - tagName) == 3) && !strncmp(tagName, "sel", 3)) { + /* + * Special case for sel tag which is not stored in + * the hash table. + */ + tagPtr = textPtr->selTagPtr; + } else { + *p = 0; + hPtr = Tcl_FindHashEntry(&sharedPtr->tagTable, tagName); + *p = '.'; + if (hPtr != NULL) { + tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); + } + } + if (tagPtr == NULL) { goto tryxy; } - tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); - TkTextMakeByteIndex(textPtr->tree, 0, 0, &first); - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, - &last); + TkTextMakeByteIndex(sharedPtr->tree, textPtr, 0, 0, &first); + TkTextMakeByteIndex(sharedPtr->tree, textPtr, + TkBTreeNumLines(sharedPtr->tree, textPtr), + 0, &last); TkBTreeStartSearch(&first, &last, tagPtr, &search); if (!TkBTreeCharTagged(&first, tagPtr) && !TkBTreeNextTag(&search)) { + if (tagPtr == textPtr->selTagPtr) { + tagName = "sel"; + } else { + tagName = Tcl_GetHashKey(&sharedPtr->tagTable, hPtr); + } Tcl_ResetResult(interp); Tcl_AppendResult(interp, "text doesn't contain any characters tagged with \"", - Tcl_GetHashKey(&textPtr->tagTable, hPtr), "\"", - (char *) NULL); + tagName, "\"", (char *) NULL); Tcl_DStringFree(©); return TCL_ERROR; } @@ -816,7 +879,8 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) } endOfBase = end; } - TkTextMakeCharIndex(textPtr->tree, lineIndex, charIndex, indexPtr); + TkTextMakeCharIndex(sharedPtr->tree, textPtr, lineIndex, + charIndex, indexPtr); canCache = 1; goto gotBase; } @@ -847,8 +911,9 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) * Base position is end of text. */ - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), - 0, indexPtr); + TkTextMakeByteIndex(sharedPtr->tree, textPtr, + TkBTreeNumLines(sharedPtr->tree, textPtr), + 0, indexPtr); canCache = 1; goto gotBase; } else { @@ -912,6 +977,9 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) if (canCachePtr != NULL) { *canCachePtr = canCache; } + if (indexPtr->linePtr == NULL) { + Tcl_Panic("Bad index created"); + } return TCL_OK; error: @@ -941,7 +1009,8 @@ GetIndex(interp, textPtr, string, indexPtr, canCachePtr) */ int -TkTextPrintIndex(indexPtr, string) +TkTextPrintIndex(textPtr, indexPtr, string) + CONST TkText *textPtr; CONST TkTextIndex *indexPtr;/* Pointer to index. */ char *string; /* Place to store the position. Must have * at least TK_POS_CHARS characters. */ @@ -967,8 +1036,9 @@ TkTextPrintIndex(indexPtr, string) } else { charIndex += numBytes; } - return sprintf(string, "%d.%d", TkBTreeLineIndex(indexPtr->linePtr) + 1, - charIndex); + return sprintf(string, "%d.%d", + TkBTreeLinesTo(textPtr, indexPtr->linePtr) + 1, + charIndex); } /* @@ -1005,8 +1075,15 @@ TkTextIndexCmp(index1Ptr, index2Ptr) return 0; } } - line1 = TkBTreeLineIndex(index1Ptr->linePtr); - line2 = TkBTreeLineIndex(index2Ptr->linePtr); + /* + * Assumption here that it is ok for comparisons to reflect + * the full B-tree and not just the portion that is available + * to any client. This should be true because the only + * indexPtr's we should be given are ones which are valid + * for the current client. + */ + line1 = TkBTreeLinesTo(NULL, index1Ptr->linePtr); + line2 = TkBTreeLinesTo(NULL, index2Ptr->linePtr); if (line1 < line2) { return -1; } @@ -1208,7 +1285,7 @@ ForwBack(textPtr, string, indexPtr) */ TkTextIndexOfX(textPtr, xOffset, indexPtr); } else { - lineIndex = TkBTreeLineIndex(indexPtr->linePtr); + lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr); if (*string == '+') { lineIndex += count; } else { @@ -1237,8 +1314,9 @@ ForwBack(textPtr, string, indexPtr) * same x-position in the new line. */ - TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex, - indexPtr); + TkTextMakeByteIndex(indexPtr->tree, textPtr, + lineIndex, indexPtr->byteIndex, + indexPtr); } } else { return NULL; @@ -1269,7 +1347,8 @@ ForwBack(textPtr, string, indexPtr) */ int -TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) +TkTextIndexForwBytes(textPtr, srcPtr, byteCount, dstPtr) + CONST TkText *textPtr; CONST TkTextIndex *srcPtr; /* Source index. */ int byteCount; /* How many bytes forward to move. May be * negative. */ @@ -1280,7 +1359,7 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) int lineLength; if (byteCount < 0) { - TkTextIndexBackBytes(srcPtr, -byteCount, dstPtr); + TkTextIndexBackBytes(textPtr, srcPtr, -byteCount, dstPtr); return 0; } @@ -1306,7 +1385,7 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) return 0; } dstPtr->byteIndex -= lineLength; - linePtr = TkBTreeNextLine(dstPtr->linePtr); + linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr); if (linePtr == NULL) { dstPtr->byteIndex = lineLength - 1; return 1; @@ -1374,7 +1453,16 @@ TkTextIndexForwChars(textPtr, srcPtr, charCount, dstPtr, type) * Move forward specified number of chars. */ - segPtr = TkTextIndexToSeg(dstPtr, &byteOffset); + if (checkElided) { + /* + * In this case we have already calculated the information + * we need, so no need to use TkTextIndexToSeg() + */ + segPtr = infoPtr->segPtr; + byteOffset = dstPtr->byteIndex - infoPtr->segOffset; + } else { + segPtr = TkTextIndexToSeg(dstPtr, &byteOffset); + } while (1) { /* @@ -1470,7 +1558,7 @@ TkTextIndexForwChars(textPtr, srcPtr, charCount, dstPtr, type) * that index. */ - linePtr = TkBTreeNextLine(dstPtr->linePtr); + linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr); if (linePtr == NULL) { dstPtr->byteIndex -= sizeof(char); goto forwardCharDone; @@ -1663,7 +1751,7 @@ TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type) * that index. */ - linePtr1 = TkBTreeNextLine(linePtr1); + linePtr1 = TkBTreeNextLine(textPtr, linePtr1); if (linePtr1 == NULL) { Tcl_Panic("Reached end of text widget when counting characters"); } @@ -1697,7 +1785,8 @@ TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type) */ void -TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) +TkTextIndexBackBytes(textPtr, srcPtr, byteCount, dstPtr) + CONST TkText *textPtr; CONST TkTextIndex *srcPtr; /* Source index. */ int byteCount; /* How many bytes backward to move. May be * negative. */ @@ -1707,7 +1796,7 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) int lineIndex; if (byteCount < 0) { - TkTextIndexForwBytes(srcPtr, -byteCount, dstPtr); + TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr); return; } @@ -1721,14 +1810,14 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) */ if (lineIndex < 0) { - lineIndex = TkBTreeLineIndex(dstPtr->linePtr); + lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr); } if (lineIndex == 0) { dstPtr->byteIndex = 0; return; } lineIndex--; - dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex); + dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); /* * Compute the length of the line and add that to dstPtr->charIndex. @@ -1801,12 +1890,21 @@ TkTextIndexBackChars(textPtr, srcPtr, charCount, dstPtr, type) lineIndex = -1; segSize = dstPtr->byteIndex; - for (segPtr = dstPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { - if (segSize <= segPtr->size) { - break; + + if (checkElided) { + segPtr = infoPtr->segPtr; + segSize -= infoPtr->segOffset; + } else { + for (segPtr = dstPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { + if (segSize <= segPtr->size) { + break; + } + segSize -= segPtr->size; } - segSize -= segPtr->size; } + /* + * Now segPtr points to the segment containing the starting index + */ while (1) { /* * If we do need to pay attention to the visibility of @@ -1907,14 +2005,14 @@ TkTextIndexBackChars(textPtr, srcPtr, charCount, dstPtr, type) */ if (lineIndex < 0) { - lineIndex = TkBTreeLineIndex(dstPtr->linePtr); + lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr); } if (lineIndex == 0) { dstPtr->byteIndex = 0; goto backwadCharDone; } lineIndex--; - dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex); + dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); /* * Compute the length of the line and add that to dstPtr->byteIndex. diff --git a/generic/tkTextMark.c b/generic/tkTextMark.c index 5933af6..a536f6c 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.11 2004/01/13 02:06:01 davygrvy Exp $ + * RCS: @(#) $Id: tkTextMark.c,v 1.12 2004/09/10 12:13:42 vincentdarley Exp $ */ #include "tkInt.h" @@ -139,13 +139,20 @@ TkTextMarkCmd(textPtr, interp, objc, objv) Tcl_WrongNumArgs(interp, 3, objv, "markName ?gravity?"); return TCL_ERROR; } - hPtr = Tcl_FindHashEntry(&textPtr->markTable, Tcl_GetString(objv[3])); - if (hPtr == NULL) { - Tcl_AppendResult(interp, "there is no mark named \"", - Tcl_GetString(objv[3]), "\"", (char *) NULL); - return TCL_ERROR; + str = Tcl_GetStringFromObj(objv[3],&length); + if (length == 6 && !strcmp(str, "insert")) { + markPtr = textPtr->insertMarkPtr; + } else if (length == 7 && !strcmp(str, "current")) { + markPtr = textPtr->currentMarkPtr; + } else { + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, str); + if (hPtr == NULL) { + Tcl_AppendResult(interp, "there is no mark named \"", + Tcl_GetString(objv[3]), "\"", (char *) NULL); + return TCL_ERROR; + } + markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); } - markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); if (objc == 4) { if (markPtr->typePtr == &tkTextRightMarkType) { Tcl_SetResult(interp, "right", TCL_STATIC); @@ -167,8 +174,7 @@ TkTextMarkCmd(textPtr, interp, objc, objv) return TCL_ERROR; } TkTextMarkSegToIndex(textPtr, markPtr, &index); - TkBTreeUnlinkSegment(textPtr->tree, markPtr, - markPtr->body.mark.linePtr); + TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr); markPtr->typePtr = newTypePtr; TkBTreeLinkSegment(markPtr, &index); break; @@ -178,10 +184,12 @@ TkTextMarkCmd(textPtr, interp, objc, objv) Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search); + Tcl_AppendElement(interp, "insert"); + Tcl_AppendElement(interp, "current"); + for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->markTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Tcl_AppendElement(interp, - Tcl_GetHashKey(&textPtr->markTable, hPtr)); + Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr)); } break; } @@ -213,15 +221,16 @@ TkTextMarkCmd(textPtr, interp, objc, objv) case MARK_UNSET: { int i; for (i = 3; i < objc; i++) { - hPtr = Tcl_FindHashEntry(&textPtr->markTable, Tcl_GetString(objv[i])); + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, + Tcl_GetString(objv[i])); if (hPtr != NULL) { markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + /* Special case not needed with peer widgets */ if ((markPtr == textPtr->insertMarkPtr) || (markPtr == textPtr->currentMarkPtr)) { continue; } - TkBTreeUnlinkSegment(textPtr->tree, markPtr, - markPtr->body.mark.linePtr); + TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr); Tcl_DeleteHashEntry(hPtr); ckfree((char *) markPtr); } @@ -260,9 +269,21 @@ TkTextSetMark(textPtr, name, indexPtr) TkTextSegment *markPtr; TkTextIndex insertIndex; int new; - - hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new); - markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + int widgetSpecific; + + if (!strcmp(name, "insert")) { + widgetSpecific = 1; + markPtr = textPtr->insertMarkPtr; + new = (markPtr == NULL ? 1 : 0); + } else if (!strcmp(name, "current")) { + widgetSpecific = 2; + markPtr = textPtr->currentMarkPtr; + new = (markPtr == NULL ? 1 : 0); + } else { + widgetSpecific = 0; + hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->markTable, name, &new); + markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + } if (!new) { /* * If this is the insertion point that's being moved, be sure @@ -279,15 +300,14 @@ TkTextSetMark(textPtr, name, indexPtr) * 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)) { + TkTextChanged(NULL, textPtr, &index, &index2); + if (TkBTreeLinesTo(textPtr, indexPtr->linePtr) + == TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)) { TkTextIndexBackChars(NULL,indexPtr, 1, &insertIndex, COUNT_INDICES); indexPtr = &insertIndex; } } - TkBTreeUnlinkSegment(textPtr->tree, markPtr, - markPtr->body.mark.linePtr); + TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr); } else { markPtr = (TkTextSegment *) ckalloc(MSEG_SIZE); markPtr->typePtr = &tkTextRightMarkType; @@ -295,7 +315,13 @@ TkTextSetMark(textPtr, name, indexPtr) markPtr->body.mark.textPtr = textPtr; markPtr->body.mark.linePtr = indexPtr->linePtr; markPtr->body.mark.hPtr = hPtr; - Tcl_SetHashValue(hPtr, markPtr); + if (widgetSpecific == 0) { + Tcl_SetHashValue(hPtr, markPtr); + } else if (widgetSpecific == 1) { + textPtr->insertMarkPtr = markPtr; + } else { + textPtr->currentMarkPtr = markPtr; + } } TkBTreeLinkSegment(markPtr, indexPtr); @@ -312,7 +338,7 @@ TkTextSetMark(textPtr, name, indexPtr) * While we wish to redisplay, no heights have changed, so * no need to call TkTextInvalidateLineMetrics */ - TkTextChanged(textPtr, indexPtr, &index2); + TkTextChanged(NULL, textPtr, indexPtr, &index2); } return markPtr; } @@ -343,7 +369,7 @@ TkTextMarkSegToIndex(textPtr, markPtr, indexPtr) { TkTextSegment *segPtr; - indexPtr->tree = textPtr->tree; + indexPtr->tree = textPtr->sharedTextPtr->tree; indexPtr->linePtr = markPtr->body.mark.linePtr; indexPtr->byteIndex = 0; for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr; @@ -379,14 +405,25 @@ TkTextMarkNameToIndex(textPtr, name, indexPtr) CONST char *name; /* Name of mark. */ TkTextIndex *indexPtr; /* Index information gets stored here. */ { - Tcl_HashEntry *hPtr; + TkTextSegment *segPtr; - hPtr = Tcl_FindHashEntry(&textPtr->markTable, name); - if (hPtr == NULL) { - return TCL_ERROR; + if (textPtr == NULL) { + return TCL_ERROR; } - TkTextMarkSegToIndex(textPtr, (TkTextSegment *) Tcl_GetHashValue(hPtr), - indexPtr); + + if (!strcmp(name, "insert")) { + segPtr = textPtr->insertMarkPtr; + } else if (!strcmp(name, "current")) { + segPtr = textPtr->currentMarkPtr; + } else { + Tcl_HashEntry *hPtr; + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name); + if (hPtr == NULL) { + return TCL_ERROR; + } + segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + } + TkTextMarkSegToIndex(textPtr, segPtr, indexPtr); return TCL_OK; } @@ -530,7 +567,9 @@ MarkLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars, /* ARGSUSED */ void -TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) +TkTextInsertDisplayProc(textPtr, chunkPtr, x, y, height, baseline, display, + dst, screenY) + TkText *textPtr; /* The current text widget. */ TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ int x; /* X-position in dst at which to * draw this chunk (may differ from @@ -547,7 +586,8 @@ TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY) int screenY; /* Y-coordinate in text window that * corresponds to y. */ { - TkText *textPtr = (TkText *) chunkPtr->clientData; + /* We have no need for the clientData */ + /* TkText *textPtr = (TkText *) chunkPtr->clientData; */ TkTextIndex index; int halfWidth = textPtr->insertWidth/2; int rightSideWidth; @@ -650,12 +690,22 @@ MarkCheckProc(markPtr, linePtr) Tcl_Panic("MarkCheckProc: markPtr->body.mark.linePtr bogus"); } + /* + * These two marks are not in the hash table + */ + if (markPtr->body.mark.textPtr->insertMarkPtr == markPtr) { + return; + } + if (markPtr->body.mark.textPtr->currentMarkPtr == markPtr) { + return; + } + /* * Make sure that the mark is still present in the text's mark * hash table. */ - for (hPtr = Tcl_FirstHashEntry(&markPtr->body.mark.textPtr->markTable, + for (hPtr = Tcl_FirstHashEntry(&markPtr->body.mark.textPtr->sharedTextPtr->markTable, &search); hPtr != markPtr->body.mark.hPtr; hPtr = Tcl_NextHashEntry(&search)) { if (hPtr == NULL) { @@ -691,30 +741,40 @@ MarkFindNext(interp, textPtr, string) register TkTextSegment *segPtr; int offset; - - hPtr = Tcl_FindHashEntry(&textPtr->markTable, string); - if (hPtr != NULL) { - /* - * If given a mark name, return the next mark in the list of - * segments, even if it happens to be at the same character position. - */ - segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + if (!strcmp(string, "insert")) { + segPtr = textPtr->insertMarkPtr; + TkTextMarkSegToIndex(textPtr, segPtr, &index); + segPtr = segPtr->nextPtr; + } else if (!strcmp(string, "current")) { + segPtr = textPtr->currentMarkPtr; TkTextMarkSegToIndex(textPtr, segPtr, &index); segPtr = segPtr->nextPtr; } else { - /* - * For non-mark name indices we want to return any marks that - * are right at the index. - */ - if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) { - return TCL_ERROR; - } - for (offset = 0, segPtr = index.linePtr->segPtr; - segPtr != NULL && offset < index.byteIndex; - offset += segPtr->size, segPtr = segPtr->nextPtr) { - /* Empty loop body */ ; + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string); + if (hPtr != NULL) { + /* + * If given a mark name, return the next mark in the list of + * segments, even if it happens to be at the same character position. + */ + segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + TkTextMarkSegToIndex(textPtr, segPtr, &index); + segPtr = segPtr->nextPtr; + } else { + /* + * For non-mark name indices we want to return any marks that + * are right at the index. + */ + if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) { + return TCL_ERROR; + } + for (offset = 0, segPtr = index.linePtr->segPtr; + segPtr != NULL && offset < index.byteIndex; + offset += segPtr->size, segPtr = segPtr->nextPtr) { + /* Empty loop body */ ; + } } } + while (1) { /* * segPtr points at the first possible candidate, @@ -723,13 +783,23 @@ MarkFindNext(interp, textPtr, string) for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) { if (segPtr->typePtr == &tkTextRightMarkType || segPtr->typePtr == &tkTextLeftMarkType) { - Tcl_SetResult(interp, - Tcl_GetHashKey(&textPtr->markTable, segPtr->body.mark.hPtr), - TCL_STATIC); + if (segPtr == textPtr->currentMarkPtr) { + Tcl_SetResult(interp, "current", TCL_STATIC); + } else if (segPtr == textPtr->insertMarkPtr) { + Tcl_SetResult(interp, "insert", TCL_STATIC); + } else if (segPtr->body.mark.textPtr != textPtr) { + /* Ignore widget-specific marks for the other widgets */ + continue; + } else { + Tcl_SetResult(interp, + Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, + segPtr->body.mark.hPtr), + TCL_STATIC); + } return TCL_OK; } } - index.linePtr = TkBTreeNextLine(index.linePtr); + index.linePtr = TkBTreeNextLine(textPtr, index.linePtr); if (index.linePtr == (TkTextLine *) NULL) { return TCL_OK; } @@ -765,27 +835,34 @@ MarkFindPrev(interp, textPtr, string) register TkTextSegment *segPtr, *seg2Ptr, *prevPtr; int offset; - - hPtr = Tcl_FindHashEntry(&textPtr->markTable, string); - if (hPtr != NULL) { - /* - * If given a mark name, return the previous mark in the list of - * segments, even if it happens to be at the same character position. - */ - segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + if (!strcmp(string, "insert")) { + segPtr = textPtr->insertMarkPtr; + TkTextMarkSegToIndex(textPtr, segPtr, &index); + } else if (!strcmp(string, "current")) { + segPtr = textPtr->currentMarkPtr; TkTextMarkSegToIndex(textPtr, segPtr, &index); } else { - /* - * For non-mark name indices we do not return any marks that - * are right at the index. - */ - if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) { - return TCL_ERROR; - } - for (offset = 0, segPtr = index.linePtr->segPtr; - segPtr != NULL && offset < index.byteIndex; - offset += segPtr->size, segPtr = segPtr->nextPtr) { - /* Empty loop body */ ; + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string); + if (hPtr != NULL) { + /* + * If given a mark name, return the previous mark in the list of + * segments, even if it happens to be at the same character position. + */ + segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + TkTextMarkSegToIndex(textPtr, segPtr, &index); + } else { + /* + * For non-mark name indices we do not return any marks that + * are right at the index. + */ + if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) { + return TCL_ERROR; + } + for (offset = 0, segPtr = index.linePtr->segPtr; + segPtr != NULL && offset < index.byteIndex; + offset += segPtr->size, segPtr = segPtr->nextPtr) { + /* Empty loop body */ ; + } } } while (1) { @@ -802,12 +879,22 @@ MarkFindPrev(interp, textPtr, string) } } if (prevPtr != NULL) { - Tcl_SetResult(interp, - Tcl_GetHashKey(&textPtr->markTable, prevPtr->body.mark.hPtr), - TCL_STATIC); + if (prevPtr == textPtr->currentMarkPtr) { + Tcl_SetResult(interp, "current", TCL_STATIC); + } else if (prevPtr == textPtr->insertMarkPtr) { + Tcl_SetResult(interp, "insert", TCL_STATIC); + } else if (segPtr->body.mark.textPtr != textPtr) { + /* Ignore widget-specific marks for the other widgets */ + continue; + } else { + Tcl_SetResult(interp, + Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, + prevPtr->body.mark.hPtr), + TCL_STATIC); + } return TCL_OK; } - index.linePtr = TkBTreePreviousLine(index.linePtr); + index.linePtr = TkBTreePreviousLine(textPtr, index.linePtr); if (index.linePtr == (TkTextLine *) NULL) { return TCL_OK; } diff --git a/generic/tkTextTag.c b/generic/tkTextTag.c index b862d1b..41e61b1 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.17 2003/12/10 12:57:59 vincentdarley Exp $ + * RCS: @(#) $Id: tkTextTag.c,v 1.18 2004/09/10 12:13:42 vincentdarley Exp $ */ #include "default.h" @@ -91,6 +91,9 @@ static void SortTags _ANSI_ARGS_((int numTags, TkTextTag **tagArrayPtr)); static int TagSortProc _ANSI_ARGS_((CONST VOID *first, CONST VOID *second)); +static void TagBindEvent _ANSI_ARGS_((TkText *textPtr, XEvent *eventPtr, + int numTags, TkTextTag **tagArrayPtr)); + /* *-------------------------------------------------------------- @@ -134,7 +137,7 @@ TkTextTagCmd(textPtr, interp, objc, objv) int i; register TkTextTag *tagPtr; - TkTextIndex first, last, index1, index2; + TkTextIndex index1, index2; if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?"); @@ -181,7 +184,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) } if (tagPtr->affectsDisplay) { - TkTextRedrawTag(textPtr, &index1, &index2, tagPtr, !addTag); + TkTextRedrawTag(textPtr->sharedTextPtr, NULL, + &index1, &index2, tagPtr, !addTag); } else { /* * Still need to trigger enter/leave events on tags that @@ -195,28 +199,22 @@ TkTextTagCmd(textPtr, interp, objc, objv) * 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. + * retrievals. We only need to check whether the + * tag is "sel" for this textPtr (not for other peer + * widget's "sel" tags) because we cannot reach this + * code path with a different widget's "sel" tag. */ if (tagPtr == textPtr->selTagPtr) { - 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); + TkTextSelectionEvent(textPtr); if (addTag && textPtr->exportSelection && !(textPtr->flags & GOT_SELECTION)) { @@ -243,8 +241,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) * one. */ - if (textPtr->bindingTable == NULL) { - textPtr->bindingTable = Tk_CreateBindingTable(interp); + if (textPtr->sharedTextPtr->bindingTable == NULL) { + textPtr->sharedTextPtr->bindingTable = Tk_CreateBindingTable(interp); } if (objc == 6) { @@ -253,15 +251,15 @@ TkTextTagCmd(textPtr, interp, objc, objv) char *fifth = Tcl_GetString(objv[5]); if (fifth[0] == 0) { - return Tk_DeleteBinding(interp, textPtr->bindingTable, - (ClientData) tagPtr, Tcl_GetString(objv[4])); + return Tk_DeleteBinding(interp, textPtr->sharedTextPtr->bindingTable, + (ClientData) tagPtr->name, Tcl_GetString(objv[4])); } if (fifth[0] == '+') { fifth++; append = 1; } - mask = Tk_CreateBinding(interp, textPtr->bindingTable, - (ClientData) tagPtr, Tcl_GetString(objv[4]), + mask = Tk_CreateBinding(interp, textPtr->sharedTextPtr->bindingTable, + (ClientData) tagPtr->name, Tcl_GetString(objv[4]), fifth, append); if (mask == 0) { return TCL_ERROR; @@ -271,8 +269,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) |Button5MotionMask|ButtonPressMask|ButtonReleaseMask |EnterWindowMask|LeaveWindowMask|KeyPressMask |KeyReleaseMask|PointerMotionMask|VirtualEventMask)) { - Tk_DeleteBinding(interp, textPtr->bindingTable, - (ClientData) tagPtr, Tcl_GetString(objv[4])); + Tk_DeleteBinding(interp, textPtr->sharedTextPtr->bindingTable, + (ClientData) tagPtr->name, Tcl_GetString(objv[4])); Tcl_ResetResult(interp); Tcl_AppendResult(interp, "requested illegal events; ", "only key, button, motion, enter, leave, and virtual ", @@ -282,8 +280,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) } else if (objc == 5) { CONST char *command; - command = Tk_GetBinding(interp, textPtr->bindingTable, - (ClientData) tagPtr, Tcl_GetString(objv[4])); + command = Tk_GetBinding(interp, textPtr->sharedTextPtr->bindingTable, + (ClientData) tagPtr->name, Tcl_GetString(objv[4])); if (command == NULL) { CONST char *string = Tcl_GetStringResult(interp); @@ -302,8 +300,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) Tcl_SetResult(interp, (char *) command, TCL_STATIC); } } else { - Tk_GetAllBindings(interp, textPtr->bindingTable, - (ClientData) tagPtr); + Tk_GetAllBindings(interp, textPtr->sharedTextPtr->bindingTable, + (ClientData) tagPtr->name); } break; } @@ -506,7 +504,15 @@ TkTextTagCmd(textPtr, interp, objc, objv) * since it can't possibly have been applied to * anything yet. */ - TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, + + /* + * VMD: If this is the 'sel' tag, then we don't + * need to call this for all peers, unless we + * actually want to synchronize sel-style changes + * across the peers. + */ + TkTextRedrawTag(textPtr->sharedTextPtr, NULL, + (TkTextIndex *) NULL, (TkTextIndex *) NULL, tagPtr, 1); } return result; @@ -521,9 +527,13 @@ TkTextTagCmd(textPtr, interp, objc, objv) return TCL_ERROR; } for (i = 3; i < objc; i++) { - hPtr = Tcl_FindHashEntry(&textPtr->tagTable, + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->tagTable, Tcl_GetString(objv[i])); if (hPtr == NULL) { + /* Either this tag doesn't exist or it's the 'sel' + * tag (which is not in the hash table). Either + * way we don't want to delete it. + */ continue; } tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); @@ -531,46 +541,12 @@ TkTextTagCmd(textPtr, interp, objc, objv) continue; } if (tagPtr->affectsDisplay) { - TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, - (TkTextIndex *) NULL, tagPtr, 1); - } - TkTextMakeByteIndex(textPtr->tree, 0, 0, &first); - TkTextMakeByteIndex(textPtr->tree, - TkBTreeNumLines(textPtr->tree), - 0, &last), - TkBTreeTag(&first, &last, tagPtr, 0); - - 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); + TkTextRedrawTag(textPtr->sharedTextPtr, NULL, + (TkTextIndex *) NULL, + (TkTextIndex *) NULL, tagPtr, 1); } - + TkTextDeleteTag(textPtr, tagPtr); Tcl_DeleteHashEntry(hPtr); - if (textPtr->bindingTable != NULL) { - Tk_DeleteAllBindings(textPtr->bindingTable, - (ClientData) tagPtr); - } - - /* - * Update the tag priorities to reflect the deletion of this tag. - */ - - ChangeTagPriority(textPtr, tagPtr, textPtr->numTags-1); - textPtr->numTags -= 1; - TkTextFreeTag(textPtr, tagPtr); } break; } @@ -600,7 +576,12 @@ TkTextTagCmd(textPtr, interp, objc, objv) prio = 0; } ChangeTagPriority(textPtr, tagPtr, prio); - TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, + /* + * If this is the 'sel' tag, then we don't actually need + * to call this for all peers. + */ + TkTextRedrawTag(textPtr->sharedTextPtr, NULL, + (TkTextIndex *) NULL, (TkTextIndex *) NULL, tagPtr, 1); break; @@ -619,19 +600,22 @@ TkTextTagCmd(textPtr, interp, objc, objv) Tcl_HashEntry *hPtr; arrayPtr = (TkTextTag **) ckalloc((unsigned) - (textPtr->numTags * sizeof(TkTextTag *))); - for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, - &search); - hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) { + (textPtr->sharedTextPtr->numTags * sizeof(TkTextTag *))); + for (i = 0, + hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->tagTable, + &search); + hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) { arrayPtr[i] = (TkTextTag *) Tcl_GetHashValue(hPtr); } - arraySize = textPtr->numTags; + /* The 'sel' tag is not in the hash table */ + arrayPtr[i] = textPtr->selTagPtr; + arraySize = textPtr->sharedTextPtr->numTags; } else { if (TkTextGetObjIndex(interp, textPtr, objv[3], &index1) != TCL_OK) { return TCL_ERROR; } - arrayPtr = TkBTreeGetTags(&index1, &arraySize); + arrayPtr = TkBTreeGetTags(&index1, textPtr, &arraySize); if (arrayPtr == NULL) { return TCL_OK; } @@ -648,6 +632,7 @@ TkTextTagCmd(textPtr, interp, objc, objv) break; } case TAG_NEXTRANGE: { + TkTextIndex last; TkTextSearch tSearch; char position[TK_POS_CHARS]; @@ -662,8 +647,9 @@ TkTextTagCmd(textPtr, interp, objc, objv) if (TkTextGetObjIndex(interp, textPtr, objv[4], &index1) != TCL_OK) { return TCL_ERROR; } - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), - 0, &last); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), + 0, &last); if (objc == 5) { index2 = last; } else if (TkTextGetObjIndex(interp, textPtr, objv[5], &index2) @@ -716,14 +702,15 @@ TkTextTagCmd(textPtr, interp, objc, objv) if (TkTextIndexCmp(&tSearch.curIndex, &index2) >= 0) { return TCL_OK; } - TkTextPrintIndex(&tSearch.curIndex, position); + TkTextPrintIndex(textPtr, &tSearch.curIndex, position); Tcl_AppendElement(interp, position); TkBTreeNextTag(&tSearch); - TkTextPrintIndex(&tSearch.curIndex, position); + TkTextPrintIndex(textPtr, &tSearch.curIndex, position); Tcl_AppendElement(interp, position); break; } case TAG_PREVRANGE: { + TkTextIndex last; TkTextSearch tSearch; char position1[TK_POS_CHARS]; char position2[TK_POS_CHARS]; @@ -740,7 +727,8 @@ TkTextTagCmd(textPtr, interp, objc, objv) return TCL_ERROR; } if (objc == 5) { - TkTextMakeByteIndex(textPtr->tree, 0, 0, &index2); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + 0, 0, &index2); } else if (TkTextGetObjIndex(interp, textPtr, objv[5], &index2) != TCL_OK) { return TCL_ERROR; @@ -756,23 +744,69 @@ TkTextTagCmd(textPtr, interp, objc, objv) TkBTreeStartSearchBack(&index1, &index2, tagPtr, &tSearch); if (!TkBTreePrevTag(&tSearch)) { + /* + * Special case, there may be a tag off toggle at + * index1, and a tag on toggle before the start of a + * partial peer widget. In this case we missed it. + */ + if (textPtr->start != NULL && (textPtr->start == index2.linePtr) + && (index2.byteIndex == 0) && TkBTreeCharTagged(&index2, tagPtr) + && (TkTextIndexCmp(&index2, &index1) < 0)) { + /* + * The first character is tagged, so just add the + * range from the first char to the start of the + * range. + */ + TkTextPrintIndex(textPtr, &index2, position1); + TkTextPrintIndex(textPtr, &index1, position2); + Tcl_AppendElement(interp, position1); + Tcl_AppendElement(interp, position2); + } return TCL_OK; } if (tSearch.segPtr->typePtr == &tkTextToggleOnType) { - TkTextPrintIndex(&tSearch.curIndex, position1); - TkTextMakeByteIndex(textPtr->tree, - TkBTreeNumLines(textPtr->tree), - 0, &last); + TkTextPrintIndex(textPtr, &tSearch.curIndex, position1); + if (textPtr->start != NULL) { + /* + * Make sure the first index is not before the + * first allowed text index in this widget. + */ + TkTextIndex firstIndex; + firstIndex.linePtr = textPtr->start; + firstIndex.byteIndex = 0; + firstIndex.textPtr = NULL; + if (TkTextIndexCmp(&tSearch.curIndex, &firstIndex) < 0) { + if (TkTextIndexCmp(&firstIndex, &index1) >= 0) { + /* + * But now the new first index is actually + * too far along in the text, so nothing + * is returned. + */ + return TCL_OK; + } + TkTextPrintIndex(textPtr, &firstIndex, position1); + } + } + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), + 0, &last); TkBTreeStartSearch(&tSearch.curIndex, &last, tagPtr, &tSearch); TkBTreeNextTag(&tSearch); - TkTextPrintIndex(&tSearch.curIndex, position2); + TkTextPrintIndex(textPtr, &tSearch.curIndex, position2); } else { - TkTextPrintIndex(&tSearch.curIndex, position2); + TkTextPrintIndex(textPtr, &tSearch.curIndex, position2); TkBTreePrevTag(&tSearch); + TkTextPrintIndex(textPtr, &tSearch.curIndex, position1); if (TkTextIndexCmp(&tSearch.curIndex, &index2) < 0) { - return TCL_OK; + if (textPtr->start != NULL + && index2.linePtr == textPtr->start + && index2.byteIndex == 0) { + /* It's ok */ + TkTextPrintIndex(textPtr, &index2, position1); + } else { + return TCL_OK; + } } - TkTextPrintIndex(&tSearch.curIndex, position1); } Tcl_AppendElement(interp, position1); Tcl_AppendElement(interp, position2); @@ -801,17 +835,24 @@ TkTextTagCmd(textPtr, interp, objc, objv) prio = tagPtr2->priority + 1; } } else { - prio = textPtr->numTags-1; + prio = textPtr->sharedTextPtr->numTags-1; } ChangeTagPriority(textPtr, tagPtr, prio); - TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL, - tagPtr, 1); + /* + * If this is the 'sel' tag, then we don't actually need + * to call this for all peers. + */ + TkTextRedrawTag(textPtr->sharedTextPtr, NULL, + (TkTextIndex *) NULL, (TkTextIndex *) NULL, + tagPtr, 1); break; } case TAG_RANGES: { + TkTextIndex first, last; TkTextSearch tSearch; Tcl_Obj *listObj = Tcl_NewListObj(0, NULL); - + int count = 0; + if (objc != 4) { Tcl_WrongNumArgs(interp, 3, objv, "tagName"); return TCL_ERROR; @@ -820,17 +861,31 @@ TkTextTagCmd(textPtr, interp, objc, objv) if (tagPtr == NULL) { return TCL_OK; } - TkTextMakeByteIndex(textPtr->tree, 0, 0, &first); - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + 0, 0, &first); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &last); TkBTreeStartSearch(&first, &last, tagPtr, &tSearch); if (TkBTreeCharTagged(&first, tagPtr)) { Tcl_ListObjAppendElement(interp, listObj, TkTextNewIndexObj(textPtr, &first)); + count++; } while (TkBTreeNextTag(&tSearch)) { Tcl_ListObjAppendElement(interp, listObj, TkTextNewIndexObj(textPtr, &tSearch.curIndex)); + count++; + } + if (count % 2 == 1) { + /* + * If a text widget uses '-end', it won't necessarily + * run to the end of the B-tree, and therefore the + * tag range might not be closed. In this case we + * add the end of the range. + */ + Tcl_ListObjAppendElement(interp, listObj, + TkTextNewIndexObj(textPtr, &last)); } Tcl_SetObjResult(interp, listObj); break; @@ -867,13 +922,30 @@ TkTextCreateTag(textPtr, tagName, newTag) register TkTextTag *tagPtr; Tcl_HashEntry *hPtr; int new; - - hPtr = Tcl_CreateHashEntry(&textPtr->tagTable, tagName, &new); - if (newTag != NULL) { - *newTag = new; - } - if (!new) { - return (TkTextTag *) Tcl_GetHashValue(hPtr); + CONST char *name; + + if (!strcmp(tagName, "sel")) { + if (textPtr->selTagPtr != NULL) { + if (newTag != NULL) { + *newTag = 0; + } + return textPtr->selTagPtr; + } else { + if (newTag != NULL) { + *newTag = 1; + } + } + name = "sel"; + } else { + hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->tagTable, + tagName, &new); + if (newTag != NULL) { + *newTag = new; + } + if (!new) { + return (TkTextTag *) Tcl_GetHashValue(hPtr); + } + name = Tcl_GetHashKey(&textPtr->sharedTextPtr->tagTable, hPtr); } /* @@ -882,10 +954,11 @@ TkTextCreateTag(textPtr, tagName, newTag) */ tagPtr = (TkTextTag *) ckalloc(sizeof(TkTextTag)); - tagPtr->name = Tcl_GetHashKey(&textPtr->tagTable, hPtr); + tagPtr->name = name; + tagPtr->textPtr = NULL; tagPtr->toggleCount = 0; tagPtr->tagRootPtr = NULL; - tagPtr->priority = textPtr->numTags; + tagPtr->priority = textPtr->sharedTextPtr->numTags; tagPtr->border = NULL; tagPtr->borderWidth = 0; tagPtr->borderWidthPtr = NULL; @@ -922,8 +995,13 @@ TkTextCreateTag(textPtr, tagName, newTag) tagPtr->wrapMode = TEXT_WRAPMODE_NULL; tagPtr->affectsDisplay = 0; tagPtr->affectsDisplayGeometry = 0; - textPtr->numTags++; - Tcl_SetHashValue(hPtr, tagPtr); + textPtr->sharedTextPtr->numTags++; + if (!strcmp(tagName, "sel")) { + tagPtr->textPtr = textPtr; + textPtr->refCount++; + } else { + Tcl_SetHashValue(hPtr, tagPtr); + } tagPtr->optionTable = Tk_CreateOptionTable(textPtr->interp, tagOptionSpecs); return tagPtr; } @@ -956,8 +1034,14 @@ FindTag(interp, textPtr, tagName) Tcl_Obj *tagName; /* Name of desired tag. */ { Tcl_HashEntry *hPtr; - - hPtr = Tcl_FindHashEntry(&textPtr->tagTable, Tcl_GetString(tagName)); + int len; + CONST char *str; + str = Tcl_GetStringFromObj(tagName, &len); + if (len == 3 && !strcmp(str,"sel")) { + return textPtr->selTagPtr; + } + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->tagTable, + Tcl_GetString(tagName)); if (hPtr != NULL) { return (TkTextTag *) Tcl_GetHashValue(hPtr); } @@ -971,6 +1055,76 @@ FindTag(interp, textPtr, tagName) /* *---------------------------------------------------------------------- * + * TkTextDeleteTag -- + * + * This procedure is called to carry out most actions associated + * with the 'tag delete' sub-command. It will remove all + * evidence of the tag from the B-tree, and then call + * TkTextFreeTag to clean up the tag structure itself. + * + * The only actions this doesn't carry out it to check if + * the deletion of the tag requires something to be + * re-displayed, and to remove the tag from the tagTable + * (hash table) if that is necessary (i.e. if it's not the + * 'sel' tag). It is expected that the caller carry out both + * of these actions. + * + * Results: + * None. + * + * Side effects: + * Memory and other resources are freed, the B-tree is + * manipulated. + * + *---------------------------------------------------------------------- + */ + +void +TkTextDeleteTag(textPtr, tagPtr) + TkText *textPtr; /* Info about overall widget. */ + register TkTextTag *tagPtr; /* Tag being deleted. */ +{ + TkTextIndex first, last; + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, &first); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), + 0, &last), + TkBTreeTag(&first, &last, tagPtr, 0); + + if (tagPtr == textPtr->selTagPtr) { + /* + * Send an event that the selection changed. + * This is equivalent to + * "event generate $textWidget <<Selection>>" + */ + TkTextSelectionEvent(textPtr); + } else { + /* + * Since all peer widgets have an independent "sel" tag, we + * don't want removal of one sel tag to remove bindings which + * are still valid in other peer widgets. + */ + if (textPtr->sharedTextPtr->bindingTable != NULL) { + Tk_DeleteAllBindings(textPtr->sharedTextPtr->bindingTable, + (ClientData) tagPtr->name); + } + } + + /* + * Update the tag priorities to reflect the deletion of this tag. + */ + + ChangeTagPriority(textPtr, tagPtr, + textPtr->sharedTextPtr->numTags-1); + textPtr->sharedTextPtr->numTags -= 1; + TkTextFreeTag(textPtr, tagPtr); + +} + +/* + *---------------------------------------------------------------------- + * * TkTextFreeTag -- * * This procedure is called when a tag is deleted to free up the @@ -1010,6 +1164,20 @@ TkTextFreeTag(textPtr, tagPtr) break; } } + /* + * If this tag is widget-specific (peer widgets) then clean + * up the refCount it holds. + */ + if (tagPtr->textPtr != NULL) { + if (textPtr != tagPtr->textPtr) { + Tcl_Panic("Tag being deleted from wrong widget"); + } + textPtr->refCount--; + if (textPtr->refCount == 0) { + ckfree((char *) textPtr); + } + tagPtr->textPtr = NULL; + } /* Finally free the tag's memory */ ckfree((char *) tagPtr); } @@ -1109,9 +1277,10 @@ TagSortProc(first, second) * * Side effects: * Priorities may be changed for some or all of the tags in - * textPtr. The tags will be arranged so that there is exactly - * one tag at each priority level between 0 and textPtr->numTags-1, - * with tagPtr at priority "prio". + * textPtr. The tags will be arranged so that there is exactly one + * tag at each priority level between 0 and + * textPtr->sharedTextPtr->numTags-1, with tagPtr at priority + * "prio". * *---------------------------------------------------------------------- */ @@ -1131,8 +1300,8 @@ ChangeTagPriority(textPtr, tagPtr, prio) if (prio < 0) { prio = 0; } - if (prio >= textPtr->numTags) { - prio = textPtr->numTags-1; + if (prio >= textPtr->sharedTextPtr->numTags) { + prio = textPtr->sharedTextPtr->numTags-1; } if (prio == tagPtr->priority) { return; @@ -1145,7 +1314,14 @@ ChangeTagPriority(textPtr, tagPtr, prio) high = prio; delta = -1; } - for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); + /* + * Adjust first the 'sel' tag, then all others from the hash table + */ + if ((textPtr->selTagPtr->priority >= low) + && (textPtr->selTagPtr->priority <= high)) { + textPtr->selTagPtr->priority += delta; + } + for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->tagTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { tagPtr2 = (TkTextTag *) Tcl_GetHashValue(hPtr); if ((tagPtr2->priority >= low) && (tagPtr2->priority <= high)) { @@ -1239,10 +1415,10 @@ TkTextBindProc(clientData, eventPtr) } TkTextPickCurrent(textPtr, eventPtr); } - if ((textPtr->numCurTags > 0) && (textPtr->bindingTable != NULL) + if ((textPtr->numCurTags > 0) && (textPtr->sharedTextPtr->bindingTable != NULL) && (textPtr->tkwin != NULL) && !(textPtr->flags & DESTROYED)) { - Tk_BindEvent(textPtr->bindingTable, eventPtr, textPtr->tkwin, - textPtr->numCurTags, (ClientData *) textPtr->curTagArrayPtr); + TagBindEvent(textPtr, eventPtr, textPtr->numCurTags, + textPtr->curTagArrayPtr); } if (repick) { unsigned int oldState; @@ -1374,7 +1550,7 @@ TkTextPickCurrent(textPtr, eventPtr) newArrayPtr = NULL; numNewTags = 0; } else { - newArrayPtr = TkBTreeGetTags(&index, &numNewTags); + newArrayPtr = TkBTreeGetTags(&index, textPtr, &numNewTags); SortTags(numNewTags, newArrayPtr); } } else { @@ -1421,7 +1597,7 @@ TkTextPickCurrent(textPtr, eventPtr) oldArrayPtr = textPtr->curTagArrayPtr; textPtr->curTagArrayPtr = newArrayPtr; if (numOldTags != 0) { - if ((textPtr->bindingTable != NULL) && (textPtr->tkwin != NULL) + if ((textPtr->sharedTextPtr->bindingTable != NULL) && (textPtr->tkwin != NULL) && !(textPtr->flags & DESTROYED)) { event = textPtr->pickEvent; event.type = LeaveNotify; @@ -1433,8 +1609,7 @@ TkTextPickCurrent(textPtr, eventPtr) */ event.xcrossing.detail = NotifyAncestor; - Tk_BindEvent(textPtr->bindingTable, &event, textPtr->tkwin, - numOldTags, (ClientData *) oldArrayPtr); + TagBindEvent(textPtr, &event, numOldTags, oldArrayPtr); } ckfree((char *) oldArrayPtr); } @@ -1450,14 +1625,81 @@ TkTextPickCurrent(textPtr, eventPtr) textPtr->pickEvent.xcrossing.y, &index, &nearby); TkTextSetMark(textPtr, "current", &index); if (numNewTags != 0) { - if ((textPtr->bindingTable != NULL) && (textPtr->tkwin != NULL) + if ((textPtr->sharedTextPtr->bindingTable != NULL) && (textPtr->tkwin != NULL) && !(textPtr->flags & DESTROYED) && !nearby) { event = textPtr->pickEvent; event.type = EnterNotify; event.xcrossing.detail = NotifyAncestor; - Tk_BindEvent(textPtr->bindingTable, &event, textPtr->tkwin, - numNewTags, (ClientData *) copyArrayPtr); + TagBindEvent(textPtr, &event, numNewTags, copyArrayPtr); } ckfree((char *) copyArrayPtr); } } + +/* + *-------------------------------------------------------------- + * + * TagBindEvent -- + * + * Trigger given events for all tags that match the + * relevant bindings. To handle the "sel" tag + * correctly in all peer widgets, we must use the + * name of the tags as the binding table element. + * + * Results: + * None. + * + * Side effects: + * Almost anything can be triggered by tag bindings, + * including deletion of the text widget. + * + *-------------------------------------------------------------- + */ + +static void +TagBindEvent(textPtr, eventPtr, numTags, tagArrayPtr) + TkText *textPtr; /* Text widget to fire bindings + * in. */ + XEvent *eventPtr; /* What actually happened. */ + int numTags; /* Number of relevant tags. */ + TkTextTag **tagArrayPtr; /* Array of relevant tags. */ +{ + #define NUM_BIND_TAGS 10 + CONST char *nameArray[NUM_BIND_TAGS]; + CONST char **nameArrPtr; + int i; + + /* + * Try to avoid allocation unless there are lots of tags + */ + if (numTags > NUM_BIND_TAGS) { + nameArrPtr = (CONST char**) ckalloc (numTags * sizeof(CONST char*)); + } else { + nameArrPtr = nameArray; + } + /* + * We use tag names as keys in the hash table. We do this instead + * of using the actual tagPtr objects because we want one "sel" tag + * binding for all peer widgets, despite the fact that each has its + * own tagPtr object. + */ + for (i = 0; i < numTags; i++) { + TkTextTag *tagPtr = tagArrayPtr[i]; + if (tagPtr != NULL) { + nameArrPtr[i] = tagPtr->name; + } else { + /* + * Tag has been deleted elsewhere, and therefore nulled + * out in this array. Tk_BindEvent is clever enough to + * cope with NULLs being thrown at it. + */ + nameArrPtr[i] = NULL; + } + } + Tk_BindEvent(textPtr->sharedTextPtr->bindingTable, eventPtr, textPtr->tkwin, + numTags, (ClientData *) nameArrPtr); + + if (numTags > NUM_BIND_TAGS) { + ckfree((char*)nameArrPtr); + } +} diff --git a/generic/tkTextWind.c b/generic/tkTextWind.c index 5c59603..5fd56e0 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.12 2004/01/13 02:06:01 davygrvy Exp $ + * RCS: @(#) $Id: tkTextWind.c,v 1.13 2004/09/10 12:13:42 vincentdarley Exp $ */ #include "tk.h" @@ -49,7 +49,8 @@ static TkTextSegment * EmbWinCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); static void EmbWinCheckProc _ANSI_ARGS_((TkTextSegment *segPtr, TkTextLine *linePtr)); -static void EmbWinBboxProc _ANSI_ARGS_((TkTextDispChunk *chunkPtr, +static void EmbWinBboxProc _ANSI_ARGS_((TkText *textPtr, + TkTextDispChunk *chunkPtr, int index, int y, int lineHeight, int baseline, int *xPtr, int *yPtr, int *widthPtr, int *heightPtr)); @@ -68,6 +69,8 @@ static void EmbWinStructureProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); static void EmbWinUndisplayProc _ANSI_ARGS_((TkText *textPtr, TkTextDispChunk *chunkPtr)); +static TkTextEmbWindowClient* EmbWinGetClient _ANSI_ARGS_((CONST TkText *textPtr, + TkTextSegment *ewPtr)); /* * The following structure declares the "embedded window" segment type. @@ -173,6 +176,7 @@ TkTextWindowCmd(textPtr, interp, objc, objv) TkTextIndex index; TkTextSegment *ewPtr; Tcl_Obj *objPtr; + TkTextEmbWindowClient *client; if (objc != 5) { Tcl_WrongNumArgs(interp, 3, objv, "index option"); @@ -187,6 +191,15 @@ TkTextWindowCmd(textPtr, interp, objc, objv) Tcl_GetString(objv[3]), "\"", (char *) NULL); return TCL_ERROR; } + + /* Copy over client specific value before querying */ + client = EmbWinGetClient(textPtr, ewPtr); + if (client != NULL) { + ewPtr->body.ew.tkwin = client->tkwin; + } else { + ewPtr->body.ew.tkwin = NULL; + } + objPtr = Tk_GetOptionValue(interp, (char *) &ewPtr->body.ew, ewPtr->body.ew.optionTable, objv[4], textPtr->tkwin); if (objPtr == NULL) { @@ -215,7 +228,18 @@ TkTextWindowCmd(textPtr, interp, objc, objv) return TCL_ERROR; } if (objc <= 5) { - Tcl_Obj* objPtr = Tk_GetOptionInfo(interp, (char *) &ewPtr->body.ew, + TkTextEmbWindowClient *client; + Tcl_Obj* objPtr; + + /* Copy over client specific value before querying */ + client = EmbWinGetClient(textPtr, ewPtr); + if (client != NULL) { + ewPtr->body.ew.tkwin = client->tkwin; + } else { + ewPtr->body.ew.tkwin = NULL; + } + + objPtr = Tk_GetOptionInfo(interp, (char *) &ewPtr->body.ew, ewPtr->body.ew.optionTable, (objc == 5) ? objv[4] : (Tcl_Obj *) NULL, textPtr->tkwin); @@ -226,13 +250,14 @@ TkTextWindowCmd(textPtr, interp, objc, objv) return TCL_OK; } } else { - TkTextChanged(textPtr, &index, &index); + TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index); /* * It's probably not true that all window configuration * can change the line height, so we could be more * efficient here and only call this when necessary. */ - TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); return EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4); } @@ -241,7 +266,9 @@ TkTextWindowCmd(textPtr, interp, objc, objv) case WIND_CREATE: { TkTextIndex index; int lineIndex; - + TkTextEmbWindowClient *client; + int res; + /* * Add a new window. Find where to put the new window, and * mark that position for redisplay. @@ -259,10 +286,11 @@ TkTextWindowCmd(textPtr, interp, objc, objv) * Don't allow insertions on the last (dummy) line of the text. */ - lineIndex = TkBTreeLineIndex(index.linePtr); - if (lineIndex == TkBTreeNumLines(textPtr->tree)) { + lineIndex = TkBTreeLinesTo(textPtr, index.linePtr); + if (lineIndex == TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr)) { lineIndex--; - TkTextMakeByteIndex(textPtr->tree, lineIndex, 1000000, &index); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineIndex, 1000000, &index); } /* @@ -272,31 +300,42 @@ TkTextWindowCmd(textPtr, interp, objc, objv) ewPtr = (TkTextSegment *) ckalloc(EW_SEG_SIZE); ewPtr->typePtr = &tkTextEmbWindowType; ewPtr->size = 1; - ewPtr->body.ew.textPtr = textPtr; + ewPtr->body.ew.sharedTextPtr = textPtr->sharedTextPtr; ewPtr->body.ew.linePtr = NULL; ewPtr->body.ew.tkwin = NULL; ewPtr->body.ew.create = NULL; ewPtr->body.ew.align = ALIGN_CENTER; ewPtr->body.ew.padX = ewPtr->body.ew.padY = 0; ewPtr->body.ew.stretch = 0; - ewPtr->body.ew.chunkCount = 0; - ewPtr->body.ew.displayed = 0; ewPtr->body.ew.optionTable = Tk_CreateOptionTable(interp, optionSpecs); + + client = (TkTextEmbWindowClient*) ckalloc(sizeof(TkTextEmbWindowClient)); + client->next = NULL; + client->textPtr = textPtr; + client->tkwin = NULL; + client->chunkCount = 0; + client->displayed = 0; + client->parent = ewPtr; + ewPtr->body.ew.clients = client; + /* * Link the segment into the text widget, then configure it (delete * it again if the configuration fails). */ - TkTextChanged(textPtr, &index, &index); + TkTextChanged(textPtr->sharedTextPtr, NULL, &index, &index); TkBTreeLinkSegment(ewPtr, &index); - if (EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4) != TCL_OK) { + res = EmbWinConfigure(textPtr, ewPtr, objc-4, objv+4); + client->tkwin = ewPtr->body.ew.tkwin; + if (res != TCL_OK) { TkTextIndex index2; - TkTextIndexForwChars(NULL,&index, 1, &index2, COUNT_INDICES); - TkBTreeDeleteChars(&index, &index2); + TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES); + TkBTreeDeleteChars(textPtr->sharedTextPtr->tree, &index, &index2); return TCL_ERROR; } - TkTextInvalidateLineMetrics(textPtr, index.linePtr, 0, + TkTextInvalidateLineMetrics(textPtr->sharedTextPtr, NULL, + index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); break; } @@ -308,10 +347,10 @@ TkTextWindowCmd(textPtr, interp, objc, objv) Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - for (hPtr = Tcl_FirstHashEntry(&textPtr->windowTable, &search); + for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->windowTable, &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { Tcl_AppendElement(interp, - Tcl_GetHashKey(&textPtr->markTable, hPtr)); + Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr)); } break; } @@ -335,6 +374,12 @@ TkTextWindowCmd(textPtr, interp, objc, objv) * Configuration information for the embedded window changes, * such as alignment, stretching, or name of the embedded * window. + * + * Note that this procedure may leave widget specific client + * information with a NULL tkwin attached to ewPtr. While we could + * choose to clean up the client data structure here, there is no + * need to do so, and it is likely that the user is going to adjust + * the tkwin again soon. * *-------------------------------------------------------------- */ @@ -349,21 +394,29 @@ EmbWinConfigure(textPtr, ewPtr, objc, objv) * options. */ { Tk_Window oldWindow; - Tcl_HashEntry *hPtr; - int new; + TkTextEmbWindowClient *client; + /* Copy over client specific value before querying or setting */ + client = EmbWinGetClient(textPtr, ewPtr); + if (client != NULL) { + ewPtr->body.ew.tkwin = client->tkwin; + } else { + ewPtr->body.ew.tkwin = NULL; + } + oldWindow = ewPtr->body.ew.tkwin; if (Tk_SetOptions(textPtr->interp, (char*)&ewPtr->body.ew, ewPtr->body.ew.optionTable, objc, objv, textPtr->tkwin, NULL, NULL) != TCL_OK) { return TCL_ERROR; } + if (oldWindow != ewPtr->body.ew.tkwin) { if (oldWindow != NULL) { - Tcl_DeleteHashEntry(Tcl_FindHashEntry(&textPtr->windowTable, + Tcl_DeleteHashEntry(Tcl_FindHashEntry(&textPtr->sharedTextPtr->windowTable, Tk_PathName(oldWindow))); Tk_DeleteEventHandler(oldWindow, StructureNotifyMask, - EmbWinStructureProc, (ClientData) ewPtr); + EmbWinStructureProc, (ClientData) client); Tk_ManageGeometry(oldWindow, (Tk_GeomMgr *) NULL, (ClientData) NULL); if (textPtr->tkwin != Tk_Parent(oldWindow)) { @@ -372,9 +425,14 @@ EmbWinConfigure(textPtr, ewPtr, objc, objv) Tk_UnmapWindow(oldWindow); } } + if (client != NULL) { + client->tkwin = NULL; + } if (ewPtr->body.ew.tkwin != NULL) { Tk_Window ancestor, parent; - + Tcl_HashEntry *hPtr; + int new; + /* * Make sure that the text is either the parent of the * embedded window or a descendant of that parent. Also, @@ -394,6 +452,9 @@ EmbWinConfigure(textPtr, ewPtr, objc, objv) Tk_PathName(ewPtr->body.ew.tkwin), " in ", Tk_PathName(textPtr->tkwin), (char *) NULL); ewPtr->body.ew.tkwin = NULL; + if (client != NULL) { + client->tkwin = NULL; + } return TCL_ERROR; } } @@ -401,6 +462,19 @@ EmbWinConfigure(textPtr, ewPtr, objc, objv) || (ewPtr->body.ew.tkwin == textPtr->tkwin)) { goto badMaster; } + + if (client == NULL) { + /* Have to make the new client */ + client = (TkTextEmbWindowClient*) ckalloc(sizeof(TkTextEmbWindowClient)); + client->next = ewPtr->body.ew.clients; + client->textPtr = textPtr; + client->tkwin = NULL; + client->chunkCount = 0; + client->displayed = 0; + client->parent = ewPtr; + ewPtr->body.ew.clients = client; + } + client->tkwin = ewPtr->body.ew.tkwin; /* * Take over geometry management for the window, plus create @@ -408,9 +482,9 @@ EmbWinConfigure(textPtr, ewPtr, objc, objv) */ Tk_ManageGeometry(ewPtr->body.ew.tkwin, &textGeomType, - (ClientData) ewPtr); + (ClientData) client); Tk_CreateEventHandler(ewPtr->body.ew.tkwin, StructureNotifyMask, - EmbWinStructureProc, (ClientData) ewPtr); + EmbWinStructureProc, (ClientData) client); /* * Special trick! Must enter into the hash table *after* @@ -420,7 +494,7 @@ EmbWinConfigure(textPtr, ewPtr, objc, objv) * entry. */ - hPtr = Tcl_CreateHashEntry(&textPtr->windowTable, + hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->windowTable, Tk_PathName(ewPtr->body.ew.tkwin), &new); Tcl_SetHashValue(hPtr, ewPtr); @@ -454,21 +528,29 @@ EmbWinStructureProc(clientData, eventPtr) ClientData clientData; /* Pointer to record describing window item. */ XEvent *eventPtr; /* Describes what just happened. */ { - register TkTextSegment *ewPtr = (TkTextSegment *) clientData; + TkTextEmbWindowClient *client = (TkTextEmbWindowClient*)clientData; + TkTextSegment *ewPtr = client->parent; TkTextIndex index; - + Tcl_HashEntry *hPtr; + if (eventPtr->type != DestroyNotify) { return; } - Tcl_DeleteHashEntry(Tcl_FindHashEntry(&ewPtr->body.ew.textPtr->windowTable, - Tk_PathName(ewPtr->body.ew.tkwin))); + hPtr = Tcl_FindHashEntry(&ewPtr->body.ew.sharedTextPtr->windowTable, + Tk_PathName(client->tkwin)); + if (hPtr != NULL) { + /* This may not exist if the entire widget is being deleted */ + Tcl_DeleteHashEntry(hPtr); + } + ewPtr->body.ew.tkwin = NULL; - index.tree = ewPtr->body.ew.textPtr->tree; + client->tkwin = NULL; + index.tree = ewPtr->body.ew.sharedTextPtr->tree; index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); - TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); - TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + TkTextChanged(ewPtr->body.ew.sharedTextPtr, NULL, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.sharedTextPtr, NULL, index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } @@ -497,14 +579,15 @@ EmbWinRequestProc(clientData, tkwin) Tk_Window tkwin; /* Window that changed its desired * size. */ { - TkTextSegment *ewPtr = (TkTextSegment *) clientData; + TkTextEmbWindowClient *client = (TkTextEmbWindowClient*)clientData; + TkTextSegment *ewPtr = client->parent; TkTextIndex index; - index.tree = ewPtr->body.ew.textPtr->tree; + index.tree = ewPtr->body.ew.sharedTextPtr->tree; index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); - TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); - TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + TkTextChanged(ewPtr->body.ew.sharedTextPtr, NULL, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.sharedTextPtr, NULL, index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } @@ -533,31 +616,107 @@ EmbWinLostSlaveProc(clientData, tkwin) Tk_Window tkwin; /* Window that was claimed away by another * geometry manager. */ { - register TkTextSegment *ewPtr = (TkTextSegment *) clientData; + TkTextEmbWindowClient *client = (TkTextEmbWindowClient*)clientData; + TkTextSegment *ewPtr = client->parent; TkTextIndex index; - - Tk_DeleteEventHandler(ewPtr->body.ew.tkwin, StructureNotifyMask, - EmbWinStructureProc, (ClientData) ewPtr); - Tcl_CancelIdleCall(EmbWinDelayedUnmap, (ClientData) ewPtr); - if (ewPtr->body.ew.textPtr->tkwin != Tk_Parent(tkwin)) { - Tk_UnmaintainGeometry(tkwin, ewPtr->body.ew.textPtr->tkwin); + Tcl_HashEntry *hPtr; + TkTextEmbWindowClient *loop; + + Tk_DeleteEventHandler(client->tkwin, StructureNotifyMask, + EmbWinStructureProc, (ClientData) client); + Tcl_CancelIdleCall(EmbWinDelayedUnmap, (ClientData) client); + if (client->textPtr->tkwin != Tk_Parent(tkwin)) { + Tk_UnmaintainGeometry(tkwin, client->textPtr->tkwin); } else { Tk_UnmapWindow(tkwin); } - Tcl_DeleteHashEntry(Tcl_FindHashEntry(&ewPtr->body.ew.textPtr->windowTable, - Tk_PathName(ewPtr->body.ew.tkwin))); + hPtr = Tcl_FindHashEntry(&ewPtr->body.ew.sharedTextPtr->windowTable, + Tk_PathName(client->tkwin)); + Tcl_DeleteHashEntry(hPtr); + client->tkwin = NULL; ewPtr->body.ew.tkwin = NULL; - index.tree = ewPtr->body.ew.textPtr->tree; + + /* Free up the memory allocation for this client */ + + loop = ewPtr->body.ew.clients; + if (loop == client) { + ewPtr->body.ew.clients = client->next; + } else { + while (loop->next != client) { + loop = loop->next; + } + loop->next = client->next; + } + ckfree((char *)client); + + index.tree = ewPtr->body.ew.sharedTextPtr->tree; index.linePtr = ewPtr->body.ew.linePtr; index.byteIndex = TkTextSegToOffset(ewPtr, ewPtr->body.ew.linePtr); - TkTextChanged(ewPtr->body.ew.textPtr, &index, &index); - TkTextInvalidateLineMetrics(ewPtr->body.ew.textPtr, + TkTextChanged(ewPtr->body.ew.sharedTextPtr, NULL, &index, &index); + TkTextInvalidateLineMetrics(ewPtr->body.ew.sharedTextPtr, NULL, index.linePtr, 0, TK_TEXT_INVALIDATE_ONLY); } /* *-------------------------------------------------------------- * + * TkTextWinFreeClient -- + * + * Free up the hash entry and client information for a + * given embedded window. + * + * It is assumed the caller will manage the linked list + * of clients associated with the relevant TkTextSegment. + * + * Results: + * Nothing. + * + * Side effects: + * The embedded window information for a single client is deleted, + * if it exists, and any resources associated with it are released. + * + *-------------------------------------------------------------- + */ + +void +TkTextWinFreeClient(hPtr, client) + Tcl_HashEntry *hPtr; /* Hash entry corresponding to + * this client, or NULL */ + TkTextEmbWindowClient *client; /* Client data structure, with + * the 'tkwin' field to be + * cleaned up. */ +{ + if (hPtr != NULL) { + /* + * (It's possible for there to be no hash table entry + * for this window, if an error occurred while creating + * the window segment but before the window got added to + * the table) + */ + + Tcl_DeleteHashEntry(hPtr); + } + + /* + * Delete the event handler for the window before destroying + * the window, so that EmbWinStructureProc doesn't get called + * (we'll already do everything that it would have done, and + * it will just get confused). + */ + + if (client->tkwin != NULL) { + Tk_DeleteEventHandler(client->tkwin, StructureNotifyMask, + EmbWinStructureProc, (ClientData) client); + Tk_DestroyWindow(client->tkwin); + } + Tcl_CancelIdleCall(EmbWinDelayedUnmap, (ClientData) client); + /* Free up this client */ + ckfree((char *) client); +} + +/* + *-------------------------------------------------------------- + * * EmbWinDeleteProc -- * * This procedure is invoked by the text B-tree code whenever @@ -582,36 +741,28 @@ EmbWinDeleteProc(ewPtr, linePtr, treeGone) * being deleted, so everything must * get cleaned up. */ { - Tcl_HashEntry *hPtr; - - if (ewPtr->body.ew.tkwin != NULL) { - hPtr = Tcl_FindHashEntry(&ewPtr->body.ew.textPtr->windowTable, - Tk_PathName(ewPtr->body.ew.tkwin)); - if (hPtr != NULL) { - /* - * (It's possible for there to be no hash table entry for this - * window, if an error occurred while creating the window segment - * but before the window got added to the table) - */ - - Tcl_DeleteHashEntry(hPtr); - } - - /* - * Delete the event handler for the window before destroying - * the window, so that EmbWinStructureProc doesn't get called - * (we'll already do everything that it would have done, and - * it will just get confused). - */ - - Tk_DeleteEventHandler(ewPtr->body.ew.tkwin, StructureNotifyMask, - EmbWinStructureProc, (ClientData) ewPtr); - Tk_DestroyWindow(ewPtr->body.ew.tkwin); + TkTextEmbWindowClient *client; + client = ewPtr->body.ew.clients; + + while (client != NULL) { + TkTextEmbWindowClient *next = client->next; + Tcl_HashEntry *hPtr = NULL; + + if (client->tkwin != NULL) { + hPtr = Tcl_FindHashEntry(&ewPtr->body.ew.sharedTextPtr->windowTable, + Tk_PathName(client->tkwin)); + } + TkTextWinFreeClient(hPtr, client); + client = next; } - Tcl_CancelIdleCall(EmbWinDelayedUnmap, (ClientData) ewPtr); + ewPtr->body.ew.clients = NULL; + Tk_FreeConfigOptions((char *) &ewPtr->body.ew, ewPtr->body.ew.optionTable, - ewPtr->body.ew.tkwin); + NULL); + + /* Free up all memory allocated */ ckfree((char *) ewPtr); + return 0; } @@ -683,17 +834,72 @@ EmbWinLayoutProc(textPtr, indexPtr, ewPtr, offset, maxX, maxChars, * been set by the caller. */ { int width, height; - + TkTextEmbWindowClient *client; + if (offset != 0) { Tcl_Panic("Non-zero offset in EmbWinLayoutProc"); } + client = EmbWinGetClient(textPtr, ewPtr); + if (client == NULL) { + ewPtr->body.ew.tkwin = NULL; + } else { + ewPtr->body.ew.tkwin = client->tkwin; + } + if ((ewPtr->body.ew.tkwin == NULL) && (ewPtr->body.ew.create != NULL)) { int code, new; Tcl_DString name; Tk_Window ancestor; Tcl_HashEntry *hPtr; + CONST char *before; + CONST char *string; + Tcl_DString buf; + Tcl_DString *dsPtr = NULL; + before = ewPtr->body.ew.create; + + /* + * Find everything up to the next % character and append it + * to the result string. + */ + string = before; + while (*string != 0) { + if (*string == '%') { + if (string[1] == '%' || string[1] == 'W') { + if (dsPtr == NULL) { + Tcl_DStringInit(&buf); + dsPtr = &buf; + } + if (string != before) { + Tcl_DStringAppend(dsPtr, before, + (int) (string-before)); + before = string; + } + if (string[1] == '%') { + Tcl_DStringAppend(dsPtr, "%", 1); + } else { + /* + * Substitute string as proper Tcl + * list element. + */ + int spaceNeeded, cvtFlags, length; + CONST char *str = Tk_PathName(textPtr->tkwin); + spaceNeeded = Tcl_ScanElement(str, &cvtFlags); + length = Tcl_DStringLength(dsPtr); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + spaceNeeded = Tcl_ConvertElement(str, + Tcl_DStringValue(dsPtr) + length, + cvtFlags | TCL_DONT_USE_BRACES); + Tcl_DStringSetLength(dsPtr, length + spaceNeeded); + } + before += 2; + string++; + } + } + string++; + } + /* * The window doesn't currently exist. Create it by evaluating * the creation script. The script must return the window's @@ -702,7 +908,14 @@ EmbWinLayoutProc(textPtr, indexPtr, ewPtr, offset, maxX, maxChars, * the window. */ - code = Tcl_GlobalEval(textPtr->interp, ewPtr->body.ew.create); + if (dsPtr != NULL) { + Tcl_DStringAppend(dsPtr, before, + (int) (string-before)); + code = Tcl_GlobalEval(textPtr->interp, Tcl_DStringValue(dsPtr)); + Tcl_DStringFree(dsPtr); + } else { + code = Tcl_GlobalEval(textPtr->interp, ewPtr->body.ew.create); + } if (code != TCL_OK) { createError: Tcl_BackgroundError(textPtr->interp); @@ -735,10 +948,27 @@ EmbWinLayoutProc(textPtr, indexPtr, ewPtr, offset, maxX, maxChars, || (textPtr->tkwin == ewPtr->body.ew.tkwin)) { goto badMaster; } - Tk_ManageGeometry(ewPtr->body.ew.tkwin, &textGeomType, - (ClientData) ewPtr); - Tk_CreateEventHandler(ewPtr->body.ew.tkwin, StructureNotifyMask, - EmbWinStructureProc, (ClientData) ewPtr); + + if (client == NULL) { + /* + * We just used a '-create' script to make a new window, + * which we now need to add to our client list. + */ + client = (TkTextEmbWindowClient*) ckalloc(sizeof(TkTextEmbWindowClient)); + client->next = ewPtr->body.ew.clients; + client->textPtr = textPtr; + client->tkwin = NULL; + client->chunkCount = 0; + client->displayed = 0; + client->parent = ewPtr; + ewPtr->body.ew.clients = client; + } + + client->tkwin = ewPtr->body.ew.tkwin; + Tk_ManageGeometry(client->tkwin, &textGeomType, + (ClientData) client); + Tk_CreateEventHandler(client->tkwin, StructureNotifyMask, + EmbWinStructureProc, (ClientData) client); /* * Special trick! Must enter into the hash table *after* @@ -748,8 +978,8 @@ EmbWinLayoutProc(textPtr, indexPtr, ewPtr, offset, maxX, maxChars, * entry. */ - hPtr = Tcl_CreateHashEntry(&textPtr->windowTable, - Tk_PathName(ewPtr->body.ew.tkwin), &new); + hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->windowTable, + Tk_PathName(client->tkwin), &new); Tcl_SetHashValue(hPtr, ewPtr); } @@ -792,7 +1022,9 @@ EmbWinLayoutProc(textPtr, indexPtr, ewPtr, offset, maxX, maxChars, chunkPtr->breakIndex = -1; chunkPtr->breakIndex = 1; chunkPtr->clientData = (ClientData) ewPtr; - ewPtr->body.ew.chunkCount += 1; + if (client != NULL) { + client->chunkCount += 1; + } return 1; } @@ -847,8 +1079,9 @@ EmbWinCheckProc(ewPtr, linePtr) */ void -TkTextEmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, +TkTextEmbWinDisplayProc(textPtr, chunkPtr, x, y, lineHeight, baseline, display, dst, screenY) + TkText *textPtr; /* Information about text widget. */ TkTextDispChunk *chunkPtr; /* Chunk that is to be drawn. */ int x; /* X-position in dst at which to * draw this chunk (differs from @@ -867,17 +1100,20 @@ TkTextEmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, int screenY; /* Y-coordinate in text window that * corresponds to y. */ { - TkTextSegment *ewPtr = (TkTextSegment *) chunkPtr->clientData; int lineX, windowX, windowY, width, height; Tk_Window tkwin; - TkText *textPtr; + TkTextSegment *ewPtr = (TkTextSegment*) chunkPtr->clientData; + TkTextEmbWindowClient *client = EmbWinGetClient(textPtr, ewPtr); + + if (client == NULL) { + return; + } - tkwin = ewPtr->body.ew.tkwin; + tkwin = client->tkwin; if (tkwin == NULL) { return; } - textPtr = ewPtr->body.ew.textPtr; if ((x + chunkPtr->width) <= 0) { /* * The window is off-screen; just unmap it. @@ -896,8 +1132,8 @@ TkTextEmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, * into account the align and stretch values for the window. */ - EmbWinBboxProc(chunkPtr, 0, screenY, lineHeight, baseline, &lineX, - &windowY, &width, &height); + EmbWinBboxProc(textPtr, chunkPtr, 0, screenY, lineHeight, baseline, + &lineX, &windowY, &width, &height); windowX = lineX - chunkPtr->x + x; if (textPtr->tkwin == Tk_Parent(tkwin)) { @@ -916,7 +1152,7 @@ TkTextEmbWinDisplayProc(chunkPtr, x, y, lineHeight, baseline, * Mark the window as displayed so that it won't get unmapped. */ - ewPtr->body.ew.displayed = 1; + client->displayed = 1; } /* @@ -943,10 +1179,13 @@ EmbWinUndisplayProc(textPtr, chunkPtr) * widget. */ TkTextDispChunk *chunkPtr; /* Chunk that is about to be freed. */ { - TkTextSegment *ewPtr = (TkTextSegment *) chunkPtr->clientData; - - ewPtr->body.ew.chunkCount--; - if (ewPtr->body.ew.chunkCount == 0) { + TkTextSegment *ewPtr = (TkTextSegment*) chunkPtr->clientData; + TkTextEmbWindowClient *client = EmbWinGetClient(textPtr, ewPtr); + + if (client == NULL) return; + + client->chunkCount--; + if (client->chunkCount == 0) { /* * Don't unmap the window immediately, since there's a good chance * that it will immediately be redisplayed, perhaps even in the @@ -955,8 +1194,8 @@ EmbWinUndisplayProc(textPtr, chunkPtr) * event that the unmap becomes unnecessary. */ - ewPtr->body.ew.displayed = 0; - Tcl_DoWhenIdle(EmbWinDelayedUnmap, (ClientData) ewPtr); + client->displayed = 0; + Tcl_DoWhenIdle(EmbWinDelayedUnmap, (ClientData) client); } } @@ -984,8 +1223,9 @@ EmbWinUndisplayProc(textPtr, chunkPtr) */ static void -EmbWinBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, +EmbWinBboxProc(textPtr, chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, widthPtr, heightPtr) + TkText *textPtr; /* Information about text widget. */ TkTextDispChunk *chunkPtr; /* Chunk containing desired char. */ int index; /* Index of desired character within * the chunk. */ @@ -1001,10 +1241,15 @@ EmbWinBboxProc(chunkPtr, index, y, lineHeight, baseline, xPtr, yPtr, int *heightPtr; /* Gets filled in with height of * window, in pixels. */ { - TkTextSegment *ewPtr = (TkTextSegment *) chunkPtr->clientData; Tk_Window tkwin; + TkTextSegment *ewPtr = (TkTextSegment*) chunkPtr->clientData; + TkTextEmbWindowClient *client = EmbWinGetClient(textPtr, ewPtr); - tkwin = ewPtr->body.ew.tkwin; + if (client == NULL) { + tkwin = NULL; + } else { + tkwin = client->tkwin; + } if (tkwin != NULL) { *widthPtr = Tk_ReqWidth(tkwin); *heightPtr = Tk_ReqHeight(tkwin); @@ -1060,14 +1305,15 @@ EmbWinDelayedUnmap(clientData) ClientData clientData; /* Token for the window to * be unmapped. */ { - TkTextSegment *ewPtr = (TkTextSegment *) clientData; + TkTextEmbWindowClient *client = (TkTextEmbWindowClient*) clientData; + TkTextSegment *ewPtr = client->parent; - if (!ewPtr->body.ew.displayed && (ewPtr->body.ew.tkwin != NULL)) { - if (ewPtr->body.ew.textPtr->tkwin != Tk_Parent(ewPtr->body.ew.tkwin)) { - Tk_UnmaintainGeometry(ewPtr->body.ew.tkwin, - ewPtr->body.ew.textPtr->tkwin); + if (!client->displayed && (client->tkwin != NULL)) { + if (client->textPtr->tkwin != Tk_Parent(client->tkwin)) { + Tk_UnmaintainGeometry(client->tkwin, + client->textPtr->tkwin); } else { - Tk_UnmapWindow(ewPtr->body.ew.tkwin); + Tk_UnmapWindow(client->tkwin); } } } @@ -1101,13 +1347,55 @@ TkTextWindowIndex(textPtr, name, indexPtr) Tcl_HashEntry *hPtr; TkTextSegment *ewPtr; - hPtr = Tcl_FindHashEntry(&textPtr->windowTable, name); + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->windowTable, name); if (hPtr == NULL) { return 0; } ewPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); - indexPtr->tree = textPtr->tree; + indexPtr->tree = textPtr->sharedTextPtr->tree; indexPtr->linePtr = ewPtr->body.ew.linePtr; indexPtr->byteIndex = TkTextSegToOffset(ewPtr, indexPtr->linePtr); return 1; } + +/* + *-------------------------------------------------------------- + * + * EmbWinGetClient -- + * + * Given a text widget and a segment which contains an + * embedded window, find the text-widget specific + * information about the embedded window, if any. + * + * This procedure performs a completely linear lookup + * for a matching data structure. If we envisage using + * this code with dozens of peer widgets, then performance + * could become an issue and a more sophisticated lookup + * mechanism might be desirable. + * + * Results: + * NULL if no widget-specific info exists, otherwise + * the structure is returned. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +static TkTextEmbWindowClient* +EmbWinGetClient(textPtr, ewPtr) + CONST TkText *textPtr; /* Information about text widget. */ + TkTextSegment *ewPtr; /* Segment containing embedded + * window. */ +{ + TkTextEmbWindowClient *client = ewPtr->body.ew.clients; + while (client != NULL) { + if (client->textPtr == textPtr) { + return client; + } + client = client->next; + } + return NULL; +} + diff --git a/generic/tkUndo.c b/generic/tkUndo.c index a1f5284..881bf28 100644 --- a/generic/tkUndo.c +++ b/generic/tkUndo.c @@ -8,13 +8,13 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkUndo.c,v 1.4 2004/06/09 19:18:14 dkf Exp $ + * RCS: @(#) $Id: tkUndo.c,v 1.5 2004/09/10 12:13:42 vincentdarley Exp $ */ #include "tkUndo.h" -static int UndoScriptsEvaluate _ANSI_ARGS_ ((Tcl_Interp *interp, - Tcl_Obj *objPtr, TkUndoAtomType type)); +static int EvaluateActionList _ANSI_ARGS_ ((Tcl_Interp *interp, + TkUndoSubAtom *action)); /* @@ -122,8 +122,29 @@ TkUndoClearStack(stack) while ((elem = TkUndoPopStack(stack)) != NULL) { if (elem->type != TK_UNDO_SEPARATOR) { - Tcl_DecrRefCount(elem->apply); - Tcl_DecrRefCount(elem->revert); + TkUndoSubAtom *sub; + + sub = elem->apply; + while (sub->next != NULL) { + TkUndoSubAtom *next = sub->next; + + if (sub->action != NULL) { + Tcl_DecrRefCount(sub->action); + } + ckfree((char *)sub); + sub = next; + } + sub = elem->revert; + while (sub->next != NULL) { + TkUndoSubAtom *next = sub->next; + + if (sub->action != NULL) { + Tcl_DecrRefCount(sub->action); + } + ckfree((char *)sub); + sub = next; + } + sub = elem->revert; } ckfree((char *)elem); } @@ -149,29 +170,155 @@ TkUndoClearStack(stack) */ void -TkUndoPushAction(stack, actionScript, revertScript, isList) +TkUndoPushAction(stack, apply, revert) TkUndoRedoStack *stack; /* An Undo or Redo stack */ - Tcl_Obj *actionScript; /* The script to get the action (redo) */ - Tcl_Obj *revertScript; /* The script to revert the action (undo) */ - int isList; /* Are the given objects lists of scripts? */ + TkUndoSubAtom *apply; + TkUndoSubAtom *revert; { TkUndoAtom *atom; atom = (TkUndoAtom *) ckalloc(sizeof(TkUndoAtom)); - if (isList) { - atom->type = TK_UNDO_ACTION_LIST; - } else { - atom->type = TK_UNDO_ACTION; + atom->type = TK_UNDO_ACTION; + atom->apply = apply; + atom->revert = revert; + + TkUndoPushStack(&stack->undoStack, atom); + TkUndoClearStack(&stack->redoStack); +} + +/* + *---------------------------------------------------------------------- + * + * TkUndoMakeCmdSubAtom + * + * Create a new undo/redo step which must later be place into an + * undo stack with TkUndoPushAction. This sub-atom, if evaluated, + * will take the given command (if non-NULL), find its full Tcl + * command string, and then evaluate that command with the list + * elements of 'actionScript' appended. + * + * If 'subAtomList' is non-NULL, the newly created sub-atom + * is added onto the end of the linked list of which + * 'subAtomList' is a part. This makes it easy to build up + * a sequence of actions which will be pushed in one step. + * + * A refCount is retained on 'actionScript'. + * + * Note: if the undo stack can persist for longer than the + * Tcl_Command provided, the stack will cause crashes when + * actions are evaluated. In this case the 'command' argument + * should not be used. This is the case with peer text widgets, + * for example. + * + * Results: + * The newly created subAtom is returned. It must be passed + * to TkUndoPushAction otherwise a memory leak will result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TkUndoSubAtom * +TkUndoMakeCmdSubAtom(command, actionScript, subAtomList) + Tcl_Command command; /* Tcl command token for actions, may + * be NULL if not needed. */ + Tcl_Obj *actionScript; /* The script to append to the command + * to perform the action (may be + * NULL if the command is not-null). */ + TkUndoSubAtom *subAtomList; /* Add to the end of this list of + * actions if non-NULL */ +{ + TkUndoSubAtom *atom; + + if (command == NULL && actionScript == NULL) { + Tcl_Panic("NULL command and actionScript in TkUndoMakeCmdSubAtom"); + } + + atom = (TkUndoSubAtom *) ckalloc(sizeof(TkUndoSubAtom)); + atom->command = command; + atom->funcPtr = NULL; + atom->clientData = NULL; + atom->next = NULL; + atom->action = actionScript; + if (atom->action != NULL) { + Tcl_IncrRefCount(atom->action); + } + + if (subAtomList != NULL) { + while (subAtomList->next != NULL) { + subAtomList = subAtomList->next; + } + subAtomList->next = atom; } + return atom; +} + +/* + *---------------------------------------------------------------------- + * + * TkUndoMakeSubAtom + * + * Create a new undo/redo step which must later be place into an + * undo stack with TkUndoPushAction. This sub-atom, if evaluated, + * will take the given C-funcPtr (which must be non-NULL), and + * call it with three arguments: the undo stack's 'interp', + * the 'clientData' given and the 'actionScript'. The callback + * should return a standard Tcl return code (TCL_OK on success). + * + * If 'subAtomList' is non-NULL, the newly created sub-atom + * is added onto the end of the linked list of which + * 'subAtomList' is a part. This makes it easy to build up + * a sequence of actions which will be pushed in one step. + * + * A refCount is retained on 'actionScript'. + * + * Results: + * The newly created subAtom is returned. It must be passed + * to TkUndoPushAction otherwise a memory leak will result. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ - atom->apply = actionScript; - Tcl_IncrRefCount(atom->apply); +TkUndoSubAtom * +TkUndoMakeSubAtom(funcPtr, clientData, actionScript, subAtomList) + TkUndoProc *funcPtr; /* Callback function to perform + * the undo/redo. */ + ClientData clientData; /* Data to pass to the callback + * function. */ + Tcl_Obj *actionScript; /* Additional Tcl data to pass to + * the callback function (may be + * NULL). */ + TkUndoSubAtom *subAtomList; /* Add to the end of this list of + * actions if non-NULL */ +{ + TkUndoSubAtom *atom; - atom->revert = revertScript; - Tcl_IncrRefCount(atom->revert); + if (funcPtr == NULL) { + Tcl_Panic("NULL funcPtr in TkUndoMakeSubAtom"); + } - TkUndoPushStack(&(stack->undoStack), atom); - TkUndoClearStack(&(stack->redoStack)); + atom = (TkUndoSubAtom *) ckalloc(sizeof(TkUndoSubAtom)); + atom->command = NULL; + atom->funcPtr = funcPtr; + atom->clientData = clientData; + atom->next = NULL; + atom->action = actionScript; + if (atom->action != NULL) { + Tcl_IncrRefCount(atom->action); + } + + if (subAtomList != NULL) { + while (subAtomList->next != NULL) { + subAtomList = subAtomList->next; + } + subAtomList->next = atom; + } + return atom; } /* @@ -239,7 +386,7 @@ TkUndoSetDepth(stack, maxdepth) elem = stack->undoStack; prevelem = NULL; while (sepNumber <= stack->maxdepth) { - if (elem!=NULL && elem->type==TK_UNDO_SEPARATOR) { + if (elem != NULL && elem->type == TK_UNDO_SEPARATOR) { sepNumber++; } prevelem = elem; @@ -247,7 +394,18 @@ TkUndoSetDepth(stack, maxdepth) } prevelem->next = NULL; while (elem != NULL) { + TkUndoSubAtom *sub = elem->apply; prevelem = elem; + + while (sub->next != NULL) { + TkUndoSubAtom *next = sub->next; + if (sub->action != NULL) { + Tcl_DecrRefCount(sub->action); + } + ckfree((char *)sub); + sub = next; + } + elem = elem->next; ckfree((char *) prevelem); } @@ -274,8 +432,8 @@ void TkUndoClearStacks(stack) TkUndoRedoStack *stack; /* An Undo/Redo stack */ { - TkUndoClearStack(&(stack->undoStack)); - TkUndoClearStack(&(stack->redoStack)); + TkUndoClearStack(&stack->undoStack); + TkUndoClearStack(&stack->redoStack); stack->depth = 0; } @@ -300,7 +458,6 @@ TkUndoFreeStack(stack) TkUndoRedoStack *stack; /* An Undo/Redo stack */ { TkUndoClearStacks(stack); - /* ckfree((TkUndoRedoStack *) stack); */ ckfree((char *) stack); } @@ -330,7 +487,7 @@ TkUndoInsertUndoSeparator(stack) * int sepNumber = 0; */ - if (TkUndoInsertSeparator(&(stack->undoStack))) { + if (TkUndoInsertSeparator(&stack->undoStack)) { stack->depth++; TkUndoSetDepth(stack, stack->maxdepth); #if 0 @@ -338,7 +495,7 @@ TkUndoInsertUndoSeparator(stack) elem = stack->undoStack; prevelem = NULL; while (sepNumber < stack->depth) { - if (elem!=NULL && elem->type==TK_UNDO_SEPARATOR) { + if (elem != NULL && elem->type == TK_UNDO_SEPARATOR) { sepNumber++; } prevelem = elem; @@ -380,31 +537,32 @@ TkUndoRevert(stack) /* insert a separator on the undo and the redo stack */ TkUndoInsertUndoSeparator(stack); - TkUndoInsertSeparator(&(stack->redoStack)); + TkUndoInsertSeparator(&stack->redoStack); /* Pop and skip the first separator if there is one*/ - elem = TkUndoPopStack(&(stack->undoStack)); + elem = TkUndoPopStack(&stack->undoStack); if (elem == NULL) { return TCL_ERROR; } - if (elem!=NULL && elem->type==TK_UNDO_SEPARATOR) { + if (elem != NULL && elem->type == TK_UNDO_SEPARATOR) { ckfree((char *) elem); - elem = TkUndoPopStack(&(stack->undoStack)); + elem = TkUndoPopStack(&stack->undoStack); } - while (elem!=NULL && elem->type!=TK_UNDO_SEPARATOR) { - UndoScriptsEvaluate(stack->interp,elem->revert,elem->type); + while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) { + /* Note that we currently ignore errors thrown here */ + EvaluateActionList(stack->interp, elem->revert); - TkUndoPushStack(&(stack->redoStack),elem); - elem = TkUndoPopStack(&(stack->undoStack)); + TkUndoPushStack(&stack->redoStack,elem); + elem = TkUndoPopStack(&stack->undoStack); } /* insert a separator on the redo stack */ - TkUndoInsertSeparator(&(stack->redoStack)); + TkUndoInsertSeparator(&stack->redoStack); stack->depth--; @@ -434,31 +592,32 @@ TkUndoApply(stack) /* insert a separator on the undo stack */ - TkUndoInsertSeparator(&(stack->undoStack)); + TkUndoInsertSeparator(&stack->undoStack); /* Pop and skip the first separator if there is one*/ - elem = TkUndoPopStack(&(stack->redoStack)); + elem = TkUndoPopStack(&stack->redoStack); if (elem == NULL) { return TCL_ERROR; } - if (elem!=NULL && elem->type==TK_UNDO_SEPARATOR) { + if (elem != NULL && elem->type == TK_UNDO_SEPARATOR) { ckfree((char *) elem); - elem = TkUndoPopStack(&(stack->redoStack)); + elem = TkUndoPopStack(&stack->redoStack); } - while (elem!=NULL && elem->type!=TK_UNDO_SEPARATOR) { - UndoScriptsEvaluate(stack->interp,elem->apply,elem->type); + while (elem != NULL && elem->type != TK_UNDO_SEPARATOR) { + /* Note that we currently ignore errors thrown here */ + EvaluateActionList(stack->interp, elem->apply); - TkUndoPushStack(&(stack->undoStack), elem); - elem = TkUndoPopStack(&(stack->redoStack)); + TkUndoPushStack(&stack->undoStack, elem); + elem = TkUndoPopStack(&stack->redoStack); } /* insert a separator on the undo stack */ - TkUndoInsertSeparator(&(stack->undoStack)); + TkUndoInsertSeparator(&stack->undoStack); stack->depth++; @@ -468,40 +627,53 @@ TkUndoApply(stack) /* *---------------------------------------------------------------------- * - * UndoScriptsEvaluate -- - * Execute either a single script, or a set of scripts + * EvaluateActionList -- + * + * Execute a linked list of undo/redo sub-atoms. If any sub-atom + * returns a non TCL_OK value, execution of subsequent sub-atoms + * is cancelled and the error returned immediately. * * Results: * A Tcl status code * * Side effects: - * None. + * The undo/redo subAtoms can perform arbitrary actions. * *---------------------------------------------------------------------- */ static int -UndoScriptsEvaluate(interp, objPtr, type) - Tcl_Interp *interp; - Tcl_Obj *objPtr; - TkUndoAtomType type; +EvaluateActionList(interp, action) + Tcl_Interp *interp; /* Interpreter to evaluate the action in. */ + TkUndoSubAtom *action; /* Head of linked list of action steps + * to perform. */ { - if (type == TK_UNDO_ACTION_LIST) { - int objc; - Tcl_Obj **objv; - int res, i; - - res = Tcl_ListObjGetElements(interp, objPtr, &objc, &objv); - if (res != TCL_OK) { - return res; - } - for (i=0 ; i<objc ; i++) { - res = Tcl_EvalObjEx(interp, objv[i], TCL_EVAL_GLOBAL); - if (res != TCL_OK) { - return res; + int result; + + while (action != NULL) { + if (action->funcPtr != NULL) { + result = (*action->funcPtr)(interp, action->clientData, + action->action); + } else if (action->command != NULL) { + Tcl_Obj *cmdNameObj, *evalObj; + + cmdNameObj = Tcl_NewObj(); + evalObj = Tcl_NewObj(); + Tcl_IncrRefCount(evalObj); + Tcl_GetCommandFullName(interp, action->command, cmdNameObj); + Tcl_ListObjAppendElement(NULL, evalObj, cmdNameObj); + if (action->action != NULL) { + Tcl_ListObjAppendList(NULL, evalObj, action->action); } + result = Tcl_EvalObjEx(interp, evalObj, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(evalObj); + } else { + result = Tcl_EvalObjEx(interp, action->action, TCL_EVAL_GLOBAL); + } + if (result != TCL_OK) { + return result; } - return res; + action = action->next; } - return Tcl_EvalObjEx(interp, objPtr, TCL_EVAL_GLOBAL); + return result; } diff --git a/generic/tkUndo.h b/generic/tkUndo.h index 9b072e6..3c34a0f 100644 --- a/generic/tkUndo.h +++ b/generic/tkUndo.h @@ -9,7 +9,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkUndo.h,v 1.2 2003/05/19 13:04:24 vincentdarley Exp $ + * RCS: @(#) $Id: tkUndo.h,v 1.3 2004/09/10 12:13:42 vincentdarley Exp $ */ #ifndef _TKUNDO @@ -24,27 +24,63 @@ # define TCL_STORAGE_CLASS DLLEXPORT #endif -/* enum definining the types used in an undo stack */ +/* Enum definining the types used in an undo stack */ typedef enum { TK_UNDO_SEPARATOR, /* Marker */ - TK_UNDO_ACTION, /* Command */ - TK_UNDO_ACTION_LIST /* Command list */ + TK_UNDO_ACTION /* Command */ } TkUndoAtomType; -/* struct defining the basic undo/redo stack element */ +/* + * Callback proc type to carry out an undo or redo action + * via C code. (Actions can also be defined by Tcl scripts). + */ + +typedef int (TkUndoProc) _ANSI_ARGS_((Tcl_Interp *interp, + ClientData clientData, + Tcl_Obj *objPtr)); + +/* + * Struct defining a single action, one or more of which may + * be defined (and stored in a linked list) separately for each + * undo and redo action of an undo atom. + */ + +typedef struct TkUndoSubAtom { + Tcl_Command command; /* Tcl token used to get the current + * Tcl command name which will be used + * to execute apply/revert scripts. If + * NULL then it is assumed the + * apply/revert scripts already contain + * everything. */ + TkUndoProc *funcPtr; /* Function pointer for callback to + * perform undo/redo actions. */ + ClientData clientData; /* data for 'funcPtr' */ + Tcl_Obj *action; /* Command to apply the action that + * was taken */ + struct TkUndoSubAtom *next; /* Pointer to the next element in the + * linked list */ +} TkUndoSubAtom; + +/* + * Struct representing a single undo+redo atom to be placed in + * the stack. + */ typedef struct TkUndoAtom { TkUndoAtomType type; /* The type that will trigger the * required action*/ - Tcl_Obj *apply; /* Command to apply the action that - * was taken */ - Tcl_Obj *revert; /* The command to undo the action */ - struct TkUndoAtom * next; /* Pointer to the next element in the + TkUndoSubAtom *apply; /* Linked list of 'apply' actions to + * perform for this operation */ + TkUndoSubAtom *revert; /* Linked list of 'revert' actions to + * perform for this operation */ + struct TkUndoAtom *next; /* Pointer to the next element in the * stack */ } TkUndoAtom; -/* struct defining the basic undo/redo stack element */ +/* + * Struct defining a single undo+redo stack. + */ typedef struct TkUndoRedoStack { TkUndoAtom * undoStack; /* The undo stack */ @@ -55,37 +91,37 @@ typedef struct TkUndoRedoStack { int depth; } TkUndoRedoStack; -/* basic functions */ - -EXTERN void TkUndoPushStack _ANSI_ARGS_((TkUndoAtom ** stack, - TkUndoAtom * elem)); - -EXTERN TkUndoAtom * TkUndoPopStack _ANSI_ARGS_((TkUndoAtom ** stack)); - -EXTERN int TkUndoInsertSeparator _ANSI_ARGS_((TkUndoAtom ** stack)); - -EXTERN void TkUndoClearStack _ANSI_ARGS_((TkUndoAtom ** stack)); - -/* functions working on an undo/redo stack */ - -EXTERN TkUndoRedoStack * TkUndoInitStack _ANSI_ARGS_((Tcl_Interp * interp, - int maxdepth)); - -EXTERN void TkUndoSetDepth _ANSI_ARGS_((TkUndoRedoStack * stack, - int maxdepth)); - -EXTERN void TkUndoClearStacks _ANSI_ARGS_((TkUndoRedoStack * stack)); - -EXTERN void TkUndoFreeStack _ANSI_ARGS_((TkUndoRedoStack * stack)); - -EXTERN void TkUndoInsertUndoSeparator _ANSI_ARGS_((TkUndoRedoStack * stack)); - -EXTERN void TkUndoPushAction _ANSI_ARGS_((TkUndoRedoStack * stack, - Tcl_Obj *actionScript, Tcl_Obj *revertScript, int isList)); - -EXTERN int TkUndoRevert _ANSI_ARGS_((TkUndoRedoStack * stack)); - -EXTERN int TkUndoApply _ANSI_ARGS_((TkUndoRedoStack * stack)); +/* Basic functions */ + +EXTERN void TkUndoPushStack _ANSI_ARGS_((TkUndoAtom **stack, + TkUndoAtom *elem)); +EXTERN TkUndoAtom * TkUndoPopStack _ANSI_ARGS_((TkUndoAtom **stack)); +EXTERN int TkUndoInsertSeparator _ANSI_ARGS_((TkUndoAtom **stack)); +EXTERN void TkUndoClearStack _ANSI_ARGS_((TkUndoAtom **stack)); + +/* Functions for working on an undo/redo stack */ + +EXTERN TkUndoRedoStack * TkUndoInitStack _ANSI_ARGS_((Tcl_Interp *interp, + int maxdepth)); +EXTERN void TkUndoSetDepth _ANSI_ARGS_((TkUndoRedoStack *stack, + int maxdepth)); +EXTERN void TkUndoClearStacks _ANSI_ARGS_((TkUndoRedoStack *stack)); +EXTERN void TkUndoFreeStack _ANSI_ARGS_((TkUndoRedoStack *stack)); +EXTERN void TkUndoInsertUndoSeparator _ANSI_ARGS_((TkUndoRedoStack *stack)); +EXTERN TkUndoSubAtom * TkUndoMakeCmdSubAtom _ANSI_ARGS_(( + Tcl_Command command, + Tcl_Obj *actionScript, + TkUndoSubAtom *subAtomList)); +EXTERN TkUndoSubAtom * TkUndoMakeSubAtom _ANSI_ARGS_(( + TkUndoProc *funcPtr, + ClientData clientData, + Tcl_Obj *actionScript, + TkUndoSubAtom *subAtomList)); +EXTERN void TkUndoPushAction _ANSI_ARGS_((TkUndoRedoStack *stack, + TkUndoSubAtom *apply, + TkUndoSubAtom *revert)); +EXTERN int TkUndoRevert _ANSI_ARGS_((TkUndoRedoStack *stack)); +EXTERN int TkUndoApply _ANSI_ARGS_((TkUndoRedoStack *stack)); # undef TCL_STORAGE_CLASS # define TCL_STORAGE_CLASS DLLIMPORT |