1/* 2 * tkTable.c -- 3 * 4 * This module implements table widgets for the Tk 5 * toolkit. An table displays a 2D array of strings 6 * and allows the strings to be edited. 7 * 8 * Based on Tk3 table widget written by Roland King 9 * 10 * Updates 1996 by: 11 * Jeffrey Hobbs jeff at hobbs org 12 * John Ellson ellson@lucent.com 13 * Peter Bruecker peter@bj-ig.de 14 * Tom Moore tmoore@spatial.ca 15 * Sebastian Wangnick wangnick@orthogon.de 16 * 17 * Copyright (c) 1997-2002 Jeffrey Hobbs 18 * 19 * See the file "license.txt" for information on usage and redistribution 20 * of this file, and for a DISCLAIMER OF ALL WARRANTIES. 21 * 22 * RCS: @(#) $Id: tkTable.c,v 1.35 2010/08/05 20:41:57 hobbs Exp $ 23 */ 24 25#include "tkTable.h" 26 27#ifdef DEBUG 28#include "dprint.h" 29#endif 30 31static char ** StringifyObjects(int objc, Tcl_Obj *CONST objv[]); 32 33static int Tk_TableObjCmd(ClientData clientData, Tcl_Interp *interp, 34 int objc, Tcl_Obj *CONST objv[]); 35 36static int TableWidgetObjCmd(ClientData clientData, Tcl_Interp *interp, 37 int objc, Tcl_Obj *CONST objv[]); 38static int TableConfigure(Tcl_Interp *interp, Table *tablePtr, 39 int objc, Tcl_Obj *CONST objv[], 40 int flags, int forceUpdate); 41#ifdef HAVE_TCL84 42static void TableWorldChanged(ClientData instanceData); 43#endif 44static void TableDestroy(ClientData clientdata); 45static void TableEventProc(ClientData clientData, XEvent *eventPtr); 46static void TableCmdDeletedProc(ClientData clientData); 47 48static void TableRedrawHighlight(Table *tablePtr); 49static void TableGetGc(Display *display, Drawable d, 50 TableTag *tagPtr, GC *tagGc); 51 52static void TableDisplay(ClientData clientdata); 53static void TableFlashEvent(ClientData clientdata); 54static char * TableVarProc(ClientData clientData, Tcl_Interp *interp, 55 char *name, char *index, int flags); 56static void TableCursorEvent(ClientData clientData); 57static int TableFetchSelection(ClientData clientData, 58 int offset, char *buffer, int maxBytes); 59static Tk_RestrictAction TableRestrictProc(ClientData arg, XEvent *eventPtr); 60 61/* 62 * The following tables define the widget commands (and sub- 63 * commands) and map the indexes into the string tables into 64 * enumerated types used to dispatch the widget command. 65 */ 66 67static CONST84 char *selCmdNames[] = { 68 "anchor", "clear", "includes", "present", "set", (char *)NULL 69}; 70enum selCommand { 71 CMD_SEL_ANCHOR, CMD_SEL_CLEAR, CMD_SEL_INCLUDES, CMD_SEL_PRESENT, 72 CMD_SEL_SET 73}; 74 75static CONST84 char *commandNames[] = { 76 "activate", "bbox", "border", "cget", "clear", "configure", 77 "curselection", "curvalue", "delete", "get", "height", 78 "hidden", "icursor", "index", "insert", 79#ifdef POSTSCRIPT 80 "postscript", 81#endif 82 "reread", "scan", "see", "selection", "set", 83 "spans", "tag", "validate", "version", "window", "width", 84 "xview", "yview", (char *)NULL 85}; 86enum command { 87 CMD_ACTIVATE, CMD_BBOX, CMD_BORDER, CMD_CGET, CMD_CLEAR, CMD_CONFIGURE, 88 CMD_CURSEL, CMD_CURVALUE, CMD_DELETE, CMD_GET, CMD_HEIGHT, 89 CMD_HIDDEN, CMD_ICURSOR, CMD_INDEX, CMD_INSERT, 90#ifdef POSTSCRIPT 91 CMD_POSTSCRIPT, 92#endif 93 CMD_REREAD, CMD_SCAN, CMD_SEE, CMD_SELECTION, CMD_SET, 94 CMD_SPANS, CMD_TAG, CMD_VALIDATE, CMD_VERSION, CMD_WINDOW, CMD_WIDTH, 95 CMD_XVIEW, CMD_YVIEW 96}; 97 98/* -selecttype selection type options */ 99static Cmd_Struct sel_vals[]= { 100 {"row", SEL_ROW}, 101 {"col", SEL_COL}, 102 {"both", SEL_BOTH}, 103 {"cell", SEL_CELL}, 104 {"", 0 } 105}; 106 107/* -resizeborders options */ 108static Cmd_Struct resize_vals[]= { 109 {"row", SEL_ROW}, /* allow rows to be dragged */ 110 {"col", SEL_COL}, /* allow cols to be dragged */ 111 {"both", SEL_ROW|SEL_COL}, /* allow either to be dragged */ 112 {"none", SEL_NONE}, /* allow nothing to be dragged */ 113 {"", 0 } 114}; 115 116/* drawmode values */ 117/* The display redraws with a pixmap using TK function calls */ 118#define DRAW_MODE_SLOW (1<<0) 119/* The redisplay is direct to the screen, but TK function calls are still 120 * used to give correct 3-d border appearance and thus remain compatible 121 * with other TK apps */ 122#define DRAW_MODE_TK_COMPAT (1<<1) 123/* the redisplay goes straight to the screen and the 3d borders are rendered 124 * with a single pixel wide line only. It cheats and uses the internal 125 * border structure to do the borders */ 126#define DRAW_MODE_FAST (1<<2) 127#define DRAW_MODE_SINGLE (1<<3) 128 129static Cmd_Struct drawmode_vals[] = { 130 {"fast", DRAW_MODE_FAST}, 131 {"compatible", DRAW_MODE_TK_COMPAT}, 132 {"slow", DRAW_MODE_SLOW}, 133 {"single", DRAW_MODE_SINGLE}, 134 {"", 0} 135}; 136 137/* stretchmode values */ 138#define STRETCH_MODE_NONE (1<<0) /* No additional pixels will be 139 added to rows or cols */ 140#define STRETCH_MODE_UNSET (1<<1) /* All default rows or columns will 141 be stretched to fill the screen */ 142#define STRETCH_MODE_ALL (1<<2) /* All rows/columns will be padded 143 to fill the window */ 144#define STRETCH_MODE_LAST (1<<3) /* Stretch last elememt to fill 145 window */ 146#define STRETCH_MODE_FILL (1<<4) /* More ROWS in Window */ 147 148static Cmd_Struct stretch_vals[] = { 149 {"none", STRETCH_MODE_NONE}, 150 {"unset", STRETCH_MODE_UNSET}, 151 {"all", STRETCH_MODE_ALL}, 152 {"last", STRETCH_MODE_LAST}, 153 {"fill", STRETCH_MODE_FILL}, 154 {"", 0} 155}; 156 157static Cmd_Struct state_vals[]= { 158 {"normal", STATE_NORMAL}, 159 {"disabled", STATE_DISABLED}, 160 {"", 0 } 161}; 162 163/* The widget configuration table */ 164static Tk_CustomOption drawOpt = { Cmd_OptionSet, Cmd_OptionGet, 165 (ClientData)(&drawmode_vals) }; 166static Tk_CustomOption resizeTypeOpt = { Cmd_OptionSet, Cmd_OptionGet, 167 (ClientData)(&resize_vals) }; 168static Tk_CustomOption stretchOpt = { Cmd_OptionSet, Cmd_OptionGet, 169 (ClientData)(&stretch_vals) }; 170static Tk_CustomOption selTypeOpt = { Cmd_OptionSet, Cmd_OptionGet, 171 (ClientData)(&sel_vals) }; 172static Tk_CustomOption stateTypeOpt = { Cmd_OptionSet, Cmd_OptionGet, 173 (ClientData)(&state_vals) }; 174static Tk_CustomOption bdOpt = { TableOptionBdSet, TableOptionBdGet, 175 (ClientData) BD_TABLE }; 176 177Tk_ConfigSpec tableSpecs[] = { 178 {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor", "center", 179 Tk_Offset(Table, defaultTag.anchor), 0}, 180 {TK_CONFIG_BOOLEAN, "-autoclear", "autoClear", "AutoClear", "0", 181 Tk_Offset(Table, autoClear), 0}, 182 {TK_CONFIG_BORDER, "-background", "background", "Background", NORMAL_BG, 183 Tk_Offset(Table, defaultTag.bg), 0}, 184 {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, 0}, 185 {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, 186 {TK_CONFIG_CURSOR, "-bordercursor", "borderCursor", "Cursor", "crosshair", 187 Tk_Offset(Table, bdcursor), TK_CONFIG_NULL_OK }, 188 {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth", "1", 189 Tk_Offset(Table, defaultTag), TK_CONFIG_NULL_OK, &bdOpt }, 190 {TK_CONFIG_STRING, "-browsecommand", "browseCommand", "BrowseCommand", "", 191 Tk_Offset(Table, browseCmd), TK_CONFIG_NULL_OK}, 192 {TK_CONFIG_SYNONYM, "-browsecmd", "browseCommand", (char *)NULL, 193 (char *)NULL, 0, TK_CONFIG_NULL_OK}, 194 {TK_CONFIG_BOOLEAN, "-cache", "cache", "Cache", "0", 195 Tk_Offset(Table, caching), 0}, 196 {TK_CONFIG_INT, "-colorigin", "colOrigin", "Origin", "0", 197 Tk_Offset(Table, colOffset), 0}, 198 {TK_CONFIG_INT, "-cols", "cols", "Cols", "10", 199 Tk_Offset(Table, cols), 0}, 200 {TK_CONFIG_STRING, "-colseparator", "colSeparator", "Separator", NULL, 201 Tk_Offset(Table, colSep), TK_CONFIG_NULL_OK }, 202 {TK_CONFIG_CUSTOM, "-colstretchmode", "colStretch", "StretchMode", "none", 203 Tk_Offset (Table, colStretch), 0 , &stretchOpt }, 204 {TK_CONFIG_STRING, "-coltagcommand", "colTagCommand", "TagCommand", NULL, 205 Tk_Offset(Table, colTagCmd), TK_CONFIG_NULL_OK }, 206 {TK_CONFIG_INT, "-colwidth", "colWidth", "ColWidth", "10", 207 Tk_Offset(Table, defColWidth), 0}, 208 {TK_CONFIG_STRING, "-command", "command", "Command", "", 209 Tk_Offset(Table, command), TK_CONFIG_NULL_OK}, 210 {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", "xterm", 211 Tk_Offset(Table, cursor), TK_CONFIG_NULL_OK }, 212 {TK_CONFIG_CUSTOM, "-drawmode", "drawMode", "DrawMode", "compatible", 213 Tk_Offset(Table, drawMode), 0, &drawOpt }, 214 {TK_CONFIG_STRING, "-ellipsis", "ellipsis", "Ellipsis", "", 215 Tk_Offset(Table, defaultTag.ellipsis), TK_CONFIG_NULL_OK}, 216 {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection", 217 "ExportSelection", "1", Tk_Offset(Table, exportSelection), 0}, 218 {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *)NULL, (char *)NULL, 0, 0}, 219 {TK_CONFIG_BOOLEAN, "-flashmode", "flashMode", "FlashMode", "0", 220 Tk_Offset(Table, flashMode), 0}, 221 {TK_CONFIG_INT, "-flashtime", "flashTime", "FlashTime", "2", 222 Tk_Offset(Table, flashTime), 0}, 223 {TK_CONFIG_FONT, "-font", "font", "Font", DEF_TABLE_FONT, 224 Tk_Offset(Table, defaultTag.tkfont), 0}, 225 {TK_CONFIG_BORDER, "-foreground", "foreground", "Foreground", "black", 226 Tk_Offset(Table, defaultTag.fg), 0}, 227#ifdef PROCS 228 {TK_CONFIG_BOOLEAN, "-hasprocs", "hasProcs", "hasProcs", "0", 229 Tk_Offset(Table, hasProcs), 0}, 230#endif 231 {TK_CONFIG_INT, "-height", "height", "Height", "0", 232 Tk_Offset(Table, maxReqRows), 0}, 233 {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", 234 "HighlightBackground", NORMAL_BG, Tk_Offset(Table, highlightBgColorPtr), 0}, 235 {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", 236 HIGHLIGHT, Tk_Offset(Table, highlightColorPtr), 0}, 237 {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", 238 "HighlightThickness", "2", Tk_Offset(Table, highlightWidth), 0}, 239 {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground", 240 "Black", Tk_Offset(Table, insertBg), 0}, 241 {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth", 242 "0", Tk_Offset(Table, insertBorderWidth), TK_CONFIG_COLOR_ONLY}, 243 {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth", 244 "0", Tk_Offset(Table, insertBorderWidth), TK_CONFIG_MONO_ONLY}, 245 {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime", "300", 246 Tk_Offset(Table, insertOffTime), 0}, 247 {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime", "600", 248 Tk_Offset(Table, insertOnTime), 0}, 249 {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth", "2", 250 Tk_Offset(Table, insertWidth), 0}, 251 {TK_CONFIG_BOOLEAN, "-invertselected", "invertSelected", "InvertSelected", 252 "0", Tk_Offset(Table, invertSelected), 0}, 253 {TK_CONFIG_PIXELS, "-ipadx", "ipadX", "Pad", "0", 254 Tk_Offset(Table, ipadX), 0}, 255 {TK_CONFIG_PIXELS, "-ipady", "ipadY", "Pad", "0", 256 Tk_Offset(Table, ipadY), 0}, 257 {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify", "left", 258 Tk_Offset(Table, defaultTag.justify), 0 }, 259 {TK_CONFIG_PIXELS, "-maxheight", "maxHeight", "MaxHeight", "600", 260 Tk_Offset(Table, maxReqHeight), 0}, 261 {TK_CONFIG_PIXELS, "-maxwidth", "maxWidth", "MaxWidth", "800", 262 Tk_Offset(Table, maxReqWidth), 0}, 263 {TK_CONFIG_BOOLEAN, "-multiline", "multiline", "Multiline", "1", 264 Tk_Offset(Table, defaultTag.multiline), 0}, 265 {TK_CONFIG_PIXELS, "-padx", "padX", "Pad", "0", Tk_Offset(Table, padX), 0}, 266 {TK_CONFIG_PIXELS, "-pady", "padY", "Pad", "0", Tk_Offset(Table, padY), 0}, 267 {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", "sunken", 268 Tk_Offset(Table, defaultTag.relief), 0}, 269 {TK_CONFIG_CUSTOM, "-resizeborders", "resizeBorders", "ResizeBorders", 270 "both", Tk_Offset(Table, resize), 0, &resizeTypeOpt }, 271 {TK_CONFIG_INT, "-rowheight", "rowHeight", "RowHeight", "1", 272 Tk_Offset(Table, defRowHeight), 0}, 273 {TK_CONFIG_INT, "-roworigin", "rowOrigin", "Origin", "0", 274 Tk_Offset(Table, rowOffset), 0}, 275 {TK_CONFIG_INT, "-rows", "rows", "Rows", "10", Tk_Offset(Table, rows), 0}, 276 {TK_CONFIG_STRING, "-rowseparator", "rowSeparator", "Separator", NULL, 277 Tk_Offset(Table, rowSep), TK_CONFIG_NULL_OK }, 278 {TK_CONFIG_CUSTOM, "-rowstretchmode", "rowStretch", "StretchMode", "none", 279 Tk_Offset(Table, rowStretch), 0 , &stretchOpt }, 280 {TK_CONFIG_STRING, "-rowtagcommand", "rowTagCommand", "TagCommand", NULL, 281 Tk_Offset(Table, rowTagCmd), TK_CONFIG_NULL_OK }, 282 {TK_CONFIG_SYNONYM, "-selcmd", "selectionCommand", (char *)NULL, 283 (char *)NULL, 0, TK_CONFIG_NULL_OK}, 284 {TK_CONFIG_STRING, "-selectioncommand", "selectionCommand", 285 "SelectionCommand", NULL, Tk_Offset(Table, selCmd), TK_CONFIG_NULL_OK }, 286 {TK_CONFIG_STRING, "-selectmode", "selectMode", "SelectMode", "browse", 287 Tk_Offset(Table, selectMode), TK_CONFIG_NULL_OK }, 288 {TK_CONFIG_BOOLEAN, "-selecttitles", "selectTitles", "SelectTitles", "0", 289 Tk_Offset(Table, selectTitles), 0}, 290 {TK_CONFIG_CUSTOM, "-selecttype", "selectType", "SelectType", "cell", 291 Tk_Offset(Table, selectType), 0, &selTypeOpt }, 292#ifdef PROCS 293 {TK_CONFIG_BOOLEAN, "-showprocs", "showProcs", "showProcs", "0", 294 Tk_Offset(Table, showProcs), 0}, 295#endif 296 {TK_CONFIG_BOOLEAN, "-sparsearray", "sparseArray", "SparseArray", "1", 297 Tk_Offset(Table, sparse), 0}, 298 {TK_CONFIG_CUSTOM, "-state", "state", "State", "normal", 299 Tk_Offset(Table, state), 0, &stateTypeOpt}, 300 {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", (char *)NULL, 301 Tk_Offset(Table, takeFocus), TK_CONFIG_NULL_OK }, 302 {TK_CONFIG_INT, "-titlecols", "titleCols", "TitleCols", "0", 303 Tk_Offset(Table, titleCols), TK_CONFIG_NULL_OK }, 304#ifdef TITLE_CURSOR 305 {TK_CONFIG_CURSOR, "-titlecursor", "titleCursor", "Cursor", "arrow", 306 Tk_Offset(Table, titleCursor), TK_CONFIG_NULL_OK }, 307#endif 308 {TK_CONFIG_INT, "-titlerows", "titleRows", "TitleRows", "0", 309 Tk_Offset(Table, titleRows), TK_CONFIG_NULL_OK }, 310 {TK_CONFIG_BOOLEAN, "-usecommand", "useCommand", "UseCommand", "1", 311 Tk_Offset(Table, useCmd), 0}, 312 {TK_CONFIG_STRING, "-variable", "variable", "Variable", (char *)NULL, 313 Tk_Offset(Table, arrayVar), TK_CONFIG_NULL_OK }, 314 {TK_CONFIG_BOOLEAN, "-validate", "validate", "Validate", "0", 315 Tk_Offset(Table, validate), 0}, 316 {TK_CONFIG_STRING, "-validatecommand", "validateCommand", "ValidateCommand", 317 "", Tk_Offset(Table, valCmd), TK_CONFIG_NULL_OK}, 318 {TK_CONFIG_SYNONYM, "-vcmd", "validateCommand", (char *)NULL, 319 (char *)NULL, 0, TK_CONFIG_NULL_OK}, 320 {TK_CONFIG_INT, "-width", "width", "Width", "0", 321 Tk_Offset(Table, maxReqCols), 0}, 322 {TK_CONFIG_BOOLEAN, "-wrap", "wrap", "Wrap", "0", 323 Tk_Offset(Table, defaultTag.wrap), 0}, 324 {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", 325 NULL, Tk_Offset(Table, xScrollCmd), TK_CONFIG_NULL_OK }, 326 {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", 327 NULL, Tk_Offset(Table, yScrollCmd), TK_CONFIG_NULL_OK }, 328 {TK_CONFIG_END, (char *)NULL, (char *)NULL, (char *)NULL, 329 (char *)NULL, 0, 0} 330}; 331 332/* 333 * This specifies the configure options that will cause an update to 334 * occur, so we should have a quick lookup table for them. 335 * Keep this in sync with the above values. 336 */ 337 338static CONST84 char *updateOpts[] = { 339 "-anchor", "-background", "-bg", "-bd", 340 "-borderwidth", "-cache", "-command", "-colorigin", 341 "-cols", "-colstretchmode", "-coltagcommand", 342 "-drawmode", "-fg", "-font", "-foreground", 343 "-hasprocs", "-height", "-highlightbackground", 344 "-highlightcolor", "-highlightthickness", "-insertbackground", 345 "-insertborderwidth", "-insertwidth", "-invertselected", 346 "-ipadx", "-ipady", 347 "-maxheight", "-maxwidth", "-multiline", 348 "-padx", "-pady", "-relief", "-roworigin", 349 "-rows", "-rowstretchmode", "-rowtagcommand", 350 "-showprocs", "-state", "-titlecols", "-titlerows", 351 "-usecommand", "-variable", "-width", "-wrap", 352 "-xscrollcommand", "-yscrollcommand", (char *) NULL 353}; 354 355#ifdef HAVE_TCL84 356/* 357 * The structure below defines widget class behavior by means of procedures 358 * that can be invoked from generic window code. 359 */ 360 361static Tk_ClassProcs tableClass = { 362 sizeof(Tk_ClassProcs), /* size */ 363 TableWorldChanged, /* worldChangedProc */ 364 NULL, /* createProc */ 365 NULL /* modalProc */ 366}; 367#endif 368 369#ifdef WIN32 370/* 371 * Some code from TkWinInt.h that we use to correct and speed up 372 * drawing of cells that need clipping in TableDisplay. 373 */ 374typedef struct { 375 int type; 376 HWND handle; 377 void *winPtr; 378} TkWinWindow; 379 380typedef struct { 381 int type; 382 HBITMAP handle; 383 Colormap colormap; 384 int depth; 385} TkWinBitmap; 386 387typedef struct { 388 int type; 389 HDC hdc; 390} TkWinDC; 391 392typedef union { 393 int type; 394 TkWinWindow window; 395 TkWinBitmap bitmap; 396 TkWinDC winDC; 397} TkWinDrawable; 398#endif 399 400/* 401 * END HEADER INFORMATION 402 */ 403 404/* 405 *--------------------------------------------------------------------------- 406 * 407 * StringifyObjects -- (from tclCmdAH.c) 408 * 409 * Helper function to bridge the gap between an object-based procedure 410 * and an older string-based procedure. 411 * 412 * Given an array of objects, allocate an array that consists of the 413 * string representations of those objects. 414 * 415 * Results: 416 * The return value is a pointer to the newly allocated array of 417 * strings. Elements 0 to (objc-1) of the string array point to the 418 * string representation of the corresponding element in the source 419 * object array; element objc of the string array is NULL. 420 * 421 * Side effects: 422 * Memory allocated. The caller must eventually free this memory 423 * by calling ckfree() on the return value. 424 * 425 int result; 426 char **argv; 427 argv = StringifyObjects(objc, objv); 428 result = StringBasedCmd(interp, objc, argv); 429 ckfree((char *) argv); 430 return result; 431 * 432 *--------------------------------------------------------------------------- 433 */ 434 435static char ** 436StringifyObjects(objc, objv) 437 int objc; /* Number of arguments. */ 438 Tcl_Obj *CONST objv[]; /* Argument objects. */ 439{ 440 int i; 441 char **argv; 442 443 argv = (char **) ckalloc((objc + 1) * sizeof(char *)); 444 for (i = 0; i < objc; i++) { 445 argv[i] = Tcl_GetString(objv[i]); 446 } 447 argv[i] = NULL; 448 return argv; 449} 450 451/* 452 * As long as we wait for the Function in general 453 * 454 * This parses the "-class" option for the table. 455 */ 456static int 457Tk_ClassOptionObjCmd(Tk_Window tkwin, char *defaultclass, 458 int objc, Tcl_Obj *CONST objv[]) 459{ 460 char *classname = defaultclass; 461 int offset = 0; 462 463 if ((objc >= 4) && STREQ(Tcl_GetString(objv[2]),"-class")) { 464 classname = Tcl_GetString(objv[3]); 465 offset = 2; 466 } 467 Tk_SetClass(tkwin, classname); 468 return offset; 469} 470 471/* 472 *-------------------------------------------------------------- 473 * 474 * Tk_TableObjCmd -- 475 * This procedure is invoked to process the "table" Tcl 476 * command. See the user documentation for details on what 477 * it does. 478 * 479 * Results: 480 * A standard Tcl result. 481 * 482 * Side effects: 483 * See the user documentation. 484 * 485 *-------------------------------------------------------------- 486 */ 487static int 488Tk_TableObjCmd(clientData, interp, objc, objv) 489 ClientData clientData; /* Main window associated with interpreter. */ 490 Tcl_Interp *interp; 491 int objc; /* Number of arguments. */ 492 Tcl_Obj *CONST objv[]; /* Argument objects. */ 493{ 494 register Table *tablePtr; 495 Tk_Window tkwin, mainWin = (Tk_Window) clientData; 496 int offset; 497 498 if (objc < 2) { 499 Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?"); 500 return TCL_ERROR; 501 } 502 503 tkwin = Tk_CreateWindowFromPath(interp, mainWin, Tcl_GetString(objv[1]), 504 (char *)NULL); 505 if (tkwin == NULL) { 506 return TCL_ERROR; 507 } 508 509 tablePtr = (Table *) ckalloc(sizeof(Table)); 510 memset((VOID *) tablePtr, 0, sizeof(Table)); 511 512 /* 513 * Set the structure elments that aren't 0/NULL by default, 514 * and that won't be set by the initial configure call. 515 */ 516 tablePtr->tkwin = tkwin; 517 tablePtr->display = Tk_Display(tkwin); 518 tablePtr->interp = interp; 519 tablePtr->widgetCmd = Tcl_CreateObjCommand(interp, 520 Tk_PathName(tablePtr->tkwin), TableWidgetObjCmd, 521 (ClientData) tablePtr, (Tcl_CmdDeleteProc *) TableCmdDeletedProc); 522 523 tablePtr->anchorRow = -1; 524 tablePtr->anchorCol = -1; 525 tablePtr->activeRow = -1; 526 tablePtr->activeCol = -1; 527 tablePtr->oldTopRow = -1; 528 tablePtr->oldLeftCol = -1; 529 tablePtr->oldActRow = -1; 530 tablePtr->oldActCol = -1; 531 tablePtr->seen[0] = -1; 532 533 tablePtr->dataSource = DATA_NONE; 534 tablePtr->activeBuf = ckalloc(1); 535 *(tablePtr->activeBuf) = '\0'; 536 537 tablePtr->cursor = None; 538 tablePtr->bdcursor = None; 539 540 tablePtr->defaultTag.justify = TK_JUSTIFY_LEFT; 541 tablePtr->defaultTag.state = STATE_UNKNOWN; 542 543 /* misc tables */ 544 tablePtr->tagTable = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 545 Tcl_InitHashTable(tablePtr->tagTable, TCL_STRING_KEYS); 546 tablePtr->winTable = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 547 Tcl_InitHashTable(tablePtr->winTable, TCL_STRING_KEYS); 548 549 /* internal value cache */ 550 tablePtr->cache = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 551 Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS); 552 553 /* style hash tables */ 554 tablePtr->colWidths = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 555 Tcl_InitHashTable(tablePtr->colWidths, TCL_ONE_WORD_KEYS); 556 tablePtr->rowHeights = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 557 Tcl_InitHashTable(tablePtr->rowHeights, TCL_ONE_WORD_KEYS); 558 559 /* style hash tables */ 560 tablePtr->rowStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 561 Tcl_InitHashTable(tablePtr->rowStyles, TCL_ONE_WORD_KEYS); 562 tablePtr->colStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 563 Tcl_InitHashTable(tablePtr->colStyles, TCL_ONE_WORD_KEYS); 564 tablePtr->cellStyles = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 565 Tcl_InitHashTable(tablePtr->cellStyles, TCL_STRING_KEYS); 566 567 /* special style hash tables */ 568 tablePtr->flashCells = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 569 Tcl_InitHashTable(tablePtr->flashCells, TCL_STRING_KEYS); 570 tablePtr->selCells = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 571 Tcl_InitHashTable(tablePtr->selCells, TCL_STRING_KEYS); 572 573 /* 574 * List of tags in priority order. 30 is a good default number to alloc. 575 */ 576 tablePtr->tagPrioMax = 30; 577 tablePtr->tagPrioNames = (char **) ckalloc( 578 sizeof(char *) * tablePtr->tagPrioMax); 579 tablePtr->tagPrios = (TableTag **) ckalloc( 580 sizeof(TableTag *) * tablePtr->tagPrioMax); 581 tablePtr->tagPrioSize = 0; 582 for (offset = 0; offset < tablePtr->tagPrioMax; offset++) { 583 tablePtr->tagPrioNames[offset] = (char *) NULL; 584 tablePtr->tagPrios[offset] = (TableTag *) NULL; 585 } 586 587#ifdef PROCS 588 tablePtr->inProc = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 589 Tcl_InitHashTable(tablePtr->inProc, TCL_STRING_KEYS); 590#endif 591 592 /* 593 * Handle class name and selection handlers 594 */ 595 offset = 2 + Tk_ClassOptionObjCmd(tkwin, "Table", objc, objv); 596#ifdef HAVE_TCL84 597 Tk_SetClassProcs(tkwin, &tableClass, (ClientData) tablePtr); 598#endif 599 Tk_CreateEventHandler(tablePtr->tkwin, 600 PointerMotionMask|ExposureMask|StructureNotifyMask|FocusChangeMask|VisibilityChangeMask, 601 TableEventProc, (ClientData) tablePtr); 602 Tk_CreateSelHandler(tablePtr->tkwin, XA_PRIMARY, XA_STRING, 603 TableFetchSelection, (ClientData) tablePtr, XA_STRING); 604 605 if (TableConfigure(interp, tablePtr, objc - offset, objv + offset, 606 0, 1 /* force update */) != TCL_OK) { 607 Tk_DestroyWindow(tkwin); 608 return TCL_ERROR; 609 } 610 TableInitTags(tablePtr); 611 612 Tcl_SetObjResult(interp, 613 Tcl_NewStringObj(Tk_PathName(tablePtr->tkwin), -1)); 614 return TCL_OK; 615} 616 617/* 618 *-------------------------------------------------------------- 619 * 620 * TableWidgetObjCmd -- 621 * This procedure is invoked to process the Tcl command 622 * that corresponds to a widget managed by this module. 623 * See the user documentation for details on what it does. 624 * 625 * Results: 626 * A standard Tcl result. 627 * 628 * Side effects: 629 * See the user documentation. 630 * 631 *-------------------------------------------------------------- 632 */ 633static int 634TableWidgetObjCmd(clientData, interp, objc, objv) 635 ClientData clientData; 636 Tcl_Interp *interp; 637 int objc; /* Number of arguments. */ 638 Tcl_Obj *CONST objv[]; /* Argument objects. */ 639{ 640 register Table *tablePtr = (Table *) clientData; 641 int row, col, i, cmdIndex, result = TCL_OK; 642 643 if (objc < 2) { 644 Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); 645 return TCL_ERROR; 646 } 647 648 /* parse the first parameter */ 649 result = Tcl_GetIndexFromObj(interp, objv[1], commandNames, 650 "option", 0, &cmdIndex); 651 if (result != TCL_OK) { 652 return result; 653 } 654 655 Tcl_Preserve((ClientData) tablePtr); 656 switch ((enum command) cmdIndex) { 657 case CMD_ACTIVATE: 658 result = Table_ActivateCmd(clientData, interp, objc, objv); 659 break; 660 661 case CMD_BBOX: 662 result = Table_BboxCmd(clientData, interp, objc, objv); 663 break; 664 665 case CMD_BORDER: 666 result = Table_BorderCmd(clientData, interp, objc, objv); 667 break; 668 669 case CMD_CGET: 670 if (objc != 3) { 671 Tcl_WrongNumArgs(interp, 2, objv, "option"); 672 result = TCL_ERROR; 673 } else { 674 result = Tk_ConfigureValue(interp, tablePtr->tkwin, tableSpecs, 675 (char *) tablePtr, Tcl_GetString(objv[2]), 0); 676 } 677 break; 678 679 case CMD_CLEAR: 680 result = Table_ClearCmd(clientData, interp, objc, objv); 681 break; 682 683 case CMD_CONFIGURE: 684 if (objc < 4) { 685 result = Tk_ConfigureInfo(interp, tablePtr->tkwin, tableSpecs, 686 (char *) tablePtr, (objc == 3) ? 687 Tcl_GetString(objv[2]) : (char *) NULL, 0); 688 } else { 689 result = TableConfigure(interp, tablePtr, objc - 2, objv + 2, 690 TK_CONFIG_ARGV_ONLY, 0); 691 } 692 break; 693 694 case CMD_CURSEL: 695 result = Table_CurselectionCmd(clientData, interp, objc, objv); 696 break; 697 698 case CMD_CURVALUE: 699 result = Table_CurvalueCmd(clientData, interp, objc, objv); 700 break; 701 702 case CMD_DELETE: 703 case CMD_INSERT: 704 result = Table_EditCmd(clientData, interp, objc, objv); 705 break; 706 707 case CMD_GET: 708 result = Table_GetCmd(clientData, interp, objc, objv); 709 break; 710 711 case CMD_HEIGHT: 712 case CMD_WIDTH: 713 result = Table_AdjustCmd(clientData, interp, objc, objv); 714 break; 715 716 case CMD_HIDDEN: 717 result = Table_HiddenCmd(clientData, interp, objc, objv); 718 break; 719 720 case CMD_ICURSOR: 721 if (objc != 2 && objc != 3) { 722 Tcl_WrongNumArgs(interp, 2, objv, "?cursorPos?"); 723 result = TCL_ERROR; 724 break; 725 } 726 if (!(tablePtr->flags & HAS_ACTIVE) || 727 (tablePtr->flags & ACTIVE_DISABLED) || 728 tablePtr->state == STATE_DISABLED) { 729 Tcl_SetObjResult(interp, Tcl_NewIntObj(-1)); 730 break; 731 } else if (objc == 3) { 732 if (TableGetIcursorObj(tablePtr, objv[2], NULL) != TCL_OK) { 733 result = TCL_ERROR; 734 break; 735 } 736 TableRefresh(tablePtr, tablePtr->activeRow, 737 tablePtr->activeCol, CELL); 738 } 739 Tcl_SetObjResult(interp, Tcl_NewIntObj(tablePtr->icursor)); 740 break; 741 742 case CMD_INDEX: { 743 char *which = NULL; 744 745 if (objc == 4) { 746 which = Tcl_GetString(objv[3]); 747 } 748 if ((objc < 3 || objc > 4) || 749 ((objc == 4) && (strcmp(which, "row") 750 && strcmp(which, "col")))) { 751 Tcl_WrongNumArgs(interp, 2, objv, "<index> ?row|col?"); 752 result = TCL_ERROR; 753 } else if (TableGetIndexObj(tablePtr, objv[2], &row, &col) 754 != TCL_OK) { 755 result = TCL_ERROR; 756 } else if (objc == 3) { 757 char buf[INDEX_BUFSIZE]; 758 /* recreate the index, just in case it got bounded */ 759 TableMakeArrayIndex(row, col, buf); 760 Tcl_SetObjResult(interp, Tcl_NewStringObj(buf, -1)); 761 } else { /* INDEX row|col */ 762 Tcl_SetObjResult(interp, 763 Tcl_NewIntObj((*which == 'r') ? row : col)); 764 } 765 break; 766 } 767 768#ifdef POSTSCRIPT 769 case CMD_POSTSCRIPT: 770 result = Table_PostscriptCmd(clientData, interp, objc, objv); 771 break; 772#endif 773 774 case CMD_REREAD: 775 if (objc != 2) { 776 Tcl_WrongNumArgs(interp, 2, objv, NULL); 777 result = TCL_ERROR; 778 } else if ((tablePtr->flags & HAS_ACTIVE) && 779 !(tablePtr->flags & ACTIVE_DISABLED) && 780 tablePtr->state != STATE_DISABLED) { 781 TableGetActiveBuf(tablePtr); 782 TableRefresh(tablePtr, tablePtr->activeRow, 783 tablePtr->activeCol, CELL|INV_FORCE); 784 } 785 break; 786 787 case CMD_SCAN: 788 result = Table_ScanCmd(clientData, interp, objc, objv); 789 break; 790 791 case CMD_SEE: 792 if (objc != 3) { 793 Tcl_WrongNumArgs(interp, 2, objv, "index"); 794 result = TCL_ERROR; 795 } else if (TableGetIndexObj(tablePtr, objv[2], 796 &row, &col) == TCL_ERROR) { 797 result = TCL_ERROR; 798 } else { 799 /* Adjust from user to master coords */ 800 row -= tablePtr->rowOffset; 801 col -= tablePtr->colOffset; 802 if (!TableCellVCoords(tablePtr, row, col, &i, &i, &i, &i, 1)) { 803 tablePtr->topRow = row-1; 804 tablePtr->leftCol = col-1; 805 TableAdjustParams(tablePtr); 806 } 807 } 808 break; 809 810 case CMD_SELECTION: 811 if (objc < 3) { 812 Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?"); 813 result = TCL_ERROR; 814 break; 815 } 816 if (Tcl_GetIndexFromObj(interp, objv[2], selCmdNames, 817 "selection option", 0, &cmdIndex) != TCL_OK) { 818 result = TCL_ERROR; 819 break; 820 } 821 switch ((enum selCommand) cmdIndex) { 822 case CMD_SEL_ANCHOR: 823 result = Table_SelAnchorCmd(clientData, interp, 824 objc, objv); 825 break; 826 case CMD_SEL_CLEAR: 827 result = Table_SelClearCmd(clientData, interp, objc, objv); 828 break; 829 case CMD_SEL_INCLUDES: 830 result = Table_SelIncludesCmd(clientData, interp, 831 objc, objv); 832 break; 833 case CMD_SEL_PRESENT: { 834 Tcl_HashSearch search; 835 int present = (Tcl_FirstHashEntry(tablePtr->selCells, 836 &search) != NULL); 837 838 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(present)); 839 break; 840 } 841 case CMD_SEL_SET: 842 result = Table_SelSetCmd(clientData, interp, objc, objv); 843 break; 844 } 845 break; 846 847 case CMD_SET: 848 result = Table_SetCmd(clientData, interp, objc, objv); 849 break; 850 851 case CMD_SPANS: 852 result = Table_SpanCmd(clientData, interp, objc, objv); 853 break; 854 855 case CMD_TAG: 856 result = Table_TagCmd(clientData, interp, objc, objv); 857 break; 858 859 case CMD_VALIDATE: 860 if (objc != 3) { 861 Tcl_WrongNumArgs(interp, 2, objv, "index"); 862 result = TCL_ERROR; 863 } else if (TableGetIndexObj(tablePtr, objv[2], 864 &row, &col) == TCL_ERROR) { 865 result = TCL_ERROR; 866 } else { 867 i = tablePtr->validate; 868 tablePtr->validate = 1; 869 result = TableValidateChange(tablePtr, row, col, (char *) NULL, 870 (char *) NULL, -1); 871 tablePtr->validate = i; 872 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(result == TCL_OK)); 873 result = TCL_OK; 874 } 875 break; 876 877 case CMD_VERSION: 878 if (objc != 2) { 879 Tcl_WrongNumArgs(interp, 2, objv, NULL); 880 result = TCL_ERROR; 881 } else { 882 Tcl_SetObjResult(interp, Tcl_NewStringObj(PACKAGE_VERSION, -1)); 883 } 884 break; 885 886 case CMD_WINDOW: 887 result = Table_WindowCmd(clientData, interp, objc, objv); 888 break; 889 890 case CMD_XVIEW: 891 case CMD_YVIEW: 892 result = Table_ViewCmd(clientData, interp, objc, objv); 893 break; 894 } 895 896 Tcl_Release((ClientData) tablePtr); 897 return result; 898} 899 900/* 901 *---------------------------------------------------------------------- 902 * 903 * TableDestroy -- 904 * This procedure is invoked by Tcl_EventuallyFree 905 * to clean up the internal structure of a table at a safe time 906 * (when no-one is using it anymore). 907 * 908 * Results: 909 * None. 910 * 911 * Side effects: 912 * Everything associated with the table is freed up (hopefully). 913 * 914 *---------------------------------------------------------------------- 915 */ 916static void 917TableDestroy(ClientData clientdata) 918{ 919 register Table *tablePtr = (Table *) clientdata; 920 Tcl_HashEntry *entryPtr; 921 Tcl_HashSearch search; 922 923 /* These may be repetitive from DestroyNotify, but it doesn't hurt */ 924 /* cancel any pending update or timer */ 925 if (tablePtr->flags & REDRAW_PENDING) { 926 Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr); 927 tablePtr->flags &= ~REDRAW_PENDING; 928 } 929 Tcl_DeleteTimerHandler(tablePtr->cursorTimer); 930 Tcl_DeleteTimerHandler(tablePtr->flashTimer); 931 932 /* delete the variable trace */ 933 if (tablePtr->arrayVar != NULL) { 934 Tcl_UntraceVar(tablePtr->interp, tablePtr->arrayVar, 935 TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_GLOBAL_ONLY, 936 (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr); 937 } 938 939 /* free the int arrays */ 940 if (tablePtr->colPixels) ckfree((char *) tablePtr->colPixels); 941 if (tablePtr->rowPixels) ckfree((char *) tablePtr->rowPixels); 942 if (tablePtr->colStarts) ckfree((char *) tablePtr->colStarts); 943 if (tablePtr->rowStarts) ckfree((char *) tablePtr->rowStarts); 944 945 /* delete cached active tag and string */ 946 if (tablePtr->activeTagPtr) ckfree((char *) tablePtr->activeTagPtr); 947 if (tablePtr->activeBuf != NULL) ckfree(tablePtr->activeBuf); 948 949 /* 950 * Delete the various hash tables, make sure to clear the STRING_KEYS 951 * tables that allocate their strings: 952 * cache, spanTbl (spanAffTbl shares spanTbl info) 953 */ 954 Table_ClearHashTable(tablePtr->cache); 955 ckfree((char *) (tablePtr->cache)); 956 Tcl_DeleteHashTable(tablePtr->rowStyles); 957 ckfree((char *) (tablePtr->rowStyles)); 958 Tcl_DeleteHashTable(tablePtr->colStyles); 959 ckfree((char *) (tablePtr->colStyles)); 960 Tcl_DeleteHashTable(tablePtr->cellStyles); 961 ckfree((char *) (tablePtr->cellStyles)); 962 Tcl_DeleteHashTable(tablePtr->flashCells); 963 ckfree((char *) (tablePtr->flashCells)); 964 Tcl_DeleteHashTable(tablePtr->selCells); 965 ckfree((char *) (tablePtr->selCells)); 966 Tcl_DeleteHashTable(tablePtr->colWidths); 967 ckfree((char *) (tablePtr->colWidths)); 968 Tcl_DeleteHashTable(tablePtr->rowHeights); 969 ckfree((char *) (tablePtr->rowHeights)); 970#ifdef PROCS 971 Tcl_DeleteHashTable(tablePtr->inProc); 972 ckfree((char *) (tablePtr->inProc)); 973#endif 974 if (tablePtr->spanTbl) { 975 Table_ClearHashTable(tablePtr->spanTbl); 976 ckfree((char *) (tablePtr->spanTbl)); 977 Tcl_DeleteHashTable(tablePtr->spanAffTbl); 978 ckfree((char *) (tablePtr->spanAffTbl)); 979 } 980 981 /* Now free up all the tag information */ 982 for (entryPtr = Tcl_FirstHashEntry(tablePtr->tagTable, &search); 983 entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { 984 TableCleanupTag(tablePtr, (TableTag *) Tcl_GetHashValue(entryPtr)); 985 ckfree((char *) Tcl_GetHashValue(entryPtr)); 986 } 987 /* free up the stuff in the default tag */ 988 TableCleanupTag(tablePtr, &(tablePtr->defaultTag)); 989 /* And delete the actual hash table */ 990 Tcl_DeleteHashTable(tablePtr->tagTable); 991 ckfree((char *) (tablePtr->tagTable)); 992 ckfree((char *) (tablePtr->tagPrios)); 993 ckfree((char *) (tablePtr->tagPrioNames)); 994 995 /* Now free up all the embedded window info */ 996 for (entryPtr = Tcl_FirstHashEntry(tablePtr->winTable, &search); 997 entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { 998 EmbWinDelete(tablePtr, (TableEmbWindow *) Tcl_GetHashValue(entryPtr)); 999 } 1000 /* And delete the actual hash table */ 1001 Tcl_DeleteHashTable(tablePtr->winTable); 1002 ckfree((char *) (tablePtr->winTable)); 1003 1004 /* free the configuration options in the widget */ 1005 Tk_FreeOptions(tableSpecs, (char *) tablePtr, tablePtr->display, 0); 1006 1007 /* and free the widget memory at last! */ 1008 ckfree((char *) (tablePtr)); 1009} 1010 1011/* 1012 *---------------------------------------------------------------------- 1013 * 1014 * TableConfigure -- 1015 * This procedure is called to process an objc/objv list, plus 1016 * the Tk option database, in order to configure (or reconfigure) 1017 * a table widget. 1018 * 1019 * Results: 1020 * The return value is a standard Tcl result. If TCL_ERROR is 1021 * returned, then interp result contains an error message. 1022 * 1023 * Side effects: 1024 * Configuration information, such as colors, border width, etc. 1025 * get set for tablePtr; old resources get freed, if there were any. 1026 * Certain values might be constrained. 1027 * 1028 *---------------------------------------------------------------------- 1029 */ 1030static int 1031TableConfigure(interp, tablePtr, objc, objv, flags, forceUpdate) 1032 Tcl_Interp *interp; /* Used for error reporting. */ 1033 register Table *tablePtr; /* Information about widget; may or may 1034 * not already have values for some fields. */ 1035 int objc; /* Number of arguments. */ 1036 Tcl_Obj *CONST objv[]; /* Argument objects. */ 1037 int flags; /* Flags to pass to Tk_ConfigureWidget. */ 1038 int forceUpdate; /* Whether to force an update - required 1039 * for initial configuration */ 1040{ 1041 Tcl_HashSearch search; 1042 int oldUse, oldCaching, oldExport, oldTitleRows, oldTitleCols; 1043 int result = TCL_OK; 1044 char *oldVar = NULL, **argv; 1045 Tcl_DString error; 1046 Tk_FontMetrics fm; 1047 1048 oldExport = tablePtr->exportSelection; 1049 oldCaching = tablePtr->caching; 1050 oldUse = tablePtr->useCmd; 1051 oldTitleRows = tablePtr->titleRows; 1052 oldTitleCols = tablePtr->titleCols; 1053 if (tablePtr->arrayVar != NULL) { 1054 oldVar = ckalloc(strlen(tablePtr->arrayVar) + 1); 1055 strcpy(oldVar, tablePtr->arrayVar); 1056 } 1057 1058 /* Do the configuration */ 1059 argv = StringifyObjects(objc, objv); 1060 result = Tk_ConfigureWidget(interp, tablePtr->tkwin, tableSpecs, 1061 objc, (CONST84 char **) argv, (char *) tablePtr, flags); 1062 ckfree((char *) argv); 1063 if (result != TCL_OK) { 1064 return TCL_ERROR; 1065 } 1066 1067 Tcl_DStringInit(&error); 1068 1069 /* Any time we configure, reevaluate what our data source is */ 1070 tablePtr->dataSource = DATA_NONE; 1071 if (tablePtr->caching) { 1072 tablePtr->dataSource |= DATA_CACHE; 1073 } 1074 if (tablePtr->command && tablePtr->useCmd) { 1075 tablePtr->dataSource |= DATA_COMMAND; 1076 } else if (tablePtr->arrayVar) { 1077 tablePtr->dataSource |= DATA_ARRAY; 1078 } 1079 1080 /* Check to see if the array variable was changed */ 1081 if (strcmp((tablePtr->arrayVar ? tablePtr->arrayVar : ""), 1082 (oldVar ? oldVar : ""))) { 1083 /* only do the following if arrayVar is our data source */ 1084 if (tablePtr->dataSource & DATA_ARRAY) { 1085 /* 1086 * ensure that the cache will flush later 1087 * so it gets the new values 1088 */ 1089 oldCaching = !(tablePtr->caching); 1090 } 1091 /* remove the trace on the old array variable if there was one */ 1092 if (oldVar != NULL) 1093 Tcl_UntraceVar(interp, oldVar, 1094 TCL_TRACE_WRITES|TCL_TRACE_UNSETS|TCL_GLOBAL_ONLY, 1095 (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr); 1096 /* Check whether variable is an array and trace it if it is */ 1097 if (tablePtr->arrayVar != NULL) { 1098 /* does the variable exist as an array? */ 1099 if (Tcl_SetVar2(interp, tablePtr->arrayVar, TEST_KEY, "", 1100 TCL_GLOBAL_ONLY) == NULL) { 1101 Tcl_DStringAppend(&error, "invalid variable value \"", -1); 1102 Tcl_DStringAppend(&error, tablePtr->arrayVar, -1); 1103 Tcl_DStringAppend(&error, "\": could not be made an array", 1104 -1); 1105 ckfree(tablePtr->arrayVar); 1106 tablePtr->arrayVar = NULL; 1107 tablePtr->dataSource &= ~DATA_ARRAY; 1108 result = TCL_ERROR; 1109 } else { 1110 Tcl_UnsetVar2(interp, tablePtr->arrayVar, TEST_KEY, 1111 TCL_GLOBAL_ONLY); 1112 /* remove the effect of the evaluation */ 1113 /* set a trace on the variable */ 1114 Tcl_TraceVar(interp, tablePtr->arrayVar, 1115 TCL_TRACE_WRITES|TCL_TRACE_UNSETS|TCL_GLOBAL_ONLY, 1116 (Tcl_VarTraceProc *)TableVarProc, 1117 (ClientData) tablePtr); 1118 1119 /* only do the following if arrayVar is our data source */ 1120 if (tablePtr->dataSource & DATA_ARRAY) { 1121 /* get the current value of the selection */ 1122 TableGetActiveBuf(tablePtr); 1123 } 1124 } 1125 } 1126 } 1127 1128 /* Free oldVar if it was allocated */ 1129 if (oldVar != NULL) ckfree(oldVar); 1130 1131 if ((tablePtr->command && tablePtr->useCmd && !oldUse) || 1132 (tablePtr->arrayVar && !(tablePtr->useCmd) && oldUse)) { 1133 /* 1134 * Our effective data source changed, so flush and 1135 * retrieve new active buffer 1136 */ 1137 Table_ClearHashTable(tablePtr->cache); 1138 Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS); 1139 TableGetActiveBuf(tablePtr); 1140 forceUpdate = 1; 1141 } else if (oldCaching != tablePtr->caching) { 1142 /* 1143 * Caching changed, so just clear the cache for safety 1144 */ 1145 Table_ClearHashTable(tablePtr->cache); 1146 Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS); 1147 forceUpdate = 1; 1148 } 1149 1150 /* 1151 * Set up the default column width and row height 1152 */ 1153 Tk_GetFontMetrics(tablePtr->defaultTag.tkfont, &fm); 1154 tablePtr->charWidth = Tk_TextWidth(tablePtr->defaultTag.tkfont, "0", 1); 1155 tablePtr->charHeight = fm.linespace + 2; 1156 1157 if (tablePtr->insertWidth <= 0) { 1158 tablePtr->insertWidth = 2; 1159 } 1160 if (tablePtr->insertBorderWidth > tablePtr->insertWidth/2) { 1161 tablePtr->insertBorderWidth = tablePtr->insertWidth/2; 1162 } 1163 tablePtr->highlightWidth = MAX(0,tablePtr->highlightWidth); 1164 1165 /* 1166 * Ensure that certain values are within proper constraints 1167 */ 1168 tablePtr->rows = MAX(1, tablePtr->rows); 1169 tablePtr->cols = MAX(1, tablePtr->cols); 1170 tablePtr->padX = MAX(0, tablePtr->padX); 1171 tablePtr->padY = MAX(0, tablePtr->padY); 1172 tablePtr->ipadX = MAX(0, tablePtr->ipadX); 1173 tablePtr->ipadY = MAX(0, tablePtr->ipadY); 1174 tablePtr->maxReqCols = MAX(0, tablePtr->maxReqCols); 1175 tablePtr->maxReqRows = MAX(0, tablePtr->maxReqRows); 1176 CONSTRAIN(tablePtr->titleRows, 0, tablePtr->rows); 1177 CONSTRAIN(tablePtr->titleCols, 0, tablePtr->cols); 1178 1179 /* 1180 * Handle change of default border style 1181 * The default borderwidth must be >= 0. 1182 */ 1183 if (tablePtr->drawMode & (DRAW_MODE_SINGLE|DRAW_MODE_FAST)) { 1184 /* 1185 * When drawing fast or single, the border must be <= 1. 1186 * We have to do this after the normal configuration 1187 * to base the borders off the first value given. 1188 */ 1189 tablePtr->defaultTag.bd[0] = MIN(1, tablePtr->defaultTag.bd[0]); 1190 tablePtr->defaultTag.borders = 1; 1191 ckfree((char *) tablePtr->defaultTag.borderStr); 1192 tablePtr->defaultTag.borderStr = (char *) ckalloc(2); 1193 strcpy(tablePtr->defaultTag.borderStr, 1194 tablePtr->defaultTag.bd[0] ? "1" : "0"); 1195 } 1196 1197 /* 1198 * Claim the selection if we've suddenly started exporting it and 1199 * there is a selection to export. 1200 */ 1201 if (tablePtr->exportSelection && !oldExport && 1202 (Tcl_FirstHashEntry(tablePtr->selCells, &search) != NULL)) { 1203 Tk_OwnSelection(tablePtr->tkwin, XA_PRIMARY, TableLostSelection, 1204 (ClientData) tablePtr); 1205 } 1206 1207 if ((tablePtr->titleRows < oldTitleRows) || 1208 (tablePtr->titleCols < oldTitleCols)) { 1209 /* 1210 * Prevent odd movement due to new possible topleft index 1211 */ 1212 if (tablePtr->titleRows < oldTitleRows) 1213 tablePtr->topRow -= oldTitleRows - tablePtr->titleRows; 1214 if (tablePtr->titleCols < oldTitleCols) 1215 tablePtr->leftCol -= oldTitleCols - tablePtr->titleCols; 1216 /* 1217 * If our title area shrank, we need to check that the items 1218 * within the new title area don't try to span outside it. 1219 */ 1220 TableSpanSanCheck(tablePtr); 1221 } 1222 1223 /* 1224 * Only do the full reconfigure if absolutely necessary 1225 */ 1226 if (!forceUpdate) { 1227 int i, dummy; 1228 for (i = 0; i < objc-1; i += 2) { 1229 if (Tcl_GetIndexFromObj(NULL, objv[i], updateOpts, "", 0, &dummy) 1230 == TCL_OK) { 1231 forceUpdate = 1; 1232 break; 1233 } 1234 } 1235 } 1236 if (forceUpdate) { 1237 /* 1238 * Calculate the row and column starts 1239 * Adjust the top left corner of the internal display 1240 */ 1241 TableAdjustParams(tablePtr); 1242 /* reset the cursor */ 1243 TableConfigCursor(tablePtr); 1244 /* set up the background colour in the window */ 1245 Tk_SetBackgroundFromBorder(tablePtr->tkwin, tablePtr->defaultTag.bg); 1246 /* set the geometry and border */ 1247 TableGeometryRequest(tablePtr); 1248 Tk_SetInternalBorder(tablePtr->tkwin, tablePtr->highlightWidth); 1249 /* invalidate the whole table */ 1250 TableInvalidateAll(tablePtr, INV_HIGHLIGHT); 1251 } 1252 /* 1253 * FIX this is goofy because the result could be munged by other 1254 * functions. Could be improved. 1255 */ 1256 Tcl_ResetResult(interp); 1257 if (result == TCL_ERROR) { 1258 Tcl_AddErrorInfo(interp, "\t(configuring table widget)"); 1259 Tcl_DStringResult(interp, &error); 1260 } 1261 Tcl_DStringFree(&error); 1262 return result; 1263} 1264#ifdef HAVE_TCL84 1265/* 1266 *--------------------------------------------------------------------------- 1267 * 1268 * TableWorldChanged -- 1269 * 1270 * This procedure is called when the world has changed in some 1271 * way and the widget needs to recompute all its graphics contexts 1272 * and determine its new geometry. 1273 * 1274 * Results: 1275 * None. 1276 * 1277 * Side effects: 1278 * Entry will be relayed out and redisplayed. 1279 * 1280 *--------------------------------------------------------------------------- 1281 */ 1282 1283static void 1284TableWorldChanged(instanceData) 1285 ClientData instanceData; /* Information about widget. */ 1286{ 1287 Table *tablePtr = (Table *) instanceData; 1288 Tk_FontMetrics fm; 1289 1290 /* 1291 * Set up the default column width and row height 1292 */ 1293 Tk_GetFontMetrics(tablePtr->defaultTag.tkfont, &fm); 1294 tablePtr->charWidth = Tk_TextWidth(tablePtr->defaultTag.tkfont, "0", 1); 1295 tablePtr->charHeight = fm.linespace + 2; 1296 1297 /* 1298 * Recompute the window's geometry and arrange for it to be redisplayed. 1299 */ 1300 1301 TableAdjustParams(tablePtr); 1302 TableGeometryRequest(tablePtr); 1303 Tk_SetInternalBorder(tablePtr->tkwin, tablePtr->highlightWidth); 1304 /* invalidate the whole table */ 1305 TableInvalidateAll(tablePtr, INV_HIGHLIGHT); 1306} 1307#endif 1308/* 1309 *-------------------------------------------------------------- 1310 * 1311 * TableEventProc -- 1312 * This procedure is invoked by the Tk dispatcher for various 1313 * events on tables. 1314 * 1315 * Results: 1316 * None. 1317 * 1318 * Side effects: 1319 * When the window gets deleted, internal structures get 1320 * cleaned up. When it gets exposed, it is redisplayed. 1321 * 1322 *-------------------------------------------------------------- 1323 */ 1324static void 1325TableEventProc(clientData, eventPtr) 1326 ClientData clientData; /* Information about window. */ 1327 XEvent *eventPtr; /* Information about event. */ 1328{ 1329 Table *tablePtr = (Table *) clientData; 1330 int row, col; 1331 1332 switch (eventPtr->type) { 1333 case MotionNotify: 1334 if (!(tablePtr->resize & SEL_NONE) 1335 && (tablePtr->bdcursor != None) && 1336 TableAtBorder(tablePtr, eventPtr->xmotion.x, 1337 eventPtr->xmotion.y, &row, &col) && 1338 ((row>=0 && (tablePtr->resize & SEL_ROW)) || 1339 (col>=0 && (tablePtr->resize & SEL_COL)))) { 1340 /* 1341 * The bordercursor is defined and we meet the criteria for 1342 * being over a border. Set the cursor to border if not 1343 * already done. 1344 */ 1345 if (!(tablePtr->flags & OVER_BORDER)) { 1346 tablePtr->flags |= OVER_BORDER; 1347 Tk_DefineCursor(tablePtr->tkwin, tablePtr->bdcursor); 1348 } 1349 } else if (tablePtr->flags & OVER_BORDER) { 1350 tablePtr->flags &= ~OVER_BORDER; 1351 if (tablePtr->cursor != None) { 1352 Tk_DefineCursor(tablePtr->tkwin, tablePtr->cursor); 1353 } else { 1354 Tk_UndefineCursor(tablePtr->tkwin); 1355 } 1356#ifdef TITLE_CURSOR 1357 } else if (tablePtr->flags & (OVER_BORDER|OVER_TITLE)) { 1358 Tk_Cursor cursor = tablePtr->cursor; 1359 1360 //tablePtr->flags &= ~(OVER_BORDER|OVER_TITLE); 1361 1362 if (tablePtr->titleCursor != None) { 1363 TableWhatCell(tablePtr, eventPtr->xmotion.x, 1364 eventPtr->xmotion.y, &row, &col); 1365 if ((row < tablePtr->titleRows) || 1366 (col < tablePtr->titleCols)) { 1367 if (tablePtr->flags & OVER_TITLE) { 1368 break; 1369 } 1370 tablePtr->flags |= OVER_TITLE; 1371 cursor = tablePtr->titleCursor; 1372 } 1373 } 1374 if (cursor != None) { 1375 Tk_DefineCursor(tablePtr->tkwin, cursor); 1376 } else { 1377 Tk_UndefineCursor(tablePtr->tkwin); 1378 } 1379 } else if (tablePtr->titleCursor != None) { 1380 Tk_Cursor cursor = tablePtr->cursor; 1381 1382 TableWhatCell(tablePtr, eventPtr->xmotion.x, 1383 eventPtr->xmotion.y, &row, &col); 1384 if ((row < tablePtr->titleRows) || 1385 (col < tablePtr->titleCols)) { 1386 if (tablePtr->flags & OVER_TITLE) { 1387 break; 1388 } 1389 tablePtr->flags |= OVER_TITLE; 1390 cursor = tablePtr->titleCursor; 1391 } 1392#endif 1393 } 1394 break; 1395 1396 case Expose: 1397 TableInvalidate(tablePtr, eventPtr->xexpose.x, eventPtr->xexpose.y, 1398 eventPtr->xexpose.width, eventPtr->xexpose.height, 1399 INV_HIGHLIGHT); 1400 break; 1401 1402 case DestroyNotify: 1403 /* remove the command from the interpreter */ 1404 if (tablePtr->tkwin != NULL) { 1405 tablePtr->tkwin = NULL; 1406 Tcl_DeleteCommandFromToken(tablePtr->interp, 1407 tablePtr->widgetCmd); 1408 } 1409 1410 /* cancel any pending update or timer */ 1411 if (tablePtr->flags & REDRAW_PENDING) { 1412 Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr); 1413 tablePtr->flags &= ~REDRAW_PENDING; 1414 } 1415 Tcl_DeleteTimerHandler(tablePtr->cursorTimer); 1416 Tcl_DeleteTimerHandler(tablePtr->flashTimer); 1417 1418 Tcl_EventuallyFree((ClientData) tablePtr, 1419 (Tcl_FreeProc *) TableDestroy); 1420 break; 1421 1422 case MapNotify: /* redraw table when remapped if it changed */ 1423 if (tablePtr->flags & REDRAW_ON_MAP) { 1424 tablePtr->flags &= ~REDRAW_ON_MAP; 1425 Tcl_Preserve((ClientData) tablePtr); 1426 TableAdjustParams(tablePtr); 1427 TableInvalidateAll(tablePtr, INV_HIGHLIGHT); 1428 Tcl_Release((ClientData) tablePtr); 1429 } 1430 break; 1431 1432 case ConfigureNotify: 1433 Tcl_Preserve((ClientData) tablePtr); 1434 TableAdjustParams(tablePtr); 1435 TableInvalidateAll(tablePtr, INV_HIGHLIGHT); 1436 Tcl_Release((ClientData) tablePtr); 1437 break; 1438 1439 case FocusIn: 1440 case FocusOut: 1441 if (eventPtr->xfocus.detail != NotifyInferior) { 1442 tablePtr->flags |= REDRAW_BORDER; 1443 if (eventPtr->type == FocusOut) { 1444 tablePtr->flags &= ~HAS_FOCUS; 1445 } else { 1446 tablePtr->flags |= HAS_FOCUS; 1447 } 1448 TableRedrawHighlight(tablePtr); 1449 /* cancel the timer */ 1450 TableConfigCursor(tablePtr); 1451 } 1452 break; 1453 } 1454} 1455 1456/* 1457 *---------------------------------------------------------------------- 1458 * 1459 * TableCmdDeletedProc -- 1460 * 1461 * This procedure is invoked when a widget command is deleted. If 1462 * the widget isn't already in the process of being destroyed, 1463 * this command destroys it. 1464 * 1465 * Results: 1466 * None. 1467 * 1468 * Side effects: 1469 * The widget is destroyed. 1470 * 1471 *---------------------------------------------------------------------- 1472 */ 1473static void 1474TableCmdDeletedProc(ClientData clientData) 1475{ 1476 Table *tablePtr = (Table *) clientData; 1477 Tk_Window tkwin; 1478 1479 /* 1480 * This procedure could be invoked either because the window was 1481 * destroyed and the command was then deleted (in which case tkwin 1482 * is NULL) or because the command was deleted, and then this procedure 1483 * destroys the widget. 1484 */ 1485 1486 if (tablePtr->tkwin != NULL) { 1487 tkwin = tablePtr->tkwin; 1488 tablePtr->tkwin = NULL; 1489 Tk_DestroyWindow(tkwin); 1490 } 1491} 1492 1493/* 1494 *---------------------------------------------------------------------- 1495 * 1496 * TableRedrawHighlight -- 1497 * Redraws just the highlight for the window 1498 * 1499 * Results: 1500 * None. 1501 * 1502 * Side effects: 1503 * None 1504 * 1505 *---------------------------------------------------------------------- 1506 */ 1507static void 1508TableRedrawHighlight(Table *tablePtr) 1509{ 1510 if ((tablePtr->flags & REDRAW_BORDER) && tablePtr->highlightWidth > 0) { 1511 GC gc = Tk_GCForColor((tablePtr->flags & HAS_FOCUS) 1512 ? tablePtr->highlightColorPtr : tablePtr->highlightBgColorPtr, 1513 Tk_WindowId(tablePtr->tkwin)); 1514 Tk_DrawFocusHighlight(tablePtr->tkwin, gc, tablePtr->highlightWidth, 1515 Tk_WindowId(tablePtr->tkwin)); 1516 } 1517 tablePtr->flags &= ~REDRAW_BORDER; 1518} 1519 1520/* 1521 *---------------------------------------------------------------------- 1522 * 1523 * TableRefresh -- 1524 * Refreshes an area of the table based on the mode. 1525 * row,col in real coords (0-based) 1526 * 1527 * Results: 1528 * Will cause redraw for visible cells 1529 * 1530 * Side effects: 1531 * None. 1532 * 1533 *---------------------------------------------------------------------- 1534 */ 1535void 1536TableRefresh(register Table *tablePtr, int row, int col, int mode) 1537{ 1538 int x, y, w, h; 1539 1540 if ((row < 0) || (col < 0)) { 1541 /* 1542 * Invalid coords passed in. This can happen when the "active" cell 1543 * is refreshed, but doesn't really exist (row==-1 && col==-1). 1544 */ 1545 return; 1546 } 1547 if (mode & CELL) { 1548 if (TableCellVCoords(tablePtr, row, col, &x, &y, &w, &h, 0)) { 1549 TableInvalidate(tablePtr, x, y, w, h, mode); 1550 } 1551 } else if (mode & ROW) { 1552 /* get the position of the leftmost cell in the row */ 1553 if ((mode & INV_FILL) && row < tablePtr->topRow) { 1554 /* Invalidate whole table */ 1555 TableInvalidateAll(tablePtr, mode); 1556 } else if (TableCellVCoords(tablePtr, row, tablePtr->leftCol, 1557 &x, &y, &w, &h, 0)) { 1558 /* Invalidate from this row, maybe to end */ 1559 TableInvalidate(tablePtr, 0, y, Tk_Width(tablePtr->tkwin), 1560 (mode&INV_FILL)?Tk_Height(tablePtr->tkwin):h, mode); 1561 } 1562 } else if (mode & COL) { 1563 /* get the position of the topmost cell on the column */ 1564 if ((mode & INV_FILL) && col < tablePtr->leftCol) { 1565 /* Invalidate whole table */ 1566 TableInvalidateAll(tablePtr, mode); 1567 } else if (TableCellVCoords(tablePtr, tablePtr->topRow, col, 1568 &x, &y, &w, &h, 0)) { 1569 /* Invalidate from this column, maybe to end */ 1570 TableInvalidate(tablePtr, x, 0, 1571 (mode&INV_FILL)?Tk_Width(tablePtr->tkwin):w, 1572 Tk_Height(tablePtr->tkwin), mode); 1573 } 1574 } 1575} 1576 1577/* 1578 *---------------------------------------------------------------------- 1579 * 1580 * TableGetGc -- 1581 * Gets a GC corresponding to the tag structure passed. 1582 * 1583 * Results: 1584 * Returns usable GC. 1585 * 1586 * Side effects: 1587 * None 1588 * 1589 *---------------------------------------------------------------------- 1590 */ 1591static void 1592TableGetGc(Display *display, Drawable d, TableTag *tagPtr, GC *tagGc) 1593{ 1594 XGCValues gcValues; 1595 gcValues.foreground = Tk_3DBorderColor(tagPtr->fg)->pixel; 1596 gcValues.background = Tk_3DBorderColor(tagPtr->bg)->pixel; 1597 gcValues.font = Tk_FontId(tagPtr->tkfont); 1598 if (*tagGc == NULL) { 1599 gcValues.graphics_exposures = False; 1600 *tagGc = XCreateGC(display, d, 1601 GCForeground|GCBackground|GCFont|GCGraphicsExposures, 1602 &gcValues); 1603 } else { 1604 XChangeGC(display, *tagGc, GCForeground|GCBackground|GCFont, 1605 &gcValues); 1606 } 1607} 1608 1609#define TableFreeGc XFreeGC 1610 1611/* 1612 *-------------------------------------------------------------- 1613 * 1614 * TableUndisplay -- 1615 * This procedure removes the contents of a table window 1616 * that have been moved offscreen. 1617 * 1618 * Results: 1619 * Embedded windows can be unmapped. 1620 * 1621 * Side effects: 1622 * Information disappears from the screen. 1623 * 1624 *-------------------------------------------------------------- 1625 */ 1626static void 1627TableUndisplay(register Table *tablePtr) 1628{ 1629 register int *seen = tablePtr->seen; 1630 int row, col; 1631 1632 /* We need to find out the true last cell, not considering spans */ 1633 tablePtr->flags |= AVOID_SPANS; 1634 TableGetLastCell(tablePtr, &row, &col); 1635 tablePtr->flags &= ~AVOID_SPANS; 1636 1637 if (seen[0] != -1) { 1638 if (seen[0] < tablePtr->topRow) { 1639 /* Remove now hidden rows */ 1640 EmbWinUnmap(tablePtr, seen[0], MIN(seen[2],tablePtr->topRow-1), 1641 seen[1], seen[3]); 1642 /* Also account for the title area */ 1643 EmbWinUnmap(tablePtr, seen[0], MIN(seen[2],tablePtr->topRow-1), 1644 0, tablePtr->titleCols-1); 1645 } 1646 if (seen[1] < tablePtr->leftCol) { 1647 /* Remove now hidden cols */ 1648 EmbWinUnmap(tablePtr, seen[0], seen[2], 1649 seen[1], MAX(seen[3],tablePtr->leftCol-1)); 1650 /* Also account for the title area */ 1651 EmbWinUnmap(tablePtr, 0, tablePtr->titleRows-1, 1652 seen[1], MAX(seen[3],tablePtr->leftCol-1)); 1653 } 1654 if (seen[2] > row) { 1655 /* Remove now off-screen rows */ 1656 EmbWinUnmap(tablePtr, MAX(seen[0],row+1), seen[2], 1657 seen[1], seen[3]); 1658 /* Also account for the title area */ 1659 EmbWinUnmap(tablePtr, MAX(seen[0],row+1), seen[2], 1660 0, tablePtr->titleCols-1); 1661 } 1662 if (seen[3] > col) { 1663 /* Remove now off-screen cols */ 1664 EmbWinUnmap(tablePtr, seen[0], seen[2], 1665 MAX(seen[1],col+1), seen[3]); 1666 /* Also account for the title area */ 1667 EmbWinUnmap(tablePtr, 0, tablePtr->titleRows-1, 1668 MAX(seen[1],col+1), seen[3]); 1669 } 1670 } 1671 seen[0] = tablePtr->topRow; 1672 seen[1] = tablePtr->leftCol; 1673 seen[2] = row; 1674 seen[3] = col; 1675} 1676 1677/* 1678 * Generally we should be able to use XSetClipRectangles on X11, but 1679 * the addition of Xft drawing to Tk 8.5+ completely ignores the clip 1680 * rectangles. Thus turn it off for all cases until clip rectangles 1681 * are known to be respected. [Bug 1805350] 1682 */ 1683#if 1 || defined(MAC_TCL) || defined(UNDER_CE) || (defined(WIN32) && defined(TCL_THREADS)) || defined(MAC_OSX_TK) 1684#define NO_XSETCLIP 1685#endif 1686/* 1687 *-------------------------------------------------------------- 1688 * 1689 * TableDisplay -- 1690 * This procedure redraws the contents of a table window. 1691 * The conditional code in this function is due to these factors: 1692 * o Lack of XSetClipRectangles on Macintosh 1693 * o Use of alternative routine for Windows 1694 * 1695 * Results: 1696 * None. 1697 * 1698 * Side effects: 1699 * Information appears on the screen. 1700 * 1701 *-------------------------------------------------------------- 1702 */ 1703static void 1704TableDisplay(ClientData clientdata) 1705{ 1706 register Table *tablePtr = (Table *) clientdata; 1707 Tk_Window tkwin = tablePtr->tkwin; 1708 Display *display = tablePtr->display; 1709 Drawable window; 1710#ifdef NO_XSETCLIP 1711 Drawable clipWind; 1712#elif defined(WIN32) 1713 TkWinDrawable *twdPtr; 1714 HDC dc; 1715 HRGN clipR; 1716#else 1717 XRectangle clipRect; 1718#endif 1719 int rowFrom, rowTo, colFrom, colTo, 1720 invalidX, invalidY, invalidWidth, invalidHeight, 1721 x, y, width, height, itemX, itemY, itemW, itemH, 1722 row, col, urow, ucol, hrow=0, hcol=0, cx, cy, cw, ch, borders, bd[6], 1723 numBytes, new, boundW, boundH, maxW, maxH, cellType, 1724 originX, originY, activeCell, shouldInvert, ipadx, ipady, padx, pady; 1725 GC tagGc = NULL, topGc, bottomGc; 1726 char *string = NULL; 1727 char buf[INDEX_BUFSIZE]; 1728 TableTag *tagPtr = NULL, *titlePtr, *selPtr, *activePtr, *flashPtr, 1729 *rowPtr, *colPtr; 1730 Tcl_HashEntry *entryPtr; 1731 static XPoint rect[3] = { {0, 0}, {0, 0}, {0, 0} }; 1732 Tcl_HashTable *colTagsCache = NULL; 1733 Tcl_HashTable *drawnCache = NULL; 1734 Tk_TextLayout textLayout = NULL; 1735 TableEmbWindow *ewPtr; 1736 Tk_FontMetrics fm; 1737 Tk_Font ellFont = NULL; 1738 char *ellipsis = NULL; 1739 int ellLen = 0, useEllLen = 0, ellEast = 0; 1740 1741 tablePtr->flags &= ~REDRAW_PENDING; 1742 if ((tkwin == NULL) || !Tk_IsMapped(tkwin)) { 1743 return; 1744 } 1745 1746 boundW = Tk_Width(tkwin) - tablePtr->highlightWidth; 1747 boundH = Tk_Height(tkwin) - tablePtr->highlightWidth; 1748 1749 /* Constrain drawable to not include highlight borders */ 1750 invalidX = MAX(tablePtr->highlightWidth, tablePtr->invalidX); 1751 invalidY = MAX(tablePtr->highlightWidth, tablePtr->invalidY); 1752 invalidWidth = MIN(tablePtr->invalidWidth, MAX(1, boundW-invalidX)); 1753 invalidHeight = MIN(tablePtr->invalidHeight, MAX(1, boundH-invalidY)); 1754 1755 ipadx = tablePtr->ipadX; 1756 ipady = tablePtr->ipadY; 1757 padx = tablePtr->padX; 1758 pady = tablePtr->padY; 1759 1760#ifndef WIN32 1761 /* 1762 * if we are using the slow drawing mode with a pixmap 1763 * create the pixmap and adjust x && y for offset in pixmap 1764 * FIX: Ignore slow mode for Win32 as the fast ClipRgn trick 1765 * below does not work for bitmaps. 1766 */ 1767 if (tablePtr->drawMode == DRAW_MODE_SLOW) { 1768 window = Tk_GetPixmap(display, Tk_WindowId(tkwin), 1769 invalidWidth, invalidHeight, Tk_Depth(tkwin)); 1770 } else 1771#endif 1772 window = Tk_WindowId(tkwin); 1773#ifdef NO_XSETCLIP 1774 clipWind = Tk_GetPixmap(display, window, 1775 invalidWidth, invalidHeight, Tk_Depth(tkwin)); 1776#endif 1777 1778 /* set up the permanent tag styles */ 1779 entryPtr = Tcl_FindHashEntry(tablePtr->tagTable, "title"); 1780 titlePtr = (TableTag *) Tcl_GetHashValue(entryPtr); 1781 entryPtr = Tcl_FindHashEntry(tablePtr->tagTable, "sel"); 1782 selPtr = (TableTag *) Tcl_GetHashValue(entryPtr); 1783 entryPtr = Tcl_FindHashEntry(tablePtr->tagTable, "active"); 1784 activePtr = (TableTag *) Tcl_GetHashValue(entryPtr); 1785 entryPtr = Tcl_FindHashEntry(tablePtr->tagTable, "flash"); 1786 flashPtr = (TableTag *) Tcl_GetHashValue(entryPtr); 1787 1788 /* We need to find out the true cell span, not considering spans */ 1789 tablePtr->flags |= AVOID_SPANS; 1790 /* find out the cells represented by the invalid region */ 1791 TableWhatCell(tablePtr, invalidX, invalidY, &rowFrom, &colFrom); 1792 TableWhatCell(tablePtr, invalidX+invalidWidth-1, 1793 invalidY+invalidHeight-1, &rowTo, &colTo); 1794 tablePtr->flags &= ~AVOID_SPANS; 1795 1796#ifdef DEBUG 1797 tcl_dprintf(tablePtr->interp, "%d,%d => %d,%d", 1798 rowFrom+tablePtr->rowOffset, colFrom+tablePtr->colOffset, 1799 rowTo+tablePtr->rowOffset, colTo+tablePtr->colOffset); 1800#endif 1801 1802 /* 1803 * Initialize colTagsCache hash table to cache column tag names. 1804 */ 1805 colTagsCache = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 1806 Tcl_InitHashTable(colTagsCache, TCL_ONE_WORD_KEYS); 1807 /* 1808 * Initialize drawnCache hash table to cache drawn cells. 1809 * This is necessary to prevent spanning cells being drawn multiple times. 1810 */ 1811 drawnCache = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); 1812 Tcl_InitHashTable(drawnCache, TCL_STRING_KEYS); 1813 1814 /* 1815 * Create the tag here. This will actually create a JoinTag 1816 * That will handle the priority management of merging for us. 1817 * We only need one allocated, and we'll reset it for each cell. 1818 */ 1819 tagPtr = TableNewTag(tablePtr); 1820 1821 /* Cycle through the cells and display them */ 1822 for (row = rowFrom; row <= rowTo; row++) { 1823 /* 1824 * are we in the 'dead zone' between the 1825 * title rows and the first displayed row 1826 */ 1827 if (row < tablePtr->topRow && row >= tablePtr->titleRows) { 1828 row = tablePtr->topRow; 1829 } 1830 1831 /* Cache the row in user terms */ 1832 urow = row+tablePtr->rowOffset; 1833 1834 /* Get the row tag once for all iterations of col */ 1835 rowPtr = FindRowColTag(tablePtr, urow, ROW); 1836 1837 for (col = colFrom; col <= colTo; col++) { 1838 activeCell = 0; 1839 /* 1840 * Adjust to first viewable column if we are in the 'dead zone' 1841 * between the title cols and the first displayed column. 1842 */ 1843 if (col < tablePtr->leftCol && col >= tablePtr->titleCols) { 1844 col = tablePtr->leftCol; 1845 } 1846 1847 /* 1848 * Get the coordinates for the cell before possible rearrangement 1849 * of row,col due to spanning cells 1850 */ 1851 cellType = TableCellCoords(tablePtr, row, col, 1852 &x, &y, &width, &height); 1853 if (cellType == CELL_HIDDEN) { 1854 /* 1855 * width,height holds the real start row,col of the span. 1856 * Put the use cell ref into a buffer for the hash lookups. 1857 */ 1858 TableMakeArrayIndex(width, height, buf); 1859 Tcl_CreateHashEntry(drawnCache, buf, &new); 1860 if (!new) { 1861 /* Not new in the entry, so it's already drawn */ 1862 continue; 1863 } 1864 hrow = row; hcol = col; 1865 row = width-tablePtr->rowOffset; 1866 col = height-tablePtr->colOffset; 1867 TableCellVCoords(tablePtr, row, col, 1868 &x, &y, &width, &height, 0); 1869 /* We have to adjust the coords back onto the visual display */ 1870 urow = row+tablePtr->rowOffset; 1871 rowPtr = FindRowColTag(tablePtr, urow, ROW); 1872 } 1873 1874 /* Constrain drawn size to the visual boundaries */ 1875 if (width > boundW-x) { width = boundW-x; } 1876 if (height > boundH-y) { height = boundH-y; } 1877 1878 /* Cache the col in user terms */ 1879 ucol = col+tablePtr->colOffset; 1880 1881 /* put the use cell ref into a buffer for the hash lookups */ 1882 TableMakeArrayIndex(urow, ucol, buf); 1883 if (cellType != CELL_HIDDEN) { 1884 Tcl_CreateHashEntry(drawnCache, buf, &new); 1885 } 1886 1887 /* 1888 * Make sure we start with a clean tag (set to table defaults). 1889 */ 1890 TableResetTag(tablePtr, tagPtr); 1891 1892 /* 1893 * Check to see if we have an embedded window in this cell. 1894 */ 1895 entryPtr = Tcl_FindHashEntry(tablePtr->winTable, buf); 1896 if (entryPtr != NULL) { 1897 ewPtr = (TableEmbWindow *) Tcl_GetHashValue(entryPtr); 1898 1899 if (ewPtr->tkwin != NULL) { 1900 /* Display embedded window instead of text */ 1901 1902 /* if active, make it disabled to avoid 1903 * unnecessary editing */ 1904 if ((tablePtr->flags & HAS_ACTIVE) 1905 && row == tablePtr->activeRow 1906 && col == tablePtr->activeCol) { 1907 tablePtr->flags |= ACTIVE_DISABLED; 1908 } 1909 1910 /* 1911 * The EmbWinDisplay function may modify values in 1912 * tagPtr, so reference those after this call. 1913 */ 1914 EmbWinDisplay(tablePtr, window, ewPtr, tagPtr, 1915 x, y, width, height); 1916 1917#ifndef WIN32 1918 if (tablePtr->drawMode == DRAW_MODE_SLOW) { 1919 /* Correctly adjust x && y with the offset */ 1920 x -= invalidX; 1921 y -= invalidY; 1922 } 1923#endif 1924 1925 Tk_Fill3DRectangle(tkwin, window, tagPtr->bg, x, y, width, 1926 height, 0, TK_RELIEF_FLAT); 1927 1928 /* border width for cell should now be properly set */ 1929 borders = TableGetTagBorders(tagPtr, &bd[0], &bd[1], 1930 &bd[2], &bd[3]); 1931 bd[4] = (bd[0] + bd[1])/2; 1932 bd[5] = (bd[2] + bd[3])/2; 1933 1934 goto DrawBorder; 1935 } 1936 } 1937 1938 /* 1939 * Don't draw what won't be seen. 1940 * Embedded windows handle this in EmbWinDisplay. 1941 */ 1942 if ((width <= 0) || (height <= 0)) { continue; } 1943 1944#ifndef WIN32 1945 if (tablePtr->drawMode == DRAW_MODE_SLOW) { 1946 /* Correctly adjust x && y with the offset */ 1947 x -= invalidX; 1948 y -= invalidY; 1949 } 1950#endif 1951 1952 shouldInvert = 0; 1953 /* 1954 * Get the combined tag structure for the cell. 1955 * First clear out a new tag structure that we will build in 1956 * then add tags as we realize they belong. 1957 * 1958 * Tags have their own priorities which TableMergeTag will 1959 * take into account when merging tags. 1960 */ 1961 1962 /* 1963 * Merge colPtr if it exists 1964 * let's see if we have the value cached already 1965 * if not, run the findColTag routine and cache the value 1966 */ 1967 entryPtr = Tcl_CreateHashEntry(colTagsCache, (char *)ucol, &new); 1968 if (new) { 1969 colPtr = FindRowColTag(tablePtr, ucol, COL); 1970 Tcl_SetHashValue(entryPtr, colPtr); 1971 } else { 1972 colPtr = (TableTag *) Tcl_GetHashValue(entryPtr); 1973 } 1974 if (colPtr != (TableTag *) NULL) { 1975 TableMergeTag(tablePtr, tagPtr, colPtr); 1976 } 1977 /* Merge rowPtr if it exists */ 1978 if (rowPtr != (TableTag *) NULL) { 1979 TableMergeTag(tablePtr, tagPtr, rowPtr); 1980 } 1981 /* Am I in the titles */ 1982 if (row < tablePtr->titleRows || col < tablePtr->titleCols) { 1983 TableMergeTag(tablePtr, tagPtr, titlePtr); 1984 } 1985 /* Does this have a cell tag */ 1986 entryPtr = Tcl_FindHashEntry(tablePtr->cellStyles, buf); 1987 if (entryPtr != NULL) { 1988 TableMergeTag(tablePtr, tagPtr, 1989 (TableTag *) Tcl_GetHashValue(entryPtr)); 1990 } 1991 /* is this cell active? */ 1992 if ((tablePtr->flags & HAS_ACTIVE) && 1993 (tablePtr->state == STATE_NORMAL) && 1994 row == tablePtr->activeRow && col == tablePtr->activeCol) { 1995 if (tagPtr->state == STATE_DISABLED) { 1996 tablePtr->flags |= ACTIVE_DISABLED; 1997 } else { 1998 TableMergeTag(tablePtr, tagPtr, activePtr); 1999 activeCell = 1; 2000 tablePtr->flags &= ~ACTIVE_DISABLED; 2001 } 2002 } 2003 /* is this cell selected? */ 2004 if (Tcl_FindHashEntry(tablePtr->selCells, buf) != NULL) { 2005 if (tablePtr->invertSelected && !activeCell) { 2006 shouldInvert = 1; 2007 } else { 2008 TableMergeTag(tablePtr, tagPtr, selPtr); 2009 } 2010 } 2011 /* if flash mode is on, is this cell flashing? */ 2012 if (tablePtr->flashMode && 2013 Tcl_FindHashEntry(tablePtr->flashCells, buf) != NULL) { 2014 TableMergeTag(tablePtr, tagPtr, flashPtr); 2015 } 2016 2017 if (shouldInvert) { 2018 TableInvertTag(tagPtr); 2019 } 2020 2021 /* 2022 * Borders for cell should now be properly set 2023 */ 2024 borders = TableGetTagBorders(tagPtr, &bd[0], &bd[1], 2025 &bd[2], &bd[3]); 2026 bd[4] = (bd[0] + bd[1])/2; 2027 bd[5] = (bd[2] + bd[3])/2; 2028 2029 /* 2030 * First fill in a blank rectangle. 2031 */ 2032 Tk_Fill3DRectangle(tkwin, window, tagPtr->bg, 2033 x, y, width, height, 0, TK_RELIEF_FLAT); 2034 2035 /* 2036 * Correct the dimensions to enforce padding constraints 2037 */ 2038 width -= bd[0] + bd[1] + (2 * padx); 2039 height -= bd[2] + bd[3] + (2 * pady); 2040 2041 /* 2042 * Don't draw what won't be seen, based on border constraints. 2043 */ 2044 if ((width <= 0) || (height <= 0)) { 2045 /* 2046 * Re-Correct the dimensions before border drawing 2047 */ 2048 width += bd[0] + bd[1] + (2 * padx); 2049 height += bd[2] + bd[3] + (2 * pady); 2050 goto DrawBorder; 2051 } 2052 2053 /* 2054 * If an image is in the tag, draw it 2055 */ 2056 if (tagPtr->image != NULL) { 2057 Tk_SizeOfImage(tagPtr->image, &itemW, &itemH); 2058 /* Handle anchoring of image in cell space */ 2059 switch (tagPtr->anchor) { 2060 case TK_ANCHOR_NW: 2061 case TK_ANCHOR_W: 2062 case TK_ANCHOR_SW: /* western position */ 2063 originX = itemX = 0; 2064 break; 2065 case TK_ANCHOR_N: 2066 case TK_ANCHOR_S: 2067 case TK_ANCHOR_CENTER: /* centered position */ 2068 itemX = MAX(0, (itemW - width) / 2); 2069 originX = MAX(0, (width - itemW) / 2); 2070 break; 2071 default: /* eastern position */ 2072 itemX = MAX(0, itemW - width); 2073 originX = MAX(0, width - itemW); 2074 } 2075 switch (tagPtr->anchor) { 2076 case TK_ANCHOR_N: 2077 case TK_ANCHOR_NE: 2078 case TK_ANCHOR_NW: /* northern position */ 2079 originY = itemY = 0; 2080 break; 2081 case TK_ANCHOR_W: 2082 case TK_ANCHOR_E: 2083 case TK_ANCHOR_CENTER: /* centered position */ 2084 itemY = MAX(0, (itemH - height) / 2); 2085 originY = MAX(0, (height - itemH) / 2); 2086 break; 2087 default: /* southern position */ 2088 itemY = MAX(0, itemH - height); 2089 originY = MAX(0, height - itemH); 2090 } 2091 Tk_RedrawImage(tagPtr->image, itemX, itemY, 2092 MIN(itemW, width-originX), MIN(itemH, height-originY), 2093 window, x + originX + bd[0] + padx, 2094 y + originY + bd[2] + pady); 2095 /* 2096 * If we don't want to display the text as well, then jump. 2097 */ 2098 if (tagPtr->showtext == 0) { 2099 /* 2100 * Re-Correct the dimensions before border drawing 2101 */ 2102 width += bd[0] + bd[1] + (2 * padx); 2103 height += bd[2] + bd[3] + (2 * pady); 2104 goto DrawBorder; 2105 } 2106 } 2107 2108 /* 2109 * Get the GC for this particular blend of tags. 2110 * This creates the GC if it never existed, otherwise it 2111 * modifies the one we have, so we only need the one 2112 */ 2113 TableGetGc(display, window, tagPtr, &tagGc); 2114 2115 /* if this is the active cell, use the buffer */ 2116 if (activeCell) { 2117 string = tablePtr->activeBuf; 2118 } else { 2119 /* Is there a value in the cell? If so, draw it */ 2120 string = TableGetCellValue(tablePtr, urow, ucol); 2121 } 2122 2123#ifdef TCL_UTF_MAX 2124 /* 2125 * We have to use strlen here because otherwise it stops 2126 * at the first \x00 unicode char it finds (!= '\0'), 2127 * although there can be more to the string than that 2128 */ 2129 numBytes = Tcl_NumUtfChars(string, (int) strlen(string)); 2130#else 2131 numBytes = strlen(string); 2132#endif 2133 2134 /* If there is a string, show it */ 2135 if (activeCell || numBytes) { 2136 register int x0 = x + bd[0] + padx; 2137 register int y0 = y + bd[2] + pady; 2138 2139 /* get the dimensions of the string */ 2140 textLayout = Tk_ComputeTextLayout(tagPtr->tkfont, 2141 string, numBytes, 2142 (tagPtr->wrap > 0) ? width : 0, tagPtr->justify, 2143 (tagPtr->multiline > 0) ? 0 : TK_IGNORE_NEWLINES, 2144 &itemW, &itemH); 2145 2146 /* 2147 * Set the origin coordinates of the string to draw using 2148 * the anchor. origin represents the (x,y) coordinate of 2149 * the lower left corner of the text box, relative to the 2150 * internal (inside the border) window 2151 */ 2152 2153 /* set the X origin first */ 2154 switch (tagPtr->anchor) { 2155 case TK_ANCHOR_NW: 2156 case TK_ANCHOR_W: 2157 case TK_ANCHOR_SW: /* western position */ 2158 originX = ipadx; 2159 break; 2160 case TK_ANCHOR_N: 2161 case TK_ANCHOR_S: 2162 case TK_ANCHOR_CENTER: /* centered position */ 2163 originX = (width - itemW) / 2; 2164 break; 2165 default: /* eastern position */ 2166 originX = width - itemW - ipadx; 2167 } 2168 2169 /* then set the Y origin */ 2170 switch (tagPtr->anchor) { 2171 case TK_ANCHOR_N: 2172 case TK_ANCHOR_NE: 2173 case TK_ANCHOR_NW: /* northern position */ 2174 originY = ipady; 2175 break; 2176 case TK_ANCHOR_W: 2177 case TK_ANCHOR_E: 2178 case TK_ANCHOR_CENTER: /* centered position */ 2179 originY = (height - itemH) / 2; 2180 break; 2181 default: /* southern position */ 2182 originY = height - itemH - ipady; 2183 } 2184 2185 /* 2186 * If this is the active cell and we are editing, 2187 * ensure that the cursor will be displayed 2188 */ 2189 if (activeCell) { 2190 Tk_CharBbox(textLayout, tablePtr->icursor, 2191 &cx, &cy, &cw, &ch); 2192 /* we have to fudge with maxW because of odd width 2193 * determination for newlines at the end of a line */ 2194 maxW = width - tablePtr->insertWidth 2195 - (cx + MIN(tablePtr->charWidth, cw)); 2196 maxH = height - (cy + ch); 2197 if (originX < bd[0] - cx) { 2198 /* cursor off cell to the left */ 2199 /* use western positioning to cet cursor at left 2200 * with slight variation to show some text */ 2201 originX = bd[0] - cx 2202 + MIN(cx, width - tablePtr->insertWidth); 2203 } else if (originX > maxW) { 2204 /* cursor off cell to the right */ 2205 /* use eastern positioning to cet cursor at right */ 2206 originX = maxW; 2207 } 2208 if (originY < bd[2] - cy) { 2209 /* cursor before top of cell */ 2210 /* use northern positioning to cet cursor at top */ 2211 originY = bd[2] - cy; 2212 } else if (originY > maxH) { 2213 /* cursor beyond bottom of cell */ 2214 /* use southern positioning to cet cursor at bottom */ 2215 originY = maxH; 2216 } 2217 tablePtr->activeTagPtr = tagPtr; 2218 tablePtr->activeX = originX; 2219 tablePtr->activeY = originY; 2220 } 2221 2222 /* 2223 * Use a clip rectangle only if necessary as it means 2224 * updating the GC in the server which slows everything down. 2225 * We can't fudge the width or height, just in case the user 2226 * wanted empty pad space. 2227 */ 2228 if ((originX < 0) || (originY < 0) || 2229 (originX+itemW > width) || (originY+itemH > height)) { 2230 if (!activeCell 2231 && (tagPtr->ellipsis != NULL) 2232 && (tagPtr->wrap <= 0) 2233 && (tagPtr->multiline <= 0) 2234 ) { 2235 /* 2236 * Check which side to draw ellipsis on 2237 */ 2238 switch (tagPtr->anchor) { 2239 case TK_ANCHOR_NE: 2240 case TK_ANCHOR_E: 2241 case TK_ANCHOR_SE: /* eastern position */ 2242 ellEast = 0; 2243 break; 2244 default: /* western position */ 2245 ellEast = 1; 2246 } 2247 if ((ellipsis != tagPtr->ellipsis) 2248 || (ellFont != tagPtr->tkfont)) { 2249 /* 2250 * Different ellipsis from last cached 2251 */ 2252 ellFont = tagPtr->tkfont; 2253 ellipsis = tagPtr->ellipsis; 2254 ellLen = Tk_TextWidth(ellFont, 2255 ellipsis, (int) strlen(ellipsis)); 2256 Tk_GetFontMetrics(tagPtr->tkfont, &fm); 2257 } 2258 useEllLen = MIN(ellLen, width); 2259 } else { 2260 ellEast = 0; 2261 useEllLen = 0; 2262 } 2263 2264 /* 2265 * The text wants to overflow the boundaries of the 2266 * displayed cell, so we must clip in some way 2267 */ 2268#ifdef NO_XSETCLIP 2269 /* 2270 * This code is basically for the Macintosh. 2271 * Copy the the current contents of the cell into the 2272 * clipped window area. This keeps any fg/bg and image 2273 * data intact. 2274 * x0 - x == pad area 2275 */ 2276 XCopyArea(display, window, clipWind, tagGc, x0, y0, 2277 width, height, x0 - x, y0 - y); 2278 /* 2279 * Now draw into the cell space on the special window. 2280 * Don't use x,y base offset for clipWind. 2281 */ 2282 Tk_DrawTextLayout(display, clipWind, tagGc, textLayout, 2283 x0 - x + originX, y0 - y + originY, 0, -1); 2284 2285 if (useEllLen) { 2286 /* 2287 * Recopy area the ellipse covers (not efficient) 2288 */ 2289 XCopyArea(display, window, clipWind, tagGc, 2290 x0 + (ellEast ? width - useEllLen : 0), y0, 2291 useEllLen, height, 2292 x0 - x + (ellEast ? width - useEllLen : 0), 2293 y0 - y); 2294 Tk_DrawChars(display, clipWind, tagGc, ellFont, 2295 ellipsis, (int) strlen(ellipsis), 2296 x0 - x + (ellEast ? width - useEllLen : 0), 2297 y0 - y + originY + fm.ascent); 2298 } 2299 /* 2300 * Now copy back only the area that we want the 2301 * text to be drawn on. 2302 */ 2303 XCopyArea(display, clipWind, window, tagGc, 2304 x0 - x, y0 - y, width, height, x0, y0); 2305#elif defined(WIN32) 2306 /* 2307 * This is evil, evil evil! but the XCopyArea 2308 * doesn't work in all cases - Michael Teske. 2309 * The general structure follows the comments below. 2310 */ 2311 twdPtr = (TkWinDrawable *) window; 2312 dc = GetDC(twdPtr->window.handle); 2313 2314 clipR = CreateRectRgn(x0 + (ellEast ? 0 : useEllLen), y0, 2315 x0 + width - (ellEast ? useEllLen : 0), y0 + height); 2316 2317 SelectClipRgn(dc, clipR); 2318 DeleteObject(clipR); 2319 /* OffsetClipRgn(dc, 0, 0); */ 2320 2321 Tk_DrawTextLayout(display, window, tagGc, textLayout, 2322 x0 + originX, y0 + originY, 0, -1); 2323 2324 if (useEllLen) { 2325 clipR = CreateRectRgn(x0, y0, x0 + width, y0 + height); 2326 SelectClipRgn(dc, clipR); 2327 DeleteObject(clipR); 2328 Tk_DrawChars(display, window, tagGc, ellFont, 2329 ellipsis, (int) strlen(ellipsis), 2330 x0 + (ellEast? width-useEllLen : 0), 2331 y0 + originY + fm.ascent); 2332 } 2333 SelectClipRgn(dc, NULL); 2334 ReleaseDC(twdPtr->window.handle, dc); 2335#else 2336 /* 2337 * Use an X clipping rectangle. The clipping is the 2338 * rectangle just for the actual text space (to allow 2339 * for empty padding space). 2340 */ 2341 clipRect.x = x0 + (ellEast ? 0 : useEllLen); 2342 clipRect.y = y0; 2343 clipRect.width = width - (ellEast ? useEllLen : 0); 2344 clipRect.height = height; 2345 XSetClipRectangles(display, tagGc, 0, 0, &clipRect, 1, 2346 Unsorted); 2347 Tk_DrawTextLayout(display, window, tagGc, textLayout, 2348 x0 + originX, 2349 y0 + originY, 0, -1); 2350 if (useEllLen) { 2351 clipRect.x = x0; 2352 clipRect.width = width; 2353 XSetClipRectangles(display, tagGc, 0, 0, &clipRect, 1, 2354 Unsorted); 2355 Tk_DrawChars(display, window, tagGc, ellFont, 2356 ellipsis, (int) strlen(ellipsis), 2357 x0 + (ellEast? width-useEllLen : 0), 2358 y0 + originY + fm.ascent); 2359 } 2360 XSetClipMask(display, tagGc, None); 2361#endif 2362 } else { 2363 Tk_DrawTextLayout(display, window, tagGc, textLayout, 2364 x0 + originX, y0 + originY, 0, -1); 2365 } 2366 2367 /* if this is the active cell draw the cursor if it's on. 2368 * this ignores clip rectangles. */ 2369 if (activeCell && (tablePtr->flags & CURSOR_ON) && 2370 (originY + cy + bd[2] + pady < height) && 2371 (originX + cx + bd[0] + padx - 2372 (tablePtr->insertWidth / 2) >= 0)) { 2373 /* make sure it will fit in the box */ 2374 maxW = MAX(0, originY + cy + bd[2] + pady); 2375 maxH = MIN(ch, height - maxW + bd[2] + pady); 2376 Tk_Fill3DRectangle(tkwin, window, tablePtr->insertBg, 2377 x0 + originX + cx - (tablePtr->insertWidth/2), 2378 y + maxW, tablePtr->insertWidth, 2379 maxH, 0, TK_RELIEF_FLAT); 2380 } 2381 } 2382 2383 /* 2384 * Re-Correct the dimensions before border drawing 2385 */ 2386 width += bd[0] + bd[1] + (2 * padx); 2387 height += bd[2] + bd[3] + (2 * pady); 2388 2389 DrawBorder: 2390 /* Draw the 3d border on the pixmap correctly offset */ 2391 if (tablePtr->drawMode == DRAW_MODE_SINGLE) { 2392 topGc = Tk_3DBorderGC(tkwin, tagPtr->bg, TK_3D_DARK_GC); 2393 /* draw a line with single pixel width */ 2394 rect[0].x = x; 2395 rect[0].y = y + height - 1; 2396 rect[1].y = -height + 1; 2397 rect[2].x = width - 1; 2398 XDrawLines(display, window, topGc, rect, 3, CoordModePrevious); 2399 } else if (tablePtr->drawMode == DRAW_MODE_FAST) { 2400 /* 2401 * This depicts a full 1 pixel border. 2402 * 2403 * Choose the GCs to get the best approximation 2404 * to the desired drawing style. 2405 */ 2406 switch(tagPtr->relief) { 2407 case TK_RELIEF_FLAT: 2408 topGc = bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg, 2409 TK_3D_FLAT_GC); 2410 break; 2411 case TK_RELIEF_RAISED: 2412 case TK_RELIEF_RIDGE: 2413 topGc = Tk_3DBorderGC(tkwin, tagPtr->bg, 2414 TK_3D_LIGHT_GC); 2415 bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg, 2416 TK_3D_DARK_GC); 2417 break; 2418 default: /* TK_RELIEF_SUNKEN TK_RELIEF_GROOVE */ 2419 bottomGc = Tk_3DBorderGC(tkwin, tagPtr->bg, 2420 TK_3D_LIGHT_GC); 2421 topGc = Tk_3DBorderGC(tkwin, tagPtr->bg, 2422 TK_3D_DARK_GC); 2423 break; 2424 } 2425 2426 /* draw a line with single pixel width */ 2427 rect[0].x = x + width - 1; 2428 rect[0].y = y; 2429 rect[1].y = height - 1; 2430 rect[2].x = -width + 1; 2431 XDrawLines(display, window, bottomGc, rect, 3, 2432 CoordModePrevious); 2433 rect[0].x = x; 2434 rect[0].y = y + height - 1; 2435 rect[1].y = -height + 1; 2436 rect[2].x = width - 1; 2437 XDrawLines(display, window, topGc, rect, 3, 2438 CoordModePrevious); 2439 } else { 2440 if (borders > 1) { 2441 if (bd[0]) { 2442 Tk_3DVerticalBevel(tkwin, window, tagPtr->bg, 2443 x, y, bd[0], height, 2444 1 /* left side */, tagPtr->relief); 2445 } 2446 if (bd[1]) { 2447 Tk_3DVerticalBevel(tkwin, window, tagPtr->bg, 2448 x + width - bd[1], y, bd[1], height, 2449 0 /* right side */, tagPtr->relief); 2450 } 2451 if ((borders == 4) && bd[2]) { 2452 Tk_3DHorizontalBevel(tkwin, window, tagPtr->bg, 2453 x, y, width, bd[2], 2454 1, 1, 1 /* top */, tagPtr->relief); 2455 } 2456 if ((borders == 4) && bd[3]) { 2457 Tk_3DHorizontalBevel(tkwin, window, tagPtr->bg, 2458 x, y + height - bd[3], width, bd[3], 2459 0, 0, 0 /* bottom */, tagPtr->relief); 2460 } 2461 } else if (borders == 1) { 2462 Tk_Draw3DRectangle(tkwin, window, tagPtr->bg, x, y, 2463 width, height, bd[0], tagPtr->relief); 2464 } 2465 } 2466 2467 /* clean up the necessaries */ 2468 if (tagPtr == tablePtr->activeTagPtr) { 2469 /* 2470 * This means it was the activeCell with text displayed. 2471 * We buffer the active tag for the 'activate' command. 2472 */ 2473 tablePtr->activeTagPtr = TableNewTag(NULL); 2474 memcpy((VOID *) tablePtr->activeTagPtr, 2475 (VOID *) tagPtr, sizeof(TableTag)); 2476 } 2477 if (textLayout) { 2478 Tk_FreeTextLayout(textLayout); 2479 textLayout = NULL; 2480 } 2481 if (cellType == CELL_HIDDEN) { 2482 /* the last cell was a hidden one, 2483 * rework row stuff back to normal */ 2484 row = hrow; col = hcol; 2485 urow = row+tablePtr->rowOffset; 2486 rowPtr = FindRowColTag(tablePtr, urow, ROW); 2487 } 2488 } 2489 } 2490 ckfree((char *) tagPtr); 2491#ifdef NO_XSETCLIP 2492 Tk_FreePixmap(display, clipWind); 2493#endif 2494 2495 /* Take care of removing embedded windows that are no longer in view */ 2496 TableUndisplay(tablePtr); 2497 2498#ifndef WIN32 2499 /* copy over and delete the pixmap if we are in slow mode */ 2500 if (tablePtr->drawMode == DRAW_MODE_SLOW) { 2501 /* Get a default valued GC */ 2502 TableGetGc(display, window, &(tablePtr->defaultTag), &tagGc); 2503 XCopyArea(display, window, Tk_WindowId(tkwin), tagGc, 0, 0, 2504 (unsigned) invalidWidth, (unsigned) invalidHeight, 2505 invalidX, invalidY); 2506 Tk_FreePixmap(display, window); 2507 window = Tk_WindowId(tkwin); 2508 } 2509#endif 2510 2511 /* 2512 * If we are at the end of the table, clear the area after the last 2513 * row/col. We discount spans here because we just need the coords 2514 * for the area that would be the last physical cell. 2515 */ 2516 tablePtr->flags |= AVOID_SPANS; 2517 TableCellCoords(tablePtr, tablePtr->rows-1, tablePtr->cols-1, 2518 &x, &y, &width, &height); 2519 tablePtr->flags &= ~AVOID_SPANS; 2520 2521 /* This should occur before moving pixmap, but this simplifies things 2522 * 2523 * Could use Tk_Fill3DRectangle instead of XFillRectangle 2524 * for best compatibility, and XClearArea could be used on Unix 2525 * for best speed, so this is the compromise w/o #ifdef's 2526 */ 2527 if (x+width < invalidX+invalidWidth) { 2528 XFillRectangle(display, window, 2529 Tk_3DBorderGC(tkwin, tablePtr->defaultTag.bg, TK_3D_FLAT_GC), 2530 x+width, invalidY, (unsigned) invalidX+invalidWidth-x-width, 2531 (unsigned) invalidHeight); 2532 } 2533 2534 if (y+height < invalidY+invalidHeight) { 2535 XFillRectangle(display, window, 2536 Tk_3DBorderGC(tkwin, tablePtr->defaultTag.bg, TK_3D_FLAT_GC), 2537 invalidX, y+height, (unsigned) invalidWidth, 2538 (unsigned) invalidY+invalidHeight-y-height); 2539 } 2540 2541 if (tagGc != NULL) { 2542 TableFreeGc(display, tagGc); 2543 } 2544 TableRedrawHighlight(tablePtr); 2545 /* 2546 * Free the hash table used to cache evaluations. 2547 */ 2548 Tcl_DeleteHashTable(colTagsCache); 2549 ckfree((char *) (colTagsCache)); 2550 Tcl_DeleteHashTable(drawnCache); 2551 ckfree((char *) (drawnCache)); 2552} 2553 2554/* 2555 *---------------------------------------------------------------------- 2556 * 2557 * TableInvalidate -- 2558 * Invalidates a rectangle and adds it to the total invalid rectangle 2559 * waiting to be redrawn. If the INV_FORCE flag bit is set, 2560 * it does an update instantly else waits until Tk is idle. 2561 * 2562 * Results: 2563 * Will schedule table (re)display. 2564 * 2565 * Side effects: 2566 * None 2567 * 2568 *---------------------------------------------------------------------- 2569 */ 2570void 2571TableInvalidate(Table * tablePtr, int x, int y, 2572 int w, int h, int flags) 2573{ 2574 Tk_Window tkwin = tablePtr->tkwin; 2575 int hl = tablePtr->highlightWidth; 2576 int height = Tk_Height(tkwin); 2577 int width = Tk_Width(tkwin); 2578 2579 /* 2580 * Make sure that the window hasn't been destroyed already. 2581 * Avoid allocating 0 sized pixmaps which would be fatal, 2582 * and check if rectangle is even on the screen. 2583 */ 2584 if ((tkwin == NULL) 2585 || (w <= 0) || (h <= 0) || (x > width) || (y > height)) { 2586 return; 2587 } 2588 2589 /* If not even mapped, wait for the remap to redraw all */ 2590 if (!Tk_IsMapped(tkwin)) { 2591 tablePtr->flags |= REDRAW_ON_MAP; 2592 return; 2593 } 2594 2595 /* 2596 * If no pending updates exist, then replace the rectangle. 2597 * Otherwise find the bounding rectangle. 2598 */ 2599 if ((flags & INV_HIGHLIGHT) && 2600 (x < hl || y < hl || x+w >= width-hl || y+h >= height-hl)) { 2601 tablePtr->flags |= REDRAW_BORDER; 2602 } 2603 2604 if (tablePtr->flags & REDRAW_PENDING) { 2605 tablePtr->invalidWidth = MAX(x + w, 2606 tablePtr->invalidX+tablePtr->invalidWidth); 2607 tablePtr->invalidHeight = MAX(y + h, 2608 tablePtr->invalidY+tablePtr->invalidHeight); 2609 if (tablePtr->invalidX > x) tablePtr->invalidX = x; 2610 if (tablePtr->invalidY > y) tablePtr->invalidY = y; 2611 tablePtr->invalidWidth -= tablePtr->invalidX; 2612 tablePtr->invalidHeight -= tablePtr->invalidY; 2613 /* Do we want to force this update out? */ 2614 if (flags & INV_FORCE) { 2615 Tcl_CancelIdleCall(TableDisplay, (ClientData) tablePtr); 2616 TableDisplay((ClientData) tablePtr); 2617 } 2618 } else { 2619 tablePtr->invalidX = x; 2620 tablePtr->invalidY = y; 2621 tablePtr->invalidWidth = w; 2622 tablePtr->invalidHeight = h; 2623 if (flags & INV_FORCE) { 2624 TableDisplay((ClientData) tablePtr); 2625 } else { 2626 tablePtr->flags |= REDRAW_PENDING; 2627 Tcl_DoWhenIdle(TableDisplay, (ClientData) tablePtr); 2628 } 2629 } 2630} 2631 2632/* 2633 *---------------------------------------------------------------------- 2634 * 2635 * TableFlashEvent -- 2636 * Called when the flash timer goes off. 2637 * 2638 * Results: 2639 * Decrements all the entries in the hash table and invalidates 2640 * any cells that expire, deleting them from the table. If the 2641 * table is now empty, stops the timer, else reenables it. 2642 * 2643 * Side effects: 2644 * None. 2645 * 2646 *---------------------------------------------------------------------- 2647 */ 2648static void 2649TableFlashEvent(ClientData clientdata) 2650{ 2651 Table *tablePtr = (Table *) clientdata; 2652 Tcl_HashEntry *entryPtr; 2653 Tcl_HashSearch search; 2654 int entries, count, row, col; 2655 2656 entries = 0; 2657 for (entryPtr = Tcl_FirstHashEntry(tablePtr->flashCells, &search); 2658 entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { 2659 count = (int) Tcl_GetHashValue(entryPtr); 2660 if (--count <= 0) { 2661 /* get the cell address and invalidate that region only */ 2662 TableParseArrayIndex(&row, &col, 2663 Tcl_GetHashKey(tablePtr->flashCells, entryPtr)); 2664 2665 /* delete the entry from the table */ 2666 Tcl_DeleteHashEntry(entryPtr); 2667 2668 TableRefresh(tablePtr, row-tablePtr->rowOffset, 2669 col-tablePtr->colOffset, CELL); 2670 } else { 2671 Tcl_SetHashValue(entryPtr, (ClientData) count); 2672 entries++; 2673 } 2674 } 2675 2676 /* do I need to restart the timer */ 2677 if (entries && tablePtr->flashMode) { 2678 tablePtr->flashTimer = Tcl_CreateTimerHandler(250, TableFlashEvent, 2679 (ClientData) tablePtr); 2680 } else { 2681 tablePtr->flashTimer = 0; 2682 } 2683} 2684 2685/* 2686 *---------------------------------------------------------------------- 2687 * 2688 * TableAddFlash -- 2689 * Adds a flash on cell row,col (real coords) with the default timeout 2690 * if flashing is enabled and flashtime > 0. 2691 * 2692 * Results: 2693 * Cell will flash. 2694 * 2695 * Side effects: 2696 * Will start flash timer if it didn't exist. 2697 * 2698 *---------------------------------------------------------------------- 2699 */ 2700void 2701TableAddFlash(Table *tablePtr, int row, int col) 2702{ 2703 char buf[INDEX_BUFSIZE]; 2704 int dummy; 2705 Tcl_HashEntry *entryPtr; 2706 2707 if (!tablePtr->flashMode || tablePtr->flashTime < 1) { 2708 return; 2709 } 2710 2711 /* create the array index in user coords */ 2712 TableMakeArrayIndex(row+tablePtr->rowOffset, col+tablePtr->colOffset, buf); 2713 2714 /* add the flash to the hash table */ 2715 entryPtr = Tcl_CreateHashEntry(tablePtr->flashCells, buf, &dummy); 2716 Tcl_SetHashValue(entryPtr, tablePtr->flashTime); 2717 2718 /* now set the timer if it's not already going and invalidate the area */ 2719 if (tablePtr->flashTimer == NULL) { 2720 tablePtr->flashTimer = Tcl_CreateTimerHandler(250, TableFlashEvent, 2721 (ClientData) tablePtr); 2722 } 2723} 2724 2725/* 2726 *---------------------------------------------------------------------- 2727 * 2728 * TableSetActiveIndex -- 2729 * Sets the "active" index of the associated array to the current 2730 * value of the active buffer. 2731 * 2732 * Results: 2733 * None. 2734 * 2735 * Side effects: 2736 * Traces on the array can cause side effects. 2737 * 2738 *---------------------------------------------------------------------- 2739 */ 2740void 2741TableSetActiveIndex(register Table *tablePtr) 2742{ 2743 if (tablePtr->arrayVar) { 2744 tablePtr->flags |= SET_ACTIVE; 2745 Tcl_SetVar2(tablePtr->interp, tablePtr->arrayVar, "active", 2746 tablePtr->activeBuf, TCL_GLOBAL_ONLY); 2747 tablePtr->flags &= ~SET_ACTIVE; 2748 } 2749} 2750 2751/* 2752 *---------------------------------------------------------------------- 2753 * 2754 * TableGetActiveBuf -- 2755 * Get the current selection into the buffer and mark it as unedited. 2756 * Set the position to the end of the string. 2757 * 2758 * Results: 2759 * None. 2760 * 2761 * Side effects: 2762 * tablePtr->activeBuf will change. 2763 * 2764 *---------------------------------------------------------------------- 2765 */ 2766void 2767TableGetActiveBuf(register Table *tablePtr) 2768{ 2769 char *data = ""; 2770 2771 if (tablePtr->flags & HAS_ACTIVE) { 2772 data = TableGetCellValue(tablePtr, 2773 tablePtr->activeRow+tablePtr->rowOffset, 2774 tablePtr->activeCol+tablePtr->colOffset); 2775 } 2776 2777 if (STREQ(tablePtr->activeBuf, data)) { 2778 /* this forced SetActiveIndex is necessary if we change array vars and 2779 * they happen to have these cells equal, we won't properly set the 2780 * active index for the new array var unless we do this here */ 2781 TableSetActiveIndex(tablePtr); 2782 return; 2783 } 2784 /* is the buffer long enough */ 2785 tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, 2786 strlen(data)+1); 2787 strcpy(tablePtr->activeBuf, data); 2788 TableGetIcursor(tablePtr, "end", (int *)0); 2789 tablePtr->flags &= ~TEXT_CHANGED; 2790 TableSetActiveIndex(tablePtr); 2791} 2792 2793/* 2794 *---------------------------------------------------------------------- 2795 * 2796 * TableVarProc -- 2797 * This is the trace procedure associated with the Tcl array. No 2798 * validation will occur here because this only triggers when the 2799 * array value is directly set, and we can't maintain the old value. 2800 * 2801 * Results: 2802 * Invalidates changed cell. 2803 * 2804 * Side effects: 2805 * Creates/Updates entry in the cache if we are caching. 2806 * 2807 *---------------------------------------------------------------------- 2808 */ 2809static char * 2810TableVarProc(clientData, interp, name, index, flags) 2811 ClientData clientData; /* Information about table. */ 2812 Tcl_Interp *interp; /* Interpreter containing variable. */ 2813 char *name; /* Not used. */ 2814 char *index; /* Not used. */ 2815 int flags; /* Information about what happened. */ 2816{ 2817 Table *tablePtr = (Table *) clientData; 2818 int row, col, update = 1; 2819 2820 /* This is redundant, as the name should always == arrayVar */ 2821 name = tablePtr->arrayVar; 2822 2823 /* is this the whole var being destroyed or just one cell being deleted */ 2824 if ((flags & TCL_TRACE_UNSETS) && index == NULL) { 2825 /* if this isn't the interpreter being destroyed reinstate the trace */ 2826 if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) { 2827 Tcl_SetVar2(interp, name, TEST_KEY, "", TCL_GLOBAL_ONLY); 2828 Tcl_UnsetVar2(interp, name, TEST_KEY, TCL_GLOBAL_ONLY); 2829 Tcl_ResetResult(interp); 2830 2831 /* set a trace on the variable */ 2832 Tcl_TraceVar(interp, name, 2833 TCL_TRACE_WRITES | TCL_TRACE_UNSETS | TCL_GLOBAL_ONLY, 2834 (Tcl_VarTraceProc *)TableVarProc, (ClientData) tablePtr); 2835 2836 /* only do the following if arrayVar is our data source */ 2837 if (tablePtr->dataSource & DATA_ARRAY) { 2838 /* clear the selection buffer */ 2839 TableGetActiveBuf(tablePtr); 2840 /* flush any cache */ 2841 Table_ClearHashTable(tablePtr->cache); 2842 Tcl_InitHashTable(tablePtr->cache, TCL_STRING_KEYS); 2843 /* and invalidate the table */ 2844 TableInvalidateAll(tablePtr, 0); 2845 } 2846 } 2847 return (char *)NULL; 2848 } 2849 /* only continue if arrayVar is our data source */ 2850 if (!(tablePtr->dataSource & DATA_ARRAY)) { 2851 return (char *)NULL; 2852 } 2853 /* get the cell address and invalidate that region only. 2854 * Make sure that it is a valid cell address. */ 2855 if (STREQ("active", index)) { 2856 if (tablePtr->flags & SET_ACTIVE) { 2857 /* If we are already setting the active cell, the update 2858 * will occur in other code */ 2859 update = 0; 2860 } else { 2861 /* modified TableGetActiveBuf */ 2862 CONST char *data = ""; 2863 2864 row = tablePtr->activeRow; 2865 col = tablePtr->activeCol; 2866 if (tablePtr->flags & HAS_ACTIVE) 2867 data = Tcl_GetVar2(interp, name, index, TCL_GLOBAL_ONLY); 2868 if (!data) data = ""; 2869 2870 if (STREQ(tablePtr->activeBuf, data)) { 2871 return (char *)NULL; 2872 } 2873 tablePtr->activeBuf = (char *)ckrealloc(tablePtr->activeBuf, 2874 strlen(data)+1); 2875 strcpy(tablePtr->activeBuf, data); 2876 /* set cursor to the last char */ 2877 TableGetIcursor(tablePtr, "end", (int *)0); 2878 tablePtr->flags |= TEXT_CHANGED; 2879 } 2880 } else if (TableParseArrayIndex(&row, &col, index) == 2) { 2881 char buf[INDEX_BUFSIZE]; 2882 2883 /* Make sure it won't trigger on array(2,3extrastuff) */ 2884 TableMakeArrayIndex(row, col, buf); 2885 if (strcmp(buf, index)) { 2886 return (char *)NULL; 2887 } 2888 if (tablePtr->caching) { 2889 Tcl_HashEntry *entryPtr; 2890 int new; 2891 char *val, *data; 2892 2893 entryPtr = Tcl_CreateHashEntry(tablePtr->cache, buf, &new); 2894 if (!new) { 2895 data = (char *) Tcl_GetHashValue(entryPtr); 2896 if (data) { ckfree(data); } 2897 } 2898 data = (char *) Tcl_GetVar2(interp, name, index, TCL_GLOBAL_ONLY); 2899 if (data && *data != '\0') { 2900 val = (char *)ckalloc(strlen(data)+1); 2901 strcpy(val, data); 2902 } else { 2903 val = NULL; 2904 } 2905 Tcl_SetHashValue(entryPtr, val); 2906 } 2907 /* convert index to real coords */ 2908 row -= tablePtr->rowOffset; 2909 col -= tablePtr->colOffset; 2910 /* did the active cell just update */ 2911 if (row == tablePtr->activeRow && col == tablePtr->activeCol) { 2912 TableGetActiveBuf(tablePtr); 2913 } 2914 /* Flash the cell */ 2915 TableAddFlash(tablePtr, row, col); 2916 } else { 2917 return (char *)NULL; 2918 } 2919 2920 if (update) { 2921 TableRefresh(tablePtr, row, col, CELL); 2922 } 2923 2924 return (char *)NULL; 2925} 2926 2927/* 2928 *---------------------------------------------------------------------- 2929 * 2930 * TableGeometryRequest -- 2931 * This procedure is invoked to request a new geometry from Tk. 2932 * 2933 * Results: 2934 * None. 2935 * 2936 * Side effects: 2937 * Geometry information is updated and a new requested size is 2938 * registered for the widget. Internal border info is also set. 2939 * 2940 *---------------------------------------------------------------------- 2941 */ 2942void 2943TableGeometryRequest(tablePtr) 2944 register Table *tablePtr; 2945{ 2946 int x, y; 2947 2948 /* Do the geometry request 2949 * If -width #cols was not specified or it is greater than the real 2950 * number of cols, use maxWidth as a lower bound, with the other lower 2951 * bound being the upper bound of the window's user-set width and the 2952 * value of -maxwidth set by the programmer 2953 * Vice versa for rows/height 2954 */ 2955 x = MIN((tablePtr->maxReqCols==0 || tablePtr->maxReqCols > tablePtr->cols)? 2956 tablePtr->maxWidth : tablePtr->colStarts[tablePtr->maxReqCols], 2957 tablePtr->maxReqWidth) + 2*tablePtr->highlightWidth; 2958 y = MIN((tablePtr->maxReqRows==0 || tablePtr->maxReqRows > tablePtr->rows)? 2959 tablePtr->maxHeight : tablePtr->rowStarts[tablePtr->maxReqRows], 2960 tablePtr->maxReqHeight) + 2*tablePtr->highlightWidth; 2961 Tk_GeometryRequest(tablePtr->tkwin, x, y); 2962} 2963 2964/* 2965 *---------------------------------------------------------------------- 2966 * 2967 * TableAdjustActive -- 2968 * This procedure is called by AdjustParams and CMD_ACTIVATE to 2969 * move the active cell. 2970 * 2971 * Results: 2972 * Old and new active cell indices will be invalidated. 2973 * 2974 * Side effects: 2975 * If the old active cell index was edited, it will be saved. 2976 * The active buffer will be updated. 2977 * 2978 *---------------------------------------------------------------------- 2979 */ 2980void 2981TableAdjustActive(tablePtr) 2982 register Table *tablePtr; /* Widget record for table */ 2983{ 2984 if (tablePtr->flags & HAS_ACTIVE) { 2985 /* 2986 * Make sure the active cell has a reasonable real index 2987 */ 2988 CONSTRAIN(tablePtr->activeRow, 0, tablePtr->rows-1); 2989 CONSTRAIN(tablePtr->activeCol, 0, tablePtr->cols-1); 2990 } 2991 2992 /* 2993 * Check the new value of active cell against the original, 2994 * Only invalidate if it changed. 2995 */ 2996 if (tablePtr->oldActRow == tablePtr->activeRow && 2997 tablePtr->oldActCol == tablePtr->activeCol) { 2998 return; 2999 } 3000 3001 if (tablePtr->oldActRow >= 0 && tablePtr->oldActCol >= 0) { 3002 /* 3003 * Set the value of the old active cell to the active buffer 3004 * SetCellValue will check if the value actually changed 3005 */ 3006 if (tablePtr->flags & TEXT_CHANGED) { 3007 /* WARNING an outside trace will be triggered here and if it 3008 * calls something that causes TableAdjustParams to be called 3009 * again, we are in data consistency trouble */ 3010 /* HACK - turn TEXT_CHANGED off now to possibly avoid the 3011 * above data inconsistency problem. */ 3012 tablePtr->flags &= ~TEXT_CHANGED; 3013 TableSetCellValue(tablePtr, 3014 tablePtr->oldActRow + tablePtr->rowOffset, 3015 tablePtr->oldActCol + tablePtr->colOffset, 3016 tablePtr->activeBuf); 3017 } 3018 /* 3019 * Invalidate the old active cell 3020 */ 3021 TableRefresh(tablePtr, tablePtr->oldActRow, tablePtr->oldActCol, CELL); 3022 } 3023 3024 /* 3025 * Store the new active cell value into the active buffer 3026 */ 3027 TableGetActiveBuf(tablePtr); 3028 3029 /* 3030 * Invalidate the new active cell 3031 */ 3032 TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); 3033 3034 /* 3035 * Cache the old active row/col for the next time this is called 3036 */ 3037 tablePtr->oldActRow = tablePtr->activeRow; 3038 tablePtr->oldActCol = tablePtr->activeCol; 3039} 3040 3041/* 3042 *---------------------------------------------------------------------- 3043 * 3044 * TableAdjustParams -- 3045 * Calculate the row and column starts. Adjusts the topleft corner 3046 * variable to keep it within the screen range, out of the titles 3047 * and keep the screen full make sure the selected cell is in the 3048 * visible area checks to see if the top left cell has changed at 3049 * all and invalidates the table if it has. 3050 * 3051 * Results: 3052 * None. 3053 * 3054 * Side Effects: 3055 * Number of rows can change if -rowstretchmode == fill. 3056 * topRow && leftCol can change to fit display. 3057 * activeRow/Col can change to ensure it is a valid cell. 3058 * 3059 *---------------------------------------------------------------------- 3060 */ 3061void 3062TableAdjustParams(register Table *tablePtr) 3063{ 3064 int topRow, leftCol, row, col, total, i, value, x, y, width, height, 3065 w, h, hl, px, py, recalc, bd[4], 3066 diff, unpreset, lastUnpreset, pad, lastPad, numPixels, 3067 defColWidth, defRowHeight; 3068 Tcl_HashEntry *entryPtr; 3069 3070 /* 3071 * Cache some values for many upcoming calculations 3072 */ 3073 hl = tablePtr->highlightWidth; 3074 w = Tk_Width(tablePtr->tkwin) - (2 * hl); 3075 h = Tk_Height(tablePtr->tkwin) - (2 * hl); 3076 TableGetTagBorders(&(tablePtr->defaultTag), 3077 &bd[0], &bd[1], &bd[2], &bd[3]); 3078 px = bd[0] + bd[1] + (2 * tablePtr->padX); 3079 py = bd[2] + bd[3] + (2 * tablePtr->padY); 3080 3081 /* 3082 * Account for whether default dimensions are in chars (>0) or 3083 * pixels (<=0). Border and Pad space is added in here for convenience. 3084 * 3085 * When a value in pixels is specified, we take that exact amount, 3086 * not adding in padding. 3087 */ 3088 if (tablePtr->defColWidth > 0) { 3089 defColWidth = tablePtr->charWidth * tablePtr->defColWidth + px; 3090 } else { 3091 defColWidth = -(tablePtr->defColWidth); 3092 } 3093 if (tablePtr->defRowHeight > 0) { 3094 defRowHeight = tablePtr->charHeight * tablePtr->defRowHeight + py; 3095 } else { 3096 defRowHeight = -(tablePtr->defRowHeight); 3097 } 3098 3099 /* 3100 * Set up the arrays to hold the col pixels and starts. 3101 * ckrealloc was fixed in 8.2.1 to handle NULLs, so we can't rely on it. 3102 */ 3103 if (tablePtr->colPixels) ckfree((char *) tablePtr->colPixels); 3104 tablePtr->colPixels = (int *) ckalloc(tablePtr->cols * sizeof(int)); 3105 if (tablePtr->colStarts) ckfree((char *) tablePtr->colStarts); 3106 tablePtr->colStarts = (int *) ckalloc((tablePtr->cols+1) * sizeof(int)); 3107 3108 /* 3109 * Get all the preset columns and set their widths 3110 */ 3111 lastUnpreset = 0; 3112 numPixels = 0; 3113 unpreset = 0; 3114 for (i = 0; i < tablePtr->cols; i++) { 3115 entryPtr = Tcl_FindHashEntry(tablePtr->colWidths, (char *) i); 3116 if (entryPtr == NULL) { 3117 tablePtr->colPixels[i] = -1; 3118 unpreset++; 3119 lastUnpreset = i; 3120 } else { 3121 value = (int) Tcl_GetHashValue(entryPtr); 3122 if (value > 0) { 3123 tablePtr->colPixels[i] = value * tablePtr->charWidth + px; 3124 } else { 3125 /* 3126 * When a value in pixels is specified, we take that exact 3127 * amount, not adding in pad or border values. 3128 */ 3129 tablePtr->colPixels[i] = -value; 3130 } 3131 numPixels += tablePtr->colPixels[i]; 3132 } 3133 } 3134 3135 /* 3136 * Work out how much to pad each col depending on the mode. 3137 */ 3138 diff = w - numPixels - (unpreset * defColWidth); 3139 total = 0; 3140 3141 /* 3142 * Now do the padding and calculate the column starts. 3143 * Diff lower than 0 means we can't see the entire set of columns, 3144 * thus no special stretching will occur & we optimize the calculation. 3145 */ 3146 if (diff <= 0) { 3147 for (i = 0; i < tablePtr->cols; i++) { 3148 if (tablePtr->colPixels[i] == -1) { 3149 tablePtr->colPixels[i] = defColWidth; 3150 } 3151 tablePtr->colStarts[i] = total; 3152 total += tablePtr->colPixels[i]; 3153 } 3154 } else { 3155 switch (tablePtr->colStretch) { 3156 case STRETCH_MODE_NONE: 3157 pad = 0; 3158 lastPad = 0; 3159 break; 3160 case STRETCH_MODE_UNSET: 3161 if (unpreset == 0) { 3162 pad = 0; 3163 lastPad = 0; 3164 } else { 3165 pad = diff / unpreset; 3166 lastPad = diff - pad * (unpreset - 1); 3167 } 3168 break; 3169 case STRETCH_MODE_LAST: 3170 pad = 0; 3171 lastPad = diff; 3172 lastUnpreset = tablePtr->cols - 1; 3173 break; 3174 default: /* STRETCH_MODE_ALL, but also FILL for cols */ 3175 pad = diff / tablePtr->cols; 3176 /* force it to be applied to the last column too */ 3177 lastUnpreset = tablePtr->cols - 1; 3178 lastPad = diff - pad * lastUnpreset; 3179 } 3180 3181 for (i = 0; i < tablePtr->cols; i++) { 3182 if (tablePtr->colPixels[i] == -1) { 3183 tablePtr->colPixels[i] = defColWidth 3184 + ((i == lastUnpreset) ? lastPad : pad); 3185 } else if (tablePtr->colStretch == STRETCH_MODE_ALL) { 3186 tablePtr->colPixels[i] += (i == lastUnpreset) ? lastPad : pad; 3187 } 3188 tablePtr->colStarts[i] = total; 3189 total += tablePtr->colPixels[i]; 3190 } 3191 } 3192 tablePtr->colStarts[i] = tablePtr->maxWidth = total; 3193 3194 /* 3195 * The 'do' loop is only necessary for rows because of FILL mode 3196 */ 3197 recalc = 0; 3198 do { 3199 /* Set up the arrays to hold the row pixels and starts */ 3200 /* FIX - this can be moved outside 'do' if you check >row size */ 3201 if (tablePtr->rowPixels) ckfree((char *) tablePtr->rowPixels); 3202 tablePtr->rowPixels = (int *) ckalloc(tablePtr->rows * sizeof(int)); 3203 3204 /* get all the preset rows and set their heights */ 3205 lastUnpreset = 0; 3206 numPixels = 0; 3207 unpreset = 0; 3208 for (i = 0; i < tablePtr->rows; i++) { 3209 entryPtr = Tcl_FindHashEntry(tablePtr->rowHeights, (char *) i); 3210 if (entryPtr == NULL) { 3211 tablePtr->rowPixels[i] = -1; 3212 unpreset++; 3213 lastUnpreset = i; 3214 } else { 3215 value = (int) Tcl_GetHashValue(entryPtr); 3216 if (value > 0) { 3217 tablePtr->rowPixels[i] = value * tablePtr->charHeight + py; 3218 } else { 3219 /* 3220 * When a value in pixels is specified, we take that exact 3221 * amount, not adding in pad or border values. 3222 */ 3223 tablePtr->rowPixels[i] = -value; 3224 } 3225 numPixels += tablePtr->rowPixels[i]; 3226 } 3227 } 3228 3229 /* work out how much to pad each row depending on the mode */ 3230 diff = h - numPixels - (unpreset * defRowHeight); 3231 switch(tablePtr->rowStretch) { 3232 case STRETCH_MODE_NONE: 3233 pad = 0; 3234 lastPad = 0; 3235 break; 3236 case STRETCH_MODE_UNSET: 3237 if (unpreset == 0) { 3238 pad = 0; 3239 lastPad = 0; 3240 } else { 3241 pad = MAX(0,diff) / unpreset; 3242 lastPad = MAX(0,diff) - pad * (unpreset - 1); 3243 } 3244 break; 3245 case STRETCH_MODE_LAST: 3246 pad = 0; 3247 lastPad = MAX(0,diff); 3248 /* force it to be applied to the last column too */ 3249 lastUnpreset = tablePtr->rows - 1; 3250 break; 3251 case STRETCH_MODE_FILL: 3252 pad = 0; 3253 lastPad = diff; 3254 if (diff && !recalc) { 3255 tablePtr->rows += (diff/defRowHeight); 3256 if (diff < 0 && tablePtr->rows <= 0) { 3257 tablePtr->rows = 1; 3258 } 3259 lastUnpreset = tablePtr->rows - 1; 3260 recalc = 1; 3261 continue; 3262 } else { 3263 lastUnpreset = tablePtr->rows - 1; 3264 recalc = 0; 3265 } 3266 break; 3267 default: /* STRETCH_MODE_ALL */ 3268 pad = MAX(0,diff) / tablePtr->rows; 3269 /* force it to be applied to the last column too */ 3270 lastUnpreset = tablePtr->rows - 1; 3271 lastPad = MAX(0,diff) - pad * lastUnpreset; 3272 } 3273 } while (recalc); 3274 3275 if (tablePtr->rowStarts) ckfree((char *) tablePtr->rowStarts); 3276 tablePtr->rowStarts = (int *) ckalloc((tablePtr->rows+1)*sizeof(int)); 3277 /* 3278 * Now do the padding and calculate the row starts 3279 */ 3280 total = 0; 3281 for (i = 0; i < tablePtr->rows; i++) { 3282 if (tablePtr->rowPixels[i] == -1) { 3283 tablePtr->rowPixels[i] = defRowHeight 3284 + ((i==lastUnpreset)?lastPad:pad); 3285 } else if (tablePtr->rowStretch == STRETCH_MODE_ALL) { 3286 tablePtr->rowPixels[i] += (i==lastUnpreset)?lastPad:pad; 3287 } 3288 /* calculate the start of each row */ 3289 tablePtr->rowStarts[i] = total; 3290 total += tablePtr->rowPixels[i]; 3291 } 3292 tablePtr->rowStarts[i] = tablePtr->maxHeight = total; 3293 3294 /* 3295 * Make sure the top row and col have reasonable real indices 3296 */ 3297 CONSTRAIN(tablePtr->topRow, tablePtr->titleRows, tablePtr->rows-1); 3298 CONSTRAIN(tablePtr->leftCol, tablePtr->titleCols, tablePtr->cols-1); 3299 3300 /* 3301 * If we don't have the info, don't bother to fix up the other parameters 3302 */ 3303 if (Tk_WindowId(tablePtr->tkwin) == None) { 3304 tablePtr->oldTopRow = tablePtr->oldLeftCol = -1; 3305 return; 3306 } 3307 3308 topRow = tablePtr->topRow; 3309 leftCol = tablePtr->leftCol; 3310 w += hl; 3311 h += hl; 3312 /* 3313 * If we use this value of topRow, will we fill the window? 3314 * if not, decrease it until we will, or until it gets to titleRows 3315 * make sure we don't cut off the bottom row 3316 */ 3317 for (; topRow > tablePtr->titleRows; topRow--) { 3318 if ((tablePtr->maxHeight-(tablePtr->rowStarts[topRow-1] - 3319 tablePtr->rowStarts[tablePtr->titleRows])) > h) { 3320 break; 3321 } 3322 } 3323 /* 3324 * If we use this value of topCol, will we fill the window? 3325 * if not, decrease it until we will, or until it gets to titleCols 3326 * make sure we don't cut off the left column 3327 */ 3328 for (; leftCol > tablePtr->titleCols; leftCol--) { 3329 if ((tablePtr->maxWidth-(tablePtr->colStarts[leftCol-1] - 3330 tablePtr->colStarts[tablePtr->titleCols])) > w) { 3331 break; 3332 } 3333 } 3334 3335 tablePtr->topRow = topRow; 3336 tablePtr->leftCol = leftCol; 3337 3338 /* 3339 * Now work out where the bottom right is for scrollbar update and to test 3340 * for one last stretch. Avoid the confusion that spans could cause for 3341 * determining the last cell dimensions. 3342 */ 3343 tablePtr->flags |= AVOID_SPANS; 3344 TableGetLastCell(tablePtr, &row, &col); 3345 TableCellVCoords(tablePtr, row, col, &x, &y, &width, &height, 0); 3346 tablePtr->flags &= ~AVOID_SPANS; 3347 3348 /* 3349 * Do we have scrollbars, if so, calculate and call the TCL functions In 3350 * order to get the scrollbar to be completely full when the whole screen 3351 * is shown and there are titles, we have to arrange for the scrollbar 3352 * range to be 0 -> rows-titleRows etc. This leads to the position 3353 * setting methods, toprow and leftcol, being relative to the titles, not 3354 * absolute row and column numbers. 3355 */ 3356 if (tablePtr->yScrollCmd != NULL || tablePtr->xScrollCmd != NULL) { 3357 Tcl_Interp *interp = tablePtr->interp; 3358 char buf[INDEX_BUFSIZE]; 3359 double first, last; 3360 3361 /* 3362 * We must hold onto the interpreter because the data referred to at 3363 * tablePtr might be freed as a result of the call to Tcl_VarEval. 3364 */ 3365 Tcl_Preserve((ClientData) interp); 3366 3367 /* Do we have a Y-scrollbar and rows to scroll? */ 3368 if (tablePtr->yScrollCmd != NULL) { 3369 if (row < tablePtr->titleRows) { 3370 first = 0; 3371 last = 1; 3372 } else { 3373 diff = tablePtr->rowStarts[tablePtr->titleRows]; 3374 last = (double) (tablePtr->rowStarts[tablePtr->rows]-diff); 3375 if (last <= 0.0) { 3376 first = 0; 3377 last = 1; 3378 } else { 3379 first = (tablePtr->rowStarts[topRow]-diff) / last; 3380 last = (height+tablePtr->rowStarts[row]-diff) / last; 3381 } 3382 } 3383 sprintf(buf, " %g %g", first, last); 3384 if (Tcl_VarEval(interp, tablePtr->yScrollCmd, 3385 buf, (char *)NULL) != TCL_OK) { 3386 Tcl_AddErrorInfo(interp, 3387 "\n\t(vertical scrolling command executed by table)"); 3388 Tcl_BackgroundError(interp); 3389 } 3390 } 3391 /* Do we have a X-scrollbar and cols to scroll? */ 3392 if (tablePtr->xScrollCmd != NULL) { 3393 if (col < tablePtr->titleCols) { 3394 first = 0; 3395 last = 1; 3396 } else { 3397 diff = tablePtr->colStarts[tablePtr->titleCols]; 3398 last = (double) (tablePtr->colStarts[tablePtr->cols]-diff); 3399 if (last <= 0.0) { 3400 first = 0; 3401 last = 1; 3402 } else { 3403 first = (tablePtr->colStarts[leftCol]-diff) / last; 3404 last = (width+tablePtr->colStarts[col]-diff) / last; 3405 } 3406 } 3407 sprintf(buf, " %g %g", first, last); 3408 if (Tcl_VarEval(interp, tablePtr->xScrollCmd, 3409 buf, (char *)NULL) != TCL_OK) { 3410 Tcl_AddErrorInfo(interp, 3411 "\n\t(horizontal scrolling command executed by table)"); 3412 Tcl_BackgroundError(interp); 3413 } 3414 } 3415 3416 Tcl_Release((ClientData) interp); 3417 } 3418 3419 /* 3420 * Adjust the last row/col to fill empty space if it is visible. 3421 * Do this after setting the scrollbars to not upset its calculations. 3422 */ 3423 if (row == tablePtr->rows-1 && tablePtr->rowStretch != STRETCH_MODE_NONE) { 3424 diff = h-(y+height); 3425 if (diff > 0) { 3426 tablePtr->rowPixels[tablePtr->rows-1] += diff; 3427 tablePtr->rowStarts[tablePtr->rows] += diff; 3428 } 3429 } 3430 if (col == tablePtr->cols-1 && tablePtr->colStretch != STRETCH_MODE_NONE) { 3431 diff = w-(x+width); 3432 if (diff > 0) { 3433 tablePtr->colPixels[tablePtr->cols-1] += diff; 3434 tablePtr->colStarts[tablePtr->cols] += diff; 3435 } 3436 } 3437 3438 TableAdjustActive(tablePtr); 3439 3440 /* 3441 * now check the new value of topleft cell against the originals, 3442 * If they changed, invalidate the area, else leave it alone 3443 */ 3444 if (tablePtr->topRow != tablePtr->oldTopRow || 3445 tablePtr->leftCol != tablePtr->oldLeftCol) { 3446 /* set the old top row/col for the next time this function is called */ 3447 tablePtr->oldTopRow = tablePtr->topRow; 3448 tablePtr->oldLeftCol = tablePtr->leftCol; 3449 /* only the upper corner title cells wouldn't change */ 3450 TableInvalidateAll(tablePtr, 0); 3451 } 3452} 3453 3454/* 3455 *---------------------------------------------------------------------- 3456 * 3457 * TableCursorEvent -- 3458 * Toggle the cursor status. Equivalent to EntryBlinkProc. 3459 * 3460 * Results: 3461 * None. 3462 * 3463 * Side effects: 3464 * The cursor will be switched off/on. 3465 * 3466 *---------------------------------------------------------------------- 3467 */ 3468static void 3469TableCursorEvent(ClientData clientData) 3470{ 3471 register Table *tablePtr = (Table *) clientData; 3472 3473 if (!(tablePtr->flags & HAS_FOCUS) || (tablePtr->insertOffTime == 0) 3474 || (tablePtr->flags & ACTIVE_DISABLED) 3475 || (tablePtr->state != STATE_NORMAL)) { 3476 return; 3477 } 3478 3479 if (tablePtr->cursorTimer != NULL) { 3480 Tcl_DeleteTimerHandler(tablePtr->cursorTimer); 3481 } 3482 3483 tablePtr->cursorTimer = 3484 Tcl_CreateTimerHandler((tablePtr->flags & CURSOR_ON) ? 3485 tablePtr->insertOffTime : tablePtr->insertOnTime, 3486 TableCursorEvent, (ClientData) tablePtr); 3487 3488 /* Toggle the cursor */ 3489 tablePtr->flags ^= CURSOR_ON; 3490 3491 /* invalidate the cell */ 3492 TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, CELL); 3493} 3494 3495/* 3496 *---------------------------------------------------------------------- 3497 * 3498 * TableConfigCursor -- 3499 * Configures the timer depending on the state of the table. 3500 * Equivalent to EntryFocusProc. 3501 * 3502 * Results: 3503 * None. 3504 * 3505 * Side effects: 3506 * The cursor will be switched off/on. 3507 * 3508 *---------------------------------------------------------------------- 3509 */ 3510void 3511TableConfigCursor(register Table *tablePtr) 3512{ 3513 /* 3514 * To have a cursor, we have to have focus and allow edits 3515 */ 3516 if ((tablePtr->flags & HAS_FOCUS) && (tablePtr->state == STATE_NORMAL) && 3517 !(tablePtr->flags & ACTIVE_DISABLED)) { 3518 /* 3519 * Turn the cursor ON 3520 */ 3521 if (!(tablePtr->flags & CURSOR_ON)) { 3522 tablePtr->flags |= CURSOR_ON; 3523 /* 3524 * Only refresh when we toggled cursor 3525 */ 3526 TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, 3527 CELL); 3528 } 3529 3530 /* set up the first timer */ 3531 if (tablePtr->insertOffTime != 0) { 3532 /* make sure nothing existed */ 3533 Tcl_DeleteTimerHandler(tablePtr->cursorTimer); 3534 tablePtr->cursorTimer = 3535 Tcl_CreateTimerHandler(tablePtr->insertOnTime, 3536 TableCursorEvent, (ClientData) tablePtr); 3537 } 3538 } else { 3539 /* 3540 * Turn the cursor OFF 3541 */ 3542 if ((tablePtr->flags & CURSOR_ON)) { 3543 tablePtr->flags &= ~CURSOR_ON; 3544 TableRefresh(tablePtr, tablePtr->activeRow, tablePtr->activeCol, 3545 CELL); 3546 } 3547 3548 /* and disable the timer */ 3549 if (tablePtr->cursorTimer != NULL) { 3550 Tcl_DeleteTimerHandler(tablePtr->cursorTimer); 3551 } 3552 tablePtr->cursorTimer = NULL; 3553 } 3554 3555} 3556 3557/* 3558 *---------------------------------------------------------------------- 3559 * 3560 * TableFetchSelection -- 3561 * This procedure is called back by Tk when the selection is 3562 * requested by someone. It returns part or all of the selection 3563 * in a buffer provided by the caller. 3564 * 3565 * Results: 3566 * The return value is the number of non-NULL bytes stored 3567 * at buffer. Buffer is filled (or partially filled) with a 3568 * NULL-terminated string containing part or all of the selection, 3569 * as given by offset and maxBytes. 3570 * 3571 * Side effects: 3572 * None. 3573 * 3574 *---------------------------------------------------------------------- 3575 */ 3576 3577static int 3578TableFetchSelection(clientData, offset, buffer, maxBytes) 3579 ClientData clientData; /* Information about table widget. */ 3580 int offset; /* Offset within selection of first 3581 * character to be returned. */ 3582 char *buffer; /* Location in which to place selection. */ 3583 int maxBytes; /* Maximum number of bytes to place at buffer, 3584 * not including terminating NULL. */ 3585{ 3586 register Table *tablePtr = (Table *) clientData; 3587 Tcl_Interp *interp = tablePtr->interp; 3588 char *value, *data, *rowsep = tablePtr->rowSep, *colsep = tablePtr->colSep; 3589 Tcl_HashEntry *entryPtr; 3590 Tcl_HashSearch search; 3591 int length, count, lastrow=0, needcs=0, r, c, listArgc, rslen=0, cslen=0; 3592 int numcols, numrows; 3593 CONST84 char **listArgv; 3594 3595 /* 3596 * We keep a static selection around so we don't have to remake the 3597 * selection if we are getting the selection in chunks (i.e. offset != 0). 3598 * Not thread-safe, but selection happens sequentially in practice. 3599 * Otherwise could move them to per-table, but then more cleanup and 3600 * tracking is needed (flag bit + extended table struct). 3601 */ 3602 static int haveSelection = 0; 3603 static Tcl_DString selection; 3604 3605 /* if we are not exporting the selection || 3606 * we have no data source, return */ 3607 if (!tablePtr->exportSelection || 3608 (tablePtr->dataSource == DATA_NONE)) { 3609 return -1; 3610 } 3611 3612 if ((offset == 0) || !haveSelection) { 3613 /* First Time thru, get the selection, otherwise, just use the 3614 * selection obtained before */ 3615 3616 if (haveSelection) { 3617 /* If we have fetched a selection before, free it */ 3618 Tcl_DStringFree(&selection); 3619 } 3620 haveSelection = 1; 3621 3622 /* First get a sorted list of the selected elements */ 3623 Tcl_DStringInit(&selection); 3624 for (entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search); 3625 entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { 3626 Tcl_DStringAppendElement(&selection, 3627 Tcl_GetHashKey(tablePtr->selCells, entryPtr)); 3628 } 3629 value = TableCellSort(tablePtr, Tcl_DStringValue(&selection)); 3630 Tcl_DStringFree(&selection); 3631 3632 if (value == NULL || 3633 Tcl_SplitList(interp, value, &listArgc, &listArgv) != TCL_OK) { 3634 return -1; 3635 } 3636 Tcl_Free(value); 3637 3638 Tcl_DStringInit(&selection); 3639 rslen = (rowsep?(strlen(rowsep)):0); 3640 cslen = (colsep?(strlen(colsep)):0); 3641 numrows = numcols = 0; 3642 for (count = 0; count < listArgc; count++) { 3643 TableParseArrayIndex(&r, &c, listArgv[count]); 3644 if (count) { 3645 if (lastrow != r) { 3646 lastrow = r; 3647 needcs = 0; 3648 if (rslen) { 3649 Tcl_DStringAppend(&selection, rowsep, rslen); 3650 } else { 3651 Tcl_DStringEndSublist(&selection); 3652 Tcl_DStringStartSublist(&selection); 3653 } 3654 ++numrows; 3655 } else { 3656 if (++needcs > numcols) 3657 numcols = needcs; 3658 } 3659 } else { 3660 lastrow = r; 3661 needcs = 0; 3662 if (!rslen) { 3663 Tcl_DStringStartSublist(&selection); 3664 } 3665 } 3666 data = TableGetCellValue(tablePtr, r, c); 3667 if (cslen) { 3668 if (needcs) { 3669 Tcl_DStringAppend(&selection, colsep, cslen); 3670 } 3671 Tcl_DStringAppend(&selection, data, -1); 3672 } else { 3673 Tcl_DStringAppendElement(&selection, data); 3674 } 3675 } 3676 if (!rslen && count) { 3677 Tcl_DStringEndSublist(&selection); 3678 } 3679 Tcl_Free((char *) listArgv); 3680 3681 if (tablePtr->selCmd != NULL) { 3682 Tcl_DString script; 3683 Tcl_DStringInit(&script); 3684 ExpandPercents(tablePtr, tablePtr->selCmd, numrows+1, numcols+1, 3685 Tcl_DStringValue(&selection), (char *)NULL, 3686 listArgc, &script, CMD_ACTIVATE); 3687 if (Tcl_EvalEx(interp, Tcl_DStringValue(&script), -1, 3688 TCL_EVAL_GLOBAL) == TCL_ERROR) { 3689 Tcl_AddErrorInfo(interp, 3690 "\n (error in table selection command)"); 3691 Tcl_BackgroundError(interp); 3692 Tcl_DStringFree(&script); 3693 Tcl_DStringFree(&selection); 3694 haveSelection = 0; 3695 return -1; 3696 } else { 3697 Tcl_DStringGetResult(interp, &selection); 3698 } 3699 Tcl_DStringFree(&script); 3700 } 3701 } 3702 length = Tcl_DStringLength(&selection); 3703 3704 if (length == 0) { 3705 return -1; 3706 } 3707 3708 /* Copy the requested portion of the selection to the buffer. */ 3709 count = length - offset; 3710 if (count <= 0) { 3711 count = 0; 3712 } else { 3713 if (count > maxBytes) { 3714 count = maxBytes; 3715 } 3716 memcpy((VOID *) buffer, 3717 (VOID *) (Tcl_DStringValue(&selection) + offset), 3718 (size_t) count); 3719 } 3720 buffer[count] = '\0'; 3721 if (count < maxBytes) { 3722 /* 3723 * This should be the last call in this range, so free selection now. 3724 */ 3725 Tcl_DStringFree(&selection); 3726 haveSelection = 0; 3727 } 3728 return count; 3729} 3730 3731/* 3732 *---------------------------------------------------------------------- 3733 * 3734 * TableLostSelection -- 3735 * This procedure is called back by Tk when the selection is 3736 * grabbed away from a table widget. 3737 * 3738 * Results: 3739 * None. 3740 * 3741 * Side effects: 3742 * The existing selection is unhighlighted, and the window is 3743 * marked as not containing a selection. 3744 * 3745 *---------------------------------------------------------------------- 3746 */ 3747void 3748TableLostSelection(clientData) 3749 ClientData clientData; /* Information about table widget. */ 3750{ 3751 register Table *tablePtr = (Table *) clientData; 3752 3753 if (tablePtr->exportSelection) { 3754 Tcl_HashEntry *entryPtr; 3755 Tcl_HashSearch search; 3756 int row, col; 3757 3758 /* Same as SEL CLEAR ALL */ 3759 for (entryPtr = Tcl_FirstHashEntry(tablePtr->selCells, &search); 3760 entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&search)) { 3761 TableParseArrayIndex(&row, &col, 3762 Tcl_GetHashKey(tablePtr->selCells,entryPtr)); 3763 Tcl_DeleteHashEntry(entryPtr); 3764 TableRefresh(tablePtr, row-tablePtr->rowOffset, 3765 col-tablePtr->colOffset, CELL); 3766 } 3767 } 3768} 3769 3770/* 3771 *---------------------------------------------------------------------- 3772 * 3773 * TableRestrictProc -- 3774 * A Tk_RestrictProc used by TableValidateChange to eliminate any 3775 * extra key input events in the event queue that 3776 * have a serial number no less than a given value. 3777 * 3778 * Results: 3779 * Returns either TK_DISCARD_EVENT or TK_DEFER_EVENT. 3780 * 3781 * Side effects: 3782 * None. 3783 * 3784 *---------------------------------------------------------------------- 3785 */ 3786static Tk_RestrictAction 3787TableRestrictProc(serial, eventPtr) 3788 ClientData serial; 3789 XEvent *eventPtr; 3790{ 3791 if ((eventPtr->type == KeyRelease || eventPtr->type == KeyPress) && 3792 ((eventPtr->xany.serial-(unsigned int)serial) > 0)) { 3793 return TK_DEFER_EVENT; 3794 } else { 3795 return TK_PROCESS_EVENT; 3796 } 3797} 3798 3799/* 3800 *-------------------------------------------------------------- 3801 * 3802 * TableValidateChange -- 3803 * This procedure is invoked when any character is added or 3804 * removed from the table widget, or a set has triggered validation. 3805 * 3806 * Results: 3807 * TCL_OK if the validatecommand accepts the new string, 3808 * TCL_BREAK if the validatecommand rejects the new string, 3809 * TCL_ERROR if any problems occured with validatecommand. 3810 * 3811 * Side effects: 3812 * The insertion/deletion may be aborted, and the 3813 * validatecommand might turn itself off (if an error 3814 * or loop condition arises). 3815 * 3816 *-------------------------------------------------------------- 3817 */ 3818int 3819TableValidateChange(tablePtr, r, c, old, new, index) 3820 register Table *tablePtr; /* Table that needs validation. */ 3821 int r, c; /* row,col index of cell in user coords */ 3822 char *old; /* current value of cell */ 3823 char *new; /* potential new value of cell */ 3824 int index; /* index of insert/delete, -1 otherwise */ 3825{ 3826 register Tcl_Interp *interp = tablePtr->interp; 3827 int code, bool; 3828 Tk_RestrictProc *rstrct; 3829 ClientData cdata; 3830 Tcl_DString script; 3831 3832 if (tablePtr->valCmd == NULL || tablePtr->validate == 0) { 3833 return TCL_OK; 3834 } 3835 3836 /* Magic code to make this bit of code UI synchronous in the face of 3837 * possible new key events */ 3838 XSync(tablePtr->display, False); 3839 rstrct = Tk_RestrictEvents(TableRestrictProc, (ClientData) 3840 NextRequest(tablePtr->display), &cdata); 3841 3842 /* 3843 * If we're already validating, then we're hitting a loop condition 3844 * Return and set validate to 0 to disallow further validations 3845 * and prevent current validation from finishing 3846 */ 3847 if (tablePtr->flags & VALIDATING) { 3848 tablePtr->validate = 0; 3849 return TCL_OK; 3850 } 3851 tablePtr->flags |= VALIDATING; 3852 3853 /* Now form command string and run through the -validatecommand */ 3854 Tcl_DStringInit(&script); 3855 ExpandPercents(tablePtr, tablePtr->valCmd, r, c, old, new, index, &script, 3856 CMD_VALIDATE); 3857 code = Tcl_GlobalEval(tablePtr->interp, Tcl_DStringValue(&script)); 3858 Tcl_DStringFree(&script); 3859 3860 if (code != TCL_OK && code != TCL_RETURN) { 3861 Tcl_AddErrorInfo(interp, 3862 "\n\t(in validation command executed by table)"); 3863 Tcl_BackgroundError(interp); 3864 code = TCL_ERROR; 3865 } else if (Tcl_GetBooleanFromObj(interp, Tcl_GetObjResult(interp), 3866 &bool) != TCL_OK) { 3867 Tcl_AddErrorInfo(interp, 3868 "\n\tboolean not returned by validation command"); 3869 Tcl_BackgroundError(interp); 3870 code = TCL_ERROR; 3871 } else { 3872 code = (bool) ? TCL_OK : TCL_BREAK; 3873 } 3874 Tcl_SetObjResult(interp, Tcl_NewObj()); 3875 3876 /* 3877 * If ->validate has become VALIDATE_NONE during the validation, 3878 * it means that a loop condition almost occured. Do not allow 3879 * this validation result to finish. 3880 */ 3881 if (tablePtr->validate == 0) { 3882 code = TCL_ERROR; 3883 } 3884 3885 /* If validate will return ERROR, then disallow further validations */ 3886 if (code == TCL_ERROR) { 3887 tablePtr->validate = 0; 3888 } 3889 3890 Tk_RestrictEvents(rstrct, cdata, &cdata); 3891 tablePtr->flags &= ~VALIDATING; 3892 3893 return code; 3894} 3895 3896/* 3897 *-------------------------------------------------------------- 3898 * 3899 * ExpandPercents -- 3900 * Given a command and an event, produce a new command 3901 * by replacing % constructs in the original command 3902 * with information from the X event. 3903 * 3904 * Results: 3905 * The new expanded command is appended to the dynamic string 3906 * given by dsPtr. 3907 * 3908 * Side effects: 3909 * None. 3910 * 3911 *-------------------------------------------------------------- 3912 */ 3913void 3914ExpandPercents(tablePtr, before, r, c, old, new, index, dsPtr, cmdType) 3915 Table *tablePtr; /* Table that needs validation. */ 3916 char *before; /* Command containing percent 3917 * expressions to be replaced. */ 3918 int r, c; /* row,col index of cell */ 3919 char *old; /* current value of cell */ 3920 char *new; /* potential new value of cell */ 3921 int index; /* index of insert/delete */ 3922 Tcl_DString *dsPtr; /* Dynamic string in which to append 3923 * new command. */ 3924 int cmdType; /* type of command to make %-subs for */ 3925{ 3926 int length, spaceNeeded, cvtFlags; 3927#ifdef TCL_UTF_MAX 3928 Tcl_UniChar ch; 3929#else 3930 char ch; 3931#endif 3932 char *string, buf[INDEX_BUFSIZE]; 3933 3934 /* This returns the static value of the string as set in the array */ 3935 if (old == NULL && cmdType == CMD_VALIDATE) { 3936 old = TableGetCellValue(tablePtr, r, c); 3937 } 3938 3939 while (1) { 3940 if (*before == '\0') { 3941 break; 3942 } 3943 /* 3944 * Find everything up to the next % character and append it 3945 * to the result string. 3946 */ 3947 3948 string = before; 3949#ifdef TCL_UTF_MAX 3950 /* No need to convert '%', as it is in ascii range */ 3951 string = (char *) Tcl_UtfFindFirst(before, '%'); 3952#else 3953 string = strchr(before, '%'); 3954#endif 3955 if (string == (char *) NULL) { 3956 Tcl_DStringAppend(dsPtr, before, -1); 3957 break; 3958 } else if (string != before) { 3959 Tcl_DStringAppend(dsPtr, before, string-before); 3960 before = string; 3961 } 3962 3963 /* 3964 * There's a percent sequence here. Process it. 3965 */ 3966 3967 before++; /* skip over % */ 3968 if (*before != '\0') { 3969#ifdef TCL_UTF_MAX 3970 before += Tcl_UtfToUniChar(before, &ch); 3971#else 3972 ch = before[0]; 3973 before++; 3974#endif 3975 } else { 3976 ch = '%'; 3977 } 3978 switch (ch) { 3979 case 'c': 3980 sprintf(buf, "%d", c); 3981 string = buf; 3982 break; 3983 case 'C': /* index of cell */ 3984 TableMakeArrayIndex(r, c, buf); 3985 string = buf; 3986 break; 3987 case 'r': 3988 sprintf(buf, "%d", r); 3989 string = buf; 3990 break; 3991 case 'i': /* index of cursor OR |number| of cells selected */ 3992 sprintf(buf, "%d", index); 3993 string = buf; 3994 break; 3995 case 's': /* Current cell value */ 3996 string = old; 3997 break; 3998 case 'S': /* Potential new value of cell */ 3999 string = (new?new:old); 4000 break; 4001 case 'W': /* widget name */ 4002 string = Tk_PathName(tablePtr->tkwin); 4003 break; 4004 default: 4005#ifdef TCL_UTF_MAX 4006 length = Tcl_UniCharToUtf(ch, buf); 4007#else 4008 buf[0] = ch; 4009 length = 1; 4010#endif 4011 buf[length] = '\0'; 4012 string = buf; 4013 break; 4014 } 4015 4016 spaceNeeded = Tcl_ScanElement(string, &cvtFlags); 4017 length = Tcl_DStringLength(dsPtr); 4018 Tcl_DStringSetLength(dsPtr, length + spaceNeeded); 4019 spaceNeeded = Tcl_ConvertElement(string, 4020 Tcl_DStringValue(dsPtr) + length, 4021 cvtFlags | TCL_DONT_USE_BRACES); 4022 Tcl_DStringSetLength(dsPtr, length + spaceNeeded); 4023 } 4024 Tcl_DStringAppend(dsPtr, "", 1); 4025} 4026 4027/* Function to call on loading the Table module */ 4028 4029#ifdef BUILD_Tktable 4030# undef TCL_STORAGE_CLASS 4031# define TCL_STORAGE_CLASS DLLEXPORT 4032#endif 4033#ifdef MAC_TCL 4034#pragma export on 4035#endif 4036EXTERN int 4037Tktable_Init(interp) 4038 Tcl_Interp *interp; 4039{ 4040 /* This defines the static chars tkTable(Safe)InitScript */ 4041#include "tkTableInitScript.h" 4042 4043 if ( 4044#ifdef USE_TCL_STUBS 4045 Tcl_InitStubs(interp, "8.0", 0) 4046#else 4047 Tcl_PkgRequire(interp, "Tcl", "8.0", 0) 4048#endif 4049 == NULL) { 4050 return TCL_ERROR; 4051 } 4052 if ( 4053#ifdef USE_TK_STUBS 4054 Tk_InitStubs(interp, "8.0", 0) 4055#else 4056# if (TK_MAJOR_VERSION == 8) && (TK_MINOR_VERSION == 0) 4057 /* We require 8.0 exact because of the Unicode in 8.1+ */ 4058 Tcl_PkgRequire(interp, "Tk", "8.0", 1) 4059# else 4060 Tcl_PkgRequire(interp, "Tk", "8.0", 0) 4061# endif 4062#endif 4063 == NULL) { 4064 return TCL_ERROR; 4065 } 4066 if (Tcl_PkgProvide(interp, "Tktable", PACKAGE_VERSION) != TCL_OK) { 4067 return TCL_ERROR; 4068 } 4069 Tcl_CreateObjCommand(interp, TBL_COMMAND, Tk_TableObjCmd, 4070 (ClientData) Tk_MainWindow(interp), 4071 (Tcl_CmdDeleteProc *) NULL); 4072 4073 /* 4074 * The init script can't make certain calls in a safe interpreter, 4075 * so we always have to use the embedded runtime for it 4076 */ 4077 return Tcl_Eval(interp, Tcl_IsSafe(interp) ? 4078 tkTableSafeInitScript : tkTableInitScript); 4079} 4080 4081EXTERN int 4082Tktable_SafeInit(interp) 4083 Tcl_Interp *interp; 4084{ 4085 return Tktable_Init(interp); 4086} 4087#ifdef MAC_TCL 4088#pragma export reset 4089#endif 4090 4091#ifdef WIN32 4092/* 4093 *---------------------------------------------------------------------- 4094 * 4095 * DllEntryPoint -- 4096 * 4097 * This wrapper function is used by Windows to invoke the 4098 * initialization code for the DLL. If we are compiling 4099 * with Visual C++, this routine will be renamed to DllMain. 4100 * routine. 4101 * 4102 * Results: 4103 * Returns TRUE; 4104 * 4105 * Side effects: 4106 * None. 4107 * 4108 *---------------------------------------------------------------------- 4109 */ 4110 4111BOOL APIENTRY 4112DllEntryPoint(hInst, reason, reserved) 4113 HINSTANCE hInst; /* Library instance handle. */ 4114 DWORD reason; /* Reason this function is being called. */ 4115 LPVOID reserved; /* Not used. */ 4116{ 4117 return TRUE; 4118} 4119#endif 4120