/* * tkListbox.c (CTk) -- * * This module implements listbox widgets for the Tk * toolkit. A listbox displays a collection of strings, * one per line, and provides scrolling and selection. * * Copyright (c) 1990-1994 The Regents of the University of California. * Copyright (c) 1994-1995 Sun Microsystems, Inc. * Copyright (c) 1994-1995 Cleveland Clinic Foundation * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * @(#) $Id: ctk.shar,v 1.50 1996/01/15 14:47:16 andrewm Exp andrewm $ */ #include "tkPort.h" #include "default.h" #include "tkInt.h" /* * One record of the following type is kept for each element * associated with a listbox widget: */ typedef struct Element { int textLength; /* # non-NULL characters in text. */ int selected; /* 1 means this item is selected, 0 means * it isn't. */ struct Element *nextPtr; /* Next in list of all elements of this * listbox, or NULL for last element. */ char text[4]; /* Characters of this element, NULL- * terminated. The actual space allocated * here will be as large as needed (> 4, * most likely). Must be the last field * of the record. */ } Element; #define ElementSize(stringLength) \ ((unsigned) (sizeof(Element) - 3 + stringLength)) /* * A data structure of the following type is kept for each listbox * widget managed by this file: */ typedef struct { Tk_Window tkwin; /* Window that embodies the listbox. NULL * means that the window has been destroyed * but the data structures haven't yet been * cleaned up.*/ Tcl_Interp *interp; /* Interpreter associated with listbox. */ Tcl_Command widgetCmd; /* Token for listbox's widget command. */ int numElements; /* Total number of elements in this listbox. */ Element *firstPtr; /* First in list of elements (NULL if no * elements). */ Element *lastPtr; /* Last in list of elements (NULL if no * elements). */ /* * Information used when displaying widget: */ int borderWidth; /* Width of 3-D border around window. */ int width; /* Desired width of window, in characters. */ int height; /* Desired height of window, in lines. */ int topIndex; /* Index of top-most element visible in * window. */ int numLines; /* Actual number of lines (elements) that * currently fit in window. */ /* * Information to support horizontal scrolling: */ int maxWidth; /* Width (in pixels) of widest string in * listbox. */ int xOffset; /* The left edge of each string in the * listbox is offset to the left by this * many pixels (0 means no offset, positive * means there is an offset). */ /* * Information about what's selected or active, if any. */ Tk_Uid selectMode; /* Selection style: single, browse, multiple, * or extended. This value isn't used in C * code, but the Tcl bindings use it. */ int numSelected; /* Number of elements currently selected. */ int selectAnchor; /* Fixed end of selection (i.e. element * at which selection was started.) */ int active; /* Index of "active" element (the one that * has been selected by keyboard traversal). * -1 means none. */ /* * Miscellaneous information: */ char *takeFocus; /* Value of -takefocus option; not used in * the C code, but used by keyboard traversal * scripts. Malloc'ed, but may be NULL. */ char *yScrollCmd; /* Command prefix for communicating with * vertical scrollbar. NULL means no command * to issue. Malloc'ed. */ char *xScrollCmd; /* Command prefix for communicating with * horizontal scrollbar. NULL means no command * to issue. Malloc'ed. */ int flags; /* Various flag bits: see below for * definitions. */ } Listbox; /* * Flag bits for listboxes: * * REDRAW_PENDING: Non-zero means a DoWhenIdle handler * has already been queued to redraw * this window. * UPDATE_V_SCROLLBAR: Non-zero means vertical scrollbar needs * to be updated. * UPDATE_H_SCROLLBAR: Non-zero means horizontal scrollbar needs * to be updated. * GOT_FOCUS: Non-zero means this widget currently * has the input focus. * BORDER_NEEDED: Non-zero means 3-D border must be redrawn * around window during redisplay. Normally * only elements needs to be redrawn. */ #define REDRAW_PENDING 1 #define UPDATE_V_SCROLLBAR 2 #define UPDATE_H_SCROLLBAR 4 #define GOT_FOCUS 8 #define BORDER_NEEDED 16 /* * Information used for argv parsing: */ static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL, (char *) NULL, 0, 0}, {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", DEF_LISTBOX_BORDER_WIDTH, Tk_Offset(Listbox, borderWidth), 0}, {TK_CONFIG_INT, "-height", "height", "Height", DEF_LISTBOX_HEIGHT, Tk_Offset(Listbox, height), 0}, {TK_CONFIG_UID, "-selectmode", "selectMode", "SelectMode", DEF_LISTBOX_SELECT_MODE, Tk_Offset(Listbox, selectMode), 0}, {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", DEF_LISTBOX_TAKE_FOCUS, Tk_Offset(Listbox, takeFocus), TK_CONFIG_NULL_OK}, {TK_CONFIG_INT, "-width", "width", "Width", DEF_LISTBOX_WIDTH, Tk_Offset(Listbox, width), 0}, {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", DEF_LISTBOX_SCROLL_COMMAND, Tk_Offset(Listbox, xScrollCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", DEF_LISTBOX_SCROLL_COMMAND, Tk_Offset(Listbox, yScrollCmd), TK_CONFIG_NULL_OK}, {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, (char *) NULL, 0, 0} }; /* * Width of element indicator in characters. */ #define INDICATOR_WIDTH 0 /* * Forward declarations for procedures defined later in this file: */ static void ChangeListboxOffset _ANSI_ARGS_((Listbox *listPtr, int offset)); static void ChangeListboxView _ANSI_ARGS_((Listbox *listPtr, int index)); static int ConfigureListbox _ANSI_ARGS_((Tcl_Interp *interp, Listbox *listPtr, int argc, char **argv, int flags)); static void DeleteEls _ANSI_ARGS_((Listbox *listPtr, int first, int last)); static void DestroyListbox _ANSI_ARGS_((ClientData clientData)); static void DisplayListbox _ANSI_ARGS_((ClientData clientData)); static int GetListboxIndex _ANSI_ARGS_((Tcl_Interp *interp, Listbox *listPtr, char *string, int numElsOK, int *indexPtr)); static void InsertEls _ANSI_ARGS_((Listbox *listPtr, int index, int argc, char **argv)); static void ListboxCmdDeletedProc _ANSI_ARGS_(( ClientData clientData)); static void ListboxComputeGeometry _ANSI_ARGS_((Listbox *listPtr, int maxIsStale)); static void ListboxEventProc _ANSI_ARGS_((ClientData clientData, XEvent *eventPtr)); static void ListboxRedrawRange _ANSI_ARGS_((Listbox *listPtr, int first, int last)); static void ListboxSelect _ANSI_ARGS_((Listbox *listPtr, int first, int last, int select)); static void ListboxUpdateHScrollbar _ANSI_ARGS_((Listbox *listPtr)); static void ListboxUpdateVScrollbar _ANSI_ARGS_((Listbox *listPtr)); static int ListboxWidgetCmd _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp, int argc, char **argv)); static int NearestListboxElement _ANSI_ARGS_((Listbox *listPtr, int y)); /* *-------------------------------------------------------------- * * Tk_ListboxCmd -- * * This procedure is invoked to process the "listbox" Tcl * command. See the user documentation for details on what * it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ int Tk_ListboxCmd(clientData, interp, argc, argv) ClientData clientData; /* Main window associated with * interpreter. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { register Listbox *listPtr; Tk_Window new; Tk_Window tkwin = (Tk_Window) clientData; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " pathName ?options?\"", (char *) NULL); return TCL_ERROR; } new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL); if (new == NULL) { return TCL_ERROR; } /* * Initialize the fields of the structure that won't be initialized * by ConfigureListbox, or that ConfigureListbox requires to be * initialized already (e.g. resource pointers). */ listPtr = (Listbox *) ckalloc(sizeof(Listbox)); listPtr->tkwin = new; listPtr->interp = interp; listPtr->widgetCmd = Tcl_CreateCommand(interp, Tk_PathName(listPtr->tkwin), ListboxWidgetCmd, (ClientData) listPtr, ListboxCmdDeletedProc); listPtr->numElements = 0; listPtr->firstPtr = NULL; listPtr->lastPtr = NULL; listPtr->borderWidth = 0; listPtr->width = 0; listPtr->height = 0; listPtr->topIndex = 0; listPtr->numLines = 1; listPtr->maxWidth = 0; listPtr->xOffset = 0; listPtr->selectMode = NULL; listPtr->numSelected = 0; listPtr->selectAnchor = 0; listPtr->active = 0; listPtr->takeFocus = NULL; listPtr->xScrollCmd = NULL; listPtr->yScrollCmd = NULL; listPtr->flags = 0; Tk_SetClass(listPtr->tkwin, "Listbox"); Tk_CreateEventHandler(listPtr->tkwin, CTK_EXPOSE_EVENT_MASK|CTK_MAP_EVENT_MASK|CTK_DESTROY_EVENT_MASK |CTK_FOCUS_EVENT_MASK, ListboxEventProc, (ClientData) listPtr); if (ConfigureListbox(interp, listPtr, argc-2, argv+2, 0) != TCL_OK) { goto error; } Tcl_SetResult(interp,Tk_PathName(listPtr->tkwin),TCL_VOLATILE); return TCL_OK; error: Tk_DestroyWindow(listPtr->tkwin); return TCL_ERROR; } /* *-------------------------------------------------------------- * * ListboxWidgetCmd -- * * This procedure is invoked to process the Tcl command * that corresponds to a widget managed by this module. * See the user documentation for details on what it does. * * Results: * A standard Tcl result. * * Side effects: * See the user documentation. * *-------------------------------------------------------------- */ static int ListboxWidgetCmd(clientData, interp, argc, argv) ClientData clientData; /* Information about listbox widget. */ Tcl_Interp *interp; /* Current interpreter. */ int argc; /* Number of arguments. */ char **argv; /* Argument strings. */ { register Listbox *listPtr = (Listbox *) clientData; int result = TCL_OK; size_t length; int c; if (argc < 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " option ?arg arg ...?\"", (char *) NULL); return TCL_ERROR; } Tk_Preserve((ClientData) listPtr); c = argv[1][0]; length = strlen(argv[1]); if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " activate index\"", (char *) NULL); goto error; } ListboxRedrawRange(listPtr, listPtr->active, listPtr->active); if (GetListboxIndex(interp, listPtr, argv[2], 0, &index) != TCL_OK) { goto error; } listPtr->active = index; ListboxRedrawRange(listPtr, listPtr->active, listPtr->active); } else if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) { int index, x, y, i; Element *elPtr; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " bbox index\"", (char *) NULL); goto error; } if (GetListboxIndex(interp, listPtr, argv[2], 0, &index) != TCL_OK) { goto error; } for (i = 0, elPtr = listPtr->firstPtr; i < index; i++, elPtr = elPtr->nextPtr) { /* Empty loop body. */ } if ((index >= listPtr->topIndex) && (index < listPtr->numElements) && (index < (listPtr->topIndex + listPtr->numLines))) { char buffer[60]; x = listPtr->borderWidth - listPtr->xOffset; y = index - listPtr->topIndex + listPtr->borderWidth; sprintf(buffer, "%d %d %d %d", x, y, elPtr->textLength, 1); Tcl_SetResult(interp,buffer,TCL_VOLATILE); } } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0) && (length >= 2)) { if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " cget option\"", (char *) NULL); goto error; } result = Tk_ConfigureValue(interp, listPtr->tkwin, configSpecs, (char *) listPtr, argv[2], 0); } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0) && (length >= 2)) { if (argc == 2) { result = Tk_ConfigureInfo(interp, listPtr->tkwin, configSpecs, (char *) listPtr, (char *) NULL, 0); } else if (argc == 3) { result = Tk_ConfigureInfo(interp, listPtr->tkwin, configSpecs, (char *) listPtr, argv[2], 0); } else { result = ConfigureListbox(interp, listPtr, argc-2, argv+2, TK_CONFIG_ARGV_ONLY); } } else if ((c == 'c') && (strncmp(argv[1], "curselection", length) == 0) && (length >= 2)) { int i, count; char index[20]; Element *elPtr; if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " curselection\"", (char *) NULL); goto error; } count = 0; for (i = 0, elPtr = listPtr->firstPtr; elPtr != NULL; i++, elPtr = elPtr->nextPtr) { if (elPtr->selected) { sprintf(index, "%d", i); Tcl_AppendElement(interp, index); count++; } } if (count != listPtr->numSelected) { panic("ListboxWidgetCmd: selection count incorrect"); } } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) { int first, last; if ((argc < 3) || (argc > 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " delete firstIndex ?lastIndex?\"", (char *) NULL); goto error; } if (GetListboxIndex(interp, listPtr, argv[2], 0, &first) != TCL_OK) { goto error; } if (argc == 3) { last = first; } else { if (GetListboxIndex(interp, listPtr, argv[3], 0, &last) != TCL_OK) { goto error; } } DeleteEls(listPtr, first, last); } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) { int first, last, i; Element *elPtr; if ((argc != 3) && (argc != 4)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " get first ?last?\"", (char *) NULL); goto error; } if (GetListboxIndex(interp, listPtr, argv[2], 0, &first) != TCL_OK) { goto error; } if ((argc == 4) && (GetListboxIndex(interp, listPtr, argv[3], 0, &last) != TCL_OK)) { goto error; } for (elPtr = listPtr->firstPtr, i = 0; i < first; i++, elPtr = elPtr->nextPtr) { /* Empty loop body. */ } if (elPtr != NULL) { if (argc == 3) { Tcl_SetResult(interp,elPtr->text,TCL_VOLATILE); } else { for ( ; i <= last; i++, elPtr = elPtr->nextPtr) { Tcl_AppendElement(interp, elPtr->text); } } } } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0) && (length >= 3)) { int index; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " index index\"", (char *) NULL); goto error; } if (GetListboxIndex(interp, listPtr, argv[2], 1, &index) != TCL_OK) { goto error; } { char buffer[20]; sprintf(buffer, "%d", index); Tcl_SetResult(interp,buffer,TCL_VOLATILE); } } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0) && (length >= 3)) { int index; if (argc < 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " insert index ?element element ...?\"", (char *) NULL); goto error; } if (GetListboxIndex(interp, listPtr, argv[2], 1, &index) != TCL_OK) { goto error; } InsertEls(listPtr, index, argc-3, argv+3); } else if ((c == 'n') && (strncmp(argv[1], "nearest", length) == 0)) { int index, y; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " nearest y\"", (char *) NULL); goto error; } if (Tcl_GetInt(interp, argv[2], &y) != TCL_OK) { goto error; } index = NearestListboxElement(listPtr, y); { char buffer[20]; sprintf(buffer, "%d", index); Tcl_SetResult(interp,buffer,TCL_VOLATILE); } } else if ((c == 's') && (length >= 2) && (strncmp(argv[1], "scan", length) == 0)) { result = Ctk_Unsupported(interp, "listbox scan"); } else if ((c == 's') && (strncmp(argv[1], "see", length) == 0) && (length >= 3)) { int index, diff; if (argc != 3) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " see index\"", (char *) NULL); goto error; } if (GetListboxIndex(interp, listPtr, argv[2], 0, &index) != TCL_OK) { goto error; } diff = listPtr->topIndex-index; if (diff > 0) { if (diff <= (listPtr->numLines/3)) { ChangeListboxView(listPtr, index); } else { ChangeListboxView(listPtr, index - (listPtr->numLines-1)/2); } } else { diff = index - (listPtr->topIndex + listPtr->numLines - 1); if (diff > 0) { if (diff <= (listPtr->numLines/3)) { ChangeListboxView(listPtr, listPtr->topIndex + diff); } else { ChangeListboxView(listPtr, index - (listPtr->numLines-1)/2); } } } } else if ((c == 's') && (length >= 3) && (strncmp(argv[1], "selection", length) == 0)) { int first, last; if ((argc != 4) && (argc != 5)) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " selection option index ?index?\"", (char *) NULL); goto error; } if (GetListboxIndex(interp, listPtr, argv[3], 0, &first) != TCL_OK) { goto error; } if (argc == 5) { if (GetListboxIndex(interp, listPtr, argv[4], 0, &last) != TCL_OK) { goto error; } } else { last = first; } length = strlen(argv[2]); c = argv[2][0]; if ((c == 'a') && (strncmp(argv[2], "anchor", length) == 0)) { if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " selection anchor index\"", (char *) NULL); goto error; } listPtr->selectAnchor = first; } else if ((c == 'c') && (strncmp(argv[2], "clear", length) == 0)) { ListboxSelect(listPtr, first, last, 0); } else if ((c == 'i') && (strncmp(argv[2], "includes", length) == 0)) { int i; Element *elPtr; if (argc != 4) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " selection includes index\"", (char *) NULL); goto error; } for (elPtr = listPtr->firstPtr, i = 0; i < first; i++, elPtr = elPtr->nextPtr) { /* Empty loop body. */ } if ((elPtr != NULL) && (elPtr->selected)) { Tcl_SetResult(interp,"1",TCL_STATIC); } else { Tcl_SetResult(interp,"0",TCL_STATIC); } } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) { ListboxSelect(listPtr, first, last, 1); } else { Tcl_AppendResult(interp, "bad selection option \"", argv[2], "\": must be anchor, clear, includes, or set", (char *) NULL); goto error; } } else if ((c == 's') && (length >= 2) && (strncmp(argv[1], "size", length) == 0)) { if (argc != 2) { Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " size\"", (char *) NULL); goto error; } { char buffer[20]; sprintf(buffer, "%d", listPtr->numElements); Tcl_SetResult(interp,buffer,TCL_VOLATILE); } } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) { int index, count, type, windowWidth, windowUnits; int offset = 0; /* Initialized to stop gcc warnings. */ double fraction, fraction2; windowWidth = Tk_Width(listPtr->tkwin) - 2*listPtr->borderWidth; if (argc == 2) { if (listPtr->maxWidth == 0) { Tcl_SetResult(interp,"0 1",TCL_STATIC); } else { fraction = listPtr->xOffset/((double) listPtr->maxWidth); fraction2 = (listPtr->xOffset + windowWidth) /((double) listPtr->maxWidth); if (fraction2 > 1.0) { fraction2 = 1.0; } { char buffer[60]; sprintf(buffer, "%g %g", fraction, fraction2); Tcl_SetResult(interp,buffer,TCL_VOLATILE); } } } else if (argc == 3) { if (Tcl_GetInt(interp, argv[2], &index) != TCL_OK) { goto error; } ChangeListboxOffset(listPtr, index); } else { type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); switch (type) { case TK_SCROLL_ERROR: goto error; case TK_SCROLL_MOVETO: offset = fraction*listPtr->maxWidth + 0.5; break; case TK_SCROLL_PAGES: windowUnits = windowWidth; if (windowUnits > 2) { offset = listPtr->xOffset + count*(windowUnits-2); } else { offset = listPtr->xOffset + count; } break; case TK_SCROLL_UNITS: offset = listPtr->xOffset + count; break; } ChangeListboxOffset(listPtr, offset); } } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)) { int index, count, type; double fraction, fraction2; if (argc == 2) { if (listPtr->numElements == 0) { Tcl_SetResult(interp,"0 1", TCL_STATIC); } else { fraction = listPtr->topIndex/((double) listPtr->numElements); fraction2 = (listPtr->topIndex+listPtr->numLines) /((double) listPtr->numElements); if (fraction2 > 1.0) { fraction2 = 1.0; } { char buffer[60]; sprintf(buffer, "%g %g", fraction, fraction2); Tcl_SetResult(interp,buffer,TCL_VOLATILE); } } } else if (argc == 3) { if (GetListboxIndex(interp, listPtr, argv[2], 0, &index) != TCL_OK) { goto error; } ChangeListboxView(listPtr, index); } else { type = Tk_GetScrollInfo(interp, argc, argv, &fraction, &count); switch (type) { case TK_SCROLL_ERROR: goto error; case TK_SCROLL_MOVETO: index = listPtr->numElements*fraction + 0.5; break; case TK_SCROLL_PAGES: if (listPtr->numLines > 2) { index = listPtr->topIndex + count*(listPtr->numLines-2); } else { index = listPtr->topIndex + count; } break; case TK_SCROLL_UNITS: index = listPtr->topIndex + count; break; } ChangeListboxView(listPtr, index); } } else { Tcl_AppendResult(interp, "bad option \"", argv[1], "\": must be activate, bbox, cget, configure, ", "curselection, delete, get, index, insert, nearest, ", "scan, see, selection, size, ", "xview, or yview", (char *) NULL); goto error; } Tk_Release((ClientData) listPtr); return result; error: Tk_Release((ClientData) listPtr); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * DestroyListbox -- * * This procedure is invoked by Tk_EventuallyFree or Tk_Release * to clean up the internal structure of a listbox at a safe time * (when no-one is using it anymore). * * Results: * None. * * Side effects: * Everything associated with the listbox is freed up. * *---------------------------------------------------------------------- */ static void DestroyListbox(clientData) ClientData clientData; /* Info about listbox widget. */ { register Listbox *listPtr = (Listbox *) clientData; register Element *elPtr, *nextPtr; /* * Free up all of the list elements. */ for (elPtr = listPtr->firstPtr; elPtr != NULL; ) { nextPtr = elPtr->nextPtr; ckfree((char *) elPtr); elPtr = nextPtr; } Tk_FreeOptions(configSpecs, (char *) listPtr, 0); ckfree((char *) listPtr); } /* *---------------------------------------------------------------------- * * ConfigureListbox -- * * This procedure is called to process an argv/argc list, plus * the Tk option database, in order to configure (or reconfigure) * a listbox widget. * * Results: * The return value is a standard Tcl result. If TCL_ERROR is * returned, then interp->result contains an error message. * * Side effects: * Configuration information, such as colors, border width, * etc. get set for listPtr; old resources get freed, * if there were any. * *---------------------------------------------------------------------- */ static int ConfigureListbox(interp, listPtr, argc, argv, flags) Tcl_Interp *interp; /* Used for error reporting. */ register Listbox *listPtr; /* Information about widget; may or may * not already have values for some fields. */ int argc; /* Number of valid entries in argv. */ char **argv; /* Arguments. */ int flags; /* Flags to pass to Tk_ConfigureWidget. */ { if (Tk_ConfigureWidget(interp, listPtr->tkwin, configSpecs, argc, argv, (char *) listPtr, flags) != TCL_OK) { return TCL_ERROR; } /* * Register the desired geometry for the window and arrange for * the window to be redisplayed. */ Tk_SetInternalBorder(listPtr->tkwin, listPtr->borderWidth); ListboxComputeGeometry(listPtr, 1); listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR; ListboxRedrawRange(listPtr, 0, listPtr->numElements-1); return TCL_OK; } /* *-------------------------------------------------------------- * * DisplayListbox -- * * This procedure redraws the contents of a listbox window. * * Results: * None. * * Side effects: * Information appears on the screen. * *-------------------------------------------------------------- */ static void DisplayListbox(clientData) ClientData clientData; /* Information about window. */ { register Listbox *listPtr = (Listbox *) clientData; register Tk_Window tkwin = listPtr->tkwin; register Element *elPtr; int width = Tk_Width(tkwin); int height = Tk_Height(tkwin); int yCurs = listPtr->borderWidth; /* Vertical position for * cursor. */ int i, x, y, limit; listPtr->flags &= ~REDRAW_PENDING; if (listPtr->flags & UPDATE_V_SCROLLBAR) { ListboxUpdateVScrollbar(listPtr); } if (listPtr->flags & UPDATE_H_SCROLLBAR) { ListboxUpdateHScrollbar(listPtr); } listPtr->flags &= ~(REDRAW_PENDING|UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR); if ((listPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) { return; } /* * Draw background. */ Ctk_FillRect(tkwin, listPtr->borderWidth, listPtr->borderWidth, width - listPtr->borderWidth, height - listPtr->borderWidth, CTK_PLAIN_STYLE, ' '); /* * Loop through elements. */ x = listPtr->borderWidth + INDICATOR_WIDTH - listPtr->xOffset ; y = listPtr->borderWidth; limit = listPtr->topIndex + listPtr->numLines - 1; for (elPtr = listPtr->firstPtr, i = 0; (elPtr != NULL) && (i <= limit); elPtr = elPtr->nextPtr, i++) { if (i < listPtr->topIndex) { continue; } if (elPtr->selected) { Ctk_FillRect(tkwin, listPtr->borderWidth, y, width - listPtr->borderWidth, y+1, CTK_SELECTED_STYLE, ' '); Ctk_DrawString(tkwin, x, y, CTK_SELECTED_STYLE, elPtr->text, elPtr->textLength); } else { Ctk_DrawString(tkwin, x, y, CTK_PLAIN_STYLE, elPtr->text, elPtr->textLength); } if (i == listPtr->active) { yCurs = y; } y++; } if (listPtr->flags & GOT_FOCUS) { Ctk_SetCursor(tkwin, listPtr->borderWidth, yCurs); } if (listPtr->flags & BORDER_NEEDED) { Ctk_DrawBorder(tkwin, CTK_PLAIN_STYLE, (char *) NULL); listPtr->flags &= ~BORDER_NEEDED; } } /* *---------------------------------------------------------------------- * * ListboxComputeGeometry -- * * This procedure is invoked to recompute geometry information * such as the sizes of the elements and the overall dimensions * desired for the listbox. * * Results: * None. * * Side effects: * Geometry information is updated and a new requested size is * registered for the widget. Internal border * information is also set. * *---------------------------------------------------------------------- */ static void ListboxComputeGeometry(listPtr, maxIsStale) Listbox *listPtr; /* Listbox whose geometry is to be * recomputed. */ int maxIsStale; /* Non-zero means the "maxWidth" field may * no longer be up-to-date and must * be recomputed. */ { register Element *elPtr; int width, height; if (maxIsStale) { listPtr->maxWidth = 0; for (elPtr = listPtr->firstPtr; elPtr != NULL; elPtr = elPtr->nextPtr) { if (elPtr->textLength > listPtr->maxWidth) { listPtr->maxWidth = elPtr->textLength; } } } width = listPtr->width; if (width <= 0) { width = listPtr->maxWidth; if (width < 1) { width = 1; } } width += 2*listPtr->borderWidth + INDICATOR_WIDTH; height = listPtr->height; if (height <= 0) { height = listPtr->numElements; if (height < 1) { height = 1; } } height += 2*listPtr->borderWidth; Tk_GeometryRequest(listPtr->tkwin, width, height); } /* *---------------------------------------------------------------------- * * InsertEls -- * * Add new elements to a listbox widget. * * Results: * None. * * Side effects: * New information gets added to listPtr; it will be redisplayed * soon, but not immediately. * *---------------------------------------------------------------------- */ static void InsertEls(listPtr, index, argc, argv) register Listbox *listPtr; /* Listbox that is to get the new * elements. */ int index; /* Add the new elements before this * element. */ int argc; /* Number of new elements to add. */ char **argv; /* New elements (one per entry). */ { register Element *prevPtr, *newPtr; int length, i, oldMaxWidth; /* * Find the element before which the new ones will be inserted. */ if (index <= 0) { index = 0; } if (index > listPtr->numElements) { index = listPtr->numElements; } if (index == 0) { prevPtr = NULL; } else if (index == listPtr->numElements) { prevPtr = listPtr->lastPtr; } else { for (prevPtr = listPtr->firstPtr, i = index - 1; i > 0; i--) { prevPtr = prevPtr->nextPtr; } } /* * For each new element, create a record, initialize it, and link * it into the list of elements. */ oldMaxWidth = listPtr->maxWidth; for (i = argc ; i > 0; i--, argv++, prevPtr = newPtr) { length = strlen(*argv); newPtr = (Element *) ckalloc(ElementSize(length)); newPtr->textLength = length; strcpy(newPtr->text, *argv); if (newPtr->textLength > listPtr->maxWidth) { listPtr->maxWidth = newPtr->textLength; } newPtr->selected = 0; if (prevPtr == NULL) { newPtr->nextPtr = listPtr->firstPtr; listPtr->firstPtr = newPtr; } else { newPtr->nextPtr = prevPtr->nextPtr; prevPtr->nextPtr = newPtr; } } if ((prevPtr != NULL) && (prevPtr->nextPtr == NULL)) { listPtr->lastPtr = prevPtr; } listPtr->numElements += argc; /* * Update the selection and other indexes to account for the * renumbering that has just occurred. Then arrange for the new * information to be displayed. */ if (index <= listPtr->selectAnchor) { listPtr->selectAnchor += argc; } if (index < listPtr->topIndex) { listPtr->topIndex += argc; } if (index <= listPtr->active) { listPtr->active += argc; if ((listPtr->active >= listPtr->numElements) && (listPtr->numElements > 0)) { listPtr->active = listPtr->numElements-1; } } listPtr->flags |= UPDATE_V_SCROLLBAR; if (listPtr->maxWidth != oldMaxWidth) { listPtr->flags |= UPDATE_H_SCROLLBAR; } ListboxComputeGeometry(listPtr, 0); ListboxRedrawRange(listPtr, index, listPtr->numElements-1); } /* *---------------------------------------------------------------------- * * DeleteEls -- * * Remove one or more elements from a listbox widget. * * Results: * None. * * Side effects: * Memory gets freed, the listbox gets modified and (eventually) * redisplayed. * *---------------------------------------------------------------------- */ static void DeleteEls(listPtr, first, last) register Listbox *listPtr; /* Listbox widget to modify. */ int first; /* Index of first element to delete. */ int last; /* Index of last element to delete. */ { register Element *prevPtr, *elPtr; int count, i, widthChanged; /* * Adjust the range to fit within the existing elements of the * listbox, and make sure there's something to delete. */ if (first < 0) { first = 0; } if (last >= listPtr->numElements) { last = listPtr->numElements-1; } count = last + 1 - first; if (count <= 0) { return; } /* * Find the element just before the ones to delete. */ if (first == 0) { prevPtr = NULL; } else { for (i = first-1, prevPtr = listPtr->firstPtr; i > 0; i--) { prevPtr = prevPtr->nextPtr; } } /* * Delete the requested number of elements. */ widthChanged = 0; for (i = count; i > 0; i--) { if (prevPtr == NULL) { elPtr = listPtr->firstPtr; listPtr->firstPtr = elPtr->nextPtr; if (listPtr->firstPtr == NULL) { listPtr->lastPtr = NULL; } } else { elPtr = prevPtr->nextPtr; prevPtr->nextPtr = elPtr->nextPtr; if (prevPtr->nextPtr == NULL) { listPtr->lastPtr = prevPtr; } } if (elPtr->textLength == listPtr->maxWidth) { widthChanged = 1; } if (elPtr->selected) { listPtr->numSelected -= 1; } ckfree((char *) elPtr); } listPtr->numElements -= count; /* * Update the selection and viewing information to reflect the change * in the element numbering, and redisplay to slide information up over * the elements that were deleted. */ if (first <= listPtr->selectAnchor) { listPtr->selectAnchor -= count; if (listPtr->selectAnchor < first) { listPtr->selectAnchor = first; } } if (first <= listPtr->topIndex) { listPtr->topIndex -= count; if (listPtr->topIndex < first) { listPtr->topIndex = first; } } if (listPtr->topIndex > (listPtr->numElements - listPtr->numLines)) { listPtr->topIndex = listPtr->numElements - listPtr->numLines; if (listPtr->topIndex < 0) { listPtr->topIndex = 0; } } if (listPtr->active > last) { listPtr->active -= count; } else if (listPtr->active >= first) { listPtr->active = first; if ((listPtr->active >= listPtr->numElements) && (listPtr->numElements > 0)) { listPtr->active = listPtr->numElements-1; } } listPtr->flags |= UPDATE_V_SCROLLBAR; ListboxComputeGeometry(listPtr, widthChanged); if (widthChanged) { listPtr->flags |= UPDATE_H_SCROLLBAR; } ListboxRedrawRange(listPtr, first, listPtr->numElements-1); } /* *-------------------------------------------------------------- * * ListboxEventProc -- * * This procedure is invoked by the Tk dispatcher for various * events on listboxes. * * Results: * None. * * Side effects: * When the window gets deleted, internal structures get * cleaned up. When it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void ListboxEventProc(clientData, eventPtr) ClientData clientData; /* Information about window. */ XEvent *eventPtr; /* Information about event. */ { Listbox *listPtr = (Listbox *) clientData; if (eventPtr->type == CTK_EXPOSE_EVENT) { ListboxRedrawRange(listPtr, NearestListboxElement(listPtr, eventPtr->u.expose.top), NearestListboxElement(listPtr, eventPtr->u.expose.bottom)); listPtr->flags |= BORDER_NEEDED; } else if (eventPtr->type == CTK_DESTROY_EVENT) { if (listPtr->tkwin != NULL) { listPtr->tkwin = NULL; Tcl_DeleteCommand(listPtr->interp, Tcl_GetCommandName(listPtr->interp, listPtr->widgetCmd)); } if (listPtr->flags & REDRAW_PENDING) { Tcl_CancelIdleCall(DisplayListbox, (ClientData) listPtr); } Tk_EventuallyFree((ClientData) listPtr, DestroyListbox); } else if (eventPtr->type == CTK_MAP_EVENT) { listPtr->numLines = Tk_Height(listPtr->tkwin) - 2*listPtr->borderWidth; listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR; ChangeListboxView(listPtr, listPtr->topIndex); ChangeListboxOffset(listPtr, listPtr->xOffset); } else if (eventPtr->type == CTK_FOCUS_EVENT) { listPtr->flags |= GOT_FOCUS; if (listPtr->active != -1) { ListboxRedrawRange(listPtr, listPtr->active, listPtr->active); } } else if (eventPtr->type == CTK_UNFOCUS_EVENT) { listPtr->flags &= ~GOT_FOCUS; } } /* *---------------------------------------------------------------------- * * ListboxCmdDeletedProc -- * * This procedure is invoked when a widget command is deleted. If * the widget isn't already in the process of being destroyed, * this command destroys it. * * Results: * None. * * Side effects: * The widget is destroyed. * *---------------------------------------------------------------------- */ static void ListboxCmdDeletedProc(clientData) ClientData clientData; /* Pointer to widget record for widget. */ { Listbox *listPtr = (Listbox *) clientData; Tk_Window tkwin = listPtr->tkwin; /* * This procedure could be invoked either because the window was * destroyed and the command was then deleted (in which case tkwin * is NULL) or because the command was deleted, and then this procedure * destroys the widget. */ if (tkwin != NULL) { listPtr->tkwin = NULL; Tk_DestroyWindow(tkwin); } } /* *-------------------------------------------------------------- * * GetListboxIndex -- * * Parse an index into a listbox and return either its value * or an error. * * Results: * A standard Tcl result. If all went well, then *indexPtr is * filled in with the index (into listPtr) corresponding to * string. Otherwise an error message is left in interp->result. * * Side effects: * None. * *-------------------------------------------------------------- */ static int GetListboxIndex(interp, listPtr, string, numElsOK, indexPtr) Tcl_Interp *interp; /* For error messages. */ Listbox *listPtr; /* Listbox for which the index is being * specified. */ char *string; /* Specifies an element in the listbox. */ int numElsOK; /* 0 means the return value must be less * less than the number of entries in * the listbox; 1 means it may also be * equal to the number of entries. */ int *indexPtr; /* Where to store converted index. */ { int c; size_t length; length = strlen(string); c = string[0]; if ((c == 'a') && (strncmp(string, "active", length) == 0) && (length >= 2)) { *indexPtr = listPtr->active; } else if ((c == 'a') && (strncmp(string, "anchor", length) == 0) && (length >= 2)) { *indexPtr = listPtr->selectAnchor; } else if ((c == 'e') && (strncmp(string, "end", length) == 0)) { *indexPtr = listPtr->numElements; } else if (c == '@') { int x, y; char *p, *end; p = string+1; x = strtol(p, &end, 0); if ((end == p) || (*end != ',')) { goto badIndex; } p = end+1; y = strtol(p, &end, 0); if ((end == p) || (*end != 0)) { goto badIndex; } *indexPtr = NearestListboxElement(listPtr, y); } else { if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) { Tcl_ResetResult(interp); goto badIndex; } } if (numElsOK) { if (*indexPtr > listPtr->numElements) { *indexPtr = listPtr->numElements; } } else if (*indexPtr >= listPtr->numElements) { *indexPtr = listPtr->numElements-1; } if (*indexPtr < 0) { *indexPtr = 0; } return TCL_OK; badIndex: Tcl_AppendResult(interp, "bad listbox index \"", string, "\": must be active, anchor, end, @x,y, or a number", (char *) NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * ChangeListboxView -- * * Change the view on a listbox widget. * * Results: * None. * * Side effects: * What's displayed on the screen is changed. If there is a * scrollbar associated with this widget, then the scrollbar * is instructed to change its display too. * *---------------------------------------------------------------------- */ static void ChangeListboxView(listPtr, index) register Listbox *listPtr; /* Information about widget. */ int index; /* Index of element in listPtr. */ { if (index >= (listPtr->numElements - listPtr->numLines)) { index = listPtr->numElements - listPtr->numLines; } if (index < 0) { index = 0; } if (listPtr->topIndex != index) { listPtr->topIndex = index; if (!(listPtr->flags & REDRAW_PENDING)) { Tcl_DoWhenIdle(DisplayListbox, (ClientData) listPtr); listPtr->flags |= REDRAW_PENDING; } listPtr->flags |= UPDATE_V_SCROLLBAR; } } /* *---------------------------------------------------------------------- * * ChangListboxOffset -- * * Change the horizontal offset for a listbox. * * Results: * None. * * Side effects: * The listbox may be redrawn to reflect its new horizontal * offset. * *---------------------------------------------------------------------- */ static void ChangeListboxOffset(listPtr, offset) register Listbox *listPtr; /* Information about widget. */ int offset; /* Desired new "xOffset" for * listbox. */ { int maxOffset; /* * Make sure that the new offset is within the allowable range. */ maxOffset = listPtr->maxWidth - (Tk_Width(listPtr->tkwin) - 2*listPtr->borderWidth - 1); if (offset > maxOffset) { offset = maxOffset; } if (offset < 0) { offset = 0; } if (offset != listPtr->xOffset) { listPtr->xOffset = offset; listPtr->flags |= UPDATE_H_SCROLLBAR; ListboxRedrawRange(listPtr, 0, listPtr->numElements); } } /* *---------------------------------------------------------------------- * * NearestListboxElement -- * * Given a y-coordinate inside a listbox, compute the index of * the element under that y-coordinate (or closest to that * y-coordinate). * * Results: * The return value is an index of an element of listPtr. If * listPtr has no elements, then 0 is always returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NearestListboxElement(listPtr, y) register Listbox *listPtr; /* Information about widget. */ int y; /* Y-coordinate in listPtr's window. */ { int index; index = y - listPtr->borderWidth; if (index >= listPtr->numLines) { index = listPtr->numLines-1; } if (index < 0) { index = 0; } index += listPtr->topIndex; if (index >= listPtr->numElements) { index = listPtr->numElements-1; } return index; } /* *---------------------------------------------------------------------- * * ListboxSelect -- * * Select or deselect one or more elements in a listbox.. * * Results: * None. * * Side effects: * All of the elements in the range between first and last are * marked as either selected or deselected, depending on the * "select" argument. Any items whose state changes are redisplayed. * The selection is claimed from X when the number of selected * elements changes from zero to non-zero. * *---------------------------------------------------------------------- */ static void ListboxSelect(listPtr, first, last, select) register Listbox *listPtr; /* Information about widget. */ int first; /* Index of first element to * select or deselect. */ int last; /* Index of last element to * select or deselect. */ int select; /* 1 means select items, 0 means * deselect them. */ { int i, firstRedisplay, lastRedisplay, increment, oldCount; Element *elPtr; if (last < first) { i = first; first = last; last = i; } if (first >= listPtr->numElements) { return; } oldCount = listPtr->numSelected; firstRedisplay = -1; increment = select ? 1 : -1; for (i = 0, elPtr = listPtr->firstPtr; i < first; i++, elPtr = elPtr->nextPtr) { /* Empty loop body. */ } for ( ; i <= last; i++, elPtr = elPtr->nextPtr) { if (elPtr->selected == select) { continue; } listPtr->numSelected += increment; elPtr->selected = select; if (firstRedisplay < 0) { firstRedisplay = i; } lastRedisplay = i; } if (firstRedisplay >= 0) { ListboxRedrawRange(listPtr, first, last); } } /* *---------------------------------------------------------------------- * * ListboxRedrawRange -- * * Ensure that a given range of elements is eventually redrawn on * the display (if those elements in fact appear on the display). * * Results: * None. * * Side effects: * Information gets redisplayed. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void ListboxRedrawRange(listPtr, first, last) register Listbox *listPtr; /* Information about widget. */ int first; /* Index of first element in list * that needs to be redrawn. */ int last; /* Index of last element in list * that needs to be redrawn. May * be less than first; * these just bracket a range. */ { if ((listPtr->tkwin == NULL) || !Tk_IsMapped(listPtr->tkwin) || (listPtr->flags & REDRAW_PENDING)) { return; } Tcl_DoWhenIdle(DisplayListbox, (ClientData) listPtr); listPtr->flags |= REDRAW_PENDING; } /* *---------------------------------------------------------------------- * * ListboxUpdateVScrollbar -- * * This procedure is invoked whenever information has changed in * a listbox in a way that would invalidate a vertical scrollbar * display. If there is an associated scrollbar, then this command * updates it by invoking a Tcl command. * * Results: * None. * * Side effects: * A Tcl command is invoked, and an additional command may be * invoked to process errors in the command. * *---------------------------------------------------------------------- */ static void ListboxUpdateVScrollbar(listPtr) register Listbox *listPtr; /* Information about widget. */ { char string[100]; double first, last; int result; if (listPtr->yScrollCmd == NULL) { return; } if (listPtr->numElements == 0) { first = 0.0; last = 1.0; } else { first = listPtr->topIndex/((double) listPtr->numElements); last = (listPtr->topIndex+listPtr->numLines) /((double) listPtr->numElements); if (last > 1.0) { last = 1.0; } } sprintf(string, " %g %g", first, last); result = Tcl_VarEval(listPtr->interp, listPtr->yScrollCmd, string, (char *) NULL); if (result != TCL_OK) { Tcl_AddErrorInfo(listPtr->interp, "\n (vertical scrolling command executed by listbox)"); Tcl_BackgroundError(listPtr->interp); } } /* *---------------------------------------------------------------------- * * ListboxUpdateHScrollbar -- * * This procedure is invoked whenever information has changed in * a listbox in a way that would invalidate a horizontal scrollbar * display. If there is an associated horizontal scrollbar, then * this command updates it by invoking a Tcl command. * * Results: * None. * * Side effects: * A Tcl command is invoked, and an additional command may be * invoked to process errors in the command. * *---------------------------------------------------------------------- */ static void ListboxUpdateHScrollbar(listPtr) register Listbox *listPtr; /* Information about widget. */ { char string[60]; int result, windowWidth; double first, last; if (listPtr->xScrollCmd == NULL) { return; } windowWidth = Tk_Width(listPtr->tkwin) - 2*listPtr->borderWidth; if (listPtr->maxWidth == 0) { first = 0; last = 1.0; } else { first = listPtr->xOffset/((double) listPtr->maxWidth); last = (listPtr->xOffset + windowWidth) /((double) listPtr->maxWidth); if (last > 1.0) { last = 1.0; } } sprintf(string, " %g %g", first, last); result = Tcl_VarEval(listPtr->interp, listPtr->xScrollCmd, string, (char *) NULL); if (result != TCL_OK) { Tcl_AddErrorInfo(listPtr->interp, "\n (horizontal scrolling command executed by listbox)"); Tcl_BackgroundError(listPtr->interp); } }