/* * tkTable.c -- * * This module implements table widgets for the Tk * toolkit. An table displays a 2D array of strings * and allows the strings to be edited. * * Based on Tk3 table widget written by Roland King * * Updates 1996 by: * Jeffrey Hobbs jeff.hobbs@acm.org * John Ellson ellson@lucent.com * Peter Bruecker peter@bj-ig.de * Tom Moore tmoore@spatial.ca * Sebastian Wangnick wangnick@orthogon.de * * Copyright (c) 1997-2001 Jeffrey Hobbs * * See the file "license.txt" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkTable.c,v 1.15 2001/07/01 01:33:17 hobbs Exp $ */ #include "tkTable.h" #ifdef DEBUG #include "dprint.h" #endif static char ** StringifyObjects _ANSI_ARGS_((int objc, Tcl_Obj *CONST objv[])); static int Tk_TableObjCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); static int TableWidgetObjCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])); static int TableConfigure _ANSI_ARGS_((Tcl_Interp *interp, Table *tablePtr, int objc, Tcl_Obj *CONST objv[], int flags, int forceUpdate)); static void TableDestroy _ANSI_ARGS_((ClientData clientdata)); static void TableEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); static void TableCmdDeletedProc _ANSI_ARGS_((ClientData clientData)); static void TableRedrawHighlight _ANSI_ARGS_((Table *tablePtr)); static void TableGetGc _ANSI_ARGS_((Display *display, Drawable d, TableTag *tagPtr, GC *tagGc)); static void TableDisplay _ANSI_ARGS_((ClientData clientdata)); static void TableFlashEvent _ANSI_ARGS_((ClientData clientdata)); static char * TableVarProc _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, char *name, char *index, int flags)); static void TableCursorEvent _ANSI_ARGS_((ClientData clientData)); static int TableFetchSelection _ANSI_ARGS_((ClientData clientData, int offset, char *buffer, int maxBytes)); static Tk_RestrictAction TableRestrictProc _ANSI_ARGS_((ClientData arg, XEvent *eventPtr)); /* * The following tables define the widget commands (and sub- * commands) and map the indexes into the string tables into * enumerated types used to dispatch the widget command. */ static char *selCmdNames[] = { "anchor", "clear", "includes", "present", "set", (char *)NULL }; enum selCommand { CMD_SEL_ANCHOR, CMD_SEL_CLEAR, CMD_SEL_INCLUDES, CMD_SEL_PRESENT, CMD_SEL_SET }; static char *commandNames[] = { "activate", "bbox", "border", "cget", "clear", "configure", "curselection", "curvalue", "delete", "get", "height", "hidden", "icursor", "index", "insert", #ifdef POSTSCRIPT "postscript", #endif "reread", "scan", "see", "selection", "set", "spans", "tag", "validate", "version", "window", "width", "xview", "yview", (char *)NULL }; enum command { CMD_ACTIVATE, CMD_BBOX, CMD_BORDER, CMD_CGET, CMD_CLEAR, CMD_CONFIGURE, CMD_CURSEL, CMD_CURVALUE, CMD_DELETE, CMD_GET, CMD_HEIGHT, CMD_HIDDEN, CMD_ICURSOR, CMD_INDEX, CMD_INSERT, #ifdef POSTSCRIPT CMD_POSTSCRIPT, #endif CMD_REREAD, CMD_SCAN, CMD_SEE, CMD_SELECTION, CMD_SET, CMD_SPANS, CMD_TAG, CMD_VALIDATE, CMD_VERSION, CMD_WINDOW, CMD_WIDTH, CMD_XVIEW, CMD_YVIEW }; /* -selecttype selection type options */ static Cmd_Struct sel_vals[]= { {"row", SEL_ROW}, {"col", SEL_COL}, {"both", SEL_BOTH}, {"cell", SEL_CELL}, {"", 0 } }; /* -resizeborders options */ static Cmd_Struct resize_vals[]= { {"row", SEL_ROW}, /* allow rows to be dragged */ {"col", SEL_COL}, /* allow cols to be dragged */ {"both", SEL_ROW|SEL_COL}, /* allow either to be dragged */ {"none", SEL_NONE}, /* allow nothing to be dragged */ {"", 0 } }; /* drawmode values */ /* The display redraws with a pixmap using TK function calls */ #define DRAW_MODE_SLOW (1<<0) /* The redisplay is direct to the screen, but TK function calls are still * used to give correct 3-d border appearance and thus remain compatible * with other TK apps */ #define DRAW_MODE_TK_COMPAT (1<<1) /* the redisplay goes straight to the screen and the 3d borders are rendered * with a single pixel wide line only. It cheats and uses the internal * border structure to do the borders */ #define DRAW_MODE_FAST (1<<2) #define DRAW_MODE_SINGLE (1<<3) static Cmd_Struct drawmode_vals[] = { {"fast", DRAW_MODE_FAST}, {"compatible", DRAW_MODE_TK_COMPAT}, {"slow", DRAW_MODE_SLOW}, {"single", DRAW_MODE_SINGLE}, {"", 0} }; /* stretchmode values */ #define STRETCH_MODE_NONE (1<<0) /* No additional pixels will be added to rows or cols */ #define STRETCH_MODE_UNSET (1<<1) /* All default rows or columns will be stretched to fill the screen */ #define STRETCH_MODE_ALL (1<<2) /* All rows/columns will be padded to fill the window */ #define STRETCH_MODE_LAST (1<<3) /* Stretch last elememt to fill window */ #define STRETCH_MODE_FILL (1<<4) /* More ROWS in Window */ static Cmd_Struct stretch_vals[] = { {"none", STRETCH_MODE_NONE}, {"unset", STRETCH_MODE_UNSET}, {"all", STRETCH_MODE_ALL}, {"last", STRETCH_MODE_LAST}, {"fill", STRETCH_MODE_FILL}, {"", 0} }; static Cmd_Struct state_vals[]= { {"normal", STATE_NORMAL}, {"disabled", STATE_DISABLED}, {"", 0 } }; /* The widget configuration table */ static Tk_CustomOption drawOpt = { Cmd_OptionSet, Cmd_OptionGet, (ClientData)(&drawmode_vals) }; static Tk_CustomOption resizeTypeOpt = { Cmd_OptionSet, Cmd_OptionGet, (ClientData)(&resize_vals) }; static Tk_CustomOption stretchOpt = { Cmd_OptionSet, Cmd_OptionGet, (ClientData)(&stretch_vals) }; static Tk_CustomOption selTypeOpt = { Cmd_OptionSet, Cmd_OptionGet, (ClientData)(&sel_vals) }; static Tk_CustomOption stateTypeOpt = { Cmd_OptionSet, Cmd_OptionGet, (ClientData)(&state_vals) }; static Tk_CustomOption bdOpt = { TableOptionBdSet, TableOptionBdGet, (ClientData) BD_TABLE }; Tk_ConfigSpec tableSpecs[] = { {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", "center", Tk_Offset(Table, defaultTag.anchor), 0}, {TK_CONFIG_BOOLEAN, "-autoclear", "autoClear", "AutoClear", "0", Tk_Offset(Table, autoClear), 0}, {TK_CONFIG_BORDER, "-background", "background", "Background", NORMAL_BG, Tk_Offset(Table, defaultTag.bg), 0}, {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_CURSOR, "-bordercursor", "borderCursor", "Cursor", "crosshair", Tk_Offset(Table, bdcursor), TK_CONFIG_NULL_OK }, {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth", "1", Tk_Offset(Table, defaultTag), TK_CONFIG_NULL_OK, &bdOpt }, {TK_CONFIG_STRING, "-browsecommand", "browseCommand", "BrowseCommand", "", Tk_Offset(Table, browseCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_SYNONYM, "-browsecmd", "browseCommand", (char *)NULL, (char *)NULL, 0, TK_CONFIG_NULL_OK}, {TK_CONFIG_BOOLEAN, "-cache", "cache", "Cache", "0", Tk_Offset(Table, caching), 0}, {TK_CONFIG_INT, "-colorigin", "colOrigin", "Origin", "0", Tk_Offset(Table, colOffset), 0}, {TK_CONFIG_INT, "-cols", "cols", "Cols", "10", Tk_Offset(Table, cols), 0}, {TK_CONFIG_STRING, "-colseparator", "colSeparator", "Separator", NULL, Tk_Offset(Table, colSep), TK_CONFIG_NULL_OK }, {TK_CONFIG_CUSTOM, "-colstretchmode", "colStretch", "StretchMode", "none", Tk_Offset (Table, colStretch), 0 , &stretchOpt }, {TK_CONFIG_STRING, "-coltagcommand", "colTagCommand", "TagCommand", NULL, Tk_Offset(Table, colTagCmd), TK_CONFIG_NULL_OK }, {TK_CONFIG_INT, "-colwidth", "colWidth", "ColWidth", "10", Tk_Offset(Table, defColWidth), 0}, {TK_CONFIG_STRING, "-command", "command", "Command", "", Tk_Offset(Table, command), TK_CONFIG_NULL_OK}, {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", "xterm", Tk_Offset(Table, cursor), TK_CONFIG_NULL_OK }, {TK_CONFIG_CUSTOM, "-drawmode", "drawMode", "DrawMode", "compatible", Tk_Offset(Table, drawMode), 0, &drawOpt }, {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection", "ExportSelection", "1", Tk_Offset(Table, exportSelection), 0}, {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_BOOLEAN, "-flashmode", "flashMode", "FlashMode", "0", Tk_Offset(Table, flashMode), 0}, {TK_CONFIG_INT, "-flashtime", "flashTime", "FlashTime", "2", Tk_Offset(Table, flashTime), 0}, {TK_CONFIG_FONT, "-font", "font", "Font", DEF_TABLE_FONT, Tk_Offset(Table, defaultTag.tkfont), 0}, {TK_CONFIG_BORDER, "-foreground", "foreground", "Foreground", "black", Tk_Offset(Table, defaultTag.fg), 0}, #ifdef PROCS {TK_CONFIG_BOOLEAN, "-hasprocs", "hasProcs", "hasProcs", "0", Tk_Offset(Table, hasProcs), 0}, #endif {TK_CONFIG_INT, "-height", "height", "Height", "0", Tk_Offset(Table, maxReqRows), 0}, {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", "HighlightBackground", NORMAL_BG, Tk_Offset(Table, highlightBgColorPtr), 0}, {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", HIGHLIGHT, Tk_Offset(Table, highlightColorPtr), 0}, {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", "HighlightThickness", "2", Tk_Offset(Table, highlightWidth), 0}, {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground", "Black", Tk_Offset(Table, insertBg), 0}, {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth", "0", Tk_Offset(Table, insertBorderWidth), TK_CONFIG_COLOR_ONLY}, {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth", "0", Tk_Offset(Table, insertBorderWidth), TK_CONFIG_MONO_ONLY}, {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime", "300", Tk_Offset(Table, insertOffTime), 0}, {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime", "600", Tk_Offset(Table, insertOnTime), 0}, {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth", "2", Tk_Offset(Table, insertWidth), 0}, {TK_CONFIG_BOOLEAN, "-invertselected", "invertSelected", "InvertSelected", "0", Tk_Offset(Table, invertSelected), 0}, {TK_CONFIG_PIXELS, "-ipadx", "ipadX", "Pad", "0", Tk_Offset(Table, ipadX), 0}, {TK_CONFIG_PIXELS, "-ipady", "ipadY", "Pad", "0", Tk_Offset(Table, ipadY), 0}, {TK_CONFIG_PIXELS, "-maxheight", "maxHeight", "MaxHeight", "600", Tk_Offset(Table, maxReqHeight), 0}, {TK_CONFIG_PIXELS, "-maxwidth", "maxWidth", "MaxWidth", "800", Tk_Offset(Table, maxReqWidth), 0}, {TK_CONFIG_BOOLEAN, "-multiline", "multiline", "Multiline", "1", Tk_Offset(Table, defaultTag.multiline), 0}, {TK_CONFIG_PIXELS, "-padx", "padX", "Pad", "0", Tk_Offset(Table, padX), 0}, {TK_CONFIG_PIXELS, "-pady", "padY", "Pad", "0", Tk_Offset(Table, padY), 0}, {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", "sunken", Tk_Offset(Table, defaultTag.relief), 0}, {TK_CONFIG_CUSTOM, "-resizeborders", "resizeBorders", "ResizeBorders", "both", Tk_Offset(Table, resize), 0, &resizeTypeOpt }, {TK_CONFIG_INT, "-rowheight", "rowHeight", "RowHeight", "1", Tk_Offset(Table, defRowHeight), 0}, {TK_CONFIG_INT, "-roworigin", "rowOrigin", "Origin", "0", Tk_Offset(Table, rowOffset), 0}, {TK_CONFIG_INT, "-rows", "rows", "Rows", "10", Tk_Offset(Table, rows), 0}, {TK_CONFIG_STRING, "-rowseparator", "rowSeparator", "Separator", NULL, Tk_Offset(Table, rowSep), TK_CONFIG_NULL_OK }, {TK_CONFIG_CUSTOM, "-rowstretchmode", "rowStretch", "StretchMode", "none", Tk_Offset(Table, rowStretch), 0 , &stretchOpt }, {TK_CONFIG_STRING, "-rowtagcommand", "rowTagCommand", "TagCommand", NULL, Tk_Offset(Table, rowTagCmd), TK_CONFIG_NULL_OK }, {TK_CONFIG_SYNONYM, "-selcmd", "selectionCommand", (char *)NULL, (char *)NULL, 0, TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-selectioncommand", "selectionCommand", "SelectionCommand", NULL, Tk_Offset(Table, selCmd), TK_CONFIG_NULL_OK }, {TK_CONFIG_STRING, "-selectmode", "selectMode", "SelectMode", "browse", Tk_Offset(Table, selectMode), TK_CONFIG_NULL_OK }, {TK_CONFIG_BOOLEAN, "-selecttitles", "selectTitles", "SelectTitles", "0", Tk_Offset(Table, selectTitles), 0}, {TK_CONFIG_CUSTOM, "-selecttype", "selectType", "SelectType", "cell", Tk_Offset(Table, selectType), 0, &selTypeOpt }, #ifdef PROCS {TK_CONFIG_BOOLEAN, "-showprocs", "showProcs", "showProcs", "0", Tk_Offset(Table, showProcs), 0}, #endif {TK_CONFIG_BOOLEAN, "-sparsearray", "sparseArray", "SparseArray", "1", Tk_Offset(Table, sparse), 0}, {TK_CONFIG_CUSTOM, "-state", "state", "State", "normal", Tk_Offset(Table, state), 0, &stateTypeOpt}, {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", (char *)NULL, Tk_Offset(Table, takeFocus), TK_CONFIG_NULL_OK }, {TK_CONFIG_INT, "-titlecols", "titleCols", "TitleCols", "0", Tk_Offset(Table, titleCols), TK_CONFIG_NULL_OK }, {TK_CONFIG_INT, "-titlerows", "titleRows", "TitleRows", "0", Tk_Offset(Table, titleRows), TK_CONFIG_NULL_OK }, {TK_CONFIG_BOOLEAN, "-usecommand", "useCommand", "UseCommand", "1", Tk_Offset(Table, useCmd), 0}, {TK_CONFIG_STRING, "-variable", "variable", "Variable", (char *)NULL, Tk_Offset(Table, arrayVar), TK_CONFIG_NULL_OK }, {TK_CONFIG_BOOLEAN, "-validate", "validate", "Validate", "0", Tk_Offset(Table, validate), 0}, {TK_CONFIG_STRING, "-validatecommand", "validateCommand", "ValidateCommand", "", Tk_Offset(Table, valCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_SYNONYM, "-vcmd", "validateCommand", (char *)NULL, (char *)NULL, 0, TK_CONFIG_NULL_OK}, {TK_CONFIG_INT, "-width", "width", "Width", "0", Tk_Offset(Table, maxReqCols), 0}, {TK_CONFIG_BOOLEAN, "-wrap", "wrap", "Wrap", "0", Tk_Offset(Table, defaultTag.wrap), 0}, {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", NULL, Tk_Offset(Table, xScrollCmd), TK_CONFIG_NULL_OK }, {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", NULL, Tk_Offset(Table, yScrollCmd), TK_CONFIG_NULL_OK }, {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, (char *)NULL, 0, 0} }; /* * This specifies the configure options that will cause an update to * occur, so we should have a quick lookup table for them. * Keep this in sync with the above values. */ static char *updateOpts[] = { "-anchor", "-background", "-bg", "-bd", "-borderwidth", "-cache", "-command", "-colorigin", "-cols", "-colstretchmode", "-coltagcommand", "-drawmode", "-fg", "-font", "-foreground", "-hasprocs", "-height", "-highlightbackground", "-highlightcolor", "-highlightthickness", "-insertbackground", "-insertborderwidth", "-insertwidth", "-invertselected", "-ipadx", "-ipady", "-maxheight", "-maxwidth", "-multiline", "-padx", "-pady", "-relief", "-roworigin", "-rows", "-rowstretchmode", "-rowtagcommand", "-showprocs", "-state", "-titlecols", "-titlerows", "-usecommand", "-variable", "-width", "-wrap", "-xscrollcommand", "-yscrollcommand", (char *) NULL }; #ifdef WIN32 /* * Some code from TkWinInt.h that we use to correct and speed up * drawing of cells that need clipping in TableDisplay. */ typedef struct { int type; HWND handle; void *winPtr; } TkWinWindow; typedef struct { int type; HBITMAP handle; Colormap colormap; int depth; } TkWinBitmap; typedef struct { int type; HDC hdc; } TkWinDC; typedef union { int type; TkWinWindow window; TkWinBitmap bitmap; TkWinDC winDC; } TkWinDrawable; #endif /* * END HEADER INFORMATION */ /* *--------------------------------------------------------------------------- * * StringifyObjects -- (from tclCmdAH.c) * * Helper function to bridge the gap between an object-based procedure * and an older string-based procedure. * * Given an array of objects, allocate an array that consists of the * string representations of those objects. * * Results: * The return value is a pointer to the newly allocated array of * strings. Elements 0 to (objc-1) of the string array point to the * string representation of the corresponding element in the source * object array; element objc of the string array is NULL. * * Side effects: * Memory allocated. The caller must eventually free this memory * by calling ckfree() on the return value. * int result; char **argv; argv = StringifyObjects(objc, objv); result = StringBasedCmd(interp, objc, argv); ckfree((char *) argv); return result; * *--------------------------------------------------------------------------- */ static char ** StringifyObjects(objc, objv) int objc; /* Number of arguments. */ Tcl_Obj *CONST objv[]; /* Argument objects. */ { int i; char **argv; argv = (char **) ckalloc((objc + 1) * sizeof(char *)); for (i = 0; i < objc; i++) { argv[i] = Tcl_GetString(objv[i]); } argv[i] = NULL; return argv; } /* * As long as we wait for the Function in general * * This parses the "-class" option for the table. */ static int Tk_ClassOptionObjCmd(Tk_Window tkwin, char *defaultclass, int objc, Tcl_Obj *CONST objv[]) { char *classname = defaultclass; int offset = 0; if ((objc >= 4) && STREQ(Tcl_GetString(objv[2]),"-class")) { classname = Tcl_GetString(objv[3]); offset = 2; } Tk_SetClass(tkwin, classname); return offset; } /* *-------------------------------------------------------------- * * Tk_TableObjCmd -- * This procedure is invoked to process the "table" Tcl * command. See the user documentation for details on what * it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ static int Tk_TableObjCmd(clientData, interp, objc, objv) ClientData clientData; /* Main window associated with interpreter. */ Tcl_Interp *interp; int objc; /* Number of arguments. */ Tcl_Obj *CONST objv[]; /* Argument objects. */ { register Table *tablePtr; Tk_Window tkwin, mainWin = (Tk_Window) clientData; int offset; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?"); return TCL_ERROR; } tkwin = Tk_CreateWindowFromPath(interp, mainWin, Tcl_GetString(objv[1]), (char *)NULL); if (tkwin == NULL) { return TCL_ERROR; } tablePtr = (Table *) ckalloc(sizeof(Table)); memset((VOID *) tablePtr, 0, sizeof(Table)); /* * Set the structure elments that aren't 0/NULL by default, * and that won't be set by the initial configure call. */ tablePtr->tkwin = tkwin; tablePtr->display = Tk_Display(tkwin); tablePtr->interp = interp; tablePtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(tablePtr->tkwin), TableWidgetObjCmd, (ClientData) tablePtr, (Tcl_CmdDeleteProc *) TableCmdDeletedProc); tablePtr->anchorRow = -1; tablePtr->anchorCol = -1; tablePtr->activeRow = -1; tablePtr->activeCol = -1; tablePtr->oldTopRow = -1; tablePtr->oldLeftCol = -1; tablePtr->oldActRow = -1; tablePtr->oldActCol = -1; tablePtr->seen[0] = -1; tablePtr->dataSource = DATA_NONE; tablePtr->activeBuf = ckalloc(1); *(tablePtr->activeBuf) = '\0'; tablePtr->cursor = None; tablePtr->bdcursor = None; tablePtr->defaultTag.justify = TK_JUSTIFY_LEFT; tablePtr->defaultTag.state = STATE_UNKNOWN; /* misc tables */ tablePtr->tagTable = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->tagTable, TCL_STRING_KEYS); tablePtr->winTable = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->winTable, TCL_STRING_KEYS); /* internal value cache */ tablePtr->cache = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS); /* style hash tables */ tablePtr->colWidths = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->colWidths, TCL_ONE_WORD_KEYS); tablePtr->rowHeights = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->rowHeights, TCL_ONE_WORD_KEYS); /* style hash tables */ tablePtr->rowStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->rowStyles, TCL_ONE_WORD_KEYS); tablePtr->colStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->colStyles, TCL_ONE_WORD_KEYS); tablePtr->cellStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->cellStyles, TCL_STRING_KEYS); /* special style hash tables */ tablePtr->flashCells = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->flashCells, TCL_STRING_KEYS); tablePtr->selCells = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->selCells, TCL_STRING_KEYS); /* * List of tags in priority order. 30 is a good default number to alloc. */ tablePtr->tagPrioMax = 30; tablePtr->tagPrioNames = (char **) ckalloc( sizeof(char *) * tablePtr->tagPrioMax); tablePtr->tagPrios = (TableTag **) ckalloc( sizeof(TableTag *) * tablePtr->tagPrioMax); tablePtr->tagPrioSize = 0; for (offset = 0; offset < tablePtr->tagPrioMax; offset++) { tablePtr->tagPrioNames[offset] = (char *) NULL; tablePtr->tagPrios[offset] = (TableTag *) NULL; } #ifdef PROCS tablePtr->inProc = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(tablePtr->inProc, TCL_STRING_KEYS); #endif /* * Handle class name and selection handlers */ offset = 2 + Tk_ClassOptionObjCmd(tkwin, "Table", objc, objv); Tk_CreateEventHandler(tablePtr->tkwin, PointerMotionMask|ExposureMask|StructureNotifyMask|FocusChangeMask|VisibilityChangeMask, TableEventProc, (ClientData) tablePtr); Tk_CreateSelHandler(tablePtr->tkwin, XA_PRIMARY, XA_STRING, TableFetchSelection, (ClientData) tablePtr, XA_STRING); if (TableConfigure(interp, tablePtr, objc - offset, objv + offset, 0, 1 /* force update */) != TCL_OK) { Tk_DestroyWindow(tkwin); return TCL_ERROR; } TableInitTags(tablePtr); Tcl_SetStringObj(Tcl_GetObjResult(interp), Tk_PathName(tablePtr->tkwin), -1); return TCL_OK; } /* *-------------------------------------------------------------- * * TableWidgetObjCmd -- * 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 TableWidgetObjCmd(clientData, interp, objc, objv) ClientData clientData; Tcl_Interp *interp; int objc; /* Number of arguments. */ Tcl_Obj *CONST objv[]; /* Argument objects. */ { register Table *tablePtr = (Table *) clientData; int row, col, i, cmdIndex, result = TCL_OK; Tcl_Obj *resultPtr; if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); return TCL_ERROR; } /* parse the first parameter */ result = Tcl_GetIndexFromObj(interp, objv[1], commandNames, "option", 0, &cmdIndex); if (result != TCL_OK) { return result; } /* * It's important to ensure that between here and setting the resultPtr, * we don't cause the result to change, as that might free resultPtr. */ resultPtr = Tcl_GetObjResult(interp); Tcl_Preserve((ClientData) tablePtr); switch ((enum command) cmdIndex) { case CMD_ACTIVATE: result = Table_ActivateCmd(clientData, interp, objc, objv); break; case CMD_BBOX: result = Table_BboxCmd(clientData, interp, objc, objv); break; case CMD_BORDER: result = Table_BorderCmd(clientData, interp, objc, objv); break; case CMD_CGET: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "option"); result = TCL_ERROR; } else { result = Tk_ConfigureValue(interp, tablePtr->tkwin, tableSpecs, (char *) tablePtr, Tcl_GetString(objv[2]), 0); } break; case CMD_CLEAR: result = Table_ClearCmd(clientData, interp, objc, objv); break; case CMD_CONFIGURE: if (objc < 4) { result = Tk_ConfigureInfo(interp, tablePtr->tkwin, tableSpecs, (char *) tablePtr, (objc == 3) ? Tcl_GetString(objv[2]) : (char *) NULL, 0); } else { result = TableConfigure(interp, tablePtr, objc - 2, objv + 2, TK_CONFIG_ARGV_ONLY, 0); } break; case CMD_CURSEL: result = Table_CurselectionCmd(clientData, interp, objc, objv); break; case CMD_CURVALUE: result = Table_CurvalueCmd(clientData, interp, objc, objv); break; case CMD_DELETE: case CMD_INSERT: result = Table_EditCmd(clientData, interp, objc, objv); break; case CMD_GET: result = Table_GetCmd(clientData, interp, objc, objv); break; case CMD_HEIGHT: case CMD_WIDTH: result = Table_AdjustCmd(clientData, interp, objc, objv); break; case CMD_HIDDEN: result = Table_HiddenCmd(clientData, interp, objc, objv); break; case CMD_ICURSOR: if (objc != 2 && objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "?cursorPos?"); result = TCL_ERROR; goto done; } if (!(tablePtr->flags & HAS_ACTIVE) || (tablePtr->flags & ACTIVE_DISABLED) || tablePtr->state == STATE_DISABLED) { Tcl_SetIntObj(resultPtr, -1); goto done; } if (objc == 3) { if (TableGetIcursorObj(tablePtr, objv[2], NULL) != TCL_OK) { result = TCL_ERROR; goto done; } TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); } Tcl_SetIntObj(resultPtr, tablePtr->icursor); break; case CMD_INDEX: { char *which = NULL; if (objc == 4) { which = Tcl_GetString(objv[3]); } if ((objc < 3 || objc > 4) || ((objc == 4) && (strcmp(which, "row") && strcmp(which, "col")))) { Tcl_WrongNumArgs(interp, 2, objv, " ?row|col?"); result = TCL_ERROR; } else if (TableGetIndexObj(tablePtr, objv[2], &row, &col) != TCL_OK) { result = TCL_ERROR; } else if (objc == 3) { char buf[INDEX_BUFSIZE]; /* recreate the index, just in case it got bounded */ TableMakeArrayIndex(row, col, buf); Tcl_SetStringObj(resultPtr, buf, -1); } else { /* INDEX row|col */ Tcl_SetIntObj(resultPtr, (*which == 'r') ? row : col); } break; } #ifdef POSTSCRIPT case CMD_POSTSCRIPT: result = Table_PostscriptCmd(clientData, interp, objc, objv); break; #endif case CMD_REREAD: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); result = TCL_ERROR; } else if ((tablePtr->flags & HAS_ACTIVE) && !(tablePtr->flags & ACTIVE_DISABLED) && tablePtr->state != STATE_DISABLED) { TableGetActiveBuf(tablePtr); TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL|INV_FORCE); } break; case CMD_SCAN: result = Table_ScanCmd(clientData, interp, objc, objv); break; case CMD_SEE: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "index"); result = TCL_ERROR; } else if (TableGetIndexObj(tablePtr, objv[2], &row, &col) == TCL_ERROR) { result = TCL_ERROR; } else { /* Adjust from user to master coords */ row -= tablePtr->rowOffset; col -= tablePtr->colOffset; if (!TableCellVCoords(tablePtr, row, col, &i, &i, &i, &i, 1)) { tablePtr->topRow = row-1; tablePtr->leftCol = col-1; TableAdjustParams(tablePtr); } } break; case CMD_SELECTION: if (objc < 3) { Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?"); result = TCL_ERROR; break; } if (Tcl_GetIndexFromObj(interp, objv[2], selCmdNames, "selection option", 0, &cmdIndex) != TCL_OK) { result = TCL_ERROR; break; } switch ((enum selCommand) cmdIndex) { case CMD_SEL_ANCHOR: result = Table_SelAnchorCmd(clientData, interp, objc, objv); break; case CMD_SEL_CLEAR: result = Table_SelClearCmd(clientData, interp, objc, objv); break; case CMD_SEL_INCLUDES: result = Table_SelIncludesCmd(clientData, interp, objc, objv); break; case CMD_SEL_PRESENT: { Tcl_HashSearch search; Tcl_SetBooleanObj(resultPtr, (Tcl_FirstHashEntry(tablePtr->selCells, &search) != NULL)); break; } case CMD_SEL_SET: result = Table_SelSetCmd(clientData, interp, objc, objv); break; } break; case CMD_SET: result = Table_SetCmd(clientData, interp, objc, objv); break; case CMD_SPANS: result = Table_SpanCmd(clientData, interp, objc, objv); break; case CMD_TAG: result = Table_TagCmd(clientData, interp, objc, objv); break; case CMD_VALIDATE: if (objc != 3) { Tcl_WrongNumArgs(interp, 2, objv, "index"); result = TCL_ERROR; } else if (TableGetIndexObj(tablePtr, objv[2], &row, &col) == TCL_ERROR) { result = TCL_ERROR; } else { i = tablePtr->validate; tablePtr->validate = 1; result = TableValidateChange(tablePtr, row, col, (char *) NULL, (char *) NULL, -1); tablePtr->validate = i; Tcl_SetBooleanObj(resultPtr, (result == TCL_OK)); result = TCL_OK; } break; case CMD_VERSION: if (objc != 2) { Tcl_WrongNumArgs(interp, 2, objv, NULL); result = TCL_ERROR; } else { Tcl_SetStringObj(resultPtr, TBL_VERSION, -1); } break; case CMD_WINDOW: result = Table_WindowCmd(clientData, interp, objc, objv); break; case CMD_XVIEW: case CMD_YVIEW: result = Table_ViewCmd(clientData, interp, objc, objv); break; } done: Tcl_Release((ClientData) tablePtr); return result; } /* *---------------------------------------------------------------------- * * TableDestroy -- * This procedure is invoked by Tcl_EventuallyFree * to clean up the internal structure of a table at a safe time * (when no-one is using it anymore). * * Results: * None. * * Side effects: * Everything associated with the table is freed up (hopefully). * *---------------------------------------------------------------------- */ static void TableDestroy(ClientData clientdata) { register Table *tablePtr = (Table *) clientdata; Tcl_HashEntry *entryPtr; Tcl_HashSearch search; /* These may be repetitive from DestroyNotify, but it doesn't hurt */ /* cancel any pending update or timer */ if (tablePtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr); tablePtr->flags &= ~REDRAW_PENDING; } Tcl_DeleteTimerHandler(tablePtr->cursorTimer); Tcl_DeleteTimerHandler(tablePtr->flashTimer); /* delete the variable trace */ if (tablePtr->arrayVar != NULL) { Tcl_UntraceVar(tablePtr->interp, tablePtr->arrayVar, TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_GLOBAL_ONLY, (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr); } /* free the int arrays */ if (tablePtr->colPixels) ckfree((char *) tablePtr->colPixels); if (tablePtr->rowPixels) ckfree((char *) tablePtr->rowPixels); if (tablePtr->colStarts) ckfree((char *) tablePtr->colStarts); if (tablePtr->rowStarts) ckfree((char *) tablePtr->rowStarts); /* delete cached active tag and string */ if (tablePtr->activeTagPtr) ckfree((char *) tablePtr->activeTagPtr); if (tablePtr->activeBuf != NULL) ckfree(tablePtr->activeBuf); /* delete the cache, row, column and cell style hash tables */ Tcl_DeleteHashTable(tablePtr->cache); ckfree((char *) (tablePtr->cache)); Tcl_DeleteHashTable(tablePtr->rowStyles); ckfree((char *) (tablePtr->rowStyles)); Tcl_DeleteHashTable(tablePtr->colStyles); ckfree((char *) (tablePtr->colStyles)); Tcl_DeleteHashTable(tablePtr->cellStyles); ckfree((char *) (tablePtr->cellStyles)); Tcl_DeleteHashTable(tablePtr->flashCells); ckfree((char *) (tablePtr->flashCells)); Tcl_DeleteHashTable(tablePtr->selCells); ckfree((char *) (tablePtr->selCells)); Tcl_DeleteHashTable(tablePtr->colWidths); ckfree((char *) (tablePtr->colWidths)); Tcl_DeleteHashTable(tablePtr->rowHeights); ckfree((char *) (tablePtr->rowHeights)); #ifdef PROCS Tcl_DeleteHashTable(tablePtr->inProc); ckfree((char *) (tablePtr->inProc)); #endif if (tablePtr->spanTbl) { for (entryPtr = Tcl_FirstHashEntry(tablePtr->spanTbl, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { ckfree((char *) Tcl_GetHashValue(entryPtr)); } Tcl_DeleteHashTable(tablePtr->spanTbl); ckfree((char *) (tablePtr->spanTbl)); Tcl_DeleteHashTable(tablePtr->spanAffTbl); ckfree((char *) (tablePtr->spanAffTbl)); } /* Now free up all the tag information */ for (entryPtr = Tcl_FirstHashEntry(tablePtr->tagTable, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { TableCleanupTag(tablePtr, (TableTag *) Tcl_GetHashValue(entryPtr)); ckfree((char *) Tcl_GetHashValue(entryPtr)); } /* free up the stuff in the default tag */ TableCleanupTag(tablePtr, &(tablePtr->defaultTag)); /* And delete the actual hash table */ Tcl_DeleteHashTable(tablePtr->tagTable); ckfree((char *) (tablePtr->tagTable)); ckfree((char *) (tablePtr->tagPrios)); ckfree((char *) (tablePtr->tagPrioNames)); /* Now free up all the embedded window info */ for (entryPtr = Tcl_FirstHashEntry(tablePtr->winTable, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { EmbWinDelete(tablePtr, (TableEmbWindow *) Tcl_GetHashValue(entryPtr)); } /* And delete the actual hash table */ Tcl_DeleteHashTable(tablePtr->winTable); ckfree((char *) (tablePtr->winTable)); /* free the configuration options in the widget */ Tk_FreeOptions(tableSpecs, (char *) tablePtr, tablePtr->display, 0); /* and free the widget memory at last! */ ckfree((char *) (tablePtr)); } /* *---------------------------------------------------------------------- * * TableConfigure -- * This procedure is called to process an objc/objv list, plus * the Tk option database, in order to configure (or reconfigure) * a table 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 tablePtr; old resources get freed, if there were any. * Certain values might be constrained. * *---------------------------------------------------------------------- */ static int TableConfigure(interp, tablePtr, objc, objv, flags, forceUpdate) Tcl_Interp *interp; /* Used for error reporting. */ register Table *tablePtr; /* Information about widget; may or may * not already have values for some fields. */ int objc; /* Number of arguments. */ Tcl_Obj *CONST objv[]; /* Argument objects. */ int flags; /* Flags to pass to Tk_ConfigureWidget. */ int forceUpdate; /* Whether to force an update - required * for initial configuration */ { Tcl_HashSearch search; int oldUse, oldCaching, oldExport, oldTitleRows, oldTitleCols; int result = TCL_OK; char *oldVar = NULL, **argv; Tcl_DString error; Tk_FontMetrics fm; oldExport = tablePtr->exportSelection; oldCaching = tablePtr->caching; oldUse = tablePtr->useCmd; oldTitleRows = tablePtr->titleRows; oldTitleCols = tablePtr->titleCols; if (tablePtr->arrayVar != NULL) { oldVar = ckalloc(strlen(tablePtr->arrayVar) + 1); strcpy(oldVar, tablePtr->arrayVar); } /* Do the configuration */ argv = StringifyObjects(objc, objv); result = Tk_ConfigureWidget(interp, tablePtr->tkwin, tableSpecs, objc, argv, (char *) tablePtr, flags); ckfree((char *) argv); if (result != TCL_OK) { return TCL_ERROR; } Tcl_DStringInit(&error); /* Any time we configure, reevaluate what our data source is */ tablePtr->dataSource = DATA_NONE; if (tablePtr->caching) { tablePtr->dataSource |= DATA_CACHE; } if (tablePtr->command && tablePtr->useCmd) { tablePtr->dataSource |= DATA_COMMAND; } else if (tablePtr->arrayVar) { tablePtr->dataSource |= DATA_ARRAY; } /* Check to see if the array variable was changed */ if (strcmp((tablePtr->arrayVar ? tablePtr->arrayVar : ""), (oldVar ? oldVar : ""))) { /* only do the following if arrayVar is our data source */ if (tablePtr->dataSource & DATA_ARRAY) { /* * ensure that the cache will flush later * so it gets the new values */ oldCaching = !(tablePtr->caching); } /* remove the trace on the old array variable if there was one */ if (oldVar != NULL) Tcl_UntraceVar(interp, oldVar, TCL_TRACE_WRITES|TCL_TRACE_UNSETS|TCL_GLOBAL_ONLY, (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr); /* Check whether variable is an array and trace it if it is */ if (tablePtr->arrayVar != NULL) { /* does the variable exist as an array? */ if (Tcl_SetVar2(interp, tablePtr->arrayVar, TEST_KEY, "", TCL_GLOBAL_ONLY) == NULL) { Tcl_DStringAppend(&error, "invalid variable value \"", -1); Tcl_DStringAppend(&error, tablePtr->arrayVar, -1); Tcl_DStringAppend(&error, "\": could not be made an array", -1); ckfree(tablePtr->arrayVar); tablePtr->arrayVar = NULL; tablePtr->dataSource &= ~DATA_ARRAY; result = TCL_ERROR; } else { Tcl_UnsetVar2(interp, tablePtr->arrayVar, TEST_KEY, TCL_GLOBAL_ONLY); /* remove the effect of the evaluation */ /* set a trace on the variable */ Tcl_TraceVar(interp, tablePtr->arrayVar, TCL_TRACE_WRITES|TCL_TRACE_UNSETS|TCL_GLOBAL_ONLY, (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr); /* only do the following if arrayVar is our data source */ if (tablePtr->dataSource & DATA_ARRAY) { /* get the current value of the selection */ TableGetActiveBuf(tablePtr); } } } } /* Free oldVar if it was allocated */ if (oldVar != NULL) ckfree(oldVar); if ((tablePtr->command && tablePtr->useCmd && !oldUse) || (tablePtr->arrayVar && !(tablePtr->useCmd) && oldUse)) { /* * Our effective data source changed, so flush and * retrieve new active buffer */ Tcl_DeleteHashTable(tablePtr->cache); Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS); TableGetActiveBuf(tablePtr); forceUpdate = 1; } else if (oldCaching != tablePtr->caching) { /* * Caching changed, so just clear the cache for safety */ Tcl_DeleteHashTable(tablePtr->cache); Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS); forceUpdate = 1; } /* * Set up the default column width and row height */ Tk_GetFontMetrics(tablePtr->defaultTag.tkfont, &fm); tablePtr->charWidth = Tk_TextWidth(tablePtr->defaultTag.tkfont, "0", 1); tablePtr->charHeight = fm.linespace + 2; if (tablePtr->insertWidth <= 0) { tablePtr->insertWidth = 2; } if (tablePtr->insertBorderWidth > tablePtr->insertWidth/2) { tablePtr->insertBorderWidth = tablePtr->insertWidth/2; } tablePtr->highlightWidth = MAX(0,tablePtr->highlightWidth); /* * Ensure that certain values are within proper constraints */ tablePtr->rows = MAX(1, tablePtr->rows); tablePtr->cols = MAX(1, tablePtr->cols); tablePtr->padX = MAX(0, tablePtr->padX); tablePtr->padY = MAX(0, tablePtr->padY); tablePtr->ipadX = MAX(0, tablePtr->ipadX); tablePtr->ipadY = MAX(0, tablePtr->ipadY); tablePtr->maxReqCols = MAX(0, tablePtr->maxReqCols); tablePtr->maxReqRows = MAX(0, tablePtr->maxReqRows); CONSTRAIN(tablePtr->titleRows, 0, tablePtr->rows); CONSTRAIN(tablePtr->titleCols, 0, tablePtr->cols); /* * Handle change of default border style * The default borderwidth must be >= 0. */ if (tablePtr->drawMode & (DRAW_MODE_SINGLE|DRAW_MODE_FAST)) { /* * When drawing fast or single, the border must be <= 1. * We have to do this after the normal configuration * to base the borders off the first value given. */ tablePtr->defaultTag.bd[0] = MIN(1, tablePtr->defaultTag.bd[0]); tablePtr->defaultTag.borders = 1; ckfree((char *) tablePtr->defaultTag.borderStr); tablePtr->defaultTag.borderStr = (char *) ckalloc(2); strcpy(tablePtr->defaultTag.borderStr, tablePtr->defaultTag.bd[0] ? "1" : "0"); } /* * Claim the selection if we've suddenly started exporting it and * there is a selection to export. */ if (tablePtr->exportSelection && !oldExport && (Tcl_FirstHashEntry(tablePtr->selCells, &search) != NULL)) { Tk_OwnSelection(tablePtr->tkwin, XA_PRIMARY, TableLostSelection, (ClientData) tablePtr); } if ((tablePtr->titleRows < oldTitleRows) || (tablePtr->titleCols < oldTitleCols)) { /* * Prevent odd movement due to new possible topleft index */ if (tablePtr->titleRows < oldTitleRows) tablePtr->topRow -= oldTitleRows - tablePtr->titleRows; if (tablePtr->titleCols < oldTitleCols) tablePtr->leftCol -= oldTitleCols - tablePtr->titleCols; /* * If our title area shrank, we need to check that the items * within the new title area don't try to span outside it. */ TableSpanSanCheck(tablePtr); } /* * Only do the full reconfigure if absolutely necessary */ if (!forceUpdate) { int i, dummy; for (i = 0; i < objc-1; i += 2) { if (Tcl_GetIndexFromObj(NULL, objv[i], updateOpts, "", 0, &dummy) == TCL_OK) { forceUpdate = 1; break; } } } if (forceUpdate) { /* * Calculate the row and column starts * Adjust the top left corner of the internal display */ TableAdjustParams(tablePtr); /* reset the cursor */ TableConfigCursor(tablePtr); /* set up the background colour in the window */ Tk_SetBackgroundFromBorder(tablePtr->tkwin, tablePtr->defaultTag.bg); /* set the geometry and border */ TableGeometryRequest(tablePtr); Tk_SetInternalBorder(tablePtr->tkwin, tablePtr->highlightWidth); /* invalidate the whole table */ TableInvalidateAll(tablePtr, INV_HIGHLIGHT); } /* * FIX this is goofy because the result could be munged by other * functions. Could be improved. */ Tcl_ResetResult(interp); if (result == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\t(configuring table widget)"); Tcl_DStringResult(interp, &error); } Tcl_DStringFree(&error); return result; } /* *-------------------------------------------------------------- * * TableEventProc -- * This procedure is invoked by the Tk dispatcher for various * events on tables. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get * cleaned up. When it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void TableEventProc(clientData, eventPtr) ClientData clientData; /* Information about window. */ XEvent *eventPtr; /* Information about event. */ { Table *tablePtr = (Table *) clientData; int row, col; switch (eventPtr->type) { case MotionNotify: if (!(tablePtr->resize & SEL_NONE) && (tablePtr->bdcursor != None) && TableAtBorder(tablePtr, eventPtr->xmotion.x, eventPtr->xmotion.y, &row, &col) && ((row>=0 && (tablePtr->resize & SEL_ROW)) || (col>=0 && (tablePtr->resize & SEL_COL)))) { /* * The bordercursor is defined and we meet the criteria for * being over a border. Set the cursor to border if not * already done. */ if (!(tablePtr->flags & OVER_BORDER)) { tablePtr->flags |= OVER_BORDER; Tk_DefineCursor(tablePtr->tkwin, tablePtr->bdcursor); } } else if (tablePtr->flags & OVER_BORDER) { tablePtr->flags &= ~OVER_BORDER; if (tablePtr->cursor != None) { Tk_DefineCursor(tablePtr->tkwin, tablePtr->cursor); } else { Tk_UndefineCursor(tablePtr->tkwin); } } break; case Expose: TableInvalidate(tablePtr, eventPtr->xexpose.x, eventPtr->xexpose.y, eventPtr->xexpose.width, eventPtr->xexpose.height, INV_HIGHLIGHT); break; case DestroyNotify: /* remove the command from the interpreter */ if (tablePtr->tkwin != NULL) { tablePtr->tkwin = NULL; Tcl_DeleteCommandFromToken(tablePtr->interp, tablePtr->widgetCmd); } /* cancel any pending update or timer */ if (tablePtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr); tablePtr->flags &= ~REDRAW_PENDING; } Tcl_DeleteTimerHandler(tablePtr->cursorTimer); Tcl_DeleteTimerHandler(tablePtr->flashTimer); Tcl_EventuallyFree((ClientData) tablePtr, (Tcl_FreeProc *) TableDestroy); break; case MapNotify: /* redraw table when remapped if it changed */ if (tablePtr->flags & REDRAW_ON_MAP) { tablePtr->flags &= ~REDRAW_ON_MAP; Tcl_Preserve((ClientData) tablePtr); TableAdjustParams(tablePtr); TableInvalidateAll(tablePtr, INV_HIGHLIGHT); Tcl_Release((ClientData) tablePtr); } break; case ConfigureNotify: Tcl_Preserve((ClientData) tablePtr); TableAdjustParams(tablePtr); TableInvalidateAll(tablePtr, INV_HIGHLIGHT); Tcl_Release((ClientData) tablePtr); break; case FocusIn: case FocusOut: if (eventPtr->xfocus.detail != NotifyInferior) { tablePtr->flags |= REDRAW_BORDER; if (eventPtr->type == FocusOut) { tablePtr->flags &= ~HAS_FOCUS; } else { tablePtr->flags |= HAS_FOCUS; } TableRedrawHighlight(tablePtr); /* cancel the timer */ TableConfigCursor(tablePtr); } break; } } /* *---------------------------------------------------------------------- * * TableCmdDeletedProc -- * * This procedure is invoked when a widget command is deleted. If * the widget isn't already in the process of being destroyed, * this command destroys it. * * Results: * None. * * Side effects: * The widget is destroyed. * *---------------------------------------------------------------------- */ static void TableCmdDeletedProc(ClientData clientData) { Table *tablePtr = (Table *) clientData; Tk_Window tkwin; /* * This procedure could be invoked either because the window was * destroyed and the command was then deleted (in which case tkwin * is NULL) or because the command was deleted, and then this procedure * destroys the widget. */ if (tablePtr->tkwin != NULL) { tkwin = tablePtr->tkwin; tablePtr->tkwin = NULL; Tk_DestroyWindow(tkwin); } } /* *---------------------------------------------------------------------- * * TableRedrawHighlight -- * Redraws just the highlight for the window * * Results: * None. * * Side effects: * None * *---------------------------------------------------------------------- */ static void TableRedrawHighlight(Table *tablePtr) { if ((tablePtr->flags & REDRAW_BORDER) && tablePtr->highlightWidth > 0) { GC gc = Tk_GCForColor((tablePtr->flags & HAS_FOCUS) ? tablePtr->highlightColorPtr : tablePtr->highlightBgColorPtr, Tk_WindowId(tablePtr->tkwin)); Tk_DrawFocusHighlight(tablePtr->tkwin, gc, tablePtr->highlightWidth, Tk_WindowId(tablePtr->tkwin)); } tablePtr->flags &= ~REDRAW_BORDER; } /* *---------------------------------------------------------------------- * * TableRefresh -- * Refreshes an area of the table based on the mode. * row,col in real coords (0-based) * * Results: * Will cause redraw for visible cells * * Side effects: * None. * *---------------------------------------------------------------------- */ void TableRefresh(register Table *tablePtr, int row, int col, int mode) { int x, y, w, h; if ((row < 0) || (col < 0)) { /* * Invalid coords passed in. This can happen when the "active" cell * is refreshed, but doesn't really exist (row==-1 && col==-1). */ return; } if (mode & CELL) { if (TableCellVCoords(tablePtr, row, col, &x, &y, &w, &h, 0)) { TableInvalidate(tablePtr, x, y, w, h, mode); } } else if (mode & ROW) { /* get the position of the leftmost cell in the row */ if ((mode & INV_FILL) && row < tablePtr->topRow) { /* Invalidate whole table */ TableInvalidateAll(tablePtr, mode); } else if (TableCellVCoords(tablePtr, row, tablePtr->leftCol, &x, &y, &w, &h, 0)) { /* Invalidate from this row, maybe to end */ TableInvalidate(tablePtr, 0, y, Tk_Width(tablePtr->tkwin), (mode&INV_FILL)?Tk_Height(tablePtr->tkwin):h, mode); } } else if (mode & COL) { /* get the position of the topmost cell on the column */ if ((mode & INV_FILL) && col < tablePtr->leftCol) { /* Invalidate whole table */ TableInvalidateAll(tablePtr, mode); } else if (TableCellVCoords(tablePtr, tablePtr->topRow, col, &x, &y, &w, &h, 0)) { /* Invalidate from this column, maybe to end */ TableInvalidate(tablePtr, x, 0, (mode&INV_FILL)?Tk_Width(tablePtr->tkwin):w, Tk_Height(tablePtr->tkwin), mode); } } } /* *---------------------------------------------------------------------- * * TableGetGc -- * Gets a GC corresponding to the tag structure passed. * * Results: * Returns usable GC. * * Side effects: * None * *---------------------------------------------------------------------- */ static void TableGetGc(Display *display, Drawable d, TableTag *tagPtr, GC *tagGc) { XGCValues gcValues; gcValues.foreground = Tk_3DBorderColor(tagPtr->fg)->pixel; gcValues.background = Tk_3DBorderColor(tagPtr->bg)->pixel; gcValues.font = Tk_FontId(tagPtr->tkfont); if (*tagGc == NULL) { gcValues.graphics_exposures = False; *tagGc = XCreateGC(display, d, GCForeground|GCBackground|GCFont|GCGraphicsExposures, &gcValues); } else { XChangeGC(display, *tagGc, GCForeground|GCBackground|GCFont, &gcValues); } } #define TableFreeGc XFreeGC /* *-------------------------------------------------------------- * * TableUndisplay -- * This procedure removes the contents of a table window * that have been moved offscreen. * * Results: * Embedded windows can be unmapped. * * Side effects: * Information disappears from the screen. * *-------------------------------------------------------------- */ static void TableUndisplay(register Table *tablePtr) { register int *seen = tablePtr->seen; int row, col; /* We need to find out the true last cell, not considering spans */ tablePtr->flags |= AVOID_SPANS; TableGetLastCell(tablePtr, &row, &col); tablePtr->flags &= ~AVOID_SPANS; if (seen[0] != -1) { if (seen[0] < tablePtr->topRow) { /* Remove now hidden rows */ EmbWinUnmap(tablePtr, seen[0], MIN(seen[2],tablePtr->topRow-1), seen[1], seen[3]); /* Also account for the title area */ EmbWinUnmap(tablePtr, seen[0], MIN(seen[2],tablePtr->topRow-1), 0, tablePtr->titleCols-1); } if (seen[1] < tablePtr->leftCol) { /* Remove now hidden cols */ EmbWinUnmap(tablePtr, seen[0], seen[2], seen[1], MAX(seen[3],tablePtr->leftCol-1)); /* Also account for the title area */ EmbWinUnmap(tablePtr, 0, tablePtr->titleRows-1, seen[1], MAX(seen[3],tablePtr->leftCol-1)); } if (seen[2] > row) { /* Remove now off-screen rows */ EmbWinUnmap(tablePtr, MAX(seen[0],row+1), seen[2], seen[1], seen[3]); /* Also account for the title area */ EmbWinUnmap(tablePtr, MAX(seen[0],row+1), seen[2], 0, tablePtr->titleCols-1); } if (seen[3] > col) { /* Remove now off-screen cols */ EmbWinUnmap(tablePtr, seen[0], seen[2], MAX(seen[1],col+1), seen[3]); /* Also account for the title area */ EmbWinUnmap(tablePtr, 0, tablePtr->titleRows-1, MAX(seen[1],col+1), seen[3]); } } seen[0] = tablePtr->topRow; seen[1] = tablePtr->leftCol; seen[2] = row; seen[3] = col; } #ifdef MAC_TCL #define NO_XSETCLIP #endif /* *-------------------------------------------------------------- * * TableDisplay -- * This procedure redraws the contents of a table window. * The conditional code in this function is due to these factors: * o Lack of XSetClipRectangles on Macintosh * o Use of alternative routine for Windows * * Results: * None. * * Side effects: * Information appears on the screen. * *-------------------------------------------------------------- */ static void TableDisplay(ClientData clientdata) { register Table *tablePtr = (Table *) clientdata; Tk_Window tkwin = tablePtr->tkwin; Display *display = tablePtr->display; Drawable window; #ifdef NO_XSETCLIP Drawable clipWind; #elif !defined(WIN32) XRectangle clipRect; #endif int rowFrom, rowTo, colFrom, colTo, invalidX, invalidY, invalidWidth, invalidHeight, x, y, width, height, itemX, itemY, itemW, itemH, row, col, urow, ucol, hrow=0, hcol=0, cx, cy, cw, ch, borders, bd[6], numBytes, new, boundW, boundH, maxW, maxH, cellType, originX, originY, activeCell, shouldInvert, ipadx, ipady, padx, pady; GC tagGc = NULL, topGc, bottomGc; char *string = NULL; char buf[INDEX_BUFSIZE]; TableTag *tagPtr = NULL, *titlePtr, *selPtr, *activePtr, *flashPtr, *rowPtr, *colPtr; Tcl_HashEntry *entryPtr; static XPoint rect[3] = { {0, 0}, {0, 0}, {0, 0} }; Tcl_HashTable *colTagsCache = NULL; Tcl_HashTable *drawnCache = NULL; Tk_TextLayout textLayout = NULL; TableEmbWindow *ewPtr; tablePtr->flags &= ~REDRAW_PENDING; if ((tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } boundW = Tk_Width(tkwin) - tablePtr->highlightWidth; boundH = Tk_Height(tkwin) - tablePtr->highlightWidth; /* Constrain drawable to not include highlight borders */ invalidX = MAX(tablePtr->highlightWidth, tablePtr->invalidX); invalidY = MAX(tablePtr->highlightWidth, tablePtr->invalidY); invalidWidth = MIN(tablePtr->invalidWidth, MAX(1, boundW-invalidX)); invalidHeight = MIN(tablePtr->invalidHeight, MAX(1, boundH-invalidY)); ipadx = tablePtr->ipadX; ipady = tablePtr->ipadY; padx = tablePtr->padX; pady = tablePtr->padY; /* * if we are using the slow drawing mode with a pixmap * create the pixmap and adjust x && y for offset in pixmap */ if (tablePtr->drawMode == DRAW_MODE_SLOW) { window = Tk_GetPixmap(display, Tk_WindowId(tkwin), invalidWidth, invalidHeight, Tk_Depth(tkwin)); } else { window = Tk_WindowId(tkwin); } #ifdef NO_XSETCLIP clipWind = Tk_GetPixmap(display, window, invalidWidth, invalidHeight, Tk_Depth(tkwin)); #endif /* set up the permanent tag styles */ entryPtr = Tcl_FindHashEntry(tablePtr->tagTable, "title"); titlePtr = (TableTag *) Tcl_GetHashValue(entryPtr); entryPtr = Tcl_FindHashEntry(tablePtr->tagTable, "sel"); selPtr = (TableTag *) Tcl_GetHashValue(entryPtr); entryPtr = Tcl_FindHashEntry(tablePtr->tagTable, "active"); activePtr = (TableTag *) Tcl_GetHashValue(entryPtr); entryPtr = Tcl_FindHashEntry(tablePtr->tagTable, "flash"); flashPtr = (TableTag *) Tcl_GetHashValue(entryPtr); /* We need to find out the true cell span, not considering spans */ tablePtr->flags |= AVOID_SPANS; /* find out the cells represented by the invalid region */ TableWhatCell(tablePtr, invalidX, invalidY, &rowFrom, &colFrom); TableWhatCell(tablePtr, invalidX+invalidWidth-1, invalidY+invalidHeight-1, &rowTo, &colTo); tablePtr->flags &= ~AVOID_SPANS; #ifdef DEBUG tcl_dprintf(tablePtr->interp, "%d,%d => %d,%d", rowFrom+tablePtr->rowOffset, colFrom+tablePtr->colOffset, rowTo+tablePtr->rowOffset, colTo+tablePtr->colOffset); #endif /* * Initialize colTagsCache hash table to cache column tag names. */ colTagsCache = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(colTagsCache, TCL_ONE_WORD_KEYS); /* * Initialize drawnCache hash table to cache drawn cells. * This is necessary to prevent spanning cells being drawn multiple times. */ drawnCache = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(drawnCache, TCL_STRING_KEYS); /* * Create the tag here. This will actually create a JoinTag * That will handle the priority management of merging for us. * We only need one allocated, and we'll reset it for each cell. */ tagPtr = TableNewTag(tablePtr); /* Cycle through the cells and display them */ for (row = rowFrom; row <= rowTo; row++) { /* * are we in the 'dead zone' between the * title rows and the first displayed row */ if (row < tablePtr->topRow && row >= tablePtr->titleRows) { row = tablePtr->topRow; } /* Cache the row in user terms */ urow = row+tablePtr->rowOffset; /* Get the row tag once for all iterations of col */ rowPtr = FindRowColTag(tablePtr, urow, ROW); for (col = colFrom; col <= colTo; col++) { activeCell = 0; /* * Adjust to first viewable column if we are in the 'dead zone' * between the title cols and the first displayed column. */ if (col < tablePtr->leftCol && col >= tablePtr->titleCols) { col = tablePtr->leftCol; } /* * Get the coordinates for the cell before possible rearrangement * of row,col due to spanning cells */ cellType = TableCellCoords(tablePtr, row, col, &x, &y, &width, &height); if (cellType == CELL_HIDDEN) { /* * width,height holds the real start row,col of the span. * Put the use cell ref into a buffer for the hash lookups. */ TableMakeArrayIndex(width, height, buf); Tcl_CreateHashEntry(drawnCache, buf, &new); if (!new) { /* Not new in the entry, so it's already drawn */ continue; } hrow = row; hcol = col; row = width-tablePtr->rowOffset; col = height-tablePtr->colOffset; TableCellVCoords(tablePtr, row, col, &x, &y, &width, &height, 0); /* We have to adjust the coords back onto the visual display */ urow = row+tablePtr->rowOffset; rowPtr = FindRowColTag(tablePtr, urow, ROW); } /* Constrain drawn size to the visual boundaries */ if (width > boundW-x) { width = boundW-x; } if (height > boundH-y) { height = boundH-y; } /* Cache the col in user terms */ ucol = col+tablePtr->colOffset; /* put the use cell ref into a buffer for the hash lookups */ TableMakeArrayIndex(urow, ucol, buf); if (cellType != CELL_HIDDEN) { Tcl_CreateHashEntry(drawnCache, buf, &new); } /* * Make sure we start with a clean tag (set to table defaults). */ TableResetTag(tablePtr, tagPtr); /* * Check to see if we have an embedded window in this cell. */ entryPtr = Tcl_FindHashEntry(tablePtr->winTable, buf); if (entryPtr != NULL) { ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr); if (ewPtr->tkwin != NULL) { /* Display embedded window instead of text */ /* if active, make it disabled to avoid * unnecessary editing */ if ((tablePtr->flags & HAS_ACTIVE) && row == tablePtr->activeRow && col == tablePtr->activeCol) { tablePtr->flags |= ACTIVE_DISABLED; } /* * The EmbWinDisplay function may modify values in * tagPtr, so reference those after this call. */ EmbWinDisplay(tablePtr, window, ewPtr, tagPtr, x, y, width, height); if (tablePtr->drawMode == DRAW_MODE_SLOW) { /* Correctly adjust x && y with the offset */ x -= invalidX; y -= invalidY; } Tk_Fill3DRectangle(tkwin, window, tagPtr->bg, x, y, width, height, 0, TK_RELIEF_FLAT); /* border width for cell should now be properly set */ borders = TableGetTagBorders(tagPtr, &bd[0], &bd[1], &bd[2], &bd[3]); bd[4] = (bd[0] + bd[1])/2; bd[5] = (bd[2] + bd[3])/2; goto DrawBorder; } } if (tablePtr->drawMode == DRAW_MODE_SLOW) { /* Correctly adjust x && y with the offset */ x -= invalidX; y -= invalidY; } shouldInvert = 0; /* * Get the combined tag structure for the cell. * First clear out a new tag structure that we will build in * then add tags as we realize they belong. * * Tags have their own priorities which TableMergeTag will * take into account when merging tags. */ /* * Merge colPtr if it exists * let's see if we have the value cached already * if not, run the findColTag routine and cache the value */ entryPtr = Tcl_CreateHashEntry(colTagsCache, (char *)ucol, &new); if (new) { colPtr = FindRowColTag(tablePtr, ucol, COL); Tcl_SetHashValue(entryPtr, colPtr); } else { colPtr = (TableTag *) Tcl_GetHashValue(entryPtr); } if (colPtr != (TableTag *) NULL) { TableMergeTag(tablePtr, tagPtr, colPtr); } /* Merge rowPtr if it exists */ if (rowPtr != (TableTag *) NULL) { TableMergeTag(tablePtr, tagPtr, rowPtr); } /* Am I in the titles */ if (row < tablePtr->titleRows || col < tablePtr->titleCols) { TableMergeTag(tablePtr, tagPtr, titlePtr); } /* Does this have a cell tag */ entryPtr = Tcl_FindHashEntry(tablePtr->cellStyles, buf); if (entryPtr != NULL) { TableMergeTag(tablePtr, tagPtr, (TableTag *) Tcl_GetHashValue(entryPtr)); } /* is this cell active? */ if ((tablePtr->flags & HAS_ACTIVE) && (tablePtr->state == STATE_NORMAL) && row == tablePtr->activeRow && col == tablePtr->activeCol) { if (tagPtr->state == STATE_DISABLED) { tablePtr->flags |= ACTIVE_DISABLED; } else { TableMergeTag(tablePtr, tagPtr, activePtr); activeCell = 1; tablePtr->flags &= ~ACTIVE_DISABLED; } } /* is this cell selected? */ if (Tcl_FindHashEntry(tablePtr->selCells, buf) != NULL) { if (tablePtr->invertSelected && !activeCell) { shouldInvert = 1; } else { TableMergeTag(tablePtr, tagPtr, selPtr); } } /* if flash mode is on, is this cell flashing? */ if (tablePtr->flashMode && Tcl_FindHashEntry(tablePtr->flashCells, buf) != NULL) { TableMergeTag(tablePtr, tagPtr, flashPtr); } if (shouldInvert) { TableInvertTag(tagPtr); } /* * Borders for cell should now be properly set */ borders = TableGetTagBorders(tagPtr, &bd[0], &bd[1], &bd[2], &bd[3]); bd[4] = (bd[0] + bd[1])/2; bd[5] = (bd[2] + bd[3])/2; /* * First fill in a blank rectangle. */ Tk_Fill3DRectangle(tkwin, window, tagPtr->bg, x, y, width, height, 0, TK_RELIEF_FLAT); /* * Correct the dimensions to enforce padding constraints */ width -= bd[0] + bd[1] + (2 * padx); height -= bd[2] + bd[3] + (2 * pady); /* * If an image is in the tag, draw it */ if (tagPtr->image != NULL) { Tk_SizeOfImage(tagPtr->image, &itemW, &itemH); /* Handle anchoring of image in cell space */ switch (tagPtr->anchor) { case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW: /* western position */ originX = itemX = 0; break; case TK_ANCHOR_N: case TK_ANCHOR_S: case TK_ANCHOR_CENTER: /* centered position */ itemX = MAX(0, (itemW - width) / 2); originX = MAX(0, (width - itemW) / 2); break; default: /* eastern position */ itemX = MAX(0, itemW - width); originX = MAX(0, width - itemW); } switch (tagPtr->anchor) { case TK_ANCHOR_N: case TK_ANCHOR_NE: case TK_ANCHOR_NW: /* northern position */ originY = itemY = 0; break; case TK_ANCHOR_W: case TK_ANCHOR_E: case TK_ANCHOR_CENTER: /* centered position */ itemY = MAX(0, (itemH - height) / 2); originY = MAX(0, (height - itemH) / 2); break; default: /* southern position */ itemY = MAX(0, itemH - height); originY = MAX(0, height - itemH); } Tk_RedrawImage(tagPtr->image, itemX, itemY, MIN(itemW, width-originX), MIN(itemH, height-originY), window, x + originX + bd[0] + padx, y + originY + bd[2] + pady); /* * If we don't want to display the text as well, then jump. */ if (tagPtr->showtext == 0) { /* * Re-Correct the dimensions before border drawing */ width += bd[0] + bd[1] + (2 * padx); height += bd[2] + bd[3] + (2 * pady); goto DrawBorder; } } /* * Get the GC for this particular blend of tags. * This creates the GC if it never existed, otherwise it * modifies the one we have, so we only need the one */ TableGetGc(display, window, tagPtr, &tagGc); /* if this is the active cell, use the buffer */ if (activeCell) { string = tablePtr->activeBuf; } else { /* Is there a value in the cell? If so, draw it */ string = TableGetCellValue(tablePtr, urow, ucol); } #ifdef TCL_UTF_MAX /* * We have to use strlen here because otherwise it stops * at the first \x00 unicode char it finds (!= '\0'), * although there can be more to the string than that */ numBytes = Tcl_NumUtfChars(string, strlen(string)); #else numBytes = strlen(string); #endif /* If there is a string, show it */ if (activeCell || numBytes) { /* get the dimensions of the string */ textLayout = Tk_ComputeTextLayout(tagPtr->tkfont, string, numBytes, (tagPtr->wrap > 0) ? width : 0, tagPtr->justify, (tagPtr->multiline > 0) ? 0 : TK_IGNORE_NEWLINES, &itemW, &itemH); /* * Set the origin coordinates of the string to draw using * the anchor. origin represents the (x,y) coordinate of * the lower left corner of the text box, relative to the * internal (inside the border) window */ /* set the X origin first */ switch (tagPtr->anchor) { case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW: /* western position */ originX = ipadx; break; case TK_ANCHOR_N: case TK_ANCHOR_S: case TK_ANCHOR_CENTER: /* centered position */ originX = (width - itemW) / 2; break; default: /* eastern position */ originX = width - itemW - ipadx; } /* then set the Y origin */ switch (tagPtr->anchor) { case TK_ANCHOR_N: case TK_ANCHOR_NE: case TK_ANCHOR_NW: /* northern position */ originY = ipady; break; case TK_ANCHOR_W: case TK_ANCHOR_E: case TK_ANCHOR_CENTER: /* centered position */ originY = (height - itemH) / 2; break; default: /* southern position */ originY = height - itemH - ipady; } /* * If this is the active cell and we are editing, * ensure that the cursor will be displayed */ if (activeCell) { Tk_CharBbox(textLayout, tablePtr->icursor, &cx, &cy, &cw, &ch); /* we have to fudge with maxW because of odd width * determination for newlines at the end of a line */ maxW = width - tablePtr->insertWidth - (cx + MIN(tablePtr->charWidth, cw)); maxH = height - (cy + ch); if (originX < bd[0] - cx) { /* cursor off cell to the left */ /* use western positioning to cet cursor at left * with slight variation to show some text */ originX = bd[0] - cx + MIN(cx, width - tablePtr->insertWidth); } else if (originX > maxW) { /* cursor off cell to the right */ /* use eastern positioning to cet cursor at right */ originX = maxW; } if (originY < bd[2] - cy) { /* cursor before top of cell */ /* use northern positioning to cet cursor at top */ originY = bd[2] - cy; } else if (originY > maxH) { /* cursor beyond bottom of cell */ /* use southern positioning to cet cursor at bottom */ originY = maxH; } tablePtr->activeTagPtr = tagPtr; tablePtr->activeX = originX; tablePtr->activeY = originY; } /* * Use a clip rectangle only if necessary as it means * updating the GC in the server which slows everything down. * We can't fudge the width or height, just in case the user * wanted empty pad space. */ if ((originX < 0) || (originY < 0) || (originX+itemW > width) || (originY+itemH > height)) { /* * The text wants to overflow the boundaries of the * displayed cell, so we must clip in some way */ #ifdef NO_XSETCLIP /* * This code is basically for the Macintosh. * Copy the the current contents of the cell into the * clipped window area. This keeps any fg/bg and image * data intact. */ XCopyArea(display, window, clipWind, tagGc, x, y, width + bd[0] + bd[1] + (2 * padx), height + bd[2] + bd[3] + (2 * pady), 0, 0); /* * Now draw into the cell space on the special window. * Don't use x,y base offset for clipWind. */ Tk_DrawTextLayout(display, clipWind, tagGc, textLayout, 0 + originX + bd[0] + padx, 0 + originY + bd[2] + pady, 0, -1); /* * Now copy back only the area that we want the * text to be drawn on. */ XCopyArea(display, clipWind, window, tagGc, bd[0] + padx, bd[2] + pady, width, height, x + bd[0] + padx, y + bd[2] + pady); #elif defined(WIN32) /* * This is evil, evil evil! but the XCopyArea * doesn't work in all cases - Michael Teske. * The general structure follows the comments below. */ TkWinDrawable *twdPtr = (TkWinDrawable *) window; HDC dc = GetDC(twdPtr->window.handle); HRGN clipR; clipR = CreateRectRgn(x + bd[0] + padx, y + bd[2] + pady, x + bd[0] + padx + width, y + bd[2] + pady + height); SelectClipRgn(dc, clipR); OffsetClipRgn(dc, 0, 0); Tk_DrawTextLayout(display, window, tagGc, textLayout, x + originX + bd[0] + padx, y + originY + bd[2] + pady, 0, -1); SelectClipRgn(dc, NULL); DeleteObject(clipR); #else /* * Use an X clipping rectangle. The clipping is the * rectangle just for the actual text space (to allow * for empty padding space). */ clipRect.x = x + bd[0] + padx; clipRect.y = y + bd[2] + pady; clipRect.width = width; clipRect.height = height; XSetClipRectangles(display, tagGc, 0, 0, &clipRect, 1, Unsorted); Tk_DrawTextLayout(display, window, tagGc, textLayout, x + originX + bd[0] + padx, y + originY + bd[2] + pady, 0, -1); XSetClipMask(display, tagGc, None); #endif } else { Tk_DrawTextLayout(display, window, tagGc, textLayout, x + originX + bd[0] + padx, y + originY + bd[2] + pady, 0, -1); } /* if this is the active cell draw the cursor if it's on. * this ignores clip rectangles. */ if (activeCell && (tablePtr->flags & CURSOR_ON) && (originY + cy + bd[2] + pady < height) && (originX + cx + bd[0] + padx - (tablePtr->insertWidth / 2) >= 0)) { /* make sure it will fit in the box */ maxW = MAX(0, originY + cy + bd[2] + pady); maxH = MIN(ch, height - maxW + bd[2] + pady); Tk_Fill3DRectangle(tkwin, window, tablePtr->insertBg, x + originX + cx + bd[0] + padx - (tablePtr->insertWidth/2), y + maxW, tablePtr->insertWidth, maxH, 0, TK_RELIEF_FLAT); } } /* * Re-Correct the dimensions before border drawing */ width += bd[0] + bd[1] + (2 * padx); height += bd[2] + bd[3] + (2 * pady); DrawBorder: /* Draw the 3d border on the pixmap correctly offset */ if (tablePtr->drawMode == DRAW_MODE_SINGLE) { topGc = Tk_3DBorderGC(tkwin, tagPtr->bg, TK_3D_DARK_GC); /* draw a line with single pixel width */ rect[0].x = x; rect[0].y = y + height - 1; rect[1].y = -height + 1; rect[2].x = width - 1; XDrawLines(display, window, topGc, rect, 3, CoordModePrevious); } else if (tablePtr->drawMode == DRAW_MODE_FAST) { /* * This depicts a full 1 pixel border. * * Choose the GCs to get the best approximation * to the desired drawing style. */ switch(tagPtr->relief) { case TK_RELIEF_FLAT: topGc = bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg, TK_3D_FLAT_GC); break; case TK_RELIEF_RAISED: case TK_RELIEF_RIDGE: topGc = Tk_3DBorderGC(tkwin, tagPtr->bg, TK_3D_LIGHT_GC); bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg, TK_3D_DARK_GC); break; default: /* TK_RELIEF_SUNKEN TK_RELIEF_GROOVE */ bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg, TK_3D_LIGHT_GC); topGc = Tk_3DBorderGC(tkwin, tagPtr->bg, TK_3D_DARK_GC); break; } /* draw a line with single pixel width */ rect[0].x = x + width - 1; rect[0].y = y; rect[1].y = height - 1; rect[2].x = -width + 1; XDrawLines(display, window, bottomGc, rect, 3, CoordModePrevious); rect[0].x = x; rect[0].y = y + height - 1; rect[1].y = -height + 1; rect[2].x = width - 1; XDrawLines(display, window, topGc, rect, 3, CoordModePrevious); } else { if (borders > 1) { if (bd[0]) { Tk_3DVerticalBevel(tkwin, window, tagPtr->bg, x, y, bd[0], height, 1 /* left side */, tagPtr->relief); } if (bd[1]) { Tk_3DVerticalBevel(tkwin, window, tagPtr->bg, x + width - bd[1], y, bd[1], height, 0 /* right side */, tagPtr->relief); } if ((borders == 4) && bd[2]) { Tk_3DHorizontalBevel(tkwin, window, tagPtr->bg, x, y, width, bd[2], 1, 1, 1 /* top */, tagPtr->relief); } if ((borders == 4) && bd[3]) { Tk_3DHorizontalBevel(tkwin, window, tagPtr->bg, x, y + height - bd[3], width, bd[3], 0, 0, 0 /* bottom */, tagPtr->relief); } } else if (borders == 1) { Tk_Draw3DRectangle(tkwin, window, tagPtr->bg, x, y, width, height, bd[0], tagPtr->relief); } } /* clean up the necessaries */ if (tagPtr == tablePtr->activeTagPtr) { /* * This means it was the activeCell with text displayed. * We buffer the active tag for the 'activate' command. */ tablePtr->activeTagPtr = TableNewTag(NULL); memcpy((VOID *) tablePtr->activeTagPtr, (VOID *) tagPtr, sizeof(TableTag)); } if (textLayout) { Tk_FreeTextLayout(textLayout); textLayout = NULL; } if (cellType == CELL_HIDDEN) { /* the last cell was a hidden one, * rework row stuff back to normal */ row = hrow; col = hcol; urow = row+tablePtr->rowOffset; rowPtr = FindRowColTag(tablePtr, urow, ROW); } } } ckfree((char *) tagPtr); #ifdef NO_XSETCLIP Tk_FreePixmap(display, clipWind); #endif /* Take care of removing embedded windows that are no longer in view */ TableUndisplay(tablePtr); /* copy over and delete the pixmap if we are in slow mode */ if (tablePtr->drawMode == DRAW_MODE_SLOW) { /* Get a default valued GC */ TableGetGc(display, window, &(tablePtr->defaultTag), &tagGc); XCopyArea(display, window, Tk_WindowId(tkwin), tagGc, 0, 0, invalidWidth, invalidHeight, invalidX, invalidY); Tk_FreePixmap(display, window); window = Tk_WindowId(tkwin); } /* * If we are at the end of the table, clear the area after the last * row/col. We discount spans here because we just need the coords * for the area that would be the last physical cell. */ tablePtr->flags |= AVOID_SPANS; TableCellCoords(tablePtr, tablePtr->rows-1, tablePtr->cols-1, &x, &y, &width, &height); tablePtr->flags &= ~AVOID_SPANS; /* This should occur before moving pixmap, but this simplifies things * * Could use Tk_Fill3DRectangle instead of XFillRectangle * for best compatibility, and XClearArea could be used on Unix * for best speed, so this is the compromise w/o #ifdef's */ if (x+width < invalidX+invalidWidth) { XFillRectangle(display, window, Tk_3DBorderGC(tkwin, tablePtr->defaultTag.bg, TK_3D_FLAT_GC), x+width, invalidY, invalidX+invalidWidth-x-width, invalidHeight); } if (y+height < invalidY+invalidHeight) { XFillRectangle(display, window, Tk_3DBorderGC(tkwin, tablePtr->defaultTag.bg, TK_3D_FLAT_GC), invalidX, y+height, invalidWidth, invalidY+invalidHeight-y-height); } if (tagGc != NULL) { TableFreeGc(display, tagGc); } TableRedrawHighlight(tablePtr); /* * Free the hash table used to cache evaluations. */ Tcl_DeleteHashTable(colTagsCache); ckfree((char *) (colTagsCache)); Tcl_DeleteHashTable(drawnCache); ckfree((char *) (drawnCache)); } /* *---------------------------------------------------------------------- * * TableInvalidate -- * Invalidates a rectangle and adds it to the total invalid rectangle * waiting to be redrawn. If the INV_FORCE flag bit is set, * it does an update instantly else waits until Tk is idle. * * Results: * Will schedule table (re)display. * * Side effects: * None * *---------------------------------------------------------------------- */ void TableInvalidate(Table * tablePtr, int x, int y, int w, int h, int flags) { Tk_Window tkwin = tablePtr->tkwin; int hl = tablePtr->highlightWidth; int height = Tk_Height(tkwin); int width = Tk_Width(tkwin); /* * Make sure that the window hasn't been destroyed already. * Avoid allocating 0 sized pixmaps which would be fatal, * and check if rectangle is even on the screen. */ if ((tkwin == NULL) || (w <= 0) || (h <= 0) || (x > width) || (y > height)) { return; } /* If not even mapped, wait for the remap to redraw all */ if (!Tk_IsMapped(tkwin)) { tablePtr->flags |= REDRAW_ON_MAP; return; } /* * If no pending updates exist, then replace the rectangle. * Otherwise find the bounding rectangle. */ if ((flags & INV_HIGHLIGHT) && (x < hl || y < hl || x+w >= width-hl || y+h >= height-hl)) { tablePtr->flags |= REDRAW_BORDER; } if (tablePtr->flags & REDRAW_PENDING) { tablePtr->invalidWidth = MAX(x + w, tablePtr->invalidX+tablePtr->invalidWidth); tablePtr->invalidHeight = MAX(y + h, tablePtr->invalidY+tablePtr->invalidHeight); if (tablePtr->invalidX > x) tablePtr->invalidX = x; if (tablePtr->invalidY > y) tablePtr->invalidY = y; tablePtr->invalidWidth -= tablePtr->invalidX; tablePtr->invalidHeight -= tablePtr->invalidY; /* Do we want to force this update out? */ if (flags & INV_FORCE) { Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr); TableDisplay((ClientData) tablePtr); } } else { tablePtr->invalidX = x; tablePtr->invalidY = y; tablePtr->invalidWidth = w; tablePtr->invalidHeight = h; if (flags & INV_FORCE) { TableDisplay((ClientData) tablePtr); } else { tablePtr->flags |= REDRAW_PENDING; Tcl_DoWhenIdle(TableDisplay, (ClientData) tablePtr); } } } /* *---------------------------------------------------------------------- * * TableFlashEvent -- * Called when the flash timer goes off. * * Results: * Decrements all the entries in the hash table and invalidates * any cells that expire, deleting them from the table. If the * table is now empty, stops the timer, else reenables it. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void TableFlashEvent(ClientData clientdata) { Table *tablePtr = (Table *) clientdata; Tcl_HashEntry *entryPtr; Tcl_HashSearch search; int entries, count, row, col; entries = 0; for (entryPtr = Tcl_FirstHashEntry(tablePtr->flashCells, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { count = (int) Tcl_GetHashValue(entryPtr); if (--count <= 0) { /* get the cell address and invalidate that region only */ TableParseArrayIndex(&row, &col, Tcl_GetHashKey(tablePtr->flashCells, entryPtr)); /* delete the entry from the table */ Tcl_DeleteHashEntry(entryPtr); TableRefresh(tablePtr, row-tablePtr->rowOffset, col-tablePtr->colOffset, CELL); } else { Tcl_SetHashValue(entryPtr, (ClientData) count); entries++; } } /* do I need to restart the timer */ if (entries && tablePtr->flashMode) { tablePtr->flashTimer = Tcl_CreateTimerHandler(250, TableFlashEvent, (ClientData) tablePtr); } else { tablePtr->flashTimer = 0; } } /* *---------------------------------------------------------------------- * * TableAddFlash -- * Adds a flash on cell row,col (real coords) with the default timeout * if flashing is enabled and flashtime > 0. * * Results: * Cell will flash. * * Side effects: * Will start flash timer if it didn't exist. * *---------------------------------------------------------------------- */ void TableAddFlash(Table *tablePtr, int row, int col) { char buf[INDEX_BUFSIZE]; int dummy; Tcl_HashEntry *entryPtr; if (!tablePtr->flashMode || tablePtr->flashTime < 1) { return; } /* create the array index in user coords */ TableMakeArrayIndex(row+tablePtr->rowOffset, col+tablePtr->colOffset, buf); /* add the flash to the hash table */ entryPtr = Tcl_CreateHashEntry(tablePtr->flashCells, buf, &dummy); Tcl_SetHashValue(entryPtr, tablePtr->flashTime); /* now set the timer if it's not already going and invalidate the area */ if (tablePtr->flashTimer == NULL) { tablePtr->flashTimer = Tcl_CreateTimerHandler(250, TableFlashEvent, (ClientData) tablePtr); } } /* *---------------------------------------------------------------------- * * TableSetActiveIndex -- * Sets the "active" index of the associated array to the current * value of the active buffer. * * Results: * None. * * Side effects: * Traces on the array can cause side effects. * *---------------------------------------------------------------------- */ void TableSetActiveIndex(register Table *tablePtr) { if (tablePtr->arrayVar) { tablePtr->flags |= SET_ACTIVE; Tcl_SetVar2(tablePtr->interp, tablePtr->arrayVar, "active", tablePtr->activeBuf, TCL_GLOBAL_ONLY); tablePtr->flags &= ~SET_ACTIVE; } } /* *---------------------------------------------------------------------- * * TableGetActiveBuf -- * Get the current selection into the buffer and mark it as unedited. * Set the position to the end of the string. * * Results: * None. * * Side effects: * tablePtr->activeBuf will change. * *---------------------------------------------------------------------- */ void TableGetActiveBuf(register Table *tablePtr) { char *data = ""; if (tablePtr->flags & HAS_ACTIVE) { data = TableGetCellValue(tablePtr, tablePtr->activeRow+tablePtr->rowOffset, tablePtr->activeCol+tablePtr->colOffset); } if (STREQ(tablePtr->activeBuf, data)) { /* this forced SetActiveIndex is necessary if we change array vars and * they happen to have these cells equal, we won't properly set the * active index for the new array var unless we do this here */ TableSetActiveIndex(tablePtr); return; } /* is the buffer long enough */ tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, strlen(data)+1); strcpy(tablePtr->activeBuf, data); TableGetIcursor(tablePtr, "end", (int *)0); tablePtr->flags &= ~TEXT_CHANGED; TableSetActiveIndex(tablePtr); } /* *---------------------------------------------------------------------- * * TableVarProc -- * This is the trace procedure associated with the Tcl array. No * validation will occur here because this only triggers when the * array value is directly set, and we can't maintain the old value. * * Results: * Invalidates changed cell. * * Side effects: * Creates/Updates entry in the cache if we are caching. * *---------------------------------------------------------------------- */ static char * TableVarProc(clientData, interp, name, index, flags) ClientData clientData; /* Information about table. */ Tcl_Interp *interp; /* Interpreter containing variable. */ char *name; /* Not used. */ char *index; /* Not used. */ int flags; /* Information about what happened. */ { Table *tablePtr = (Table *) clientData; int dummy, row, col, update = 1; /* This is redundant, as the name should always == arrayVar */ name = tablePtr->arrayVar; /* is this the whole var being destroyed or just one cell being deleted */ if ((flags & TCL_TRACE_UNSETS) && index == NULL) { /* if this isn't the interpreter being destroyed reinstate the trace */ if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { Tcl_SetVar2(interp, name, TEST_KEY, "", TCL_GLOBAL_ONLY); Tcl_UnsetVar2(interp, name, TEST_KEY, TCL_GLOBAL_ONLY); Tcl_ResetResult(interp); /* set a trace on the variable */ Tcl_TraceVar(interp, name, TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_GLOBAL_ONLY, (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr); /* only do the following if arrayVar is our data source */ if (tablePtr->dataSource & DATA_ARRAY) { /* clear the selection buffer */ TableGetActiveBuf(tablePtr); /* flush any cache */ Tcl_DeleteHashTable(tablePtr->cache); Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS); /* and invalidate the table */ TableInvalidateAll(tablePtr, 0); } } return (char *)NULL; } /* only continue if arrayVar is our data source */ if (!(tablePtr->dataSource & DATA_ARRAY)) { return (char *)NULL; } /* get the cell address and invalidate that region only. * Make sure that it is a valid cell address. */ if (STREQ("active", index)) { if (tablePtr->flags & SET_ACTIVE) { /* If we are already setting the active cell, the update * will occur in other code */ update = 0; } else { /* modified TableGetActiveBuf */ char *data = ""; row = tablePtr->activeRow; col = tablePtr->activeCol; if (tablePtr->flags & HAS_ACTIVE) data = Tcl_GetVar2(interp, name, index, TCL_GLOBAL_ONLY); if (!data) data = ""; if (STREQ(tablePtr->activeBuf, data)) { return (char *)NULL; } tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, strlen(data)+1); strcpy(tablePtr->activeBuf, data); /* set cursor to the last char */ TableGetIcursor(tablePtr, "end", (int *)0); tablePtr->flags |= TEXT_CHANGED; } } else if (TableParseArrayIndex(&row, &col, index) == 2) { char buf[INDEX_BUFSIZE]; /* Make sure it won't trigger on array(2,3extrastuff) */ TableMakeArrayIndex(row, col, buf); if (strcmp(buf, index)) { return (char *)NULL; } if (tablePtr->caching) { Tcl_HashEntry *entryPtr; char *val, *data = NULL; data = Tcl_GetVar2(interp, name, index, TCL_GLOBAL_ONLY); if (!data) data = ""; val = (char *)ckalloc(strlen(data)+1); strcpy(val, data); entryPtr = Tcl_CreateHashEntry(tablePtr->cache, buf, &dummy); Tcl_SetHashValue(entryPtr, val); } /* convert index to real coords */ row -= tablePtr->rowOffset; col -= tablePtr->colOffset; /* did the active cell just update */ if (row == tablePtr->activeRow && col == tablePtr->activeCol) { TableGetActiveBuf(tablePtr); } /* Flash the cell */ TableAddFlash(tablePtr, row, col); } else { return (char *)NULL; } if (update) { TableRefresh(tablePtr, row, col, CELL); } return (char *)NULL; } /* *---------------------------------------------------------------------- * * TableGeometryRequest -- * This procedure is invoked to request a new geometry from Tk. * * Results: * None. * * Side effects: * Geometry information is updated and a new requested size is * registered for the widget. Internal border info is also set. * *---------------------------------------------------------------------- */ void TableGeometryRequest(tablePtr) register Table *tablePtr; { int x, y; /* Do the geometry request * If -width #cols was not specified or it is greater than the real * number of cols, use maxWidth as a lower bound, with the other lower * bound being the upper bound of the window's user-set width and the * value of -maxwidth set by the programmer * Vice versa for rows/height */ x = MIN((tablePtr->maxReqCols==0 || tablePtr->maxReqCols > tablePtr->cols)? tablePtr->maxWidth : tablePtr->colStarts[tablePtr->maxReqCols], tablePtr->maxReqWidth) + 2*tablePtr->highlightWidth; y = MIN((tablePtr->maxReqRows==0 || tablePtr->maxReqRows > tablePtr->rows)? tablePtr->maxHeight : tablePtr->rowStarts[tablePtr->maxReqRows], tablePtr->maxReqHeight) + 2*tablePtr->highlightWidth; Tk_GeometryRequest(tablePtr->tkwin, x, y); } /* *---------------------------------------------------------------------- * * TableAdjustActive -- * This procedure is called by AdjustParams and CMD_ACTIVATE to * move the active cell. * * Results: * Old and new active cell indices will be invalidated. * * Side effects: * If the old active cell index was edited, it will be saved. * The active buffer will be updated. * *---------------------------------------------------------------------- */ void TableAdjustActive(tablePtr) register Table *tablePtr; /* Widget record for table */ { if (tablePtr->flags & HAS_ACTIVE) { /* * Make sure the active cell has a reasonable real index */ CONSTRAIN(tablePtr->activeRow, 0, tablePtr->rows-1); CONSTRAIN(tablePtr->activeCol, 0, tablePtr->cols-1); } /* * Check the new value of active cell against the original, * Only invalidate if it changed. */ if (tablePtr->oldActRow == tablePtr->activeRow && tablePtr->oldActCol == tablePtr->activeCol) { return; } if (tablePtr->oldActRow >= 0 && tablePtr->oldActCol >= 0) { /* * Set the value of the old active cell to the active buffer * SetCellValue will check if the value actually changed */ if (tablePtr->flags & TEXT_CHANGED) { /* WARNING an outside trace will be triggered here and if it * calls something that causes TableAdjustParams to be called * again, we are in data consistency trouble */ /* HACK - turn TEXT_CHANGED off now to possibly avoid the * above data inconsistency problem. */ tablePtr->flags &= ~TEXT_CHANGED; TableSetCellValue(tablePtr, tablePtr->oldActRow + tablePtr->rowOffset, tablePtr->oldActCol + tablePtr->colOffset, tablePtr->activeBuf); } /* * Invalidate the old active cell */ TableRefresh(tablePtr, tablePtr->oldActRow, tablePtr->oldActCol, CELL); } /* * Store the new active cell value into the active buffer */ TableGetActiveBuf(tablePtr); /* * Invalidate the new active cell */ TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); /* * Cache the old active row/col for the next time this is called */ tablePtr->oldActRow = tablePtr->activeRow; tablePtr->oldActCol = tablePtr->activeCol; } /* *---------------------------------------------------------------------- * * TableAdjustParams -- * Calculate the row and column starts. Adjusts the topleft corner * variable to keep it within the screen range, out of the titles * and keep the screen full make sure the selected cell is in the * visible area checks to see if the top left cell has changed at * all and invalidates the table if it has. * * Results: * None. * * Side Effects: * Number of rows can change if -rowstretchmode == fill. * topRow && leftCol can change to fit display. * activeRow/Col can change to ensure it is a valid cell. * *---------------------------------------------------------------------- */ void TableAdjustParams(register Table *tablePtr) { int topRow, leftCol, row, col, total, i, value, x, y, width, height, w, h, hl, px, py, recalc, bd[4], diff, unpreset, lastUnpreset, pad, lastPad, numPixels, defColWidth, defRowHeight; Tcl_HashEntry *entryPtr; /* * Cache some values for many upcoming calculations */ hl = tablePtr->highlightWidth; w = Tk_Width(tablePtr->tkwin) - (2 * hl); h = Tk_Height(tablePtr->tkwin) - (2 * hl); TableGetTagBorders(&(tablePtr->defaultTag), &bd[0], &bd[1], &bd[2], &bd[3]); px = bd[0] + bd[1] + (2 * tablePtr->padX); py = bd[2] + bd[3] + (2 * tablePtr->padY); /* * Account for whether default dimensions are in chars (>0) or * pixels (<=0). Border and Pad space is added in here for convenience. * * When a value in pixels is specified, we take that exact amount, * not adding in padding. */ if (tablePtr->defColWidth > 0) { defColWidth = tablePtr->charWidth * tablePtr->defColWidth + px; } else { defColWidth = -(tablePtr->defColWidth); } if (tablePtr->defRowHeight > 0) { defRowHeight = tablePtr->charHeight * tablePtr->defRowHeight + py; } else { defRowHeight = -(tablePtr->defRowHeight); } /* * Set up the arrays to hold the col pixels and starts. * ckrealloc was fixed in 8.2.1 to handle NULLs, so we can't rely on it. */ if (tablePtr->colPixels) ckfree((char *) tablePtr->colPixels); tablePtr->colPixels = (int *) ckalloc(tablePtr->cols * sizeof(int)); if (tablePtr->colStarts) ckfree((char *) tablePtr->colStarts); tablePtr->colStarts = (int *) ckalloc((tablePtr->cols+1) * sizeof(int)); /* * Get all the preset columns and set their widths */ lastUnpreset = 0; numPixels = 0; unpreset = 0; for (i = 0; i < tablePtr->cols; i++) { entryPtr = Tcl_FindHashEntry(tablePtr->colWidths, (char *) i); if (entryPtr == NULL) { tablePtr->colPixels[i] = -1; unpreset++; lastUnpreset = i; } else { value = (int) Tcl_GetHashValue(entryPtr); if (value > 0) { tablePtr->colPixels[i] = value * tablePtr->charWidth + px; } else { /* * When a value in pixels is specified, we take that exact * amount, not adding in pad or border values. */ tablePtr->colPixels[i] = -value; } numPixels += tablePtr->colPixels[i]; } } /* * Work out how much to pad each col depending on the mode. */ diff = w - numPixels - (unpreset * defColWidth); total = 0; /* * Now do the padding and calculate the column starts. * Diff lower than 0 means we can't see the entire set of columns, * thus no special stretching will occur & we optimize the calculation. */ if (diff <= 0) { for (i = 0; i < tablePtr->cols; i++) { if (tablePtr->colPixels[i] == -1) { tablePtr->colPixels[i] = defColWidth; } tablePtr->colStarts[i] = total; total += tablePtr->colPixels[i]; } } else { switch (tablePtr->colStretch) { case STRETCH_MODE_NONE: pad = 0; lastPad = 0; break; case STRETCH_MODE_UNSET: if (unpreset == 0) { pad = 0; lastPad = 0; } else { pad = diff / unpreset; lastPad = diff - pad * (unpreset - 1); } break; case STRETCH_MODE_LAST: pad = 0; lastPad = diff; lastUnpreset = tablePtr->cols - 1; break; default: /* STRETCH_MODE_ALL, but also FILL for cols */ pad = diff / tablePtr->cols; /* force it to be applied to the last column too */ lastUnpreset = tablePtr->cols - 1; lastPad = diff - pad * lastUnpreset; } for (i = 0; i < tablePtr->cols; i++) { if (tablePtr->colPixels[i] == -1) { tablePtr->colPixels[i] = defColWidth + ((i == lastUnpreset) ? lastPad : pad); } else if (tablePtr->colStretch == STRETCH_MODE_ALL) { tablePtr->colPixels[i] += (i == lastUnpreset) ? lastPad : pad; } tablePtr->colStarts[i] = total; total += tablePtr->colPixels[i]; } } tablePtr->colStarts[i] = tablePtr->maxWidth = total; /* * The 'do' loop is only necessary for rows because of FILL mode */ recalc = 0; do { /* Set up the arrays to hold the row pixels and starts */ /* FIX - this can be moved outside 'do' if you check >row size */ if (tablePtr->rowPixels) ckfree((char *) tablePtr->rowPixels); tablePtr->rowPixels = (int *) ckalloc(tablePtr->rows * sizeof(int)); /* get all the preset rows and set their heights */ lastUnpreset = 0; numPixels = 0; unpreset = 0; for (i = 0; i < tablePtr->rows; i++) { entryPtr = Tcl_FindHashEntry(tablePtr->rowHeights, (char *) i); if (entryPtr == NULL) { tablePtr->rowPixels[i] = -1; unpreset++; lastUnpreset = i; } else { value = (int) Tcl_GetHashValue(entryPtr); if (value > 0) { tablePtr->rowPixels[i] = value * tablePtr->charHeight + py; } else { /* * When a value in pixels is specified, we take that exact * amount, not adding in pad or border values. */ tablePtr->rowPixels[i] = -value; } numPixels += tablePtr->rowPixels[i]; } } /* work out how much to pad each row depending on the mode */ diff = h - numPixels - (unpreset * defRowHeight); switch(tablePtr->rowStretch) { case STRETCH_MODE_NONE: pad = 0; lastPad = 0; break; case STRETCH_MODE_UNSET: if (unpreset == 0) { pad = 0; lastPad = 0; } else { pad = MAX(0,diff) / unpreset; lastPad = MAX(0,diff) - pad * (unpreset - 1); } break; case STRETCH_MODE_LAST: pad = 0; lastPad = MAX(0,diff); /* force it to be applied to the last column too */ lastUnpreset = tablePtr->rows - 1; break; case STRETCH_MODE_FILL: pad = 0; lastPad = diff; if (diff && !recalc) { tablePtr->rows += (diff/defRowHeight); if (diff < 0 && tablePtr->rows <= 0) { tablePtr->rows = 1; } lastUnpreset = tablePtr->rows - 1; recalc = 1; continue; } else { lastUnpreset = tablePtr->rows - 1; recalc = 0; } break; default: /* STRETCH_MODE_ALL */ pad = MAX(0,diff) / tablePtr->rows; /* force it to be applied to the last column too */ lastUnpreset = tablePtr->rows - 1; lastPad = MAX(0,diff) - pad * lastUnpreset; } } while (recalc); if (tablePtr->rowStarts) ckfree((char *) tablePtr->rowStarts); tablePtr->rowStarts = (int *) ckalloc((tablePtr->rows+1)*sizeof(int)); /* * Now do the padding and calculate the row starts */ total = 0; for (i = 0; i < tablePtr->rows; i++) { if (tablePtr->rowPixels[i] == -1) { tablePtr->rowPixels[i] = defRowHeight + ((i==lastUnpreset)?lastPad:pad); } else if (tablePtr->rowStretch == STRETCH_MODE_ALL) { tablePtr->rowPixels[i] += (i==lastUnpreset)?lastPad:pad; } /* calculate the start of each row */ tablePtr->rowStarts[i] = total; total += tablePtr->rowPixels[i]; } tablePtr->rowStarts[i] = tablePtr->maxHeight = total; /* * Make sure the top row and col have reasonable real indices */ CONSTRAIN(tablePtr->topRow, tablePtr->titleRows, tablePtr->rows-1); CONSTRAIN(tablePtr->leftCol, tablePtr->titleCols, tablePtr->cols-1); /* * If we don't have the info, don't bother to fix up the other parameters */ if (Tk_WindowId(tablePtr->tkwin) == None) { tablePtr->oldTopRow = tablePtr->oldLeftCol = -1; return; } topRow = tablePtr->topRow; leftCol = tablePtr->leftCol; w += hl; h += hl; /* * If we use this value of topRow, will we fill the window? * if not, decrease it until we will, or until it gets to titleRows * make sure we don't cut off the bottom row */ for (; topRow > tablePtr->titleRows; topRow--) { if ((tablePtr->maxHeight-(tablePtr->rowStarts[topRow-1] - tablePtr->rowStarts[tablePtr->titleRows])) > h) { break; } } /* * If we use this value of topCol, will we fill the window? * if not, decrease it until we will, or until it gets to titleCols * make sure we don't cut off the left column */ for (; leftCol > tablePtr->titleCols; leftCol--) { if ((tablePtr->maxWidth-(tablePtr->colStarts[leftCol-1] - tablePtr->colStarts[tablePtr->titleCols])) > w) { break; } } tablePtr->topRow = topRow; tablePtr->leftCol = leftCol; /* * Now work out where the bottom right is for scrollbar update and to test * for one last stretch. Avoid the confusion that spans could cause for * determining the last cell dimensions. */ tablePtr->flags |= AVOID_SPANS; TableGetLastCell(tablePtr, &row, &col); TableCellVCoords(tablePtr, row, col, &x, &y, &width, &height, 0); tablePtr->flags &= ~AVOID_SPANS; /* * Do we have scrollbars, if so, calculate and call the TCL functions In * order to get the scrollbar to be completely full when the whole screen * is shown and there are titles, we have to arrange for the scrollbar * range to be 0 -> rows-titleRows etc. This leads to the position * setting methods, toprow and leftcol, being relative to the titles, not * absolute row and column numbers. */ if (tablePtr->yScrollCmd != NULL || tablePtr->xScrollCmd != NULL) { Tcl_Interp *interp = tablePtr->interp; char buf[INDEX_BUFSIZE]; double first, last; /* * We must hold onto the interpreter because the data referred to at * tablePtr might be freed as a result of the call to Tcl_VarEval. */ Tcl_Preserve((ClientData) interp); /* Do we have a Y-scrollbar and rows to scroll? */ if (tablePtr->yScrollCmd != NULL) { if (row < tablePtr->titleRows) { first = 0; last = 1; } else { diff = tablePtr->rowStarts[tablePtr->titleRows]; last = (double) (tablePtr->rowStarts[tablePtr->rows]-diff); if (last <= 0.0) { first = 0; last = 1; } else { first = (tablePtr->rowStarts[topRow]-diff) / last; last = (height+tablePtr->rowStarts[row]-diff) / last; } } sprintf(buf, " %g %g", first, last); if (Tcl_VarEval(interp, tablePtr->yScrollCmd, buf, (char *)NULL) != TCL_OK) { Tcl_AddErrorInfo(interp, "\n\t(vertical scrolling command executed by table)"); Tcl_BackgroundError(interp); } } /* Do we have a X-scrollbar and cols to scroll? */ if (tablePtr->xScrollCmd != NULL) { if (col < tablePtr->titleCols) { first = 0; last = 1; } else { diff = tablePtr->colStarts[tablePtr->titleCols]; last = (double) (tablePtr->colStarts[tablePtr->cols]-diff); if (last <= 0.0) { first = 0; last = 1; } else { first = (tablePtr->colStarts[leftCol]-diff) / last; last = (width+tablePtr->colStarts[col]-diff) / last; } } sprintf(buf, " %g %g", first, last); if (Tcl_VarEval(interp, tablePtr->xScrollCmd, buf, (char *)NULL) != TCL_OK) { Tcl_AddErrorInfo(interp, "\n\t(horizontal scrolling command executed by table)"); Tcl_BackgroundError(interp); } } Tcl_Release((ClientData) interp); } /* * Adjust the last row/col to fill empty space if it is visible. * Do this after setting the scrollbars to not upset its calculations. */ if (row == tablePtr->rows-1 && tablePtr->rowStretch != STRETCH_MODE_NONE) { diff = h-(y+height); if (diff > 0) { tablePtr->rowPixels[tablePtr->rows-1] += diff; tablePtr->rowStarts[tablePtr->rows] += diff; } } if (col == tablePtr->cols-1 && tablePtr->colStretch != STRETCH_MODE_NONE) { diff = w-(x+width); if (diff > 0) { tablePtr->colPixels[tablePtr->cols-1] += diff; tablePtr->colStarts[tablePtr->cols] += diff; } } TableAdjustActive(tablePtr); /* * now check the new value of topleft cell against the originals, * If they changed, invalidate the area, else leave it alone */ if (tablePtr->topRow != tablePtr->oldTopRow || tablePtr->leftCol != tablePtr->oldLeftCol) { /* set the old top row/col for the next time this function is called */ tablePtr->oldTopRow = tablePtr->topRow; tablePtr->oldLeftCol = tablePtr->leftCol; /* only the upper corner title cells wouldn't change */ TableInvalidateAll(tablePtr, 0); } } /* *---------------------------------------------------------------------- * * TableCursorEvent -- * Toggle the cursor status. Equivalent to EntryBlinkProc. * * Results: * None. * * Side effects: * The cursor will be switched off/on. * *---------------------------------------------------------------------- */ static void TableCursorEvent(ClientData clientData) { register Table *tablePtr = (Table *) clientData; if (!(tablePtr->flags & HAS_FOCUS) || (tablePtr->insertOffTime == 0) || (tablePtr->flags & ACTIVE_DISABLED) || (tablePtr->state != STATE_NORMAL)) { return; } if (tablePtr->cursorTimer != NULL) { Tcl_DeleteTimerHandler(tablePtr->cursorTimer); } tablePtr->cursorTimer = Tcl_CreateTimerHandler((tablePtr->flags & CURSOR_ON) ? tablePtr->insertOffTime : tablePtr->insertOnTime, TableCursorEvent, (ClientData) tablePtr); /* Toggle the cursor */ tablePtr->flags ^= CURSOR_ON; /* invalidate the cell */ TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); } /* *---------------------------------------------------------------------- * * TableConfigCursor -- * Configures the timer depending on the state of the table. * Equivalent to EntryFocusProc. * * Results: * None. * * Side effects: * The cursor will be switched off/on. * *---------------------------------------------------------------------- */ void TableConfigCursor(register Table *tablePtr) { /* * To have a cursor, we have to have focus and allow edits */ if ((tablePtr->flags & HAS_FOCUS) && (tablePtr->state == STATE_NORMAL) && !(tablePtr->flags & ACTIVE_DISABLED)) { /* * Turn the cursor ON */ if (!(tablePtr->flags & CURSOR_ON)) { tablePtr->flags |= CURSOR_ON; /* * Only refresh when we toggled cursor */ TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); } /* set up the first timer */ if (tablePtr->insertOffTime != 0) { /* make sure nothing existed */ Tcl_DeleteTimerHandler(tablePtr->cursorTimer); tablePtr->cursorTimer = Tcl_CreateTimerHandler(tablePtr->insertOnTime, TableCursorEvent, (ClientData) tablePtr); } } else { /* * Turn the cursor OFF */ if ((tablePtr->flags & CURSOR_ON)) { tablePtr->flags &= ~CURSOR_ON; TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); } /* and disable the timer */ if (tablePtr->cursorTimer != NULL) { Tcl_DeleteTimerHandler(tablePtr->cursorTimer); } tablePtr->cursorTimer = NULL; } } /* *---------------------------------------------------------------------- * * TableFetchSelection -- * 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 TableFetchSelection(clientData, offset, buffer, maxBytes) ClientData clientData; /* Information about table 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. */ { register Table *tablePtr = (Table *) clientData; Tcl_Interp *interp = tablePtr->interp; char *value, *data, *rowsep = tablePtr->rowSep, *colsep = tablePtr->colSep; Tcl_DString selection; Tcl_HashEntry *entryPtr; Tcl_HashSearch search; int length, count, lastrow=0, needcs=0, r, c, listArgc, rslen=0, cslen=0; int numcols, numrows; char **listArgv; /* if we are not exporting the selection || * we have no data source, return */ if (!tablePtr->exportSelection || (tablePtr->dataSource == DATA_NONE)) { return -1; } /* First get a sorted list of the selected elements */ Tcl_DStringInit(&selection); for (entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { Tcl_DStringAppendElement(&selection, Tcl_GetHashKey(tablePtr->selCells, entryPtr)); } value = TableCellSort(tablePtr, Tcl_DStringValue(&selection)); Tcl_DStringFree(&selection); if (value == NULL || Tcl_SplitList(interp, value, &listArgc, &listArgv) != TCL_OK) { return -1; } Tcl_Free(value); Tcl_DStringInit(&selection); rslen = (rowsep?(strlen(rowsep)):0); cslen = (colsep?(strlen(colsep)):0); numrows = numcols = 0; for (count = 0; count < listArgc; count++) { TableParseArrayIndex(&r, &c, listArgv[count]); if (count) { if (lastrow != r) { lastrow = r; needcs = 0; if (rslen) { Tcl_DStringAppend(&selection, rowsep, rslen); } else { Tcl_DStringEndSublist(&selection); Tcl_DStringStartSublist(&selection); } ++numrows; } else { if (++needcs > numcols) numcols = needcs; } } else { lastrow = r; needcs = 0; if (!rslen) { Tcl_DStringStartSublist(&selection); } } data = TableGetCellValue(tablePtr, r, c); if (cslen) { if (needcs) { Tcl_DStringAppend(&selection, colsep, cslen); } Tcl_DStringAppend(&selection, data, -1); } else { Tcl_DStringAppendElement(&selection, data); } } if (!rslen && count) { Tcl_DStringEndSublist(&selection); } Tcl_Free((char *) listArgv); if (tablePtr->selCmd != NULL) { Tcl_DString script; Tcl_DStringInit(&script); ExpandPercents(tablePtr, tablePtr->selCmd, numrows+1, numcols+1, Tcl_DStringValue(&selection), (char *)NULL, listArgc, &script, CMD_ACTIVATE); if (Tcl_GlobalEval(interp, Tcl_DStringValue(&script)) == TCL_ERROR) { Tcl_AddErrorInfo(interp, "\n (error in table selection command)"); Tcl_BackgroundError(interp); Tcl_DStringFree(&script); Tcl_DStringFree(&selection); return -1; } else { Tcl_DStringGetResult(interp, &selection); } Tcl_DStringFree(&script); } length = Tcl_DStringLength(&selection); if (length == 0) return -1; /* Copy the requested portion of the selection to the buffer. */ count = length - offset; if (count <= 0) { count = 0; } else { if (count > maxBytes) { count = maxBytes; } memcpy((VOID *) buffer, (VOID *) (Tcl_DStringValue(&selection) + offset), (size_t) count); } buffer[count] = '\0'; Tcl_DStringFree(&selection); return count; } /* *---------------------------------------------------------------------- * * TableLostSelection -- * This procedure is called back by Tk when the selection is * grabbed away from a table widget. * * Results: * None. * * Side effects: * The existing selection is unhighlighted, and the window is * marked as not containing a selection. * *---------------------------------------------------------------------- */ void TableLostSelection(clientData) ClientData clientData; /* Information about table widget. */ { register Table *tablePtr = (Table *) clientData; if (tablePtr->exportSelection) { Tcl_HashEntry *entryPtr; Tcl_HashSearch search; int row, col; /* Same as SEL CLEAR ALL */ for (entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search); entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { TableParseArrayIndex(&row, &col, Tcl_GetHashKey(tablePtr->selCells,entryPtr)); Tcl_DeleteHashEntry(entryPtr); TableRefresh(tablePtr, row-tablePtr->rowOffset, col-tablePtr->colOffset, CELL); } } } /* *---------------------------------------------------------------------- * * TableRestrictProc -- * A Tk_RestrictProc used by TableValidateChange to eliminate any * extra key input events in the event queue that * have a serial number no less than a given value. * * Results: * Returns either TK_DISCARD_EVENT or TK_DEFER_EVENT. * * Side effects: * None. * *---------------------------------------------------------------------- */ static Tk_RestrictAction TableRestrictProc(serial, eventPtr) ClientData serial; XEvent *eventPtr; { if ((eventPtr->type == KeyRelease || eventPtr->type == KeyPress) && ((eventPtr->xany.serial-(unsigned int)serial) > 0)) { return TK_DEFER_EVENT; } else { return TK_PROCESS_EVENT; } } /* *-------------------------------------------------------------- * * TableValidateChange -- * This procedure is invoked when any character is added or * removed from the table widget, or a set has triggered validation. * * Results: * TCL_OK if the validatecommand accepts the new string, * TCL_BREAK if the validatecommand rejects the new string, * TCL_ERROR if any problems occured with validatecommand. * * Side effects: * The insertion/deletion may be aborted, and the * validatecommand might turn itself off (if an error * or loop condition arises). * *-------------------------------------------------------------- */ int TableValidateChange(tablePtr, r, c, old, new, index) register Table *tablePtr; /* Table that needs validation. */ int r, c; /* row,col index of cell in user coords */ char *old; /* current value of cell */ char *new; /* potential new value of cell */ int index; /* index of insert/delete, -1 otherwise */ { register Tcl_Interp *interp = tablePtr->interp; int code, bool; Tk_RestrictProc *rstrct; ClientData cdata; Tcl_DString script; if (tablePtr->valCmd == NULL || tablePtr->validate == 0) { return TCL_OK; } /* Magic code to make this bit of code UI synchronous in the face of * possible new key events */ XSync(tablePtr->display, False); rstrct = Tk_RestrictEvents(TableRestrictProc, (ClientData) NextRequest(tablePtr->display), &cdata); /* * If we're already validating, then we're hitting a loop condition * Return and set validate to 0 to disallow further validations * and prevent current validation from finishing */ if (tablePtr->flags & VALIDATING) { tablePtr->validate = 0; return TCL_OK; } tablePtr->flags |= VALIDATING; /* Now form command string and run through the -validatecommand */ Tcl_DStringInit(&script); ExpandPercents(tablePtr, tablePtr->valCmd, r, c, old, new, index, &script, CMD_VALIDATE); code = Tcl_GlobalEval(tablePtr->interp, Tcl_DStringValue(&script)); Tcl_DStringFree(&script); if (code != TCL_OK && code != TCL_RETURN) { Tcl_AddErrorInfo(interp, "\n\t(in validation command executed by table)"); Tcl_BackgroundError(interp); code = TCL_ERROR; } else if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp), &bool) != TCL_OK) { Tcl_AddErrorInfo(interp, "\n\tboolean not returned by validation command"); Tcl_BackgroundError(interp); code = TCL_ERROR; } else { code = (bool) ? TCL_OK : TCL_BREAK; } Tcl_SetStringObj(Tcl_GetObjResult(interp), (char *) NULL, 0); /* * If ->validate has become VALIDATE_NONE during the validation, * it means that a loop condition almost occured. Do not allow * this validation result to finish. */ if (tablePtr->validate == 0) { code = TCL_ERROR; } /* If validate will return ERROR, then disallow further validations */ if (code == TCL_ERROR) { tablePtr->validate = 0; } Tk_RestrictEvents(rstrct, cdata, &cdata); tablePtr->flags &= ~VALIDATING; return code; } /* *-------------------------------------------------------------- * * ExpandPercents -- * Given a command and an event, produce a new command * by replacing % constructs in the original command * with information from the X event. * * Results: * The new expanded command is appended to the dynamic string * given by dsPtr. * * Side effects: * None. * *-------------------------------------------------------------- */ void ExpandPercents(tablePtr, before, r, c, old, new, index, dsPtr, cmdType) Table *tablePtr; /* Table that needs validation. */ char *before; /* Command containing percent * expressions to be replaced. */ int r, c; /* row,col index of cell */ char *old; /* current value of cell */ char *new; /* potential new value of cell */ int index; /* index of insert/delete */ Tcl_DString *dsPtr; /* Dynamic string in which to append * new command. */ int cmdType; /* type of command to make %-subs for */ { int length, spaceNeeded, cvtFlags; #ifdef TCL_UTF_MAX Tcl_UniChar ch; #else char ch; #endif char *string, buf[INDEX_BUFSIZE]; /* This returns the static value of the string as set in the array */ if (old == NULL && cmdType == CMD_VALIDATE) { old = TableGetCellValue(tablePtr, r, c); } while (1) { if (*before == '\0') { break; } /* * Find everything up to the next % character and append it * to the result string. */ string = before; #ifdef TCL_UTF_MAX /* No need to convert '%', as it is in ascii range */ string = Tcl_UtfFindFirst(before, '%'); #else string = strchr(before, '%'); #endif if (string == (char *) NULL) { Tcl_DStringAppend(dsPtr, before, -1); break; } else if (string != before) { Tcl_DStringAppend(dsPtr, before, string-before); before = string; } /* * There's a percent sequence here. Process it. */ before++; /* skip over % */ if (*before != '\0') { #ifdef TCL_UTF_MAX before += Tcl_UtfToUniChar(before, &ch); #else ch = before[0]; before++; #endif } else { ch = '%'; } switch (ch) { case 'c': sprintf(buf, "%d", c); string = buf; break; case 'C': /* index of cell */ TableMakeArrayIndex(r, c, buf); string = buf; break; case 'r': sprintf(buf, "%d", r); string = buf; break; case 'i': /* index of cursor OR |number| of cells selected */ sprintf(buf, "%d", index); string = buf; break; case 's': /* Current cell value */ string = old; break; case 'S': /* Potential new value of cell */ string = (new?new:old); break; case 'W': /* widget name */ string = Tk_PathName(tablePtr->tkwin); break; default: #ifdef TCL_UTF_MAX length = Tcl_UniCharToUtf(ch, buf); #else buf[0] = ch; length = 1; #endif buf[length] = '\0'; string = buf; break; } spaceNeeded = Tcl_ScanElement(string, &cvtFlags); length = Tcl_DStringLength(dsPtr); Tcl_DStringSetLength(dsPtr, length + spaceNeeded); spaceNeeded = Tcl_ConvertElement(string, Tcl_DStringValue(dsPtr) + length, cvtFlags | TCL_DONT_USE_BRACES); Tcl_DStringSetLength(dsPtr, length + spaceNeeded); } Tcl_DStringAppend(dsPtr, "", 1); } /* Function to call on loading the Table module */ #ifdef BUILD_tkTable # undef TCL_STORAGE_CLASS # define TCL_STORAGE_CLASS DLLEXPORT #endif #ifdef MAC_TCL #pragma export on #endif EXTERN int Tktable_Init(interp) Tcl_Interp *interp; { /* This defines the static chars tkTable(Safe)InitScript */ #include "tkTableInitScript.h" if ( #ifdef USE_TCL_STUBS Tcl_InitStubs(interp, "8.0", 0) #else Tcl_PkgRequire(interp, "Tcl", "8.0", 0) #endif == NULL) { return TCL_ERROR; } if ( #ifdef USE_TK_STUBS Tk_InitStubs(interp, "8.0", 0) #else # if (TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION == 0) /* We require 8.0 exact because of the Unicode in 8.1+ */ Tcl_PkgRequire(interp, "Tk", "8.0", 1) # else Tcl_PkgRequire(interp, "Tk", "8.0", 0) # endif #endif == NULL) { return TCL_ERROR; } if (Tcl_PkgProvide(interp, "Tktable", TBL_VERSION) != TCL_OK) { return TCL_ERROR; } Tcl_CreateObjCommand(interp, TBL_COMMAND, Tk_TableObjCmd, (ClientData) Tk_MainWindow(interp), (Tcl_CmdDeleteProc *) NULL); /* * The init script can't make certain calls in a safe interpreter, * so we always have to use the embedded runtime for it */ return Tcl_Eval(interp, Tcl_IsSafe(interp) ? tkTableSafeInitScript : tkTableInitScript); } EXTERN int Tktable_SafeInit(interp) Tcl_Interp *interp; { return Tktable_Init(interp); } #ifdef MAC_TCL #pragma export reset #endif #ifdef WIN32 /* *---------------------------------------------------------------------- * * DllEntryPoint -- * * This wrapper function is used by Windows to invoke the * initialization code for the DLL. If we are compiling * with Visual C++, this routine will be renamed to DllMain. * routine. * * Results: * Returns TRUE; * * Side effects: * None. * *---------------------------------------------------------------------- */ BOOL APIENTRY DllEntryPoint(hInst, reason, reserved) HINSTANCE hInst; /* Library instance handle. */ DWORD reason; /* Reason this function is being called. */ LPVOID reserved; /* Not used. */ { return TRUE; } #endif