/* * tkText.c -- * * This module implements Text widgets for the Tk * toolkit. A Text widget displays a string and allows * the string to be edited. * * Copyright 1990 Regents of the University of California. * Permission to use, copy, modify, and distribute this * software and its documentation for any purpose and without * fee is hereby granted, provided that the above copyright * notice appear in all copies. The University of California * makes no representations about the suitability of this * software for any purpose. It is provided "as is" without * express or implied warranty. */ #ifndef lint static char rcsid[] = "$Header: /user5/ouster/wish/RCS/tkText.c,v 1.17 91/04/05 17:37:41 ouster Exp $ SPRITE (Berkeley)"; #endif not lint #include "default.h" #include "tkConfig.h" #include "tkInt.h" /* * A data structure of the following type is kept for each Text * widget managed by this file: */ typedef struct { int total, window, first, last; } ScrollValues; typedef struct Line { struct Line *next; struct Line *prev; int flags; /* see below */ int index; /* offset into text */ int num; /* line number in text */ int length; /* length of this line */ int width; /* width in pixels */ int size; /* the allocated length of this string. * Not necessarily equal to length */ char *mem; char *text; /* the string itself */ } Line; #define TEXT_LINE_DIRTY 1 typedef struct { 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.*/ Tcl_Interp *interp; /* Interpreter associated with Text. */ int numChars; /* Number of non-NULL characters in * string (may be 0). */ int numLines; int displayedLines; Line *firstLine; /* Pointer to storage for text */ Line *lastLine; Line *topLine; /* * Information used when displaying widget: */ Tk_3DBorder normalBorder; /* Used for drawing border around whole * window, plus used for background. */ int borderWidth; /* Width of 3-D border around window. */ int relief; /* 3-D effect: TK_RELIEF_RAISED, etc. */ XFontStruct *fontPtr; /* Information about text font, or NULL. */ XColor *fgColorPtr; /* Text color in normal mode. */ GC textGC; /* For drawing normal text. */ Tk_3DBorder selBorder; /* Border and background for selected * characters. */ int selBorderWidth; /* Width of border around selection. */ XColor *selFgColorPtr; /* Foreground color for selected text. */ GC selTextGC; /* For drawing selected text. */ Tk_3DBorder cursorBorder; /* Used to draw vertical bar for insertion * cursor. */ int cursorWidth; /* Total width of insert cursor. */ int cursorBorderWidth; /* Width of 3-D border around insert cursor. */ int cursorOnTime; /* Number of milliseconds cursor should spend * in "on" state for each blink. */ int cursorOffTime; /* Number of milliseconds cursor should spend * in "off" state for each blink. */ Tk_TimerToken cursorBlinkHandler; /* Timer handler used to blink cursor on and * off. */ int avgWidth; /* Width of average character. */ int prefWidth; /* Desired width of window, measured in * average characters. */ int prefHeight; /* Desired height of window, measured in * average characters. */ int offset; /* 0 if window is flat, or borderWidth if * raised or sunken. */ int leftIndex; /* Index of left-most character visible in * window. */ int cursorPos; /* Index of character before which next * typed character will be inserted. */ Line *cursorLine; int wrapMode; Pixmap pm; /* Used to do redisplays off-screen, then * blast on-screen for minimal flashing. * None means not allocated yet. */ Display *pmDisplay; /* Display associated with pm; needed to * delete it. */ int pmWidth, pmHeight; /* Dimensions of pm. */ /* * Information about what's selected, if any. */ int selectFirst; /* Index of first selected character (-1 means * nothing selected. */ int selectLast; /* Index of last selected character (-1 means * nothing selected. */ int selectAnchor; /* Fixed end of selection (i.e. "select to" * operation will use this as one end of the * selection). */ /* * Information for scanning: */ int scanMarkX; /* X-position at which scan started (e.g. * button was pressed here). */ int scanMarkIndex; /* Index of character that was at left of * window when scan started. */ /* * Miscellaneous information: */ Tk_Uid scrollHCmd; /* Command prefix for communicating with * scrollbar(s). NULL means no command * to issue. */ ScrollValues lastScrollH; Tk_Uid scrollVCmd; /* Command prefix for communicating with * scrollbar(s). NULL means no command * to issue. */ ScrollValues lastScrollV; int flags; /* Miscellaneous flags; see below for * definitions. */ } Text; /* * Assigned bits of "flags" fields of Text structures, and what those * bits mean: * * REDRAW_PENDING: Non-zero means a DoWhenIdle handler has * already been queued to redisplay the Text. * CLEAR_NEEDED: Non-zero means that the Text's window * should be cleared during redraw (this is * needed when things change like the size of * the text). * BORDER_NEEDED: Non-zero means 3-D border must be redrawn * around window during redisplay. Normally * only text portion needs to be redrawn. * CLEAR_NEEDED implies BORDER_NEEDED. * CURSOR_ON: Non-zero means cursor is displayed at * present. 0 means it isn't displayed. * GOT_FOCUS: Non-zero means this window has the input * focus. * REFRESH_NEEDED: Non-zero means that the Text's window * should be redrawn whether text lines have * changed or not. Needed when scrolling or * inserting or deleting lines. */ #define REDRAW_PENDING 1 #define CLEAR_NEEDED 2 #define BORDER_NEEDED 4 #define CURSOR_ON 8 #define GOT_FOCUS 16 #define REFRESH_NEEDED 32 /* * Information used for argv parsing. */ static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_TEXT_BG_COLOR, Tk_Offset(Text, normalBorder), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_TEXT_BG_MONO, Tk_Offset(Text, normalBorder), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_INT, "-borderwidth", "borderWidth", "BorderWidth", DEF_TEXT_BORDER_WIDTH, Tk_Offset(Text, borderWidth), 0}, {TK_CONFIG_BORDER, "-cursorbackground", "cursorBackground", "Foreground", DEF_TEXT_CURSOR_BG, Tk_Offset(Text, cursorBorder), 0}, {TK_CONFIG_INT, "-cursorborderwidth", "cursorBorderWidth", "BorderWidth", DEF_TEXT_CURSOR_BD_COLOR, Tk_Offset(Text, cursorBorderWidth), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_INT, "-cursorborderwidth", "cursorBorderWidth", "BorderWidth", DEF_TEXT_CURSOR_BD_MONO, Tk_Offset(Text, cursorBorderWidth), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_INT, "-cursorofftime", "cursorOffTime", "OffTime", DEF_TEXT_CURSOR_OFF_TIME, Tk_Offset(Text, cursorOffTime), 0}, {TK_CONFIG_INT, "-cursorontime", "cursorOnTime", "OnTime", DEF_TEXT_CURSOR_ON_TIME, Tk_Offset(Text, cursorOnTime), 0}, {TK_CONFIG_INT, "-cursorwidth", "cursorWidth", "CursorWidth", DEF_TEXT_CURSOR_WIDTH, Tk_Offset(Text, cursorWidth), 0}, {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_FONT, "-font", "font", "Font", DEF_TEXT_FONT, Tk_Offset(Text, fontPtr), 0}, {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", DEF_TEXT_FG, Tk_Offset(Text, fgColorPtr), 0}, {TK_CONFIG_INT, "-height", "height", "Height", DEF_TEXT_HEIGHT, Tk_Offset(Text, prefHeight), 0}, {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_TEXT_RELIEF, Tk_Offset(Text, relief), 0}, {TK_CONFIG_UID, "-scrollhcommand", "scrollHCommand", "ScrollHCommand", DEF_TEXT_SCROLL_H_COMMAND, Tk_Offset(Text, scrollHCmd), 0}, {TK_CONFIG_UID, "-scrollvcommand", "scrollVCommand", "ScrollVCommand", DEF_TEXT_SCROLL_V_COMMAND, Tk_Offset(Text, scrollVCmd), 0}, {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", DEF_TEXT_SELECT_COLOR, Tk_Offset(Text, selBorder), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", DEF_TEXT_SELECT_MONO, Tk_Offset(Text, selBorder), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_INT, "-selectborderwidth", "selectBorderWidth", "BorderWidth", DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(Text, selBorderWidth), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_INT, "-selectborderwidth", "selectBorderWidth", "BorderWidth", DEF_TEXT_SELECT_BD_MONO, Tk_Offset(Text, selBorderWidth), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", DEF_TEXT_SELECT_FG_COLOR, Tk_Offset(Text, selFgColorPtr), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", DEF_TEXT_SELECT_FG_MONO, Tk_Offset(Text, selFgColorPtr), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_INT, "-width", "width", "Width", DEF_TEXT_WIDTH, Tk_Offset(Text, prefWidth), 0}, {TK_CONFIG_WRAP, "-wrapmode", "wrapMode", "WrapMode", DEF_TEXT_WRAP, Tk_Offset(Text, wrapMode), 0}, {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, 0} }; /* * Flags for GetTextIndex procedure: */ #define ZERO_OK 1 #define LAST_PLUS_ONE_OK 2 /* * Forward declarations for procedures defined later in this file: */ static int ConfigureText _ANSI_ARGS_((Tcl_Interp *interp, Text *TextPtr, int argc, char **argv, int flags)); static void DeleteText _ANSI_ARGS_((Text *TextPtr, int index, int count)); static void DestroyText _ANSI_ARGS_((ClientData clientData)); static void DisplayText _ANSI_ARGS_((ClientData clientData)); static int GetTextIndex _ANSI_ARGS_((Tcl_Interp *interp, Text *TextPtr, char *string, int *indexPtr)); static void InsertText _ANSI_ARGS_((Text *TextPtr, int index, char *string)); static void TextBlinkProc _ANSI_ARGS_((ClientData clientData)); static void TextEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); static void TextFocusProc _ANSI_ARGS_ ((ClientData clientData, int gotFocus)); static int TextFetchSelection _ANSI_ARGS_((ClientData clientData, int offset, char *buffer, int maxBytes)); static void TextLostSelection _ANSI_ARGS_((ClientData clientData)); static void EventuallyRedraw _ANSI_ARGS_((Text *TextPtr)); static void TextScanTo _ANSI_ARGS_((Text *TextPtr, int y)); static void TextSelectTo _ANSI_ARGS_((Text *TextPtr, int index)); static void TextUpdateHScrollbar _ANSI_ARGS_((Text *TextPtr)); static void TextUpdateVScrollbar _ANSI_ARGS_((Text *TextPtr)); static int TextWidgetCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv)); static void EnsureCursorVisible(); static Line * FindLine _ANSI_ARGS_ ((Text *TextPtr, int num)); static Line * FindChar _ANSI_ARGS_ ((Text *TextPtr, int index)); static char * GetText _ANSI_ARGS_((Text *TextPtr, int index, int count)); static void FixLines _ANSI_ARGS_((Text *TextPtr, Line *start, int min)); int Tk_GetWrapMode(interp, name, wrapPtr) Tcl_Interp *interp; /* For error messages. */ char *name; /* Name of a relief type. */ int *wrapPtr; /* Where to store converted wrap mode. */ { char c; int length; c = name[0]; length = strlen(name); if ((c == 'n') && (strncmp(name, "none", length) == 0)) { *wrapPtr = TK_WRAP_NONE; } else if ((c == 'c') && (strncmp(name, "char", length) == 0)) { *wrapPtr = TK_WRAP_CHAR; } else if ((c == 'w') && (strncmp(name, "word", length) == 0)) { *wrapPtr = TK_WRAP_WORD; } else if ((c == 'f') && (strncmp(name, "fixed", length) == 0)) { *wrapPtr = TK_WRAP_FIXED; } else { sprintf(interp->result, "bad wrap mode \"%.50s\": must be %s", name, "none, char, word or fixed"); return TCL_ERROR; } return TCL_OK; } char * Tk_NameOfWrapMode(wrap) int wrap; { if (wrap == TK_WRAP_NONE) { return "flat"; } else if (wrap == TK_WRAP_CHAR) { return "sunken"; } else if (wrap == TK_WRAP_WORD) { return "raised"; } else if (wrap == TK_WRAP_FIXED) { return "fixed"; } else { return "unknown relief"; } } /* *-------------------------------------------------------------- * * Tk_TextCmd -- * * This procedure is invoked to process the "Text" Tcl * command. See the user documentation for details on what * it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ int Tk_TextCmd(clientData, interp, argc, argv) ClientData clientData; /* Main window associated with * interpreter. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { Tk_Window tkwin = (Tk_Window) clientData; register Text *TextPtr; Tk_Window new; static ScrollValues scrollInitial = { -1, -1, -1, -1 }; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " pathName ?options?\"", (char *) NULL); return TCL_ERROR; } new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL); if (new == NULL) { return TCL_ERROR; } /* * Initialize the fields of the structure that won't be initialized * by ConfigureText, or that ConfigureText requires to be * initialized already (e.g. resource pointers). */ TextPtr = (Text *) ckalloc(sizeof(Text)); TextPtr->tkwin = new; TextPtr->interp = interp; TextPtr->numChars = 0; TextPtr->numLines = 0; TextPtr->displayedLines = 0; TextPtr->firstLine = (Line *)ckalloc(sizeof(Line)); TextPtr->firstLine->next = TextPtr->firstLine->prev = NULL; TextPtr->firstLine->flags = 0; TextPtr->firstLine->index = 0; TextPtr->firstLine->length = 0; TextPtr->firstLine->width = 0; TextPtr->firstLine->size = 0; TextPtr->firstLine->mem = TextPtr->firstLine->text = NULL; TextPtr->lastLine = TextPtr->topLine = TextPtr->firstLine; TextPtr->normalBorder = NULL; TextPtr->fontPtr = NULL; TextPtr->fgColorPtr = NULL; TextPtr->textGC = None; TextPtr->selBorder = NULL; TextPtr->selFgColorPtr = NULL; TextPtr->selTextGC = NULL; TextPtr->cursorBorder = NULL; TextPtr->cursorBlinkHandler = (Tk_TimerToken) NULL; TextPtr->leftIndex = 0; TextPtr->cursorPos = 0; TextPtr->cursorLine = TextPtr->firstLine; TextPtr->wrapMode = TK_WRAP_NONE; TextPtr->pm = None; TextPtr->pmDisplay = NULL; TextPtr->pmWidth = -1; TextPtr->pmHeight = -1; TextPtr->selectFirst = -1; TextPtr->selectLast = -1; TextPtr->selectAnchor = 0; TextPtr->scanMarkX = 0; TextPtr->scrollHCmd = NULL; TextPtr->lastScrollH = scrollInitial; TextPtr->scrollVCmd = NULL; TextPtr->lastScrollV = scrollInitial; TextPtr->flags = 0; Tk_SetClass(TextPtr->tkwin, "Text"); Tk_CreateEventHandler(TextPtr->tkwin, ExposureMask|StructureNotifyMask, TextEventProc, (ClientData) TextPtr); Tk_CreateSelHandler(TextPtr->tkwin, XA_STRING, TextFetchSelection, (ClientData) TextPtr, XA_STRING); Tcl_CreateCommand(interp, Tk_PathName(TextPtr->tkwin), TextWidgetCmd, (ClientData) TextPtr, (void (*)()) NULL); if (ConfigureText(interp, TextPtr, argc-2, argv+2, 0) != TCL_OK) { goto error; } Tk_CreateFocusHandler(TextPtr->tkwin, TextFocusProc, (ClientData) TextPtr); interp->result = Tk_PathName(TextPtr->tkwin); return TCL_OK; error: Tk_DestroyWindow(TextPtr->tkwin); return TCL_ERROR; } /* *-------------------------------------------------------------- * * TextWidgetCmd -- * * This procedure is invoked to process the Tcl command * that corresponds to a widget managed by this module. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ static int TextWidgetCmd(clientData, interp, argc, argv) ClientData clientData; /* Information about Text widget. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { register Text *TextPtr = (Text *) clientData; int result = TCL_OK; int length; char c; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " option ?arg arg ...?\"", (char *) NULL); return TCL_ERROR; } Tk_Preserve((ClientData) TextPtr); c = argv[1][0]; length = strlen(argv[1]); if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0) && (length >= 2)) { if (argc == 2) { result = Tk_ConfigureInfo(interp, TextPtr->tkwin, configSpecs, (char *) TextPtr, (char *) NULL, 0); } else if (argc == 3) { result = Tk_ConfigureInfo(interp, TextPtr->tkwin, configSpecs, (char *) TextPtr, argv[2], 0); } else { result = ConfigureText(interp, TextPtr, argc-2, argv+2, TK_CONFIG_ARGV_ONLY); } } else if ((c == 'c') && (strncmp(argv[1], "cursor", length) == 0) && (length >= 2)) { if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " cursor pos\"", (char *) NULL); goto error; } if (GetTextIndex(interp, TextPtr, argv[2], &TextPtr->cursorPos) != TCL_OK) { goto error; } TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; TextPtr->cursorLine = FindChar(TextPtr, TextPtr->cursorPos); EnsureCursorVisible(TextPtr); TextUpdateVScrollbar(TextPtr); EventuallyRedraw(TextPtr); } else if ((c == 'e') && (strncmp(argv[1], "edit", length) == 0)) { if (argc > 2) result = TextEditCmd(TextPtr, interp, argc, argv); else { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " edit option [arg arg ...]\"", (char *) NULL); goto error; } } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) { int first, last; if ((argc < 3) || (argc > 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " delete firstIndex ?lastIndex?\"", (char *) NULL); goto error; } if (GetTextIndex(interp, TextPtr, argv[2], &first) != TCL_OK) { goto error; } if (argc == 3) { last = first; } else { if (GetTextIndex(interp, TextPtr, argv[3], &last) != TCL_OK) { goto error; } } if (last >= first) { DeleteText(TextPtr, first, last+1-first); } } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) { int first = 0; int last = TextPtr->numChars - 1; if ((argc < 2) || (argc > 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " get [firstIndex [lastIndex]]\"", (char *) NULL); goto error; } if (argc > 2) { if (GetTextIndex(interp, TextPtr, argv[2], &first) != TCL_OK) { goto error; } last = first; } if (argc > 3) { if (GetTextIndex(interp, TextPtr, argv[3], &last) != TCL_OK) { goto error; } } if (last >= first) { char *text; text = GetText(TextPtr, first, last+1-first); if (text != NULL) { Tcl_AppendResult(interp, text, NULL); ckfree(text); } } } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0) && (length >= 2)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " index string\"", (char *) NULL); goto error; } if (GetTextIndex(interp, TextPtr, argv[2], &index) != TCL_OK) { goto error; } sprintf(interp->result, "%d", index); } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0) && (length >= 2)) { int index; if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " insert index text\"", (char *) NULL); goto error; } if (GetTextIndex(interp, TextPtr, argv[2], &index) != TCL_OK) { goto error; } InsertText(TextPtr, index, argv[3]); } else if ((c == 's') && (length >= 2) && (strncmp(argv[1], "scan", length) == 0)) { int x; if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " scan mark|dragto x\"", (char *) NULL); goto error; } if (Tcl_GetInt(interp, argv[3], &x) != TCL_OK) { goto error; } if ((argv[2][0] == 'm') && (strncmp(argv[2], "mark", strlen(argv[2])) == 0)) { TextPtr->scanMarkX = x; TextPtr->scanMarkIndex = TextPtr->leftIndex; } else if ((argv[2][0] == 'd') && (strncmp(argv[2], "dragto", strlen(argv[2])) == 0)) { TextScanTo(TextPtr, x); } else { Tcl_AppendResult(interp, "bad scan option \"", argv[2], "\": must be mark or dragto", (char *) NULL); goto error; } } else if ((c == 's') && (length >= 2) && (strncmp(argv[1], "select", length) == 0)) { int index; if (argc < 3) { Tcl_AppendResult(interp, "too few args: should be \"", argv[0], " select option ?index?\"", (char *) NULL); goto error; } length = strlen(argv[2]); c = argv[2][0]; if ((c == 'c') && (argv[2] != NULL) && (strncmp(argv[2], "clear", length) == 0)) { if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " select clear\"", (char *) NULL); goto error; } if (TextPtr->selectFirst != -1) { TextPtr->selectFirst = TextPtr->selectLast = -1; EventuallyRedraw(TextPtr); } goto error; } if (argc >= 4) { if (GetTextIndex(interp, TextPtr, argv[3], &index) != TCL_OK) { goto error; } } if ((c == 'a') && (strncmp(argv[2], "adjust", length) == 0)) { if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " select adjust index\"", (char *) NULL); goto error; } if (TextPtr->selectFirst >= 0) { if (index < (TextPtr->selectFirst + TextPtr->selectLast)/2) { TextPtr->selectAnchor = TextPtr->selectLast + 1; } else { TextPtr->selectAnchor = TextPtr->selectFirst; } } TextSelectTo(TextPtr, index); } else if ((c == 'f') && (strncmp(argv[2], "from", length) == 0)) { if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " select from index\"", (char *) NULL); goto error; } TextPtr->selectAnchor = index; } else if ((c == 't') && (strncmp(argv[2], "to", length) == 0)) { if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " select to index\"", (char *) NULL); goto error; } TextSelectTo(TextPtr, index); } else { Tcl_AppendResult(interp, "bad select option \"", argv[2], "\": must be adjust, clear, from, or to", (char *) NULL); goto error; } } else if (c == 'v') { if (strncmp(argv[1], "view", length) == 0) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " view index\"", (char *) NULL); goto error; } if (GetTextIndex(interp, TextPtr, argv[2], &index) != TCL_OK) { goto error; } TextPtr->topLine = FindChar(TextPtr, index); EventuallyRedraw(TextPtr); TextUpdateVScrollbar(TextPtr); } else if (strncmp(argv[1], "viewline", length) == 0) { Line *line; int num; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " viewline line\"", (char *) NULL); goto error; } if (Tcl_GetInt(interp, argv[2], &num) != TCL_OK) { goto error; } TextPtr->topLine = FindLine(TextPtr, num); TextPtr->flags |= REFRESH_NEEDED; EventuallyRedraw(TextPtr); TextUpdateVScrollbar(TextPtr); } else goto error; } else { Tcl_AppendResult(interp, "bad option \"", argv[1], "\": must be configure, cursor, delete, get, index, ", "insert, scan, select, or view", (char *) NULL); goto error; } Tk_Release((ClientData) TextPtr); return result; error: Tk_Release((ClientData) TextPtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * DestroyText -- * * This procedure is invoked by Tk_EventuallyFree or Tk_Release * to clean up the internal structure of a Text at a safe time * (when no-one is using it anymore). * * Results: * None. * * Side effects: * Everything associated with the Text is freed up. * *---------------------------------------------------------------------- */ static void DestroyText(clientData) ClientData clientData; /* Info about Text widget. */ { register Text *TextPtr = (Text *) clientData; Line *line; line = TextPtr->firstLine; while (line != NULL) { Line *victim = line; line = victim->next; if (victim->mem != NULL) ckfree(victim->mem); ckfree(victim); } if (TextPtr->normalBorder != NULL) { Tk_Free3DBorder(TextPtr->normalBorder); } if (TextPtr->fontPtr != NULL) { Tk_FreeFontStruct(TextPtr->fontPtr); } if (TextPtr->fgColorPtr != NULL) { Tk_FreeColor(TextPtr->fgColorPtr); } if (TextPtr->textGC != None) { Tk_FreeGC(TextPtr->textGC); } if (TextPtr->selBorder != NULL) { Tk_Free3DBorder(TextPtr->selBorder); } if (TextPtr->selFgColorPtr != NULL) { Tk_FreeColor(TextPtr->selFgColorPtr); } if (TextPtr->selTextGC != None) { Tk_FreeGC(TextPtr->selTextGC); } if (TextPtr->cursorBorder != NULL) { Tk_Free3DBorder(TextPtr->cursorBorder); } Tk_DeleteTimerHandler(TextPtr->cursorBlinkHandler); if (TextPtr->pm != None) { XFreePixmap(TextPtr->pmDisplay, TextPtr->pm); } ckfree((char *) TextPtr); } /* *---------------------------------------------------------------------- * * ConfigureText -- * * This procedure is called to process an argv/argc list, plus * the Tk option database, in order to configure (or reconfigure) * a Text widget. * * Results: * The return value is a standard Tcl result. If TCL_ERROR is * returned, then interp->result contains an error message. * * Side effects: * Configuration information, such as colors, border width, * etc. get set for TextPtr; old resources get freed, * if there were any. * *---------------------------------------------------------------------- */ static int ConfigureText(interp, TextPtr, argc, argv, flags) Tcl_Interp *interp; /* Used for error reporting. */ register Text *TextPtr; /* Information about widget; may or may * not already have values for some fields. */ int argc; /* Number of valid entries in argv. */ char **argv; /* Arguments. */ int flags; /* Flags to pass to Tk_ConfigureWidget. */ { XGCValues gcValues; GC new; int width, height, fontHeight; if (Tk_ConfigureWidget(interp, TextPtr->tkwin, configSpecs, argc, argv, (char *) TextPtr, flags) != TCL_OK) { return TCL_ERROR; } /* * A few options need special processing, such as parsing the * geometry and setting the background from a 3-D border. */ Tk_SetBackgroundFromBorder(TextPtr->tkwin, TextPtr->normalBorder); gcValues.foreground = TextPtr->fgColorPtr->pixel; gcValues.font = TextPtr->fontPtr->fid; new = Tk_GetGC(TextPtr->tkwin, GCForeground|GCFont, &gcValues); if (TextPtr->textGC != None) { Tk_FreeGC(TextPtr->textGC); } TextPtr->textGC = new; gcValues.foreground = TextPtr->selFgColorPtr->pixel; gcValues.font = TextPtr->fontPtr->fid; new = Tk_GetGC(TextPtr->tkwin, GCForeground|GCFont, &gcValues); if (TextPtr->selTextGC != None) { Tk_FreeGC(TextPtr->selTextGC); } TextPtr->selTextGC = new; if (TextPtr->cursorWidth > 2*TextPtr->fontPtr->min_bounds.width) { TextPtr->cursorWidth = 2*TextPtr->fontPtr->min_bounds.width; } if (TextPtr->cursorBorderWidth > TextPtr->cursorWidth/2) { TextPtr->cursorBorderWidth = TextPtr->cursorWidth/2; } /* * Restart the cursor timing sequence in case the on-time or off-time * just changed. */ if (TextPtr->flags & GOT_FOCUS) { TextFocusProc((ClientData) TextPtr, 1); } /* * Register the desired geometry for the window, and arrange for * the window to be redisplayed. */ fontHeight = TextPtr->fontPtr->ascent + TextPtr->fontPtr->descent; TextPtr->avgWidth = XTextWidth(TextPtr->fontPtr, "0m", 2)/2; width = TextPtr->prefWidth*TextPtr->avgWidth + (15*fontHeight)/10; height = fontHeight*TextPtr->prefHeight + 2*TextPtr->borderWidth + 2; Tk_GeometryRequest(TextPtr->tkwin, width, height); Tk_SetInternalBorder(TextPtr->tkwin, TextPtr->borderWidth); if (TextPtr->relief != TK_RELIEF_FLAT) { TextPtr->offset = TextPtr->borderWidth; } else { TextPtr->offset = 0; } FixLines(TextPtr, TextPtr->firstLine, TextPtr->numLines); EventuallyRedraw(TextPtr); TextPtr->flags |= CLEAR_NEEDED; TextUpdateVScrollbar(TextPtr); return TCL_OK; } /* *-------------------------------------------------------------- * * TextEditCmd -- * * This procedure is invoked to process the an editing * command for a text widget. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ static int TextEditCmd(TextPtr, interp, argc, argv) Text *TextPtr; Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { char *name = argv[0]; int result = TCL_OK; int length; char c; argv += 2; argc -= 2; c = argv[0][0]; length = strlen(argv[0]); if (c == 'b') { if (strncmp(argv[0], "bkw", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "backword-kill-word", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "bc", length) == 0 || strncmp(argv[0], "backward-character", length) == 0) { if (TextPtr->cursorPos > 0) { if (TextPtr->cursorPos == TextPtr->cursorLine->index) { TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; TextPtr->cursorLine = TextPtr->cursorLine->prev; if (TextPtr->cursorLine->text[TextPtr->cursorLine->length - 1] == '\n') TextPtr->cursorPos--; } else TextPtr->cursorPos--; EnsureCursorVisible(TextPtr); } } else if (strncmp(argv[0], "bof", length) == 0 || strncmp(argv[0], "beginning-of-file", length) == 0) { if (TextPtr->cursorPos != 0) { TextPtr->cursorPos = 0; TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; TextPtr->cursorLine = TextPtr->firstLine; EnsureCursorVisible(TextPtr); } } else if (strncmp(argv[0], "bol", length) == 0 || strncmp(argv[0], "beginning-of-line", length) == 0) { TextPtr->cursorPos = TextPtr->cursorLine->index; EnsureCursorVisible(TextPtr); } else if (strncmp(argv[0], "bp", length) == 0 || strncmp(argv[0], "backward-paragraph", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "bw", length) == 0 || strncmp(argv[0], "backword-word", length) == 0) { goto not_implemented; } } else if (c == 'd') { if (strncmp(argv[0], "display-caret", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "dnc", length) == 0 || strncmp(argv[0], "delete-next-character", length) == 0) { if (TextPtr->cursorPos != TextPtr->numChars) { DeleteText(TextPtr, TextPtr->cursorPos, 1); } } else if (strncmp(argv[0], "dnw", length) == 0 || strncmp(argv[0], "delete-next-word", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "dpc", length) == 0 || strncmp(argv[0], "delete-previous-character", length) == 0) { if (TextPtr->cursorPos != 0) { DeleteText(TextPtr, TextPtr->cursorPos - 1, 1); } EnsureCursorVisible(TextPtr); } else if (strncmp(argv[0], "dpw", length) == 0 || strncmp(argv[0], "delete-previous-word", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "ds", length) == 0 || strncmp(argv[0], "delete-selection", length) == 0) { goto not_implemented; } } else if (c == 'e') { if (strncmp(argv[0], "eof", length) == 0 || strncmp(argv[0], "end-of-file", length) == 0) { TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; TextPtr->cursorPos = TextPtr->numChars; TextPtr->cursorLine = FindChar(TextPtr, TextPtr->cursorPos); EnsureCursorVisible(TextPtr); } else if (strncmp(argv[0], "eol", length) == 0 || strncmp(argv[0], "end-of-line", length) == 0) { int index = TextPtr->cursorLine->length; if (TextPtr->cursorLine->text[index - 1] == '\n') TextPtr->cursorPos = TextPtr->cursorLine->index + index - 1; else TextPtr->cursorPos = TextPtr->cursorLine->index + index; EnsureCursorVisible(TextPtr); } else if (strncmp(argv[0], "extend-adjust", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "extend-end", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "extend-start", length) == 0) { goto not_implemented; } } else if (c == 'f') { if (strncmp(argv[0], "fc", length) == 0 || strncmp(argv[0], "forward-character", length) == 0) { if (TextPtr->cursorPos < TextPtr->numChars) { int index = TextPtr->cursorPos - TextPtr->cursorLine->index; if (index == TextPtr->cursorLine->length || TextPtr->cursorLine->text[index] == '\n') { TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; if (index < TextPtr->cursorLine->length) TextPtr->cursorPos++; TextPtr->cursorLine = TextPtr->cursorLine->next; } else TextPtr->cursorPos++; EnsureCursorVisible(TextPtr); } } else if (strncmp(argv[0], "focus-in", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "focus-out", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "form-paragraph", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "fp", length) == 0 || strncmp(argv[0], "forward-paragraph", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "fw", length) == 0 || strncmp(argv[0], "forward-word", length) == 0) { goto not_implemented; } } else if (c == 'i') { if (strncmp(argv[0], "ic", length) == 0 || strncmp(argv[0], "insert-char", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "if", length) == 0 || strncmp(argv[0], "insert-file", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "insert-selection", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "is", length) == 0 || strncmp(argv[0], "insert-string", length) == 0) { goto not_implemented; } } else if (c == 'k') { if (strncmp(argv[0], "kp", length) == 0 || strncmp(argv[0], "kill-paragraph", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "ks", length) == 0 || strncmp(argv[0], "kill-selection", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "kill", length) == 0 || strncmp(argv[0], "kill-to-end-of-line", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "keop", length) == 0 || strncmp(argv[0], "kill-to-end-of-paragraph", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "kill-word", length) == 0) { goto not_implemented; } } else if ((c == 'm') && (strncmp(argv[0], "multiply", length) == 0)) { goto not_implemented; } else if (c == 'n') { if (strncmp(argv[0], "new-line", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "newline-and-backup", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "newline-and-indent", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "nl", length) == 0 || strncmp(argv[0], "next-line", length) == 0) { if (TextPtr->cursorLine->next != NULL) { int index = TextPtr->cursorPos - TextPtr->cursorLine->index; Line *line = TextPtr->cursorLine->next; TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; if (index >= line->length) index = line->length; if (line->text && index == line->length && line->text[index - 1] == '\n') index--; TextPtr->cursorLine = line; TextPtr->cursorPos = line->index + index; EnsureCursorVisible(TextPtr); } } else if (strncmp(argv[0], "no-op", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "np", length) == 0 || strncmp(argv[0], "next-page", length) == 0) { goto not_implemented; } } else if (c == 'p') { if (strncmp(argv[0], "pl", length) == 0 || strncmp(argv[0], "previous-line", length) == 0) { if (TextPtr->cursorLine->prev != NULL) { int index = TextPtr->cursorPos - TextPtr->cursorLine->index; Line *line = TextPtr->cursorLine->prev; TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; if (index >= line->length) index = line->length; if (line->text && index == line->length && line->text[index - 1] == '\n') index--; TextPtr->cursorLine = line; TextPtr->cursorPos = line->index + index; EnsureCursorVisible(TextPtr); } } else if (strncmp(argv[0], "pp", length) == 0 || strncmp(argv[0], "previous-page", length) == 0) { goto not_implemented; } } else if ((c == 'r') && (strncmp(argv[0], "redraw-display", length) == 0)) { goto not_implemented; } else if (c == 's') { if (strncmp(argv[0], "sd", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "scroll-one-line-down", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "search", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "select-adjust", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "select-all", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "select-end", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "select-start", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "select-word", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "su", length) == 0) { goto not_implemented; } else if (strncmp(argv[0], "scroll-one-line-up", length) == 0) { goto not_implemented; } } else if ((c == 't') && (strncmp(argv[0], "transpose-characters", length) == 0)) { goto not_implemented; } else { Tcl_AppendResult(interp, "bad option \"", argv[0], "\"", (char *) NULL); goto error; } EventuallyRedraw(TextPtr); return result; not_implemented: Tcl_AppendResult(interp, "option \"", argv[0], "\" not implemented", (char *) NULL); error: return TCL_ERROR; } /* *-------------------------------------------------------------- * * DisplayLine -- * * This procedure redraws the contents of a line in a Text * window. * * Results: * None. * * Side effects: * Information appears on the screen. * *-------------------------------------------------------------- */ static void DisplayLine(TextPtr, line, where) register Text *TextPtr; Line *line; int where; { register Tk_Window tkwin = TextPtr->tkwin; int startX, baseY, selStartX, selEndX, index, cursorX; int leftIndex, leftOffset; int selectFirst = -1, selectLast = -1; int height, width, count; /* * In order to avoid screen flashes, this procedure redraws the * textual area of the Text into off-screen memory, then copies * it back on-screen in a single operation. This means there's * no point in time where the on-screen image has been cleared. * First, make sure we've got a Pixmap large enough to do the * job. */ width = TextPtr->pmWidth; height = TextPtr->pmHeight; Tk_Fill3DRectangle(Tk_Display(tkwin), TextPtr->pm, TextPtr->normalBorder, 0, 0, TextPtr->pmWidth, TextPtr->pmHeight, 0, TK_RELIEF_FLAT); if (line == NULL) goto copy; /* * Compute x-coordinate of the "leftIndex" character, plus * vertical position of baseline of text. */ startX = 0; baseY = (TextPtr->pmHeight + TextPtr->fontPtr->ascent - TextPtr->fontPtr->descent)/2; /* compute the left index */ if (TextPtr->leftIndex > 0) { leftIndex = TkMeasureChars(TextPtr->fontPtr, line->text, line->length, 0, TextPtr->leftIndex * TextPtr->avgWidth, 0, &leftOffset); leftOffset -= TextPtr->leftIndex * TextPtr->avgWidth; } else { leftIndex = 0; leftOffset = 0; } /* compute selection offsets */ if (TextPtr->selectFirst < line->index + line->length && TextPtr->selectLast >= line->index) { selectFirst = TextPtr->selectFirst - line->index; if (selectFirst < 0) selectFirst = 0; selectLast = TextPtr->selectLast - line->index; if (selectLast >= line->length) selectLast = line->length - 1; } /* * Draw the background in three layers. From bottom to top the * layers are: normal background, selection background, and * insertion cursor background. */ Tk_Fill3DRectangle(Tk_Display(tkwin), TextPtr->pm, TextPtr->normalBorder, 0, 0, TextPtr->pmWidth, TextPtr->pmHeight, 0, TK_RELIEF_FLAT); if (selectLast >= leftIndex) { if (selectFirst <= leftIndex) { selStartX = startX; index = leftIndex; } else { (void) TkMeasureChars(TextPtr->fontPtr, line->text + leftIndex, selectFirst - leftIndex, startX, TextPtr->pmWidth, TK_PARTIAL_OK, &selStartX); index = selectFirst; } if (selStartX < TextPtr->pmWidth) { (void) TkMeasureChars(TextPtr->fontPtr, line->text + index, selectLast + 1 - index, selStartX, TextPtr->pmWidth, TK_PARTIAL_OK, &selEndX); Tk_Fill3DRectangle(Tk_Display(tkwin), TextPtr->pm, TextPtr->selBorder, selStartX - TextPtr->selBorderWidth, baseY - TextPtr->fontPtr->ascent - TextPtr->selBorderWidth, (selEndX - selStartX) + 2*TextPtr->selBorderWidth, TextPtr->fontPtr->ascent + TextPtr->fontPtr->descent + 2*TextPtr->selBorderWidth, TextPtr->selBorderWidth, TK_RELIEF_RAISED); } else { selEndX = TextPtr->pmWidth; } } /* * Draw a special background for the insertion cursor, overriding * even the selection background. As a special hack to keep the * cursor visible on mono displays, write background in the cursor * area (instead of nothing) when the cursor isn't on. Otherwise * the selection would hide the cursor. */ if (line == TextPtr->cursorLine && TextPtr->cursorPos >= line->index && TextPtr->cursorPos <= (line->index + line->length)) { (void) TkMeasureChars(TextPtr->fontPtr, line->text + leftIndex, TextPtr->cursorPos - line->index - leftIndex, startX, TextPtr->pmWidth, TK_PARTIAL_OK, &cursorX); if (cursorX <= TextPtr->pmWidth) { if (TextPtr->flags & CURSOR_ON) { Tk_Fill3DRectangle(Tk_Display(tkwin), TextPtr->pm, TextPtr->cursorBorder, cursorX - (TextPtr->cursorWidth)/2, baseY - TextPtr->fontPtr->ascent, TextPtr->cursorWidth, TextPtr->fontPtr->ascent + TextPtr->fontPtr->descent, TextPtr->cursorBorderWidth, TK_RELIEF_RAISED); } else if (DefaultDepthOfScreen(Tk_Screen(tkwin)) == 1) { Tk_Fill3DRectangle(Tk_Display(tkwin), TextPtr->pm, TextPtr->normalBorder, cursorX - (TextPtr->cursorWidth)/2, baseY - TextPtr->fontPtr->ascent, TextPtr->cursorWidth, TextPtr->fontPtr->ascent + TextPtr->fontPtr->descent, 0, TK_RELIEF_FLAT); } } } /* * Draw the text in three pieces: first the piece to the left of * the selection, then the selection, then the piece to the right * of the selection. */ if (selectLast < leftIndex) { TkDisplayChars(Tk_Display(tkwin), TextPtr->pm, TextPtr->textGC, TextPtr->fontPtr, line->text + leftIndex, line->length - leftIndex, startX, baseY); } else { count = selectFirst - leftIndex; if (count > 0) { TkDisplayChars(Tk_Display(tkwin), TextPtr->pm, TextPtr->textGC, TextPtr->fontPtr, line->text + leftIndex, count, startX, baseY); index = selectFirst; } else { index = leftIndex; } count = selectLast + 1 - index; if ((selStartX < TextPtr->pmWidth) && (count > 0)) { TkDisplayChars(Tk_Display(tkwin), TextPtr->pm, TextPtr->selTextGC, TextPtr->fontPtr, line->text + index, count, selStartX, baseY); } count = line->length - selectLast - 1; if ((selEndX < TextPtr->pmWidth) && (count > 0)) { TkDisplayChars(Tk_Display(tkwin), TextPtr->pm, TextPtr->textGC, TextPtr->fontPtr, line->text + selectLast + 1, count, selEndX, baseY); } } /* * Everything's been redisplayed; now copy the pixmap onto the screen. */ line->flags &= ~TEXT_LINE_DIRTY; copy: XCopyArea(Tk_Display(tkwin), TextPtr->pm, Tk_WindowId(tkwin), TextPtr->textGC, 0, 0, TextPtr->pmWidth, TextPtr->pmHeight, TextPtr->offset, TextPtr->borderWidth + where * TextPtr->pmHeight + 1); } /* *-------------------------------------------------------------- * * DisplayText -- * * This procedure redraws the contents of a text widget. * * Results: * None. * * Side effects: * Information appears on the screen. * *-------------------------------------------------------------- */ static void DisplayText(clientdata) char *clientdata; { register Text *TextPtr = (Text *)clientdata; register Tk_Window tkwin = TextPtr->tkwin; Line *line; int height, width; int where = 0; int visible; int displayedLines = 0; ScrollValues scrollValues; TextPtr->flags &= ~REDRAW_PENDING; if ((TextPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } if (TextPtr->flags & CLEAR_NEEDED) { XClearWindow(Tk_Display(tkwin), Tk_WindowId(tkwin)); } height = TextPtr->fontPtr->ascent + TextPtr->fontPtr->descent + 2*TextPtr->selBorderWidth; width = Tk_Width(tkwin) - 2*TextPtr->offset; if ((TextPtr->pmWidth != width) || (TextPtr->pmHeight != height)) { if (TextPtr->pm != None) { XFreePixmap(Tk_Display(tkwin), TextPtr->pm); } TextPtr->pm = XCreatePixmap(Tk_Display(tkwin), Tk_WindowId(tkwin), width, height, DefaultDepthOfScreen(Tk_Screen(tkwin))); TextPtr->pmDisplay = Tk_Display(tkwin); TextPtr->pmWidth = width; TextPtr->pmHeight = height; } visible = (Tk_Height(tkwin) - 2 * (TextPtr->borderWidth + 1)) / (TextPtr->fontPtr->ascent + TextPtr->fontPtr->descent); line = TextPtr->topLine; scrollValues.total = 0; scrollValues.window = Tk_Width(tkwin); scrollValues.first = 0; scrollValues.last = 0; while (visible--) { if ((TextPtr->flags & (REFRESH_NEEDED|CLEAR_NEEDED)) || (line != NULL && ((line->flags & TEXT_LINE_DIRTY) || (line == TextPtr->cursorLine))) || ((line == NULL) && (displayedLines < TextPtr->displayedLines))) DisplayLine(TextPtr, line, where); if (line != NULL) { if (line->width > scrollValues.total) scrollValues.total = line->width; line = line->next; displayedLines++; } where++; } TextPtr->displayedLines = displayedLines; if (scrollValues.total > scrollValues.window) scrollValues.last = scrollValues.window - 1; else scrollValues.last = scrollValues.total - 1; if ((scrollValues.first != TextPtr->lastScrollH.first) || (scrollValues.last != TextPtr->lastScrollH.last) || (scrollValues.total != TextPtr->lastScrollH.total) || (scrollValues.window != TextPtr->lastScrollH.window)) { TextPtr->lastScrollH = scrollValues; TextUpdateHScrollbar(TextPtr); } /* * Draw the border last, so it will overwrite any text that extends * past the viewable part of the window. */ if ((TextPtr->relief != TK_RELIEF_FLAT) && (TextPtr->flags & (CLEAR_NEEDED|BORDER_NEEDED|REFRESH_NEEDED))) { Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin), TextPtr->normalBorder, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin), TextPtr->borderWidth, TextPtr->relief); } TextPtr->flags &= ~(CLEAR_NEEDED | BORDER_NEEDED | REFRESH_NEEDED); } /* *-------------------------------------------------------------- * * EnsureCursorVisible -- * * This procedure adjusts the top visible line in a text * widget so the line containing the cursor is visible. * * Results: * None. * * Side effects: * The top line may be adjusted. * *-------------------------------------------------------------- */ static void EnsureCursorVisible(TextPtr) register Text *TextPtr; { int distance = TextPtr->cursorLine->num - TextPtr->topLine->num; if (distance < 0) { TextPtr->topLine = TextPtr->cursorLine; TextPtr->flags |= REFRESH_NEEDED; } else { int visible = (Tk_Height(TextPtr->tkwin) - 2 * (TextPtr->borderWidth + 1)) / (TextPtr->fontPtr->ascent + TextPtr->fontPtr->descent); if (distance >= visible) { int count = distance + 1 - visible; while (count--) TextPtr->topLine = TextPtr->topLine->next; TextPtr->flags |= REFRESH_NEEDED; } } } /* *-------------------------------------------------------------- * * FindLine -- * * This procedure locates a line in the linked list based * on the line number. * * Results: * A pointer to a line structure. * * Side effects: * None. * *-------------------------------------------------------------- */ static Line * FindLine(TextPtr, num) register Text *TextPtr; int num; { Line *line; if (TextPtr->topLine->num <= num) line = TextPtr->topLine; else line = TextPtr->firstLine; while (line->length != 0 && num > (line->num)) line = line->next; if (line->length == 0 && line->prev != NULL && line->prev->text[line->prev->length - 1] != '\n') line = line->prev; return line; } /* *-------------------------------------------------------------- * * FindLine -- * * This procedure locates a line in the linked list based * on a character position in the text. * * Results: * A pointer to a line structure. * * Side effects: * None. * *-------------------------------------------------------------- */ static Line * FindChar(TextPtr, index) register Text *TextPtr; int index; { Line *line; if (TextPtr->topLine->index <= index) line = TextPtr->topLine; else line = TextPtr->firstLine; while (line->length != 0 && index >= (line->index + line->length)) line = line->next; if (line->length == 0 && line->prev != NULL && line->prev->text[line->prev->length - 1] != '\n') line = line->prev; return line; } /* *-------------------------------------------------------------- * * NewLine -- * * Allocates memory and initializes a new line structure. * * Results: * A pointer to a line structure. * * Side effects: * Memory is allocated. * *-------------------------------------------------------------- */ static Line * NewLine() { Line *new; new = (Line *)ckalloc(sizeof(Line)); new->next = NULL; new->prev = NULL; new->flags = 0; new->size = 0; new->index = 0; new->length = 0; new->width = 0; new->text = NULL; return new; } /* *-------------------------------------------------------------- * * DestroyLine -- * * Deallocates a line structure. * * Results: * None. * * Side effects: * Memory is deallocated. * *-------------------------------------------------------------- */ static void DestroyLine(line) Line *line; { if (line->length == 0) fprintf(stderr, "\nattempt to delete last line\n"); if (line->mem != NULL) ckfree(line->mem); ckfree(line); } /* *-------------------------------------------------------------- * * LinkLine -- * * Inserts a line structure into a linked list. * * Results: * None. * * Side effects: * None. * *-------------------------------------------------------------- */ static void LinkLine(TextPtr, line, before) Text *TextPtr; Line *line, *before; { if (before == TextPtr->firstLine) TextPtr->firstLine = line; if (before == TextPtr->topLine) TextPtr->topLine = line; line->next = before; line->prev = before->prev; if (before->prev != NULL) before->prev->next = line; before->prev = line; if (line->width == 0) { (void) TkMeasureChars(TextPtr->fontPtr, line->text, line->length, 0, 1<<30, 0, &line->width); } TextPtr->numLines++; } /* *-------------------------------------------------------------- * * UnlinkLine -- * * Removes a line structure from a linked list. * * Results: * None. * * Side effects: * None. * *-------------------------------------------------------------- */ static void UnlinkLine(TextPtr, line) Text *TextPtr; Line *line; { if (line == TextPtr->firstLine) TextPtr->firstLine = line->next; if (line == TextPtr->topLine) TextPtr->topLine = line->next; if (line->prev != NULL) line->prev->next = line->next; if (line->next != NULL) line->next->prev = line->prev; TextPtr->numLines--; } /* *-------------------------------------------------------------- * * AppendToLine -- * * Appends text to a line. * * Results: * None. * * Side effects: * None. * *-------------------------------------------------------------- */ static void AppendToLine(line, text, length) Line *line; char *text; int length; { int space = line->size - ((line->text - line->mem) + line->length + 1); if (space < length) { char *old = line->text; line->size = line->length + length + 40; line->text = (char *)ckalloc(line->size); strcpy(line->text, old); ckfree(line->mem); line->mem = line->text; } strncpy(line->text + line->length, text, length); line->length += length; line->text[line->length] = '\0'; line->width = 0; } /* *-------------------------------------------------------------- * * JoinLines -- * * Joins two adjacent lines into one. * * Results: * None. * * Side effects: * Second line is unlinked and destroyed. * *-------------------------------------------------------------- */ static void JoinLines(TextPtr, line1, line2) Text *TextPtr; Line *line1, *line2; { int newwidth = 0; if (line2->length == 0) return; if (line1->width > 0 && line2->width > 0) newwidth = line1->width + line2->width; AppendToLine(line1, line2->text, line2->length); line1->flags |= TEXT_LINE_DIRTY; if (newwidth == 0) { (void) TkMeasureChars(TextPtr->fontPtr, line1->text, line1->length, 0, 1<<30, 0, &line1->width); } else line1->width = newwidth; UnlinkLine(TextPtr, line2); DestroyLine(line2); } /* *-------------------------------------------------------------- * * SplitLine -- * * Splits a line into two lines. * * Results: * None. * * Side effects: * A new line is allocated and linked in. * *-------------------------------------------------------------- */ static void SplitLine(TextPtr, line, where) Text *TextPtr; Line *line; int where; { Line *new; int left, right; int oldwidth = line->width; left = where; right = line->length - where; if (left == 0 || right == 0) return; new = NewLine(); if (left > right) { new->mem = new->text = (char *)strdup(line->text + where); new->length = line->length - where; new->size = new->length + 1; line->length = where; line->text[where] = '\0'; (void) TkMeasureChars(TextPtr->fontPtr, new->text, new->length, 0, 1<<30, 0, &new->width); /* if (line->text[where-1] != '\n') line->width -= new->width; else */ (void) TkMeasureChars(TextPtr->fontPtr, line->text, line->length, 0, 1<<30, 0, &line->width); } else { new->mem = line->mem; new->text = line->text; new->length = line->length - where; new->size = line->size; new->width = line->width; line->size = where + 1; line->mem = line->text = (char *)ckalloc(line->size); line->length = where; strncpy(line->text, new->text, line->length); line->text[line->length] = '\0'; (void) TkMeasureChars(TextPtr->fontPtr, line->text, line->length, 0, 1<<30, 0, &line->width); new->text = new->text + where; /* if (line->text[where-1] != '\n') new->width -= line->width; else {*/ (void) TkMeasureChars(TextPtr->fontPtr, new->text, new->length, 0, 1<<30, 0, &new->width); } line->flags |= TEXT_LINE_DIRTY; new->flags |= TEXT_LINE_DIRTY; LinkLine(TextPtr, new, line->next); } static void DumpLines(start, count) Line *start; { while (start != NULL && count-- > 0) { fprintf(stderr, "mem = %x, text = %x, size = %d, length = %d, width = %d\n", start->mem, start->text, start->size, start->length, start->width); if (start->length > 0) fprintf(stderr, "%.100s", start->text); fprintf(stderr, "\n---\n"); start = start->next; } } /* *-------------------------------------------------------------- * * FixLines -- * * Adjusts lines according to the wrapping mode. * * Results: * None. * * Side effects: * Lines are changed. * *-------------------------------------------------------------- */ static void FixLines(TextPtr, start, min) register Text *TextPtr; Line *start; int min; { char *cp; int next; int winwidth = Tk_Width(TextPtr->tkwin) - TextPtr->offset; int changed = 0; while ((start->length > 0) && (changed || (min-- > 0))) { int measureflags = 0; changed = 0; switch (TextPtr->wrapMode) { int where; case TK_WRAP_NONE: cp = strchr(start->text, '\n'); if (cp == NULL) { /* too few */ Line *next = start->next; /* get more */ while (next->length > 0 && (cp = strchr(next->text, '\n')) == NULL) { JoinLines(TextPtr, start, next); next = start->next; changed++; } if (next->length > 0) { where = cp - next->text + 1; if (where == next->length) { JoinLines(TextPtr, start, next); } else { SplitLine(TextPtr, next, where); JoinLines(TextPtr, start, next); } changed++; } } else if ((where = cp - start->text + 1) < start->length) { /* too many */ SplitLine(TextPtr, start, where); changed++; } break; case TK_WRAP_WORD: measureflags = TK_WHOLE_WORDS; /* fall */ case TK_WRAP_CHAR: if ((cp = strchr(start->text, '\n')) != NULL) where = cp - start->text + 1; else where = start->length; if (start->width < winwidth && cp == NULL) { Line *next = start->next; int remain = winwidth - start->width; /* get more */ if (next->length > 0 && (cp = strchr(next->text, '\n')) != NULL) where = cp - next->text + 1; else where = next->length; while (next->length > 0 && next->width < remain && where == next->length && cp == NULL) { remain -= next->width; JoinLines(TextPtr, start, next); next = start->next; if (next->length > 0 && (cp = strchr(next->text, '\n')) != NULL) where = cp - next->text + 1; else where = next->length; changed++; } if (next->length > 0) { int width; /* This bit doesn't quite work right for word wrap. Should really join the lines and then do the measuring. The way it is now, some wrapped words which should be coalesced aren't */ where = TkMeasureChars(TextPtr->fontPtr, next->text, next->length, 0, remain, measureflags, &width); if (next->text[where] == '\n') where++; /* else suck up white space? */ if (where == next->length) { JoinLines(TextPtr, start, next); changed++; } else if (where > 0) { SplitLine(TextPtr, next, where); JoinLines(TextPtr, start, next); changed++; } } } else if (start->width > winwidth) { int width; where = TkMeasureChars(TextPtr->fontPtr, start->text, start->length, 0, winwidth, measureflags, &width); if (start->text[where] == '\n') where++; if (where == 0) { /* degenerate case when wrap mode = WORD and leading whitespace extends beyond right border. We try again using character mode and require at least one */ where = TkMeasureChars(TextPtr->fontPtr, start->text, start->length, 0, winwidth, TK_AT_LEAST_ONE, &width); if (start->text[where] == '\n') where++; } SplitLine(TextPtr, start, where); changed++; } else if (where < start->length) { SplitLine(TextPtr, start, where); changed++; } break; case TK_WRAP_FIXED: if ((cp = strchr(start->text, '\n')) != NULL) where = cp - start->text + 1; else where = start->length; if (where < TextPtr->prefWidth && cp == NULL) { Line *next = start->next; int remain = TextPtr->prefWidth - where; /* get more */ if (next->length > 0 && (cp = strchr(next->text, '\n')) != NULL) where = cp - next->text + 1; else where = next->length; while (next->length > 0 && where < remain && where == next->length && cp == NULL) { remain -= next->length; JoinLines(TextPtr, start, next); next = start->next; if (next->length > 0 && (cp = strchr(next->text, '\n')) != NULL) where = cp - next->text + 1; else where = next->length; changed++; } if (next->length > 0) { if (where > remain) where = remain; if (where == next->length) { JoinLines(TextPtr, start, next); } else { SplitLine(TextPtr, next, where); JoinLines(TextPtr, start, next); } changed++; } } else if (where > TextPtr->prefWidth) { SplitLine(TextPtr, start, TextPtr->prefWidth); changed++; } else if (where < start->length) { SplitLine(TextPtr, start, where); changed++; } break; } start = start->next; } /* fix the indexes */ TextPtr->firstLine->index = 0; TextPtr->firstLine->num = next = 0; start = TextPtr->firstLine->next; while (start != NULL) { int newindex = start->prev->index + start->prev->length; next++; if (start->index != newindex || start->num != next) start->flags |= TEXT_LINE_DIRTY; start->index = start->prev->index + start->prev->length; start->num = next; start = start->next; } } /* *---------------------------------------------------------------------- * * InsertText -- * * Add new characters to a Text widget. * * Results: * None. * * Side effects: * New information gets added to TextPtr; it will be redisplayed * soon, but not necessarily immediately. * *---------------------------------------------------------------------- */ static void InsertText(TextPtr, index, string) register Text *TextPtr; /* Text that is to get the new * elements. */ int index; /* Add the new elements before this * element. */ char *string; /* New characters to add (NULL-terminated * string). */ { int length; int where; register Line *line, *new; length = strlen(string); if (length == 0) { return; } new = NewLine(); new->mem = new->text = (char *)strdup(string); new->length = length; new->size = new->length + 1; new->flags |= TEXT_LINE_DIRTY; line = FindChar(TextPtr, index); where = index - line->index; if (line->length == 0 || where == 0) { LinkLine(TextPtr, new, line); line = line->prev; } else { if (where < line->length) SplitLine(TextPtr, line, where); LinkLine(TextPtr, new, line->next); } if (line->prev != NULL) FixLines(TextPtr, line->prev, 4); else FixLines(TextPtr, line, 3); TextPtr->numChars += length; /* * Inserting characters invalidates all indexes into the string. * Touch up the indexes so that they still refer to the same * characters (at new positions). */ if (TextPtr->selectFirst >= index) { TextPtr->selectFirst += length; } if (TextPtr->selectLast >= index) { TextPtr->selectLast += length; } if (TextPtr->selectAnchor >= index) { TextPtr->selectAnchor += length; } if (TextPtr->leftIndex > index) { TextPtr->leftIndex += length; } if (TextPtr->cursorPos >= index) { TextPtr->cursorPos += length; } TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; TextPtr->cursorLine = FindChar(TextPtr, TextPtr->cursorPos); EnsureCursorVisible(TextPtr); EventuallyRedraw(TextPtr); TextUpdateVScrollbar(TextPtr); } /* *---------------------------------------------------------------------- * * DeleteText -- * * Remove one or more characters from a Text widget. * * Results: * None. * * Side effects: * Memory gets freed, the Text gets modified and (eventually) * redisplayed. * *---------------------------------------------------------------------- */ static void DeleteText(TextPtr, index, count) register Text *TextPtr; /* Text widget to modify. */ int index; /* Index of first character to delete. */ int count; /* How many characters to delete. */ { char *new; Line *l, *prev; int c, where; if ((index + count) > TextPtr->numChars) count = TextPtr->numChars - index; if (count <= 0) return; l = FindChar(TextPtr, index); prev = l->prev; c = count; /* first line */ where = index - l->index; if (where > 0) { if (c < l->length - where) { SplitLine(TextPtr, l, where + c); c = 0; } else c -= l->length - where; l->length = where; l->text[l->length] = '\0'; (void) TkMeasureChars(TextPtr->fontPtr, l->text, l->length, 0, 1<<30, 0, &l->width); l->flags |= TEXT_LINE_DIRTY; l = l->next; } /* intervening lines */ while (c > 0 && c >= l->length) { Line *victim = l; c -= l->length; l = l->next; UnlinkLine(TextPtr, victim); DestroyLine(victim); } /* last line */ if (c > 0) { l->text += c; l->length -= c; (void) TkMeasureChars(TextPtr->fontPtr, l->text, l->length, 0, 1<<30, 0, &l->width); l->flags |= TEXT_LINE_DIRTY; } TextPtr->numChars -= count; if (prev != NULL) FixLines(TextPtr, prev, 3); else FixLines(TextPtr, TextPtr->firstLine, 2); /* * Deleting characters results in the remaining characters being * renumbered. Update the various indexes into the string to reflect * this change. */ if (TextPtr->selectFirst >= index) { if (TextPtr->selectFirst >= (index+count)) { TextPtr->selectFirst -= count; } else { TextPtr->selectFirst = index; } } if (TextPtr->selectLast >= index) { if (TextPtr->selectLast >= (index+count)) { TextPtr->selectLast -= count; } else { TextPtr->selectLast = index-1; } } if (TextPtr->selectLast < TextPtr->selectFirst) { TextPtr->selectFirst = TextPtr->selectLast = -1; } if (TextPtr->selectAnchor >= index) { if (TextPtr->selectAnchor >= (index+count)) { TextPtr->selectAnchor -= count; } else { TextPtr->selectAnchor = index; } } if (TextPtr->leftIndex > index) { if (TextPtr->leftIndex >= (index+count)) { TextPtr->leftIndex -= count; } else { TextPtr->leftIndex = index; } } if (TextPtr->cursorPos >= index) { if (TextPtr->cursorPos >= (index+count)) { TextPtr->cursorPos -= count; } else { TextPtr->cursorPos = index; } } TextPtr->cursorLine->flags |= TEXT_LINE_DIRTY; TextPtr->cursorLine = FindChar(TextPtr, TextPtr->cursorPos); EnsureCursorVisible(TextPtr); EventuallyRedraw(TextPtr); TextUpdateVScrollbar(TextPtr); } /* *---------------------------------------------------------------------- * * GetText -- * * Get some text. * * Results: * None. * * Side effects: * interpreter's result is set. * *---------------------------------------------------------------------- */ static char * GetText(TextPtr, index, count) register Text *TextPtr; /* Text widget to modify. */ int index; /* Index of first character to delete. */ int count; /* How many characters to delete. */ { char *result, *cp; Line *line, *l; int c, where; if ((index + count) > TextPtr->numChars) count = TextPtr->numChars - index; if (count <= 0) return NULL; cp = result = (char *)ckalloc(count + 1); l = FindChar(TextPtr, index); c = count; /* first line */ where = index - l->index; if (where > 0) { int n; if (c < l->length - where) n = c; else n = l->length - where; strncpy(cp, l->text + where, n); cp += n; c -= n; l = l->next; } /* intervening lines */ while (c > 0 && c >= l->length) { strcpy(cp, l->text); c -= l->length; cp += l->length; l = l->next; } /* last line */ if (c > 0) { strncpy(cp, l->text, c); cp += c; c = 0; } *cp = '\0'; return result; } /* *-------------------------------------------------------------- * * TextEventProc -- * * This procedure is invoked by the Tk dispatcher for various * events on Textes. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get * cleaned up. When it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void TextEventProc(clientData, eventPtr) ClientData clientData; /* Information about window. */ XEvent *eventPtr; /* Information about event. */ { Text *TextPtr = (Text *) clientData; if (eventPtr->type == Expose) { EventuallyRedraw(TextPtr); TextPtr->flags |= (BORDER_NEEDED | REFRESH_NEEDED); } else if (eventPtr->type == DestroyNotify) { Tcl_DeleteCommand(TextPtr->interp, Tk_PathName(TextPtr->tkwin)); TextPtr->tkwin = NULL; if (TextPtr->flags & REDRAW_PENDING) { Tk_CancelIdleCall(DisplayText, (ClientData) TextPtr); } Tk_EventuallyFree((ClientData) TextPtr, DestroyText); } else if (eventPtr->type == ConfigureNotify) { Tk_Preserve((ClientData) TextPtr); EventuallyRedraw(TextPtr); TextUpdateVScrollbar(TextPtr); Tk_Release((ClientData) TextPtr); } } static int TextXYToIndex(TextPtr, x, y) Text *TextPtr; int x, y; { Line *line = TextPtr->topLine; int result, dummy; /* locate the line */ y -= TextPtr->borderWidth + 1; if (y > 0) { int count = y / TextPtr->pmHeight; while (line->length > 0 && count--) line = line->next; } if (line->length == 0) result = TextPtr->numChars; else result = line->index + TkMeasureChars(TextPtr->fontPtr, line->text, line->length, TextPtr->offset, x + TextPtr->leftIndex * TextPtr->avgWidth, 0, &dummy); return result; } /* *-------------------------------------------------------------- * * GetTextIndex -- * * Parse an index into a Text widget and return either its value * or an error. * * Results: * A standard Tcl result. If all went well, then *indexPtr is * filled in with the index (into TextPtr) corresponding to * string. Otherwise an error message is left in interp->result. * * Side effects: * None. * *-------------------------------------------------------------- */ static int GetTextIndex(interp, TextPtr, string, indexPtr) Tcl_Interp *interp; /* For error messages. */ Text *TextPtr; /* Text for which the index is being * specified. */ char *string; /* Numerical index into TextPtr's element * list, or "end" to refer to last element. */ int *indexPtr; /* Where to store converted relief. */ { int length; length = strlen(string); if (string[0] == 'e') { if (strncmp(string, "end", length) == 0) { *indexPtr = TextPtr->numChars; } else { badIndex: /* * Some of the paths here leave messages in interp->result, * so we have to clear it out before storing our own message. */ Tcl_SetResult(interp, (char *) NULL, TCL_STATIC); Tcl_AppendResult(interp, "bad Text index \"", string, "\"", (char *) NULL); return TCL_ERROR; } } else if (string[0] == 'c') { if (strncmp(string, "cursor", length) == 0) { *indexPtr = TextPtr->cursorPos; } else { goto badIndex; } } else if (string[0] == 's') { if (TextPtr->selectFirst == -1) { interp->result = "selection isn't in Text"; return TCL_ERROR; } if (length < 5) { goto badIndex; } if (strncmp(string, "sel.first", length) == 0) { *indexPtr = TextPtr->selectFirst; } else if (strncmp(string, "sel.last", length) == 0) { *indexPtr = TextPtr->selectLast; } else { goto badIndex; } } else if (string[0] == 't') { if (strncmp(string, "top", length) == 0) { *indexPtr = TextPtr->topLine->index; } else { goto badIndex; } } else if (string[0] == '@') { char *y_string; int x, y, dummy; if ((y_string = strchr(string+1, ',')) == NULL) goto badIndex; else *y_string++ = '\0'; if (Tcl_GetInt(interp, string+1, &x) != TCL_OK) { goto badIndex; } if (Tcl_GetInt(interp, y_string, &y) != TCL_OK) { goto badIndex; } if (TextPtr->numChars == 0) { *indexPtr = 0; } else { *indexPtr = TextXYToIndex(TextPtr, x, y); } } else { if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) { goto badIndex; } if (*indexPtr < 0){ *indexPtr = 0; } else if (*indexPtr > TextPtr->numChars) { *indexPtr = TextPtr->numChars; } } return TCL_OK; } /* *---------------------------------------------------------------------- * * TextScanTo -- * * Given a y-coordinate (presumably of the curent mouse location) * drag the view in the window to implement the scan operation. * * Results: * None. * * Side effects: * The view in the window may change. * *---------------------------------------------------------------------- */ static void TextScanTo(TextPtr, x) register Text *TextPtr; /* Information about widget. */ int x; /* X-coordinate to use for scan * operation. */ { int newLeftIndex; /* * Compute new leftIndex for Text by amplifying the difference * between the current position and the place where the scan * started (the "mark" position). If we run off the left or right * side of the Text, then reset the mark point so that the current * position continues to correspond to the edge of the window. * This means that the picture will start dragging as soon as the * mouse reverses direction (without this reset, might have to slide * mouse a long ways back before the picture starts moving again). */ newLeftIndex = TextPtr->scanMarkIndex - (10*(x - TextPtr->scanMarkX))/TextPtr->avgWidth; if (newLeftIndex < 0) { newLeftIndex = TextPtr->scanMarkIndex = 0; TextPtr->scanMarkX = x; } else if (newLeftIndex >= TextPtr->numChars) { newLeftIndex = TextPtr->scanMarkIndex = TextPtr->numChars-1; TextPtr->scanMarkX = x; } if (newLeftIndex != TextPtr->leftIndex) { TextPtr->leftIndex = newLeftIndex; EventuallyRedraw(TextPtr); TextUpdateVScrollbar(TextPtr); } } /* *---------------------------------------------------------------------- * * TextSelectTo -- * * Modify the selection by moving its un-anchored end. This could * make the selection either larger or smaller. * * Results: * None. * * Side effects: * The selection changes. * *---------------------------------------------------------------------- */ static void TextSelectTo(TextPtr, index) register Text *TextPtr; /* Information about widget. */ int index; /* Index of element that is to * become the "other" end of the * selection. */ { int newFirst, newLast; /* * Grab the selection if we don't own it already. */ if (TextPtr->selectFirst == -1) { Tk_OwnSelection(TextPtr->tkwin, TextLostSelection, (ClientData) TextPtr); } if (index < 0) { index = 0; } if (index >= TextPtr->numChars) { index = TextPtr->numChars-1; } if (TextPtr->selectAnchor > TextPtr->numChars) { TextPtr->selectAnchor = TextPtr->numChars; } if (TextPtr->selectAnchor <= index) { newFirst = TextPtr->selectAnchor; newLast = index; } else { newFirst = index; newLast = TextPtr->selectAnchor - 1; if (newLast < 0) { newFirst = newLast = -1; } } if ((TextPtr->selectFirst == newFirst) && (TextPtr->selectLast == newLast)) { return; } TextPtr->selectFirst = newFirst; TextPtr->selectLast = newLast; TextPtr->flags |= REFRESH_NEEDED; EventuallyRedraw(TextPtr); } /* *---------------------------------------------------------------------- * * TextFetchSelection -- * * This procedure is called back by Tk when the selection is * requested by someone. It returns part or all of the selection * in a buffer provided by the caller. * * Results: * The return value is the number of non-NULL bytes stored * at buffer. Buffer is filled (or partially filled) with a * NULL-terminated string containing part or all of the selection, * as given by offset and maxBytes. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int TextFetchSelection(clientData, offset, buffer, maxBytes) ClientData clientData; /* Information about Text widget. */ int offset; /* Offset within selection of first * character to be returned. */ char *buffer; /* Location in which to place * selection. */ int maxBytes; /* Maximum number of bytes to place * at buffer, not including terminating * NULL character. */ { Text *TextPtr = (Text *) clientData; char *text; int count; if (TextPtr->selectFirst < 0) { return -1; } count = TextPtr->selectLast + 1 - TextPtr->selectFirst - offset; if (count > maxBytes) { count = maxBytes; } if (count <= 0) { return 0; } text = GetText(TextPtr, TextPtr->selectFirst + offset, count); if (text != NULL) { strcpy(buffer, text); ckfree(text); } else count = 0; return count; } /* *---------------------------------------------------------------------- * * TextLostSelection -- * * This procedure is called back by Tk when the selection is * grabbed away from a Text widget. * * Results: * None. * * Side effects: * The existing selection is unhighlighted, and the window is * marked as not containing a selection. * *---------------------------------------------------------------------- */ static void TextLostSelection(clientData) ClientData clientData; /* Information about Text widget. */ { Text *TextPtr = (Text *) clientData; TextPtr->selectFirst = -1; TextPtr->selectLast = -1; EventuallyRedraw(TextPtr); } /* *---------------------------------------------------------------------- * * EventuallyRedraw -- * * Ensure that a Text is eventually redrawn on the display. * * Results: * None. * * Side effects: * Information gets redisplayed. Right now we don't do selective * redisplays: the whole window will be redrawn. This doesn't * seem to hurt performance noticeably, but if it does then this * could be changed. * *---------------------------------------------------------------------- */ static void EventuallyRedraw(TextPtr) register Text *TextPtr; /* Information about widget. */ { if ((TextPtr->tkwin == NULL) || !Tk_IsMapped(TextPtr->tkwin)) { return; } /* * Right now we don't do selective redisplays: the whole window * will be redrawn. This doesn't seem to hurt performance noticeably, * but if it does then this could be changed. */ if (!(TextPtr->flags & REDRAW_PENDING)) { TextPtr->flags |= REDRAW_PENDING; Tk_DoWhenIdle(DisplayText, (ClientData) TextPtr); } } /* *---------------------------------------------------------------------- * * TextUpdateHScrollbar -- * * This procedure is invoked whenever information has changed in * a Text in a way that would invalidate a scrollbar display. * If there is an associated scrollbar, then this command updates * it by invoking a Tcl command. * * Results: * None. * * Side effects: * A Tcl command is invoked, and an additional command may be * invoked to process errors in the command. * *---------------------------------------------------------------------- */ static void TextUpdateHScrollbar(TextPtr) register Text *TextPtr; /* Information about widget. */ { char args[100]; int result; if (TextPtr->scrollHCmd != NULL) { sprintf(args, " %d %d %d %d", TextPtr->lastScrollH.total, TextPtr->lastScrollH.window, TextPtr->lastScrollH.first, TextPtr->lastScrollH.last); result = Tcl_VarEval(TextPtr->interp, TextPtr->scrollHCmd, args, (char *) NULL); if (result != TCL_OK) { TkBindError(TextPtr->interp); } } } /* *---------------------------------------------------------------------- * * TextUpdateVScrollbar -- * * This procedure is invoked whenever information has changed in * a Text in a way that would invalidate a scrollbar display. * If there is an associated scrollbar, then this command updates * it by invoking a Tcl command. * * Results: * None. * * Side effects: * A Tcl command is invoked, and an additional command may be * invoked to process errors in the command. * *---------------------------------------------------------------------- */ static void TextUpdateVScrollbar(TextPtr) register Text *TextPtr; /* Information about widget. */ { char args[100]; int result; if (TextPtr->scrollVCmd != NULL) { ScrollValues scrollValues; scrollValues.window = (Tk_Height(TextPtr->tkwin) - 2 * (TextPtr->borderWidth + 1)) / (TextPtr->fontPtr->ascent + TextPtr->fontPtr->descent); scrollValues.first = TextPtr->topLine->num; scrollValues.last = scrollValues.first + scrollValues.window - 1; if (scrollValues.last >= TextPtr->lastLine->num) scrollValues.last = TextPtr->lastLine->num - 1; if (scrollValues.last < 0) scrollValues.last = 0; scrollValues.total = TextPtr->numLines; if ((scrollValues.first != TextPtr->lastScrollV.first) || (scrollValues.last != TextPtr->lastScrollV.last) || (scrollValues.total != TextPtr->lastScrollV.total) || (scrollValues.window != TextPtr->lastScrollV.window)) { sprintf(args, " %d %d %d %d", scrollValues.total, scrollValues.window, scrollValues.first, scrollValues.last); result = Tcl_VarEval(TextPtr->interp, TextPtr->scrollVCmd, args, (char *) NULL); if (result != TCL_OK) { TkBindError(TextPtr->interp); } TextPtr->lastScrollV = scrollValues; } } Tcl_SetResult(TextPtr->interp, (char *) NULL, TCL_STATIC); } /* *---------------------------------------------------------------------- * * TextBlinkProc -- * * This procedure is called as a timer handler to blink the * insertion cursor off and on. * * Results: * None. * * Side effects: * The cursor gets turned on or off, redisplay gets invoked, * and this procedure reschedules itself. * *---------------------------------------------------------------------- */ static void TextBlinkProc(clientData) ClientData clientData; /* Pointer to record describing Text. */ { register Text *TextPtr = (Text *) clientData; if (!(TextPtr->flags & GOT_FOCUS) || (TextPtr->cursorOffTime == 0)) { return; } if (TextPtr->flags & CURSOR_ON) { TextPtr->flags &= ~CURSOR_ON; TextPtr->cursorBlinkHandler = Tk_CreateTimerHandler( TextPtr->cursorOffTime, TextBlinkProc, (ClientData) TextPtr); } else { TextPtr->flags |= CURSOR_ON; TextPtr->cursorBlinkHandler = Tk_CreateTimerHandler( TextPtr->cursorOnTime, TextBlinkProc, (ClientData) TextPtr); } EventuallyRedraw(TextPtr); } /* *---------------------------------------------------------------------- * * TextFocusProc -- * * This procedure is called whenever the Text gets or loses the * input focus. It's also called whenever the window is reconfigured * while it has the focus. * * Results: * None. * * Side effects: * The cursor gets turned on or off. * *---------------------------------------------------------------------- */ static void TextFocusProc(clientData, gotFocus) ClientData clientData; /* Pointer to structure describing Text. */ int gotFocus; /* 1 means window is getting focus, 0 means * it's losing it. */ { register Text *TextPtr = (Text *) clientData; Tk_DeleteTimerHandler(TextPtr->cursorBlinkHandler); if (gotFocus) { TextPtr->flags |= GOT_FOCUS | CURSOR_ON; if (TextPtr->cursorOffTime != 0) { TextPtr->cursorBlinkHandler = Tk_CreateTimerHandler( TextPtr->cursorOnTime, TextBlinkProc, (ClientData) TextPtr); } } else { TextPtr->flags &= ~(GOT_FOCUS | CURSOR_ON); TextPtr->cursorBlinkHandler = (Tk_TimerToken) NULL; } EventuallyRedraw(TextPtr); }