|
|
1.1 root 1: /*
2: * tkText.c --
3: *
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.
9: *
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.
18: */
19:
20: #ifndef lint
21: static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkText.c,v 1.23 92/08/14 14:45:44 ouster Exp $ SPRITE (Berkeley)";
22: #endif
23:
24: #include "default.h"
25: #include "tkconfig.h"
26: #include "tk.h"
27: #include "tktext.h"
28:
29: /*
30: * Information used to parse text configuration options:
31: */
32:
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,
39: (char *) NULL, 0, 0},
40: {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
41: (char *) NULL, 0, 0},
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,
50: (char *) NULL, 0, 0},
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),
64: TK_CONFIG_MONO_ONLY},
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),
82: TK_CONFIG_MONO_ONLY},
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),
88: TK_CONFIG_MONO_ONLY},
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),
94: TK_CONFIG_MONO_ONLY},
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),
105: TK_CONFIG_NULL_OK},
106: {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
107: (char *) NULL, 0, 0}
108: };
109:
110: /*
111: * The following definition specifies the maximum number of characters
112: * needed in a string to hold a position specifier.
113: */
114:
115: #define POS_CHARS 30
116:
117: /*
118: * Tk_Uid's used to represent text states:
119: */
120:
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;
126:
127: /*
128: * Forward declarations for procedures defined later in this file:
129: */
130:
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,
140: XEvent *eventPtr));
141: static int TextFetchSelection _ANSI_ARGS_((ClientData clientData,
142: int offset, char *buffer, int maxBytes));
143: static void TextFocusProc _ANSI_ARGS_((ClientData clientData,
144: int gotFocus));
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));
151:
152: /*
153: *--------------------------------------------------------------
154: *
155: * Tk_TextCmd --
156: *
157: * This procedure is invoked to process the "text" Tcl command.
158: * See the user documentation for details on what it does.
159: *
160: * Results:
161: * A standard Tcl result.
162: *
163: * Side effects:
164: * See the user documentation.
165: *
166: *--------------------------------------------------------------
167: */
168:
169: int
170: Tk_TextCmd(clientData, interp, argc, argv)
171: ClientData clientData; /* Main window associated with
172: * interpreter. */
173: Tcl_Interp *interp; /* Current interpreter. */
174: int argc; /* Number of arguments. */
175: char **argv; /* Argument strings. */
176: {
177: Tk_Window tkwin = (Tk_Window) clientData;
178: Tk_Window new;
179: register TkText *textPtr;
180:
181: if (argc < 2) {
182: Tcl_AppendResult(interp, "wrong # args: should be \"",
183: argv[0], " pathName ?options?\"", (char *) NULL);
184: return TCL_ERROR;
185: }
186:
187: /*
188: * Perform once-only initialization:
189: */
190:
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");
197: }
198:
199: /*
200: * Create the window.
201: */
202:
203: new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
204: if (new == NULL) {
205: return TCL_ERROR;
206: }
207:
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;
238: textPtr->flags = 0;
239: textPtr->updateTimerToken = 0; // Added by Don to optimize rapid updates.
240: TkTextCreateDInfo(textPtr);
241: TkTextSetView(textPtr, 0, 0);
242:
243: /*
244: * Create the "sel" tag and the "current" and "insert" marks.
245: */
246:
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);
251:
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);
265: return TCL_ERROR;
266: }
267: Tk_CreateFocusHandler(textPtr->tkwin, TextFocusProc, (ClientData) textPtr);
268: interp->result = Tk_PathName(textPtr->tkwin);
269:
270: return TCL_OK;
271: }
272:
273: /*
274: *--------------------------------------------------------------
275: *
276: * TextWidgetCmd --
277: *
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.
281: *
282: * Results:
283: * A standard Tcl result.
284: *
285: * Side effects:
286: * See the user documentation.
287: *
288: *--------------------------------------------------------------
289: */
290:
291: static int
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. */
297: {
298: register TkText *textPtr = (TkText *) clientData;
299: int result = TCL_OK;
300: int length;
301: char c;
302: int line1, line2, ch1, ch2;
303:
304: if (argc < 2) {
305: Tcl_AppendResult(interp, "wrong # args: should be \"",
306: argv[0], " option ?arg arg ...?\"", (char *) NULL);
307: return TCL_ERROR;
308: }
309: Tk_Preserve((ClientData) textPtr);
310: c = argv[1][0];
311: length = strlen(argv[1]);
312: if ((c == 'c') && (strncmp(argv[1], "compare", length) == 0)
313: && (length >= 3)) {
314: int less, equal, greater, value;
315: char *p;
316:
317: if (argc != 5) {
318: Tcl_AppendResult(interp, "wrong # args: should be \"",
319: argv[0], " compare index1 op index2\"", (char *) NULL);
320: result = TCL_ERROR;
321: goto done;
322: }
323: if ((TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK)
324: || (TkTextGetIndex(interp, textPtr, argv[4], &line2, &ch2)
325: != TCL_OK)) {
326: result = TCL_ERROR;
327: goto done;
328: }
329: less = equal = greater = 0;
330: if (line1 < line2) {
331: less = 1;
332: } else if (line1 > line2) {
333: greater = 1;
334: } else {
335: if (ch1 < ch2) {
336: less = 1;
337: } else if (ch1 > ch2) {
338: greater = 1;
339: } else {
340: equal = 1;
341: }
342: }
343: p = argv[3];
344: if (p[0] == '<') {
345: value = less;
346: if ((p[1] == '=') && (p[2] == 0)) {
347: value = less || equal;
348: } else if (p[1] != 0) {
349: compareError:
350: Tcl_AppendResult(interp, "bad comparison operator \"",
351: argv[3], "\": must be <, <=, ==, >=, >, or !=",
352: (char *) NULL);
353: result = TCL_ERROR;
354: goto done;
355: }
356: } else if (p[0] == '>') {
357: value = greater;
358: if ((p[1] == '=') && (p[2] == 0)) {
359: value = greater || equal;
360: } else if (p[1] != 0) {
361: goto compareError;
362: }
363: } else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
364: value = equal;
365: } else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
366: value = !equal;
367: } else {
368: goto compareError;
369: }
370: interp->result = (value) ? "1" : "0";
371: } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
372: && (length >= 3)) {
373: if (argc == 2) {
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);
379: } else {
380: result = ConfigureText(interp, textPtr, argc-2, argv+2,
381: TK_CONFIG_ARGV_ONLY);
382: }
383: } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0)
384: && (length >= 3)) {
385: if (argc > 3) {
386: Tcl_AppendResult(interp, "wrong # args: should be \"",
387: argv[0], " debug ?on|off?\"", (char *) NULL);
388: result = TCL_ERROR;
389: goto done;
390: }
391: if (argc == 2) {
392: interp->result = (tkBTreeDebug) ? "on" : "off";
393: } else {
394: if (Tcl_GetBoolean(interp, argv[2], &tkBTreeDebug) != TCL_OK) {
395: result = TCL_ERROR;
396: goto done;
397: }
398: }
399: } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)
400: && (length >= 3)) {
401: if ((argc != 3) && (argc != 4)) {
402: Tcl_AppendResult(interp, "wrong # args: should be \"",
403: argv[0], " delete index1 ?index2?\"", (char *) NULL);
404: result = TCL_ERROR;
405: goto done;
406: }
407: if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
408: result = TCL_ERROR;
409: goto done;
410: }
411: if (argc == 3) {
412: line2 = line1;
413: ch2 = ch1+1;
414: } else if (TkTextGetIndex(interp, textPtr, argv[3], &line2, &ch2)
415: != TCL_OK) {
416: result = TCL_ERROR;
417: goto done;
418: }
419: if (textPtr->state == tkTextNormalUid) {
420: DeleteChars(textPtr, line1, ch1, line2, ch2);
421: }
422: } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
423: register TkTextLine *linePtr;
424:
425: if ((argc != 3) && (argc != 4)) {
426: Tcl_AppendResult(interp, "wrong # args: should be \"",
427: argv[0], " get index1 ?index2?\"", (char *) NULL);
428: result = TCL_ERROR;
429: goto done;
430: }
431: if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
432: result = TCL_ERROR;
433: goto done;
434: }
435: if (argc == 3) {
436: line2 = line1;
437: ch2 = ch1+1;
438: } else if (TkTextGetIndex(interp, textPtr, argv[3], &line2, &ch2)
439: != TCL_OK) {
440: result = TCL_ERROR;
441: goto done;
442: }
443: if (line1 < 0) {
444: line1 = 0;
445: ch1 = 0;
446: }
447: for (linePtr = TkBTreeFindLine(textPtr->tree, line1);
448: (linePtr != NULL) && (line1 <= line2);
449: linePtr = TkBTreeNextLine(linePtr), line1++, ch1 = 0) {
450: int savedChar, last;
451:
452: if (line1 == line2) {
453: last = ch2;
454: if (last > linePtr->numBytes) {
455: last = linePtr->numBytes;
456: }
457: } else {
458: last = linePtr->numBytes;
459: }
460: if (ch1 >= last) {
461: continue;
462: }
463: savedChar = linePtr->bytes[last];
464: linePtr->bytes[last] = 0;
465: Tcl_AppendResult(interp, linePtr->bytes+ch1, (char *) NULL);
466: linePtr->bytes[last] = savedChar;
467: }
468: } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
469: && (length >= 3)) {
470: if (argc != 3) {
471: Tcl_AppendResult(interp, "wrong # args: should be \"",
472: argv[0], " index index\"",
473: (char *) NULL);
474: result = TCL_ERROR;
475: goto done;
476: }
477: if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
478: result = TCL_ERROR;
479: goto done;
480: }
481: TkTextPrintIndex(line1, ch1, interp->result);
482: } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
483: && (length >= 3)) {
484: if (argc != 4) {
485: Tcl_AppendResult(interp, "wrong # args: should be \"",
486: argv[0], " insert index chars ?chars ...?\"",
487: (char *) NULL);
488: result = TCL_ERROR;
489: goto done;
490: }
491: if (TkTextGetIndex(interp, textPtr, argv[2], &line1, &ch1) != TCL_OK) {
492: result = TCL_ERROR;
493: goto done;
494: }
495: if (textPtr->state == tkTextNormalUid) {
496: InsertChars(textPtr, line1, ch1, argv[3]);
497: }
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;
506:
507: if (argc < 3) {
508: yviewSyntax:
509: Tcl_AppendResult(interp, "wrong # args: should be \"",
510: argv[0], " yview ?-pickplace? lineNum|index\"",
511: (char *) NULL);
512: result = TCL_ERROR;
513: goto done;
514: }
515: pickPlace = 0;
516: if (argv[2][0] == '-') {
517: int switchLength;
518:
519: switchLength = strlen(argv[2]);
520: if ((switchLength >= 2)
521: && (strncmp(argv[2], "-pickplace", switchLength) == 0)) {
522: pickPlace = 1;
523: }
524: }
525: if ((pickPlace+3) != argc) {
526: goto yviewSyntax;
527: }
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) {
532: result = TCL_ERROR;
533: goto done;
534: }
535: }
536: numLines = TkBTreeNumLines(textPtr->tree);
537: if (line1 >= numLines) {
538: line1 = numLines-1;
539: }
540: if (line1 < 0) {
541: line1 = 0;
542: }
543: TkTextSetView(textPtr, line1, pickPlace);
544: } else {
545: Tcl_AppendResult(interp, "bad option \"", argv[1],
546: "\": must be compare, configure, debug, delete, get, ",
547: "index, insert, mark, scan, tag, or yview",
548: (char *) NULL);
549: result = TCL_ERROR;
550: }
551:
552: done:
553: Tk_Release((ClientData) textPtr);
554: return result;
555: }
556:
557: /*
558: *----------------------------------------------------------------------
559: *
560: * DestroyText --
561: *
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).
565: *
566: * Results:
567: * None.
568: *
569: * Side effects:
570: * Everything associated with the text is freed up.
571: *
572: *----------------------------------------------------------------------
573: */
574:
575: static void
576: DestroyText(clientData)
577: ClientData clientData; /* Info about text widget. */
578: {
579: register TkText *textPtr = (TkText *) clientData;
580: Tcl_HashSearch search;
581: Tcl_HashEntry *hPtr;
582: TkTextTag *tagPtr;
583:
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);
589: }
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));
594: }
595: Tcl_DeleteHashTable(&textPtr->markTable);
596: if (textPtr->border != NULL) {
597: Tk_Free3DBorder(textPtr->border);
598: }
599: if (textPtr->cursor != None) {
600: Tk_FreeCursor(textPtr->cursor);
601: }
602: if (textPtr->fgColor != NULL) {
603: Tk_FreeColor(textPtr->fgColor);
604: }
605: if (textPtr->fontPtr != NULL) {
606: Tk_FreeFontStruct(textPtr->fontPtr);
607: }
608: TkTextFreeDInfo(textPtr);
609:
610: /*
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.
614: */
615:
616: if (textPtr->insertBorder != NULL) {
617: Tk_Free3DBorder(textPtr->insertBorder);
618: }
619: if (textPtr->insertBlinkHandler != NULL) {
620: Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
621: }
622: if (textPtr->updateTimerToken != NULL) {
623: Tk_DeleteTimerHandler(textPtr->updateTimerToken);
624: textPtr->updateTimerToken = 0;
625: }
626: if (textPtr->bindingTable != NULL) {
627: Tk_DeleteBindingTable(textPtr->bindingTable);
628: }
629: if (textPtr->yScrollCmd != NULL) {
630: ckfree(textPtr->yScrollCmd);
631: }
632: ckfree((char *) textPtr);
633: }
634:
635: /*
636: *----------------------------------------------------------------------
637: *
638: * ConfigureText --
639: *
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.
643: *
644: * Results:
645: * The return value is a standard Tcl result. If TCL_ERROR is
646: * returned, then interp->result contains an error message.
647: *
648: * Side effects:
649: * Configuration information, such as text string, colors, font,
650: * etc. get set for textPtr; old resources get freed, if there
651: * were any.
652: *
653: *----------------------------------------------------------------------
654: */
655:
656: static int
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. */
664: {
665: int oldExport = textPtr->exportSelection;
666: int charWidth, charHeight;
667:
668: if (Tk_ConfigureWidget(interp, textPtr->tkwin, configSpecs,
669: argc, argv, (char *) textPtr, flags) != TCL_OK) {
670: return TCL_ERROR;
671: }
672:
673: /*
674: * A few other options also need special processing, such as parsing
675: * the geometry and setting the background from a 3-D border.
676: */
677:
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;
683: return TCL_ERROR;
684: }
685:
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;
692: return TCL_ERROR;
693: }
694:
695: Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border);
696: Tk_SetInternalBorder(textPtr->tkwin, textPtr->borderWidth);
697: Tk_GeometryRequest(textPtr->tkwin, 200, 100);
698:
699: /*
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
704: * record.
705: */
706:
707: textPtr->selTagPtr->border = textPtr->selBorder;
708: textPtr->selTagPtr->borderWidth = textPtr->selBorderWidth;
709: textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
710:
711: /*
712: * Claim the selection if we've suddenly started exporting it and there
713: * are tagged characters.
714: */
715:
716: if (textPtr->exportSelection && (!oldExport)) {
717: TkTextSearch search;
718:
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;
725: }
726: }
727:
728: /*
729: * Register the desired geometry for the window, and arrange for
730: * the window to be redisplayed.
731: */
732:
733: if (textPtr->width <= 0) {
734: textPtr->width = 1;
735: }
736: if (textPtr->height <= 0) {
737: textPtr->height = 1;
738: }
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
743: + 2*textPtr->padX,
744: textPtr->height * charHeight + 2*textPtr->borderWidth
745: + 2*textPtr->padX);
746: Tk_SetInternalBorder(textPtr->tkwin, textPtr->borderWidth);
747: if (textPtr->setGrid) {
748: Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
749: charWidth, charHeight);
750: }
751:
752: TkTextRelayoutWindow(textPtr);
753: return TCL_OK;
754: }
755:
756: /*
757: *--------------------------------------------------------------
758: *
759: * TextEventProc --
760: *
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.
764: *
765: * Results:
766: * None.
767: *
768: * Side effects:
769: * When the window gets deleted, internal structures get
770: * cleaned up. When it gets exposed, it is redisplayed.
771: *
772: *--------------------------------------------------------------
773: */
774:
775: static void
776: TextEventProc(clientData, eventPtr)
777: ClientData clientData; /* Information about window. */
778: register XEvent *eventPtr; /* Information about event. */
779: {
780: register TkText *textPtr = (TkText *) clientData;
781:
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);
790: }
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);
795: }
796: }
797:
798: /*
799: *----------------------------------------------------------------------
800: *
801: * InsertChars --
802: *
803: * This procedure implements most of the functionality of the
804: * "insert" widget command.
805: *
806: * Results:
807: * None.
808: *
809: * Side effects:
810: * The characters in "string" get added to the text just before
811: * the character indicated by "line" and "ch".
812: *
813: *----------------------------------------------------------------------
814: */
815:
816: static void
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. */
823: {
824: register TkTextLine *linePtr;
825:
826: /*
827: * Locate the line where the insertion will occur.
828: */
829:
830: linePtr = TkTextRoundIndex(textPtr, &line, &ch);
831:
832: /*
833: * Notify the display module that lines are about to change, then do
834: * the insertion.
835: */
836:
837: TkTextLinesChanged(textPtr, line, line);
838: TkBTreeInsertChars(textPtr->tree, linePtr, ch, string);
839:
840: /*
841: * If the line containing the insertion point was textPtr->topLinePtr,
842: * we must reset this pointer since the line structure was re-allocated.
843: */
844:
845: if (linePtr == textPtr->topLinePtr) {
846: TkTextSetView(textPtr, line, 0);
847: }
848:
849: /*
850: * Invalidate any selection retrievals in progress.
851: */
852:
853: textPtr->selOffset = -1;
854: }
855:
856: /*
857: *----------------------------------------------------------------------
858: *
859: * DeleteChars --
860: *
861: * This procedure implements most of the functionality of the
862: * "delete" widget command.
863: *
864: * Results:
865: * None.
866: *
867: * Side effects:
868: * None.
869: *
870: *----------------------------------------------------------------------
871: */
872:
873: static void
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
878: * one to delete. */
879: {
880: register TkTextLine *line1Ptr, *line2Ptr;
881: int numLines, topLine;
882:
883: /*
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.
890: */
891:
892: while (1) {
893:
894: /*
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.
899: */
900:
901: numLines = TkBTreeNumLines(textPtr->tree);
902: line1Ptr = TkTextRoundIndex(textPtr, &line1, &ch1);
903: if (line2 < 0) {
904: return;
905: } else if (line2 >= numLines) {
906: line2 = numLines-1;
907: line2Ptr = TkBTreeFindLine(textPtr->tree, line2);
908: ch2 = line2Ptr->numBytes;
909: } else {
910: line2Ptr = TkBTreeFindLine(textPtr->tree, line2);
911: if (ch2 < 0) {
912: ch2 = 0;
913: }
914: }
915:
916: /*
917: * If the deletion range ends after the last character of a line,
918: * do one of three things:
919: *
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
922: * line.
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.
926: */
927:
928: if (ch2 >= line2Ptr->numBytes) {
929: if (line2 < (numLines-1)) {
930: line2++;
931: line2Ptr = TkBTreeNextLine(line2Ptr);
932: ch2 = 0;
933: } else {
934: ch2 = line2Ptr->numBytes-1;
935: if ((ch1 == 0) && (line1 > 0)) {
936: line1--;
937: line1Ptr = TkBTreeFindLine(textPtr->tree, line1);
938: ch1 = line1Ptr->numBytes;
939: ch2 = line2Ptr->numBytes;
940: } else {
941: ch2 = line2Ptr->numBytes-1;
942: }
943: }
944: }
945:
946: if ((line1 > line2) || ((line1 == line2) && (ch1 >= ch2))) {
947: return;
948: }
949:
950: /*
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.
954: */
955:
956: if (!(textPtr->flags & IN_CURRENT)) {
957: break;
958: }
959: if ((textPtr->currentAnnotPtr->linePtr == line1Ptr)
960: && (textPtr->currentAnnotPtr->ch < ch1)) {
961: break;
962: }
963: if ((textPtr->currentAnnotPtr->linePtr == line2Ptr)
964: && (textPtr->currentAnnotPtr->ch >= ch2)) {
965: break;
966: }
967: if (line2 > (line1+1)) {
968: int currentLine;
969:
970: currentLine = TkBTreeLineIndex(textPtr->currentAnnotPtr->linePtr);
971: if ((currentLine <= line1) || (currentLine >= line2)) {
972: break;
973: }
974: }
975: TkTextUnpickCurrent(textPtr);
976: }
977:
978: /*
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.
984: */
985:
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,
992: 0);
993: }
994:
995: /*
996: * Invalidate any selection retrievals in progress.
997: */
998:
999: textPtr->selOffset = -1;
1000: }
1001:
1002: /*
1003: *----------------------------------------------------------------------
1004: *
1005: * TextFetchSelection --
1006: *
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.
1010: *
1011: * Results:
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.
1016: *
1017: * Side effects:
1018: * None.
1019: *
1020: *----------------------------------------------------------------------
1021: */
1022:
1023: static int
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
1029: * selection. */
1030: int maxBytes; /* Maximum number of bytes to place
1031: * at buffer, not including terminating
1032: * NULL character. */
1033: {
1034: register TkText *textPtr = (TkText *) clientData;
1035: register TkTextLine *linePtr;
1036: int count, chunkSize;
1037: TkTextSearch search;
1038:
1039: if (!textPtr->exportSelection) {
1040: return -1;
1041: }
1042:
1043: /*
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).
1049: */
1050:
1051: if (offset == 0) {
1052: textPtr->selLine = 0;
1053: textPtr->selCh = 0;
1054: textPtr->selOffset = 0;
1055: } else if (textPtr->selOffset != offset) {
1056: return 0;
1057: }
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)) {
1063: if (offset == 0) {
1064: return -1;
1065: } else {
1066: return 0;
1067: }
1068: }
1069: textPtr->selLine = search.line1;
1070: textPtr->selCh = search.ch1;
1071: }
1072:
1073: /*
1074: * Each iteration through the outer loop below scans one selected range.
1075: * Each iteration through the nested loop scans one line in the
1076: * selected range.
1077: */
1078:
1079: count = 0;
1080: while (1) {
1081: linePtr = search.linePtr;
1082:
1083: /*
1084: * Find the end of the current range of selected text.
1085: */
1086:
1087: if (!TkBTreeNextTag(&search)) {
1088: panic("TextFetchSelection couldn't find end of range");
1089: }
1090:
1091: /*
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.
1095: */
1096:
1097: while (1) {
1098: chunkSize = ((linePtr == search.linePtr) ? search.ch1
1099: : linePtr->numBytes) - textPtr->selCh;
1100: if (chunkSize > maxBytes) {
1101: chunkSize = maxBytes;
1102: }
1103: memcpy((VOID *) buffer, (VOID *) (linePtr->bytes + textPtr->selCh),
1104: chunkSize);
1105: buffer += chunkSize;
1106: maxBytes -= chunkSize;
1107: count += chunkSize;
1108: textPtr->selOffset += chunkSize;
1109: if (maxBytes == 0) {
1110: textPtr->selCh += chunkSize;
1111: goto done;
1112: }
1113: if (linePtr == search.linePtr) {
1114: break;
1115: }
1116: textPtr->selCh = 0;
1117: textPtr->selLine++;
1118: linePtr = TkBTreeNextLine(linePtr);
1119: }
1120:
1121: /*
1122: * Find the beginning of the next range of selected text.
1123: */
1124:
1125: if (!TkBTreeNextTag(&search)) {
1126: break;
1127: }
1128: textPtr->selLine = search.line1;
1129: textPtr->selCh = search.ch1;
1130: }
1131:
1132: done:
1133: *buffer = 0;
1134: return count;
1135: }
1136:
1137: /*
1138: *----------------------------------------------------------------------
1139: *
1140: * TkTextLostSelection --
1141: *
1142: * This procedure is called back by Tk when the selection is
1143: * grabbed away from a text widget.
1144: *
1145: * Results:
1146: * None.
1147: *
1148: * Side effects:
1149: * The "sel" tag is cleared from the window.
1150: *
1151: *----------------------------------------------------------------------
1152: */
1153:
1154: void
1155: TkTextLostSelection(clientData)
1156: ClientData clientData; /* Information about text widget. */
1157: {
1158: register TkText *textPtr = (TkText *) clientData;
1159:
1160: if (!textPtr->exportSelection) {
1161: return;
1162: }
1163:
1164: /*
1165: * Just remove the "sel" tag from everything in the widget.
1166: */
1167:
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;
1173: }
1174:
1175: /*
1176: *--------------------------------------------------------------
1177: *
1178: * TextMarkCmd --
1179: *
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.
1183: *
1184: * Results:
1185: * A standard Tcl result.
1186: *
1187: * Side effects:
1188: * See the user documentation.
1189: *
1190: *--------------------------------------------------------------
1191: */
1192:
1193: static int
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". */
1201: {
1202: int length, line, ch, i;
1203: char c;
1204: Tcl_HashEntry *hPtr;
1205: TkAnnotation *markPtr;
1206: Tcl_HashSearch search;
1207:
1208: if (argc < 3) {
1209: Tcl_AppendResult(interp, "wrong # args: should be \"",
1210: argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
1211: return TCL_ERROR;
1212: }
1213: c = argv[2][0];
1214: length = strlen(argv[2]);
1215: if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
1216: if (argc != 3) {
1217: Tcl_AppendResult(interp, "wrong # args: should be \"",
1218: argv[0], " mark names\"", (char *) NULL);
1219: return TCL_ERROR;
1220: }
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);
1225: }
1226: } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
1227: if (argc != 5) {
1228: Tcl_AppendResult(interp, "wrong # args: should be \"",
1229: argv[0], " mark set markName index\"", (char *) NULL);
1230: return TCL_ERROR;
1231: }
1232: if (TkTextGetIndex(interp, textPtr, argv[4], &line, &ch) != TCL_OK) {
1233: return TCL_ERROR;
1234: }
1235: TkTextSetMark(textPtr, argv[3], line, ch);
1236: } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
1237: if (argc < 4) {
1238: Tcl_AppendResult(interp, "wrong # args: should be \"",
1239: argv[0], " mark unset markName ?markName ...?\"",
1240: (char *) NULL);
1241: return TCL_ERROR;
1242: }
1243: for (i = 3; i < argc; i++) {
1244: hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
1245: if (hPtr != NULL) {
1246: markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
1247: if (markPtr == textPtr->insertAnnotPtr) {
1248: interp->result = "can't delete \"insert\" mark";
1249: return TCL_ERROR;
1250: }
1251: if (markPtr == textPtr->currentAnnotPtr) {
1252: interp->result = "can't delete \"current\" mark";
1253: return TCL_ERROR;
1254: }
1255: TkBTreeRemoveAnnotation(markPtr);
1256: Tcl_DeleteHashEntry(hPtr);
1257: ckfree((char *) markPtr);
1258: }
1259: }
1260: } else {
1261: Tcl_AppendResult(interp, "bad mark option \"", argv[2],
1262: "\": must be names, set, or unset",
1263: (char *) NULL);
1264: return TCL_ERROR;
1265: }
1266: return TCL_OK;
1267: }
1268:
1269: /*
1270: *----------------------------------------------------------------------
1271: *
1272: * TkTextSetMark --
1273: *
1274: * Set a mark to a particular position, creating a new mark if
1275: * one doesn't already exist.
1276: *
1277: * Results:
1278: * The return value is a pointer to the mark that was just set.
1279: *
1280: * Side effects:
1281: * A new mark is created, or an existing mark is moved.
1282: *
1283: *----------------------------------------------------------------------
1284: */
1285:
1286: TkAnnotation *
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
1292: * to place mark. */
1293: {
1294: Tcl_HashEntry *hPtr;
1295: TkAnnotation *markPtr;
1296: int new;
1297:
1298: hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
1299: markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr);
1300: if (!new) {
1301: /*
1302: * If this is the insertion point that's being moved, be sure
1303: * to force a display update at the old position.
1304: */
1305:
1306: if (markPtr == textPtr->insertAnnotPtr) {
1307: int oldLine;
1308:
1309: oldLine = TkBTreeLineIndex(markPtr->linePtr);
1310: TkTextLinesChanged(textPtr, oldLine, oldLine);
1311: }
1312: TkBTreeRemoveAnnotation(markPtr);
1313: } else {
1314: markPtr = (TkAnnotation *) ckalloc(sizeof(TkAnnotation));
1315: markPtr->type = TK_ANNOT_MARK;
1316: markPtr->info.hPtr = hPtr;
1317: Tcl_SetHashValue(hPtr, markPtr);
1318: }
1319: if (line < 0) {
1320: line = 0;
1321: markPtr->ch = 0;
1322: } else if (ch < 0) {
1323: markPtr->ch = 0;
1324: } else {
1325: markPtr->ch = ch;
1326: }
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;
1332: } else {
1333: if (markPtr->ch >= markPtr->linePtr->numBytes) {
1334: TkTextLine *nextLinePtr;
1335:
1336: nextLinePtr = TkBTreeNextLine(markPtr->linePtr);
1337: if (nextLinePtr == NULL) {
1338: markPtr->ch = markPtr->linePtr->numBytes-1;
1339: } else {
1340: markPtr->linePtr = nextLinePtr;
1341: line++;
1342: markPtr->ch = 0;
1343: }
1344: }
1345: }
1346: TkBTreeAddAnnotation(markPtr);
1347:
1348: /*
1349: * If the mark is the insertion cursor, then update the screen at the
1350: * mark's new location.
1351: */
1352:
1353: if (markPtr == textPtr->insertAnnotPtr) {
1354: TkTextLinesChanged(textPtr, line, line);
1355: }
1356: return markPtr;
1357: }
1358:
1359: /*
1360: *----------------------------------------------------------------------
1361: *
1362: * TextBlinkProc --
1363: *
1364: * This procedure is called as a timer handler to blink the
1365: * insertion cursor off and on.
1366: *
1367: * Results:
1368: * None.
1369: *
1370: * Side effects:
1371: * The cursor gets turned on or off, redisplay gets invoked,
1372: * and this procedure reschedules itself.
1373: *
1374: *----------------------------------------------------------------------
1375: */
1376:
1377: static void
1378: TextBlinkProc(clientData)
1379: ClientData clientData; /* Pointer to record describing text. */
1380: {
1381: register TkText *textPtr = (TkText *) clientData;
1382: int lineNum;
1383:
1384: if (!(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
1385: return;
1386: }
1387: if (textPtr->flags & INSERT_ON) {
1388: textPtr->flags &= ~INSERT_ON;
1389: textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
1390: textPtr->insertOffTime, TextBlinkProc, (ClientData) textPtr);
1391: } else {
1392: textPtr->flags |= INSERT_ON;
1393: textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
1394: textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr);
1395: }
1396: lineNum = TkBTreeLineIndex(textPtr->insertAnnotPtr->linePtr);
1397: TkTextLinesChanged(textPtr, lineNum, lineNum);
1398: }
1399:
1400: /*
1401: *----------------------------------------------------------------------
1402: *
1403: * TextFocusProc --
1404: *
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.
1408: *
1409: * Results:
1410: * None.
1411: *
1412: * Side effects:
1413: * The cursor gets turned on or off.
1414: *
1415: *----------------------------------------------------------------------
1416: */
1417:
1418: static void
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. */
1423: {
1424: register TkText *textPtr = (TkText *) clientData;
1425: int lineNum;
1426:
1427: Tk_DeleteTimerHandler(textPtr->insertBlinkHandler);
1428: if (gotFocus) {
1429: textPtr->flags |= GOT_FOCUS | INSERT_ON;
1430: if (textPtr->insertOffTime != 0) {
1431: textPtr->insertBlinkHandler = Tk_CreateTimerHandler(
1432: textPtr->insertOnTime, TextBlinkProc,
1433: (ClientData) textPtr);
1434: }
1435: } else {
1436: textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
1437: textPtr->insertBlinkHandler = (Tk_TimerToken) NULL;
1438: }
1439: lineNum = TkBTreeLineIndex(textPtr->insertAnnotPtr->linePtr);
1440: TkTextLinesChanged(textPtr, lineNum, lineNum);
1441: }
1442:
1443: /*
1444: *--------------------------------------------------------------
1445: *
1446: * TextScanCmd --
1447: *
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.
1451: *
1452: * Results:
1453: * A standard Tcl result.
1454: *
1455: * Side effects:
1456: * See the user documentation.
1457: *
1458: *--------------------------------------------------------------
1459: */
1460:
1461: static int
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". */
1469: {
1470: int length, y, line, lastLine;
1471: char c;
1472:
1473: if (argc != 4) {
1474: Tcl_AppendResult(interp, "wrong # args: should be \"",
1475: argv[0], " scan mark|dragto y\"", (char *) NULL);
1476: return TCL_ERROR;
1477: }
1478: if (Tcl_GetInt(interp, argv[3], &y) != TCL_OK) {
1479: return TCL_ERROR;
1480: }
1481: c = argv[2][0];
1482: length = strlen(argv[2]);
1483: if ((c == 'd') && (strncmp(argv[2], "dragto", length) == 0)) {
1484: /*
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).
1494: */
1495:
1496: line = textPtr->scanMarkLine + (10*(textPtr->scanMarkY - y))
1497: / (textPtr->fontPtr->ascent + textPtr->fontPtr->descent);
1498: lastLine = TkBTreeNumLines(textPtr->tree) - 1;
1499: if (line < 0) {
1500: textPtr->scanMarkLine = line = 0;
1501: textPtr->scanMarkY = y;
1502: } else if (line > lastLine) {
1503: textPtr->scanMarkLine = line = lastLine;
1504: textPtr->scanMarkY = y;
1505: }
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;
1510: } else {
1511: Tcl_AppendResult(interp, "bad scan option \"", argv[2],
1512: "\": must be mark or dragto", (char *) NULL);
1513: return TCL_ERROR;
1514: }
1515: return TCL_OK;
1516: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.