From 09324dada308a84a1d5ba8b14bff2a5ce8b6eaf9 Mon Sep 17 00:00:00 2001 From: vincentdarley Date: Fri, 10 Sep 2004 12:13:38 +0000 Subject: text widget 'peer' subcommand -- TIP#169 implementation --- ChangeLog | 27 + doc/text.n | 157 ++++- generic/tkTest.c | 11 +- generic/tkText.c | 1650 +++++++++++++++++++++++++++++++++++++---------- generic/tkText.h | 355 ++++++---- generic/tkTextBTree.c | 1045 ++++++++++++++++++++++++------ generic/tkTextDisp.c | 489 +++++++++----- generic/tkTextImage.c | 71 +- generic/tkTextIndex.c | 216 +++++-- generic/tkTextMark.c | 247 ++++--- generic/tkTextTag.c | 486 ++++++++++---- generic/tkTextWind.c | 510 +++++++++++---- generic/tkUndo.c | 302 +++++++-- generic/tkUndo.h | 118 ++-- library/demos/twind.tcl | 96 ++- library/demos/widget | 6 +- library/text.tcl | 77 +-- tests/text.test | 537 ++++++++++++++- tests/textImage.test | 22 +- tests/textIndex.test | 25 +- tests/textWind.test | 160 ++++- 21 files changed, 5189 insertions(+), 1418 deletions(-) diff --git a/ChangeLog b/ChangeLog index 365d3b6..70303ced 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,30 @@ +2004-09-10 Vince Darley + + * doc/text.n + * generic/tkTest.c + * generic/tkText.c + * generic/tkText.h + * generic/tkTextBTree.c + * generic/tkTextDisp.c + * generic/tkTextImage.c + * generic/tkTextIndex.c + * generic/tkTextMark.c + * generic/tkTextTag.c + * generic/tkTextWind.c + * generic/tkUndo.c + * generic/tkUndo.h + * library/text.tcl + * library/demos/twind.tcl + * library/demos/widget + * tests/text.test + * tests/textImage.test + * tests/textIndex.test + * tests/textWind.test: implementation of TIP#169, which provides + the new '$text peer' widget subcommand. This includes new + documentation, tests, and an extension to the text widget demos + to illustrate some of the new features. Many thanks also to + Brian Griffin for the initial implementation. + 2004-09-09 Jeff Hobbs * tests/panedwindow.test: bulletproof 23.2 result [Bug #1019100] diff --git a/doc/text.n b/doc/text.n index a670c14..7ce4e6a 100644 --- a/doc/text.n +++ b/doc/text.n @@ -5,7 +5,7 @@ '\" See the file "license.terms" for information on usage and redistribution '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES. '\" -'\" RCS: @(#) $Id: text.n,v 1.27 2004/08/17 22:37:24 dkf Exp $ +'\" RCS: @(#) $Id: text.n,v 1.28 2004/09/10 12:13:40 vincentdarley Exp $ '\" .so man.macros .TH text n 8.5 Tk "Tk Built-In Commands" @@ -44,10 +44,25 @@ Specifies a boolean that says whether the blinking insertion cursor should be drawn as a character-sized rectangular block. If false (the default) a thin vertical line is used for the insertion cursor. .VE 8.5 +.OP \-endline endLine EndLine +.VS 8.5 +Specifies an integer line index representing the last line of the +underlying textual data store that should be contained in the widget. +This allows a text widget to reflect only a portion of a larger piece +of text. Instead of an integer, the empty string can be provided to +this configuration option, which will configure the widget to end +at the very last line in the textual data store. +.VE 8.5 .OP \-height height Height Specifies the desired height for the window, in units of characters in the font given by the \fB\-font\fR option. Must be at least one. +.OP \-inactiveselectionbackground inactiveSelectionBackground Foreground +.VS 8.5 +Specifies the colour to use for the selection (the \fB\sel\fR tag) when +the window does not have the input focus. If empty, \fB\{}\fR, then no +selection is shown when the window does not have the focus. +.VE 8.5 .OP \-maxundo maxUndo MaxUndo .VS 8.4 Specifies the maximum number of compound undo actions on the undo @@ -74,6 +89,15 @@ If a line wraps, this option only applies to the last line on the display. This option may be overridden with \fB\-spacing3\fR options in tags. +.OP \-startline startLine StartLine +.VS 8.5 +Specifies an integer line index representing the first line of the +underlying textual data store that should be contained in the widget. +This allows a text widget to reflect only a portion of a larger piece +of text. Instead of an integer, the empty string can be provided to +this configuration option, which will configure the widget to start +at the very first line in the textual data store. +.VE 8.5 .OP \-state state State Specifies one of two states for the text: \fBnormal\fR or \fBdisabled\fR. If the text is disabled then characters may not be inserted or deleted @@ -174,6 +198,12 @@ See EMBEDDED IMAGES below for more details. The text widget also has a built-in undo/redo mechanism. See THE UNDO MECHANISM below for more details. .VE 8.4 +.PP +.VS 8.5 +The text widget allows for the creation of peer widgets. These are +other text widgets which share the same underlying data (text, marks, +tags, images, etc). See PEER WIDGETS below for more details. +.VE 8.5 .SH INDICES .PP @@ -571,10 +601,15 @@ Tag bindings can be used to give behaviors to ranges of characters; among other things, this allows hypertext-like features to be implemented. For details, see the description of the \fBtag bind\fR widget -command below. +command below. Tag bindings are shared between all peer widgets +(including any bindings for the special \fBsel\fR tag). .PP The third use for tags is in managing the selection. -See THE SELECTION below. +See THE SELECTION below. With the exception of the special \fBsel\fR +tag, all tags are shared between peer text widgets, and may be +manipulated on an equal basis from any such widget. The \fBsel\fR +tag exists separately and independently in each peer text widget (but +any tag bindings to \fBsel\fR are shared). .SH MARKS .PP @@ -619,7 +654,9 @@ mouse position and any changes to the text in the widget (one exception: \fBcurrent\fR is not updated in response to mouse motions if a mouse button is down; the update will be deferred until all mouse buttons have been released). -Neither of these special marks may be deleted. +Neither of these special marks may be deleted. With the exception of +these two special marks, all marks are shared between peer text +widgets, and may be manipulated on an equal basis from any peer. .SH "EMBEDDED WINDOWS" .PP @@ -639,7 +676,8 @@ in the text widget, and it may be referred to either by the name of its embedded window or by its position in the widget's index space. If the range of text containing the embedded window is deleted then -the window is destroyed. +the window is destroyed. Similarly if the text widget as a whole +is deleted, then the window is destroyed. .PP When an embedded window is added to a text widget with the \fBwindow create\fR widget command, several configuration @@ -665,7 +703,10 @@ If no \fB\-window\fR option has been specified for the annotation this script will be evaluated when the annotation is about to be displayed on the screen. \fIScript\fR must create a window for the annotation and return -the name of that window as its result. +the name of that window as its result. Two substitutions will be +performed in \fIscript\fR before evaluation. \fI%W\fR will be +substituted by the name of the parent text widget, and \fI%%\fR +will be substitutde by a single \fI%\fR. If the annotation's window should ever be deleted, \fIscript\fR will be evaluated again the next time the annotation is displayed. .TP @@ -688,8 +729,14 @@ If the \fB\-pady\fR option has been specified as well, then the requested padding will be retained even if the window is stretched. .TP -\fB\-window \fIpathName\fR -Specifies the name of a window to display in the annotation. +\fB\-window \fIpathName\fR +Specifies the name of a window to display in the annotation. Note that +if a \fIpathName\fR has been set, then later configuring a window to the +empty string will not delete the widget corresponding to the old +\fIpathName\fR. Rather it will remove the association between the old +\fIpathName\fR and the text widget. If multiple peer widgets are in +use, it is usually simpler to use the \fB\-create\fR option if embedded +windows are desired in each peer. .SH "EMBEDDED IMAGES" .PP @@ -780,13 +827,19 @@ Whenever the \fBsel\fR tag range changes a virtual event \fB<>\fR is generated. .PP The \fBsel\fR tag is automatically defined when a text widget is -created, and it may not be deleted with the ``\fIpathName \fBtag delete\fR'' -widget command. Furthermore, the \fBselectBackground\fR, -\fBselectBorderWidth\fR, and \fBselectForeground\fR options for -the text widget are tied to the \fB\-background\fR, -\fB\-borderwidth\fR, and \fB\-foreground\fR options for the \fBsel\fR -tag: changes in either will automatically be reflected in the -other. +created, and it may not be deleted with the ``\fIpathName \fBtag +delete\fR'' widget command. Furthermore, the \fBselectBackground\fR, +\fBselectBorderWidth\fR, and \fBselectForeground\fR options for the text +widget are tied to the \fB\-background\fR, \fB\-borderwidth\fR, and +\fB\-foreground\fR options for the \fBsel\fR tag: changes in either will +automatically be reflected in the other. Also the +\fB\-inactiveselectionbackground\fR option for the text widget is used +instead of \fB-selectbackground\fR when the text widget does not have +the focus. This allows programmatic control over the visualization of +the \fBsel\fR tag for foreground and background windows, or to have +\fBsel\fR not shown at all (when \fB\-inactiveselectionbackground\fR is +empty) for background windows. Each peer text widget has its own +\fBsel\fR tag which can be separately configured and set. .SH "THE INSERTION CURSOR" .PP @@ -838,6 +891,64 @@ See below for the \fBedit\fR widget command that controls the undo mechanism. .VE 8.4 +.SH "PEER WIDGETS" +.PP +.VS 8.5 +The text widget has a separate store of all its data concerning each +line's textual contents, marks, tags, images and windows, and the undo +stack. +.PP +While this data store cannot be accessed directly (i.e. without a text +widget as an intermediary), multiple text widgets can be created, each +of which present different views on the same underlying data. Such +text widgets are known as peer text widgets. +.PP +As text is added, deleted, edited and coloured in any one widget, and as +images, marks, tags are adjusted, all such changes will be reflected in +all peers. +.PP +All data and markup is shared, except for a few small details. First, +the \fBsel\fR tag may be set and configured (in its display style) +differently for each peer. Second, each peer has its own \fBinsert\fR +and \fBcurrent\fR mark positions (but all other marks are shared). +Third, embedded windows, which are arbitrary other widgets, cannot be +shared between peers. This means the \fB-window\fR option of embedded +windows is independently set for each peer (it is advisable to use +the \fB-create\fR script capabilities to allow each peer to create its +own embedded windows as needed). Fourth, all of the configuration +options of each peer (e.g. \fB-font\fR, etc) can be set independently, +with the exception of \fB-undo\fR, \fB-maxUndo\fR, \fB-autoSeparators\fR +(i.e. all undo, redo and modified state issues are shared). +.PP +Finally any single peer need not contain all lines from the underlying +data store. When creating a peer, a contiguous range of lines (e.g. +only lines 52 through 125) may be specified. This allows a peer to +contain just a small portion of the overall text. The range of lines +will expand and contract as text is inserted or deleted. The peer will +only ever display complete lines of text (one cannot share just part of +a line). If the peer's contents contracts to nothing (i.e. all complete +lines in the peer widget have been deleted from another widget), then it +is impossible for new lines to be inserted. The peer will simply become +an empty shell on which the background can be configured, but which will +never show any content (without manual reconfiguration of the start and +end lines). Note that a peer which does not contain all of the +underlying data store still has indices numbered from "1.0" to "end". +It is simply that those indices reflect a subset of the total data, and +data outside the contained range is not accessible to the peer. This +means that the command \fB$peer index end\fR may return quite different +values in different peers. Similarly, commands like \fB$peer tag +ranges\fR will not return index ranges outside that which is meaningful +to the peer. The configuration options \fB-startline\fR and +\fB-endline\fR may be used to control how much of the underlying data is +contained in any given text widget. +.PP +Note that peers are really peers. Deleting the 'original' text widget +will not cause any other peers to be deleted, or otherwise affected. +.PP +See below for the \fBpeer\fR widget command that controls the creation +of peer widgets. +.VE 8.5 + .SH "WIDGET COMMAND" .PP The \fBtext\fR command creates a new Tcl command whose @@ -1270,6 +1381,22 @@ returned by future calls to ``\fIpathName \fBmark names\fR''. This command returns an empty string. .RE .TP +\fIpathName \fBpeer\fR \fIoption args\fR +This command is used to create and query widget peers. It has +two forms, depending on \fIoption\fR: +.RS +.TP +\fIpathName \fBpeer create \fInewPathName\fR ?fIoptions\fR? +Creates a peer text widget with the given \fInewPathName\fR, and any +optional standard configuration options (as for the \fItext\fR command). +By default the peer will have the same start and end line as the +parent widget, but these can be overridden with the standard +configuration options. +.TP +\fIpathName \fBpeer names\fR +Returns a list of peers of this widget (this does not include the widget +itself). The order within this list is undefined. +.TP \fIpathName \fBreplace\fR \fIindex1 index2 chars\fR ?\fItagList chars tagList ...\fR? Replaces the range of characters between \fIindex1\fR and \fIindex2\fR with the given characters and tags. See the section on \fIpathName 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); } @@ -1532,16 +1969,96 @@ ConfigureText(interp, textPtr, objc, objv) objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) { return TCL_ERROR; } - - TkUndoSetDepth(textPtr->undoStack, textPtr->maxUndo); - - /* - * A few other options also need special processing, such as parsing - * the geometry and setting the background from a 3-D border. - */ - - Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border); - + + /* + * 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 + * the geometry and setting the background from a 3-D border. + */ + + 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 <>" */ - if (tagPtr == textPtr->selTagPtr) { - XEvent event; - /* - * Send an event that the selection changed. - * This is equivalent to - * "event generate $textWidget <>" - */ + TkTextSelectionEvent(textPtr); - memset((VOID *) &event, 0, sizeof(event)); - event.xany.type = VirtualEvent; - event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); - event.xany.send_event = False; - event.xany.window = Tk_WindowId(textPtr->tkwin); - event.xany.display = Tk_Display(textPtr->tkwin); - ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection"); - Tk_HandleEvent(&event); - - textPtr->abortSelections = 1; - } + 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 <>" */ + TkTextSelectionEvent(textPtr); + + textPtr->flags &= ~GOT_SELECTION; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextSelectionEvent -- + * + * When anything relevant to the "sel" tag has been changed, + * call this procedure to generate a <> event. + * + * Results: + * None. + * + * Side effects: + * If <> 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 <>" + */ + 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 <>" - */ + 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 <>" + */ - 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; ilevel == 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; ipixels[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; inumPixels[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; inumPixels[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; ipixelReferences; 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; ipixelReferences; 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; refpixelReferences; 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; refpixelReferences; 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; refpixelReferences; 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 <>" */ - 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 <>" - */ - - 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 <>" + */ + 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 ; ifuncPtr != 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 diff --git a/library/demos/twind.tcl b/library/demos/twind.tcl index 48a0c20..91d7158 100644 --- a/library/demos/twind.tcl +++ b/library/demos/twind.tcl @@ -3,7 +3,7 @@ # This demonstration script creates a text widget with a bunch of # embedded windows. # -# RCS: @(#) $Id: twind.tcl,v 1.5 2003/12/04 12:28:37 vincentdarley Exp $ +# RCS: @(#) $Id: twind.tcl,v 1.6 2004/09/10 12:13:43 vincentdarley Exp $ if {![info exists widgetDemo]} { error "This script should be run from the \"widget\" demo." @@ -27,7 +27,12 @@ text $t -yscrollcommand "$w.scroll set" -setgrid true -font $font -width 70 \ pack $t -expand yes -fill both scrollbar $w.scroll -command "$t yview" pack $w.scroll -side right -fill y -pack $w.f -expand yes -fill both +panedwindow $w.pane +pack $w.pane -expand yes -fill both +$w.pane add $w.f +# Import to raise given creation order above +raise $w.f + $t tag configure center -justify center -spacing1 5m -spacing3 5m $t tag configure buttons -lmargin1 1c -lmargin2 1c -rmargin 1c \ -spacing1 3m -spacing2 0 -spacing3 0 @@ -36,10 +41,6 @@ button $t.on -text "Turn On" -command "textWindOn $w" \ -cursor top_left_arrow button $t.off -text "Turn Off" -command "textWindOff $w" \ -cursor top_left_arrow -button $t.click -text "Click Here" -command "textWindPlot $t" \ - -cursor top_left_arrow -button $t.delete -text "Delete" -command "textWindDel $w" \ - -cursor top_left_arrow $t insert end "A text widget can contain many different kinds of items, " $t insert end "both active and passive. It can lay these out in various " @@ -60,15 +61,40 @@ $t window create end -window $t.off $t insert end " horizontal scrolling and turn back on word wrapping.\n\n" $t insert end "Or, here is another example. If you " -$t window create end -window $t.click +$t window create end -create { + button %W.click -text "Click Here" -command "textWindPlot %W" \ + -cursor top_left_arrow} + $t insert end " a canvas displaying an x-y plot will appear right here." $t mark set plot insert $t mark gravity plot left $t insert end " You can drag the data points around with the mouse, " $t insert end "or you can click here to " -$t window create end -window $t.delete +$t window create end -create { + button %W.delete -text "Delete" -command "textWindDel %W" \ + -cursor top_left_arrow +} $t insert end " the plot again.\n\n" +$t insert end "You can also create multiple text widgets each of which " +$t insert end "display the same underlying text. Click this button to " +$t window create end \ + -create {button %W.peer -text "Make A Peer" -command "textMakePeer %W" \ + -cursor top_left_arrow} -padx 3 +$t insert end " widget. Notice how peer widgets can have different " +$t insert end "font settings, and by default contain all the images " +$t insert end "of the 'parent', but many of the embedded windows, " +$t insert end "such as buttons will not be there. The easiest way " +$t insert end "to ensure they are in all peers is to use '-create' " +$t insert end "embedded window creation scripts " +$t insert end "(the plot above and the 'Make A Peer' button are " +$t insert end "designed to show up in all peers). A good use of " +$t insert end "peers is for " +$t window create end \ + -create {button %W.split -text "Split Windows" -command "textSplitWindow %W" \ + -cursor top_left_arrow} -padx 3 +$t insert end " \n\n" + $t insert end "You may also find it useful to put embedded windows in " $t insert end "a text without any actual text. In this case the " $t insert end "text widget acts like a geometry manager. For " @@ -179,6 +205,20 @@ proc textWindPlot t { if {[winfo exists $c]} { return } + + while {[string first [$t get plot] " \t\n"] >= 0} { + $t delete plot + } + $t insert plot "\n" + + $t window create plot -create {createPlot %W} + $t tag add center plot + $t insert plot "\n" +} + +proc createPlot {t} { + set c $t.c + canvas $c -relief sunken -width 450 -height 300 -cursor top_left_arrow set font {Helvetica 18} @@ -214,13 +254,7 @@ proc textWindPlot t { $c bind point <1> "embPlotDown $c %x %y" $c bind point "$c dtag selected" bind $c "embPlotMove $c %x %y" - while {[string first [$t get plot] " \t\n"] >= 0} { - $t delete plot - } - $t insert plot "\n" - $t window create plot -window $c - $t tag add center plot - $t insert plot "\n" + return $c } set embPlot(lastX) 0 @@ -242,8 +276,7 @@ proc embPlotMove {w x y} { set embPlot(lastY) $y } -proc textWindDel w { - set t $w.f.text +proc textWindDel t { if {[winfo exists $t.c]} { $t delete $t.c while {[string first [$t get plot] " \t\n"] >= 0} { @@ -256,3 +289,32 @@ proc textWindDel w { proc embDefBg t { $t configure -background [lindex [$t configure -background] 3] } + +proc textMakePeer {parent} { + set n 1 + while {[winfo exists .peer$n]} { incr n } + set w [toplevel .peer$n] + wm title $w "Text Peer #$n" + frame $w.f -highlightthickness 2 -borderwidth 2 -relief sunken + set t [$parent peer create $w.f.text -yscrollcommand "$w.scroll set"] + pack $t -expand yes -fill both + scrollbar $w.scroll -command "$t yview" + pack $w.scroll -side right -fill y + pack $w.f -expand yes -fill both +} + +proc textSplitWindow {textW} { + if {$textW eq ".twind.f.text"} { + if {[winfo exists .twind.peer]} { + destroy .twind.peer + } else { + set parent [winfo parent $textW] + set w [winfo parent $parent] + set t [$textW peer create $w.peer \ + -yscrollcommand "$w.scroll set"] + $w.pane add $t + } + } else { + return + } +} diff --git a/library/demos/widget b/library/demos/widget index 8c59d6c..67d3b6a 100644 --- a/library/demos/widget +++ b/library/demos/widget @@ -11,10 +11,10 @@ exec wish "$0" "$@" # ".tcl" files is this directory, which are sourced by this script # as needed. # -# RCS: @(#) $Id: widget,v 1.23 2004/03/17 18:15:45 das Exp $ +# RCS: @(#) $Id: widget,v 1.24 2004/09/10 12:13:43 vincentdarley Exp $ -package require Tcl 8.4 -package require Tk 8.4 +package require Tcl 8.5 +package require Tk 8.5 package require msgcat eval destroy [winfo child .] diff --git a/library/text.tcl b/library/text.tcl index fde0a16..fc40aeb 100644 --- a/library/text.tcl +++ b/library/text.tcl @@ -3,7 +3,7 @@ # This file defines the default bindings for Tk text widgets and provides # procedures that help in implementing the bindings. # -# RCS: @(#) $Id: text.tcl,v 1.32 2004/08/26 18:03:30 hobbs Exp $ +# RCS: @(#) $Id: text.tcl,v 1.33 2004/09/10 12:13:42 vincentdarley Exp $ # # Copyright (c) 1992-1994 The Regents of the University of California. # Copyright (c) 1994-1997 Sun Microsystems, Inc. @@ -222,10 +222,10 @@ bind Text { } bind Text { - %W mark set anchor insert + %W mark set tk::anchor%W insert } bind Text