4 * This module provides a big chunk of the implementation of
5 * multi-line editable text widgets for Tk. Among other things,
6 * it provides the Tcl command interfaces to text widgets and
7 * the display code. The B-tree representation of text is
8 * implemented elsewhere.
10 * Copyright 1992 Regents of the University of California.
11 * Permission to use, copy, modify, and distribute this
12 * software and its documentation for any purpose and without
13 * fee is hereby granted, provided that the above copyright
14 * notice appear in all copies. The University of California
15 * makes no representations about the suitability of this
16 * software for any purpose. It is provided "as is" without
17 * express or implied warranty.
21 static char rcsid
[] = "$Header: /user6/ouster/wish/RCS/tkText.c,v 1.23 92/08/14 14:45:44 ouster Exp $ SPRITE (Berkeley)";
30 * Information used to parse text configuration options:
33 static Tk_ConfigSpec configSpecs
[] = {
34 {TK_CONFIG_BORDER
, "-background", "background", "Background",
35 DEF_TEXT_BG_COLOR
, Tk_Offset(TkText
, border
), TK_CONFIG_COLOR_ONLY
},
36 {TK_CONFIG_BORDER
, "-background", "background", "Background",
37 DEF_TEXT_BG_MONO
, Tk_Offset(TkText
, border
), TK_CONFIG_MONO_ONLY
},
38 {TK_CONFIG_SYNONYM
, "-bd", "borderWidth", (char *) NULL
,
40 {TK_CONFIG_SYNONYM
, "-bg", "background", (char *) NULL
,
42 {TK_CONFIG_PIXELS
, "-borderwidth", "borderWidth", "BorderWidth",
43 DEF_TEXT_BORDER_WIDTH
, Tk_Offset(TkText
, borderWidth
), 0},
44 {TK_CONFIG_ACTIVE_CURSOR
, "-cursor", "cursor", "Cursor",
45 DEF_TEXT_CURSOR
, Tk_Offset(TkText
, cursor
), TK_CONFIG_NULL_OK
},
46 {TK_CONFIG_BOOLEAN
, "-exportselection", "exportSelection",
47 "ExportSelection", DEF_TEXT_EXPORT_SELECTION
,
48 Tk_Offset(TkText
, exportSelection
), 0},
49 {TK_CONFIG_SYNONYM
, "-fg", "foreground", (char *) NULL
,
51 {TK_CONFIG_FONT
, "-font", "font", "Font",
52 DEF_TEXT_FONT
, Tk_Offset(TkText
, fontPtr
), 0},
53 {TK_CONFIG_COLOR
, "-foreground", "foreground", "Foreground",
54 DEF_TEXT_FG
, Tk_Offset(TkText
, fgColor
), 0},
55 {TK_CONFIG_INT
, "-height", "height", "Height",
56 DEF_TEXT_HEIGHT
, Tk_Offset(TkText
, height
), 0},
57 {TK_CONFIG_BORDER
, "-insertbackground", "insertBackground", "Foreground",
58 DEF_TEXT_INSERT_BG
, Tk_Offset(TkText
, insertBorder
), 0},
59 {TK_CONFIG_PIXELS
, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
60 DEF_TEXT_INSERT_BD_COLOR
, Tk_Offset(TkText
, insertBorderWidth
),
61 TK_CONFIG_COLOR_ONLY
},
62 {TK_CONFIG_PIXELS
, "-insertborderwidth", "insertBorderWidth", "BorderWidth",
63 DEF_TEXT_INSERT_BD_MONO
, Tk_Offset(TkText
, insertBorderWidth
),
65 {TK_CONFIG_INT
, "-insertofftime", "insertOffTime", "OffTime",
66 DEF_TEXT_INSERT_OFF_TIME
, Tk_Offset(TkText
, insertOffTime
), 0},
67 {TK_CONFIG_INT
, "-insertontime", "insertOnTime", "OnTime",
68 DEF_TEXT_INSERT_ON_TIME
, Tk_Offset(TkText
, insertOnTime
), 0},
69 {TK_CONFIG_PIXELS
, "-insertwidth", "insertWidth", "InsertWidth",
70 DEF_TEXT_INSERT_WIDTH
, Tk_Offset(TkText
, insertWidth
), 0},
71 {TK_CONFIG_PIXELS
, "-padx", "padX", "Pad",
72 DEF_TEXT_PADX
, Tk_Offset(TkText
, padX
), 0},
73 {TK_CONFIG_PIXELS
, "-pady", "padY", "Pad",
74 DEF_TEXT_PADY
, Tk_Offset(TkText
, padY
), 0},
75 {TK_CONFIG_RELIEF
, "-relief", "relief", "Relief",
76 DEF_TEXT_RELIEF
, Tk_Offset(TkText
, relief
), 0},
77 {TK_CONFIG_BORDER
, "-selectbackground", "selectBackground", "Foreground",
78 DEF_ENTRY_SELECT_COLOR
, Tk_Offset(TkText
, selBorder
),
79 TK_CONFIG_COLOR_ONLY
},
80 {TK_CONFIG_BORDER
, "-selectbackground", "selectBackground", "Foreground",
81 DEF_TEXT_SELECT_MONO
, Tk_Offset(TkText
, selBorder
),
83 {TK_CONFIG_PIXELS
, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
84 DEF_TEXT_SELECT_BD_COLOR
, Tk_Offset(TkText
, selBorderWidth
),
85 TK_CONFIG_COLOR_ONLY
},
86 {TK_CONFIG_PIXELS
, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
87 DEF_TEXT_SELECT_BD_MONO
, Tk_Offset(TkText
, selBorderWidth
),
89 {TK_CONFIG_COLOR
, "-selectforeground", "selectForeground", "Background",
90 DEF_TEXT_SELECT_FG_COLOR
, Tk_Offset(TkText
, selFgColorPtr
),
91 TK_CONFIG_COLOR_ONLY
},
92 {TK_CONFIG_COLOR
, "-selectforeground", "selectForeground", "Background",
93 DEF_TEXT_SELECT_FG_MONO
, Tk_Offset(TkText
, selFgColorPtr
),
95 {TK_CONFIG_BOOLEAN
, "-setgrid", "setGrid", "SetGrid",
96 DEF_TEXT_SET_GRID
, Tk_Offset(TkText
, setGrid
), 0},
97 {TK_CONFIG_UID
, "-state", "state", "State",
98 DEF_TEXT_STATE
, Tk_Offset(TkText
, state
), 0},
99 {TK_CONFIG_INT
, "-width", "width", "Width",
100 DEF_TEXT_WIDTH
, Tk_Offset(TkText
, width
), 0},
101 {TK_CONFIG_UID
, "-wrap", "wrap", "Wrap",
102 DEF_TEXT_WRAP
, Tk_Offset(TkText
, wrapMode
), 0},
103 {TK_CONFIG_STRING
, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
104 DEF_TEXT_YSCROLL_COMMAND
, Tk_Offset(TkText
, yScrollCmd
),
106 {TK_CONFIG_END
, (char *) NULL
, (char *) NULL
, (char *) NULL
,
111 * The following definition specifies the maximum number of characters
112 * needed in a string to hold a position specifier.
118 * Tk_Uid's used to represent text states:
121 Tk_Uid tkTextCharUid
= NULL
;
122 Tk_Uid tkTextDisabledUid
= NULL
;
123 Tk_Uid tkTextNoneUid
= NULL
;
124 Tk_Uid tkTextNormalUid
= NULL
;
125 Tk_Uid tkTextWordUid
= NULL
;
128 * Forward declarations for procedures defined later in this file:
131 static int ConfigureText
_ANSI_ARGS_((Tcl_Interp
*interp
,
132 TkText
*textPtr
, int argc
, char **argv
, int flags
));
133 static void DeleteChars
_ANSI_ARGS_((TkText
*textPtr
, int line1
,
134 int ch1
, int line2
, int ch2
));
135 static void DestroyText
_ANSI_ARGS_((ClientData clientData
));
136 static void InsertChars
_ANSI_ARGS_((TkText
*textPtr
, int line
,
137 int ch
, char *string
));
138 static void TextBlinkProc
_ANSI_ARGS_((ClientData clientData
));
139 static void TextEventProc
_ANSI_ARGS_((ClientData clientData
,
141 static int TextFetchSelection
_ANSI_ARGS_((ClientData clientData
,
142 int offset
, char *buffer
, int maxBytes
));
143 static void TextFocusProc
_ANSI_ARGS_((ClientData clientData
,
145 static int TextMarkCmd
_ANSI_ARGS_((TkText
*textPtr
,
146 Tcl_Interp
*interp
, int argc
, char **argv
));
147 static int TextScanCmd
_ANSI_ARGS_((TkText
*textPtr
,
148 Tcl_Interp
*interp
, int argc
, char **argv
));
149 static int TextWidgetCmd
_ANSI_ARGS_((ClientData clientData
,
150 Tcl_Interp
*interp
, int argc
, char **argv
));
153 *--------------------------------------------------------------
157 * This procedure is invoked to process the "text" Tcl command.
158 * See the user documentation for details on what it does.
161 * A standard Tcl result.
164 * See the user documentation.
166 *--------------------------------------------------------------
170 Tk_TextCmd(clientData
, interp
, argc
, argv
)
171 ClientData clientData
; /* Main window associated with
173 Tcl_Interp
*interp
; /* Current interpreter. */
174 int argc
; /* Number of arguments. */
175 char **argv
; /* Argument strings. */
177 Tk_Window tkwin
= (Tk_Window
) clientData
;
179 register TkText
*textPtr
;
182 Tcl_AppendResult(interp
, "wrong # args: should be \"",
183 argv
[0], " pathName ?options?\"", (char *) NULL
);
188 * Perform once-only initialization:
191 if (tkTextNormalUid
== NULL
) {
192 tkTextCharUid
= Tk_GetUid("char");
193 tkTextDisabledUid
= Tk_GetUid("disabled");
194 tkTextNoneUid
= Tk_GetUid("none");
195 tkTextNormalUid
= Tk_GetUid("normal");
196 tkTextWordUid
= Tk_GetUid("word");
203 new = Tk_CreateWindowFromPath(interp
, tkwin
, argv
[1], (char *) NULL
);
208 textPtr
= (TkText
*) ckalloc(sizeof(TkText
));
209 textPtr
->tkwin
= new;
210 textPtr
->interp
= interp
;
211 textPtr
->tree
= TkBTreeCreate();
212 Tcl_InitHashTable(&textPtr
->tagTable
, TCL_STRING_KEYS
);
213 textPtr
->numTags
= 0;
214 Tcl_InitHashTable(&textPtr
->markTable
, TCL_STRING_KEYS
);
215 textPtr
->state
= tkTextNormalUid
;
216 textPtr
->border
= NULL
;
217 textPtr
->cursor
= None
;
218 textPtr
->fgColor
= NULL
;
219 textPtr
->fontPtr
= NULL
;
220 textPtr
->prevWidth
= Tk_Width(new);
221 textPtr
->prevHeight
= Tk_Height(new);
222 textPtr
->topLinePtr
= NULL
;
223 // Moved down so flags were set right.
224 // TkTextCreateDInfo(textPtr);
225 // TkTextSetView(textPtr, 0, 0);
226 textPtr
->selBorder
= NULL
;
227 textPtr
->selFgColorPtr
= NULL
;
228 textPtr
->exportSelection
= 1;
229 textPtr
->selOffset
= -1;
230 textPtr
->insertAnnotPtr
= NULL
;
231 textPtr
->insertBorder
= NULL
;
232 textPtr
->insertBlinkHandler
= (Tk_TimerToken
) NULL
;
233 textPtr
->bindingTable
= NULL
;
234 textPtr
->pickEvent
.type
= LeaveNotify
;
235 textPtr
->yScrollCmd
= NULL
;
236 textPtr
->scanMarkLine
= 0;
237 textPtr
->scanMarkY
= 0;
239 textPtr
->updateTimerToken
= 0; // Added by Don to optimize rapid updates.
240 TkTextCreateDInfo(textPtr
);
241 TkTextSetView(textPtr
, 0, 0);
244 * Create the "sel" tag and the "current" and "insert" marks.
247 textPtr
->selTagPtr
= TkTextCreateTag(textPtr
, "sel");
248 textPtr
->selTagPtr
->relief
= TK_RELIEF_RAISED
;
249 textPtr
->currentAnnotPtr
= TkTextSetMark(textPtr
, "current", 0, 0);
250 textPtr
->insertAnnotPtr
= TkTextSetMark(textPtr
, "insert", 0, 0);
252 Tk_SetClass(new, "Text");
253 Tk_CreateEventHandler(textPtr
->tkwin
, ExposureMask
|StructureNotifyMask
,
254 TextEventProc
, (ClientData
) textPtr
);
255 Tk_CreateEventHandler(textPtr
->tkwin
, KeyPressMask
|KeyReleaseMask
256 |ButtonPressMask
|ButtonReleaseMask
|EnterWindowMask
257 |LeaveWindowMask
|PointerMotionMask
, TkTextBindProc
,
258 (ClientData
) textPtr
);
259 Tk_CreateSelHandler(textPtr
->tkwin
, XA_STRING
, TextFetchSelection
,
260 (ClientData
) textPtr
, XA_STRING
);
261 Tcl_CreateCommand(interp
, Tk_PathName(textPtr
->tkwin
),
262 TextWidgetCmd
, (ClientData
) textPtr
, (void (*)()) NULL
);
263 if (ConfigureText(interp
, textPtr
, argc
-2, argv
+2, 0) != TCL_OK
) {
264 Tk_DestroyWindow(textPtr
->tkwin
);
267 Tk_CreateFocusHandler(textPtr
->tkwin
, TextFocusProc
, (ClientData
) textPtr
);
268 interp
->result
= Tk_PathName(textPtr
->tkwin
);
274 *--------------------------------------------------------------
278 * This procedure is invoked to process the Tcl command
279 * that corresponds to a text widget. See the user
280 * documentation for details on what it does.
283 * A standard Tcl result.
286 * See the user documentation.
288 *--------------------------------------------------------------
292 TextWidgetCmd(clientData
, interp
, argc
, argv
)
293 ClientData clientData
; /* Information about text widget. */
294 Tcl_Interp
*interp
; /* Current interpreter. */
295 int argc
; /* Number of arguments. */
296 char **argv
; /* Argument strings. */
298 register TkText
*textPtr
= (TkText
*) clientData
;
302 int line1
, line2
, ch1
, ch2
;
305 Tcl_AppendResult(interp
, "wrong # args: should be \"",
306 argv
[0], " option ?arg arg ...?\"", (char *) NULL
);
309 Tk_Preserve((ClientData
) textPtr
);
311 length
= strlen(argv
[1]);
312 if ((c
== 'c') && (strncmp(argv
[1], "compare", length
) == 0)
314 int less
, equal
, greater
, value
;
318 Tcl_AppendResult(interp
, "wrong # args: should be \"",
319 argv
[0], " compare index1 op index2\"", (char *) NULL
);
323 if ((TkTextGetIndex(interp
, textPtr
, argv
[2], &line1
, &ch1
) != TCL_OK
)
324 || (TkTextGetIndex(interp
, textPtr
, argv
[4], &line2
, &ch2
)
329 less
= equal
= greater
= 0;
332 } else if (line1
> line2
) {
337 } else if (ch1
> ch2
) {
346 if ((p
[1] == '=') && (p
[2] == 0)) {
347 value
= less
|| equal
;
348 } else if (p
[1] != 0) {
350 Tcl_AppendResult(interp
, "bad comparison operator \"",
351 argv
[3], "\": must be <, <=, ==, >=, >, or !=",
356 } else if (p
[0] == '>') {
358 if ((p
[1] == '=') && (p
[2] == 0)) {
359 value
= greater
|| equal
;
360 } else if (p
[1] != 0) {
363 } else if ((p
[0] == '=') && (p
[1] == '=') && (p
[2] == 0)) {
365 } else if ((p
[0] == '!') && (p
[1] == '=') && (p
[2] == 0)) {
370 interp
->result
= (value
) ? "1" : "0";
371 } else if ((c
== 'c') && (strncmp(argv
[1], "configure", length
) == 0)
374 result
= Tk_ConfigureInfo(interp
, textPtr
->tkwin
, configSpecs
,
375 (char *) textPtr
, (char *) NULL
, 0);
376 } else if (argc
== 3) {
377 result
= Tk_ConfigureInfo(interp
, textPtr
->tkwin
, configSpecs
,
378 (char *) textPtr
, argv
[2], 0);
380 result
= ConfigureText(interp
, textPtr
, argc
-2, argv
+2,
381 TK_CONFIG_ARGV_ONLY
);
383 } else if ((c
== 'd') && (strncmp(argv
[1], "debug", length
) == 0)
386 Tcl_AppendResult(interp
, "wrong # args: should be \"",
387 argv
[0], " debug ?on|off?\"", (char *) NULL
);
392 interp
->result
= (tkBTreeDebug
) ? "on" : "off";
394 if (Tcl_GetBoolean(interp
, argv
[2], &tkBTreeDebug
) != TCL_OK
) {
399 } else if ((c
== 'd') && (strncmp(argv
[1], "delete", length
) == 0)
401 if ((argc
!= 3) && (argc
!= 4)) {
402 Tcl_AppendResult(interp
, "wrong # args: should be \"",
403 argv
[0], " delete index1 ?index2?\"", (char *) NULL
);
407 if (TkTextGetIndex(interp
, textPtr
, argv
[2], &line1
, &ch1
) != TCL_OK
) {
414 } else if (TkTextGetIndex(interp
, textPtr
, argv
[3], &line2
, &ch2
)
419 if (textPtr
->state
== tkTextNormalUid
) {
420 DeleteChars(textPtr
, line1
, ch1
, line2
, ch2
);
422 } else if ((c
== 'g') && (strncmp(argv
[1], "get", length
) == 0)) {
423 register TkTextLine
*linePtr
;
425 if ((argc
!= 3) && (argc
!= 4)) {
426 Tcl_AppendResult(interp
, "wrong # args: should be \"",
427 argv
[0], " get index1 ?index2?\"", (char *) NULL
);
431 if (TkTextGetIndex(interp
, textPtr
, argv
[2], &line1
, &ch1
) != TCL_OK
) {
438 } else if (TkTextGetIndex(interp
, textPtr
, argv
[3], &line2
, &ch2
)
447 for (linePtr
= TkBTreeFindLine(textPtr
->tree
, line1
);
448 (linePtr
!= NULL
) && (line1
<= line2
);
449 linePtr
= TkBTreeNextLine(linePtr
), line1
++, ch1
= 0) {
452 if (line1
== line2
) {
454 if (last
> linePtr
->numBytes
) {
455 last
= linePtr
->numBytes
;
458 last
= linePtr
->numBytes
;
463 savedChar
= linePtr
->bytes
[last
];
464 linePtr
->bytes
[last
] = 0;
465 Tcl_AppendResult(interp
, linePtr
->bytes
+ch1
, (char *) NULL
);
466 linePtr
->bytes
[last
] = savedChar
;
468 } else if ((c
== 'i') && (strncmp(argv
[1], "index", length
) == 0)
471 Tcl_AppendResult(interp
, "wrong # args: should be \"",
472 argv
[0], " index index\"",
477 if (TkTextGetIndex(interp
, textPtr
, argv
[2], &line1
, &ch1
) != TCL_OK
) {
481 TkTextPrintIndex(line1
, ch1
, interp
->result
);
482 } else if ((c
== 'i') && (strncmp(argv
[1], "insert", length
) == 0)
485 Tcl_AppendResult(interp
, "wrong # args: should be \"",
486 argv
[0], " insert index chars ?chars ...?\"",
491 if (TkTextGetIndex(interp
, textPtr
, argv
[2], &line1
, &ch1
) != TCL_OK
) {
495 if (textPtr
->state
== tkTextNormalUid
) {
496 InsertChars(textPtr
, line1
, ch1
, argv
[3]);
498 } else if ((c
== 'm') && (strncmp(argv
[1], "mark", length
) == 0)) {
499 result
= TextMarkCmd(textPtr
, interp
, argc
, argv
);
500 } else if ((c
== 's') && (strcmp(argv
[1], "scan") == 0)) {
501 result
= TextScanCmd(textPtr
, interp
, argc
, argv
);
502 } else if ((c
== 't') && (strcmp(argv
[1], "tag") == 0)) {
503 result
= TkTextTagCmd(textPtr
, interp
, argc
, argv
);
504 } else if ((c
== 'y') && (strncmp(argv
[1], "yview", length
) == 0)) {
505 int numLines
, pickPlace
;
509 Tcl_AppendResult(interp
, "wrong # args: should be \"",
510 argv
[0], " yview ?-pickplace? lineNum|index\"",
516 if (argv
[2][0] == '-') {
519 switchLength
= strlen(argv
[2]);
520 if ((switchLength
>= 2)
521 && (strncmp(argv
[2], "-pickplace", switchLength
) == 0)) {
525 if ((pickPlace
+3) != argc
) {
528 if (Tcl_GetInt(interp
, argv
[2+pickPlace
], &line1
) != TCL_OK
) {
529 Tcl_ResetResult(interp
);
530 if (TkTextGetIndex(interp
, textPtr
, argv
[2+pickPlace
],
531 &line1
, &ch1
) != TCL_OK
) {
536 numLines
= TkBTreeNumLines(textPtr
->tree
);
537 if (line1
>= numLines
) {
543 TkTextSetView(textPtr
, line1
, pickPlace
);
545 Tcl_AppendResult(interp
, "bad option \"", argv
[1],
546 "\": must be compare, configure, debug, delete, get, ",
547 "index, insert, mark, scan, tag, or yview",
553 Tk_Release((ClientData
) textPtr
);
558 *----------------------------------------------------------------------
562 * This procedure is invoked by Tk_EventuallyFree or Tk_Release
563 * to clean up the internal structure of a text at a safe time
564 * (when no-one is using it anymore).
570 * Everything associated with the text is freed up.
572 *----------------------------------------------------------------------
576 DestroyText(clientData
)
577 ClientData clientData
; /* Info about text widget. */
579 register TkText
*textPtr
= (TkText
*) clientData
;
580 Tcl_HashSearch search
;
584 TkBTreeDestroy(textPtr
->tree
);
585 for (hPtr
= Tcl_FirstHashEntry(&textPtr
->tagTable
, &search
);
586 hPtr
!= NULL
; hPtr
= Tcl_NextHashEntry(&search
)) {
587 tagPtr
= (TkTextTag
*) Tcl_GetHashValue(hPtr
);
588 TkTextFreeTag(tagPtr
);
590 Tcl_DeleteHashTable(&textPtr
->tagTable
);
591 for (hPtr
= Tcl_FirstHashEntry(&textPtr
->markTable
, &search
);
592 hPtr
!= NULL
; hPtr
= Tcl_NextHashEntry(&search
)) {
593 ckfree((char *) Tcl_GetHashValue(hPtr
));
595 Tcl_DeleteHashTable(&textPtr
->markTable
);
596 if (textPtr
->border
!= NULL
) {
597 Tk_Free3DBorder(textPtr
->border
);
599 if (textPtr
->cursor
!= None
) {
600 Tk_FreeCursor(textPtr
->cursor
);
602 if (textPtr
->fgColor
!= NULL
) {
603 Tk_FreeColor(textPtr
->fgColor
);
605 if (textPtr
->fontPtr
!= NULL
) {
606 Tk_FreeFontStruct(textPtr
->fontPtr
);
608 TkTextFreeDInfo(textPtr
);
611 * NOTE: do NOT free up selBorder or selFgColorPtr: they are
612 * duplicates of information in the "sel" tag, which was freed
613 * up as part of deleting the tags above.
616 if (textPtr
->insertBorder
!= NULL
) {
617 Tk_Free3DBorder(textPtr
->insertBorder
);
619 if (textPtr
->insertBlinkHandler
!= NULL
) {
620 Tk_DeleteTimerHandler(textPtr
->insertBlinkHandler
);
622 if (textPtr
->updateTimerToken
!= NULL
) {
623 Tk_DeleteTimerHandler(textPtr
->updateTimerToken
);
624 textPtr
->updateTimerToken
= 0;
626 if (textPtr
->bindingTable
!= NULL
) {
627 Tk_DeleteBindingTable(textPtr
->bindingTable
);
629 if (textPtr
->yScrollCmd
!= NULL
) {
630 ckfree(textPtr
->yScrollCmd
);
632 ckfree((char *) textPtr
);
636 *----------------------------------------------------------------------
640 * This procedure is called to process an argv/argc list, plus
641 * the Tk option database, in order to configure (or
642 * reconfigure) a text widget.
645 * The return value is a standard Tcl result. If TCL_ERROR is
646 * returned, then interp->result contains an error message.
649 * Configuration information, such as text string, colors, font,
650 * etc. get set for textPtr; old resources get freed, if there
653 *----------------------------------------------------------------------
657 ConfigureText(interp
, textPtr
, argc
, argv
, flags
)
658 Tcl_Interp
*interp
; /* Used for error reporting. */
659 register TkText
*textPtr
; /* Information about widget; may or may
660 * not already have values for some fields. */
661 int argc
; /* Number of valid entries in argv. */
662 char **argv
; /* Arguments. */
663 int flags
; /* Flags to pass to Tk_ConfigureWidget. */
665 int oldExport
= textPtr
->exportSelection
;
666 int charWidth
, charHeight
;
668 if (Tk_ConfigureWidget(interp
, textPtr
->tkwin
, configSpecs
,
669 argc
, argv
, (char *) textPtr
, flags
) != TCL_OK
) {
674 * A few other options also need special processing, such as parsing
675 * the geometry and setting the background from a 3-D border.
678 if ((textPtr
->state
!= tkTextNormalUid
)
679 && (textPtr
->state
!= tkTextDisabledUid
)) {
680 Tcl_AppendResult(interp
, "bad state value \"", textPtr
->state
,
681 "\": must be normal or disabled", (char *) NULL
);
682 textPtr
->state
= tkTextNormalUid
;
686 if ((textPtr
->wrapMode
!= tkTextCharUid
)
687 && (textPtr
->wrapMode
!= tkTextNoneUid
)
688 && (textPtr
->wrapMode
!= tkTextWordUid
)) {
689 Tcl_AppendResult(interp
, "bad wrap mode \"", textPtr
->state
,
690 "\": must be char, none, or word", (char *) NULL
);
691 textPtr
->wrapMode
= tkTextCharUid
;
695 Tk_SetBackgroundFromBorder(textPtr
->tkwin
, textPtr
->border
);
696 Tk_SetInternalBorder(textPtr
->tkwin
, textPtr
->borderWidth
);
697 Tk_GeometryRequest(textPtr
->tkwin
, 200, 100);
700 * Make sure that configuration options are properly mirrored
701 * between the widget record and the "sel" tags. NOTE: we don't
702 * have to free up information during the mirroring; old
703 * information was freed when it was replaced in the widget
707 textPtr
->selTagPtr
->border
= textPtr
->selBorder
;
708 textPtr
->selTagPtr
->borderWidth
= textPtr
->selBorderWidth
;
709 textPtr
->selTagPtr
->fgColor
= textPtr
->selFgColorPtr
;
712 * Claim the selection if we've suddenly started exporting it and there
713 * are tagged characters.
716 if (textPtr
->exportSelection
&& (!oldExport
)) {
719 TkBTreeStartSearch(textPtr
->tree
, 0, 0, TkBTreeNumLines(textPtr
->tree
),
720 0, textPtr
->selTagPtr
, &search
);
721 if (TkBTreeNextTag(&search
)) {
722 Tk_OwnSelection(textPtr
->tkwin
, TkTextLostSelection
,
723 (ClientData
) textPtr
);
724 textPtr
->flags
|= GOT_SELECTION
;
729 * Register the desired geometry for the window, and arrange for
730 * the window to be redisplayed.
733 if (textPtr
->width
<= 0) {
736 if (textPtr
->height
<= 0) {
739 charWidth
= XTextWidth(textPtr
->fontPtr
, "0", 1);
740 charHeight
= (textPtr
->fontPtr
->ascent
+ textPtr
->fontPtr
->descent
);
741 Tk_GeometryRequest(textPtr
->tkwin
,
742 textPtr
->width
* charWidth
+ 2*textPtr
->borderWidth
744 textPtr
->height
* charHeight
+ 2*textPtr
->borderWidth
746 Tk_SetInternalBorder(textPtr
->tkwin
, textPtr
->borderWidth
);
747 if (textPtr
->setGrid
) {
748 Tk_SetGrid(textPtr
->tkwin
, textPtr
->width
, textPtr
->height
,
749 charWidth
, charHeight
);
752 TkTextRelayoutWindow(textPtr
);
757 *--------------------------------------------------------------
761 * This procedure is invoked by the Tk dispatcher on
762 * structure changes to a text. For texts with 3D
763 * borders, this procedure is also invoked for exposures.
769 * When the window gets deleted, internal structures get
770 * cleaned up. When it gets exposed, it is redisplayed.
772 *--------------------------------------------------------------
776 TextEventProc(clientData
, eventPtr
)
777 ClientData clientData
; /* Information about window. */
778 register XEvent
*eventPtr
; /* Information about event. */
780 register TkText
*textPtr
= (TkText
*) clientData
;
782 if (eventPtr
->type
== Expose
) {
783 TkTextRedrawRegion(textPtr
, eventPtr
->xexpose
.x
,
784 eventPtr
->xexpose
.y
, eventPtr
->xexpose
.width
,
785 eventPtr
->xexpose
.height
);
786 } else if (eventPtr
->type
== ConfigureNotify
) {
787 if ((textPtr
->prevWidth
!= Tk_Width(textPtr
->tkwin
))
788 || (textPtr
->prevHeight
!= Tk_Height(textPtr
->tkwin
))) {
789 TkTextRelayoutWindow(textPtr
);
791 } else if (eventPtr
->type
== DestroyNotify
) {
792 Tcl_DeleteCommand(textPtr
->interp
, Tk_PathName(textPtr
->tkwin
));
793 textPtr
->tkwin
= NULL
;
794 Tk_EventuallyFree((ClientData
) textPtr
, DestroyText
);
799 *----------------------------------------------------------------------
803 * This procedure implements most of the functionality of the
804 * "insert" widget command.
810 * The characters in "string" get added to the text just before
811 * the character indicated by "line" and "ch".
813 *----------------------------------------------------------------------
817 InsertChars(textPtr
, line
, ch
, string
)
818 TkText
*textPtr
; /* Overall information about text widget. */
819 int line
, ch
; /* Identifies character just before which
820 * new information is to be inserted. */
821 char *string
; /* Null-terminated string containing new
822 * information to add to text. */
824 register TkTextLine
*linePtr
;
827 * Locate the line where the insertion will occur.
830 linePtr
= TkTextRoundIndex(textPtr
, &line
, &ch
);
833 * Notify the display module that lines are about to change, then do
837 TkTextLinesChanged(textPtr
, line
, line
);
838 TkBTreeInsertChars(textPtr
->tree
, linePtr
, ch
, string
);
841 * If the line containing the insertion point was textPtr->topLinePtr,
842 * we must reset this pointer since the line structure was re-allocated.
845 if (linePtr
== textPtr
->topLinePtr
) {
846 TkTextSetView(textPtr
, line
, 0);
850 * Invalidate any selection retrievals in progress.
853 textPtr
->selOffset
= -1;
857 *----------------------------------------------------------------------
861 * This procedure implements most of the functionality of the
862 * "delete" widget command.
870 *----------------------------------------------------------------------
874 DeleteChars(textPtr
, line1
, ch1
, line2
, ch2
)
875 TkText
*textPtr
; /* Overall information about text widget. */
876 int line1
, ch1
; /* Position of first character to delete. */
877 int line2
, ch2
; /* Position of character just after last
880 register TkTextLine
*line1Ptr
, *line2Ptr
;
881 int numLines
, topLine
;
884 * The loop below is needed because a LeaveNotify event may be
885 * generated on the current charcter if it's about to be deleted.
886 * If this happens, then the bindings that trigger could modify
887 * the text, invalidating the range information computed here.
888 * So, go back and recompute all the range information after
889 * synthesizing a leave event.
895 * Locate the starting and ending lines for the deletion and adjust
896 * the endpoints if necessary to ensure that they are within valid
897 * ranges. Adjust the deletion range if necessary to ensure that the
898 * text (and each invidiual line) always ends in a newline.
901 numLines
= TkBTreeNumLines(textPtr
->tree
);
902 line1Ptr
= TkTextRoundIndex(textPtr
, &line1
, &ch1
);
905 } else if (line2
>= numLines
) {
907 line2Ptr
= TkBTreeFindLine(textPtr
->tree
, line2
);
908 ch2
= line2Ptr
->numBytes
;
910 line2Ptr
= TkBTreeFindLine(textPtr
->tree
, line2
);
917 * If the deletion range ends after the last character of a line,
918 * do one of three things:
920 * (a) if line2Ptr isn't the last line of the text, just adjust the
921 * ending point to be just before the 0th character of the next
923 * (b) if ch1 is at the beginning of a line, then adjust line1Ptr and
924 * ch1 to point just after the last character of the previous line.
925 * (c) otherwise, adjust ch2 so the final newline isn't deleted.
928 if (ch2
>= line2Ptr
->numBytes
) {
929 if (line2
< (numLines
-1)) {
931 line2Ptr
= TkBTreeNextLine(line2Ptr
);
934 ch2
= line2Ptr
->numBytes
-1;
935 if ((ch1
== 0) && (line1
> 0)) {
937 line1Ptr
= TkBTreeFindLine(textPtr
->tree
, line1
);
938 ch1
= line1Ptr
->numBytes
;
939 ch2
= line2Ptr
->numBytes
;
941 ch2
= line2Ptr
->numBytes
-1;
946 if ((line1
> line2
) || ((line1
== line2
) && (ch1
>= ch2
))) {
951 * If the current character is within the range being deleted,
952 * unpick it and synthesize a leave event for its tags, then
953 * go back and recompute the range ends.
956 if (!(textPtr
->flags
& IN_CURRENT
)) {
959 if ((textPtr
->currentAnnotPtr
->linePtr
== line1Ptr
)
960 && (textPtr
->currentAnnotPtr
->ch
< ch1
)) {
963 if ((textPtr
->currentAnnotPtr
->linePtr
== line2Ptr
)
964 && (textPtr
->currentAnnotPtr
->ch
>= ch2
)) {
967 if (line2
> (line1
+1)) {
970 currentLine
= TkBTreeLineIndex(textPtr
->currentAnnotPtr
->linePtr
);
971 if ((currentLine
<= line1
) || (currentLine
>= line2
)) {
975 TkTextUnpickCurrent(textPtr
);
979 * Tell the display what's about to happen so it can discard
980 * obsolete display information, then do the deletion. Also,
981 * check to see if textPtr->topLinePtr is in the range of
982 * characters deleted. If so, call the display module to reset
983 * it after doing the deletion.
986 topLine
= TkBTreeLineIndex(textPtr
->topLinePtr
);
987 TkTextLinesChanged(textPtr
, line1
, line2
);
988 TkBTreeDeleteChars(textPtr
->tree
, line1Ptr
, ch1
, line2Ptr
, ch2
);
989 if ((topLine
>= line1
) && (topLine
<= line2
)) {
990 numLines
= TkBTreeNumLines(textPtr
->tree
);
991 TkTextSetView(textPtr
, (line1
> (numLines
-1)) ? (numLines
-1) : line1
,
996 * Invalidate any selection retrievals in progress.
999 textPtr
->selOffset
= -1;
1003 *----------------------------------------------------------------------
1005 * TextFetchSelection --
1007 * This procedure is called back by Tk when the selection is
1008 * requested by someone. It returns part or all of the selection
1009 * in a buffer provided by the caller.
1012 * The return value is the number of non-NULL bytes stored
1013 * at buffer. Buffer is filled (or partially filled) with a
1014 * NULL-terminated string containing part or all of the selection,
1015 * as given by offset and maxBytes.
1020 *----------------------------------------------------------------------
1024 TextFetchSelection(clientData
, offset
, buffer
, maxBytes
)
1025 ClientData clientData
; /* Information about text widget. */
1026 int offset
; /* Offset within selection of first
1027 * character to be returned. */
1028 char *buffer
; /* Location in which to place
1030 int maxBytes
; /* Maximum number of bytes to place
1031 * at buffer, not including terminating
1032 * NULL character. */
1034 register TkText
*textPtr
= (TkText
*) clientData
;
1035 register TkTextLine
*linePtr
;
1036 int count
, chunkSize
;
1037 TkTextSearch search
;
1039 if (!textPtr
->exportSelection
) {
1044 * Find the beginning of the next range of selected text. Note: if
1045 * the selection is being retrieved in multiple pieces (offset != 0)
1046 * and some modification has been made to the text that affects the
1047 * selection (textPtr->selOffset != offset) then reject the selection
1048 * request (make 'em start over again).
1052 textPtr
->selLine
= 0;
1054 textPtr
->selOffset
= 0;
1055 } else if (textPtr
->selOffset
!= offset
) {
1058 TkBTreeStartSearch(textPtr
->tree
, textPtr
->selLine
, textPtr
->selCh
+1,
1059 TkBTreeNumLines(textPtr
->tree
), 0, textPtr
->selTagPtr
, &search
);
1060 if (!TkBTreeCharTagged(search
.linePtr
, textPtr
->selCh
,
1061 textPtr
->selTagPtr
)) {
1062 if (!TkBTreeNextTag(&search
)) {
1069 textPtr
->selLine
= search
.line1
;
1070 textPtr
->selCh
= search
.ch1
;
1074 * Each iteration through the outer loop below scans one selected range.
1075 * Each iteration through the nested loop scans one line in the
1081 linePtr
= search
.linePtr
;
1084 * Find the end of the current range of selected text.
1087 if (!TkBTreeNextTag(&search
)) {
1088 panic("TextFetchSelection couldn't find end of range");
1092 * Copy information from text lines into the buffer until
1093 * either we run out of space in the buffer or we get to
1094 * the end of this range of text.
1098 chunkSize
= ((linePtr
== search
.linePtr
) ? search
.ch1
1099 : linePtr
->numBytes
) - textPtr
->selCh
;
1100 if (chunkSize
> maxBytes
) {
1101 chunkSize
= maxBytes
;
1103 memcpy((VOID
*) buffer
, (VOID
*) (linePtr
->bytes
+ textPtr
->selCh
),
1105 buffer
+= chunkSize
;
1106 maxBytes
-= chunkSize
;
1108 textPtr
->selOffset
+= chunkSize
;
1109 if (maxBytes
== 0) {
1110 textPtr
->selCh
+= chunkSize
;
1113 if (linePtr
== search
.linePtr
) {
1118 linePtr
= TkBTreeNextLine(linePtr
);
1122 * Find the beginning of the next range of selected text.
1125 if (!TkBTreeNextTag(&search
)) {
1128 textPtr
->selLine
= search
.line1
;
1129 textPtr
->selCh
= search
.ch1
;
1138 *----------------------------------------------------------------------
1140 * TkTextLostSelection --
1142 * This procedure is called back by Tk when the selection is
1143 * grabbed away from a text widget.
1149 * The "sel" tag is cleared from the window.
1151 *----------------------------------------------------------------------
1155 TkTextLostSelection(clientData
)
1156 ClientData clientData
; /* Information about text widget. */
1158 register TkText
*textPtr
= (TkText
*) clientData
;
1160 if (!textPtr
->exportSelection
) {
1165 * Just remove the "sel" tag from everything in the widget.
1168 TkTextRedrawTag(textPtr
, 0, 0, TkBTreeNumLines(textPtr
->tree
),
1169 0, textPtr
->selTagPtr
, 1);
1170 TkBTreeTag(textPtr
->tree
, 0, 0, TkBTreeNumLines(textPtr
->tree
),
1171 0, textPtr
->selTagPtr
, 0);
1172 textPtr
->flags
&= ~GOT_SELECTION
;
1176 *--------------------------------------------------------------
1180 * This procedure is invoked to process the "mark" options of
1181 * the widget command for text widgets. See the user documentation
1182 * for details on what it does.
1185 * A standard Tcl result.
1188 * See the user documentation.
1190 *--------------------------------------------------------------
1194 TextMarkCmd(textPtr
, interp
, argc
, argv
)
1195 register TkText
*textPtr
; /* Information about text widget. */
1196 Tcl_Interp
*interp
; /* Current interpreter. */
1197 int argc
; /* Number of arguments. */
1198 char **argv
; /* Argument strings. Someone else has already
1199 * parsed this command enough to know that
1200 * argv[1] is "mark". */
1202 int length
, line
, ch
, i
;
1204 Tcl_HashEntry
*hPtr
;
1205 TkAnnotation
*markPtr
;
1206 Tcl_HashSearch search
;
1209 Tcl_AppendResult(interp
, "wrong # args: should be \"",
1210 argv
[0], " mark option ?arg arg ...?\"", (char *) NULL
);
1214 length
= strlen(argv
[2]);
1215 if ((c
== 'n') && (strncmp(argv
[2], "names", length
) == 0)) {
1217 Tcl_AppendResult(interp
, "wrong # args: should be \"",
1218 argv
[0], " mark names\"", (char *) NULL
);
1221 for (hPtr
= Tcl_FirstHashEntry(&textPtr
->markTable
, &search
);
1222 hPtr
!= NULL
; hPtr
= Tcl_NextHashEntry(&search
)) {
1223 Tcl_AppendElement(interp
,
1224 Tcl_GetHashKey(&textPtr
->markTable
, hPtr
), 0);
1226 } else if ((c
== 's') && (strncmp(argv
[2], "set", length
) == 0)) {
1228 Tcl_AppendResult(interp
, "wrong # args: should be \"",
1229 argv
[0], " mark set markName index\"", (char *) NULL
);
1232 if (TkTextGetIndex(interp
, textPtr
, argv
[4], &line
, &ch
) != TCL_OK
) {
1235 TkTextSetMark(textPtr
, argv
[3], line
, ch
);
1236 } else if ((c
== 'u') && (strncmp(argv
[2], "unset", length
) == 0)) {
1238 Tcl_AppendResult(interp
, "wrong # args: should be \"",
1239 argv
[0], " mark unset markName ?markName ...?\"",
1243 for (i
= 3; i
< argc
; i
++) {
1244 hPtr
= Tcl_FindHashEntry(&textPtr
->markTable
, argv
[i
]);
1246 markPtr
= (TkAnnotation
*) Tcl_GetHashValue(hPtr
);
1247 if (markPtr
== textPtr
->insertAnnotPtr
) {
1248 interp
->result
= "can't delete \"insert\" mark";
1251 if (markPtr
== textPtr
->currentAnnotPtr
) {
1252 interp
->result
= "can't delete \"current\" mark";
1255 TkBTreeRemoveAnnotation(markPtr
);
1256 Tcl_DeleteHashEntry(hPtr
);
1257 ckfree((char *) markPtr
);
1261 Tcl_AppendResult(interp
, "bad mark option \"", argv
[2],
1262 "\": must be names, set, or unset",
1270 *----------------------------------------------------------------------
1274 * Set a mark to a particular position, creating a new mark if
1275 * one doesn't already exist.
1278 * The return value is a pointer to the mark that was just set.
1281 * A new mark is created, or an existing mark is moved.
1283 *----------------------------------------------------------------------
1287 TkTextSetMark(textPtr
, name
, line
, ch
)
1288 TkText
*textPtr
; /* Text widget in which to create mark. */
1289 char *name
; /* Name of mark to set. */
1290 int line
; /* Index of line at which to place mark. */
1291 int ch
; /* Index of character within line at which
1294 Tcl_HashEntry
*hPtr
;
1295 TkAnnotation
*markPtr
;
1298 hPtr
= Tcl_CreateHashEntry(&textPtr
->markTable
, name
, &new);
1299 markPtr
= (TkAnnotation
*) Tcl_GetHashValue(hPtr
);
1302 * If this is the insertion point that's being moved, be sure
1303 * to force a display update at the old position.
1306 if (markPtr
== textPtr
->insertAnnotPtr
) {
1309 oldLine
= TkBTreeLineIndex(markPtr
->linePtr
);
1310 TkTextLinesChanged(textPtr
, oldLine
, oldLine
);
1312 TkBTreeRemoveAnnotation(markPtr
);
1314 markPtr
= (TkAnnotation
*) ckalloc(sizeof(TkAnnotation
));
1315 markPtr
->type
= TK_ANNOT_MARK
;
1316 markPtr
->info
.hPtr
= hPtr
;
1317 Tcl_SetHashValue(hPtr
, markPtr
);
1322 } else if (ch
< 0) {
1327 markPtr
->linePtr
= TkBTreeFindLine(textPtr
->tree
, line
);
1328 if (markPtr
->linePtr
== NULL
) {
1329 line
= TkBTreeNumLines(textPtr
->tree
)-1;
1330 markPtr
->linePtr
= TkBTreeFindLine(textPtr
->tree
, line
);
1331 markPtr
->ch
= markPtr
->linePtr
->numBytes
-1;
1333 if (markPtr
->ch
>= markPtr
->linePtr
->numBytes
) {
1334 TkTextLine
*nextLinePtr
;
1336 nextLinePtr
= TkBTreeNextLine(markPtr
->linePtr
);
1337 if (nextLinePtr
== NULL
) {
1338 markPtr
->ch
= markPtr
->linePtr
->numBytes
-1;
1340 markPtr
->linePtr
= nextLinePtr
;
1346 TkBTreeAddAnnotation(markPtr
);
1349 * If the mark is the insertion cursor, then update the screen at the
1350 * mark's new location.
1353 if (markPtr
== textPtr
->insertAnnotPtr
) {
1354 TkTextLinesChanged(textPtr
, line
, line
);
1360 *----------------------------------------------------------------------
1364 * This procedure is called as a timer handler to blink the
1365 * insertion cursor off and on.
1371 * The cursor gets turned on or off, redisplay gets invoked,
1372 * and this procedure reschedules itself.
1374 *----------------------------------------------------------------------
1378 TextBlinkProc(clientData
)
1379 ClientData clientData
; /* Pointer to record describing text. */
1381 register TkText
*textPtr
= (TkText
*) clientData
;
1384 if (!(textPtr
->flags
& GOT_FOCUS
) || (textPtr
->insertOffTime
== 0)) {
1387 if (textPtr
->flags
& INSERT_ON
) {
1388 textPtr
->flags
&= ~INSERT_ON
;
1389 textPtr
->insertBlinkHandler
= Tk_CreateTimerHandler(
1390 textPtr
->insertOffTime
, TextBlinkProc
, (ClientData
) textPtr
);
1392 textPtr
->flags
|= INSERT_ON
;
1393 textPtr
->insertBlinkHandler
= Tk_CreateTimerHandler(
1394 textPtr
->insertOnTime
, TextBlinkProc
, (ClientData
) textPtr
);
1396 lineNum
= TkBTreeLineIndex(textPtr
->insertAnnotPtr
->linePtr
);
1397 TkTextLinesChanged(textPtr
, lineNum
, lineNum
);
1401 *----------------------------------------------------------------------
1405 * This procedure is called whenever the entry gets or loses the
1406 * input focus. It's also called whenever the window is reconfigured
1407 * while it has the focus.
1413 * The cursor gets turned on or off.
1415 *----------------------------------------------------------------------
1419 TextFocusProc(clientData
, gotFocus
)
1420 ClientData clientData
; /* Pointer to structure describing text. */
1421 int gotFocus
; /* 1 means window is getting focus, 0 means
1422 * it's losing it. */
1424 register TkText
*textPtr
= (TkText
*) clientData
;
1427 Tk_DeleteTimerHandler(textPtr
->insertBlinkHandler
);
1429 textPtr
->flags
|= GOT_FOCUS
| INSERT_ON
;
1430 if (textPtr
->insertOffTime
!= 0) {
1431 textPtr
->insertBlinkHandler
= Tk_CreateTimerHandler(
1432 textPtr
->insertOnTime
, TextBlinkProc
,
1433 (ClientData
) textPtr
);
1436 textPtr
->flags
&= ~(GOT_FOCUS
| INSERT_ON
);
1437 textPtr
->insertBlinkHandler
= (Tk_TimerToken
) NULL
;
1439 lineNum
= TkBTreeLineIndex(textPtr
->insertAnnotPtr
->linePtr
);
1440 TkTextLinesChanged(textPtr
, lineNum
, lineNum
);
1444 *--------------------------------------------------------------
1448 * This procedure is invoked to process the "scan" options of
1449 * the widget command for text widgets. See the user documentation
1450 * for details on what it does.
1453 * A standard Tcl result.
1456 * See the user documentation.
1458 *--------------------------------------------------------------
1462 TextScanCmd(textPtr
, interp
, argc
, argv
)
1463 register TkText
*textPtr
; /* Information about text widget. */
1464 Tcl_Interp
*interp
; /* Current interpreter. */
1465 int argc
; /* Number of arguments. */
1466 char **argv
; /* Argument strings. Someone else has already
1467 * parsed this command enough to know that
1468 * argv[1] is "tag". */
1470 int length
, y
, line
, lastLine
;
1474 Tcl_AppendResult(interp
, "wrong # args: should be \"",
1475 argv
[0], " scan mark|dragto y\"", (char *) NULL
);
1478 if (Tcl_GetInt(interp
, argv
[3], &y
) != TCL_OK
) {
1482 length
= strlen(argv
[2]);
1483 if ((c
== 'd') && (strncmp(argv
[2], "dragto", length
) == 0)) {
1485 * Amplify the difference between the current y position and the
1486 * mark position to compute how many lines up or down the view
1487 * should shift, then update the mark position to correspond to
1488 * the new view. If we run off the top or bottom of the text,
1489 * reset the mark point so that the current position continues
1490 * to correspond to the edge of the window. This means that the
1491 * picture will start dragging as soon as the mouse reverses
1492 * direction (without this reset, might have to slide mouse a
1493 * long ways back before the picture starts moving again).
1496 line
= textPtr
->scanMarkLine
+ (10*(textPtr
->scanMarkY
- y
))
1497 / (textPtr
->fontPtr
->ascent
+ textPtr
->fontPtr
->descent
);
1498 lastLine
= TkBTreeNumLines(textPtr
->tree
) - 1;
1500 textPtr
->scanMarkLine
= line
= 0;
1501 textPtr
->scanMarkY
= y
;
1502 } else if (line
> lastLine
) {
1503 textPtr
->scanMarkLine
= line
= lastLine
;
1504 textPtr
->scanMarkY
= y
;
1506 TkTextSetView(textPtr
, line
, 0);
1507 } else if ((c
== 'm') && (strncmp(argv
[2], "mark", length
) == 0)) {
1508 textPtr
->scanMarkLine
= TkBTreeLineIndex(textPtr
->topLinePtr
);
1509 textPtr
->scanMarkY
= y
;
1511 Tcl_AppendResult(interp
, "bad scan option \"", argv
[2],
1512 "\": must be mark or dragto", (char *) NULL
);