home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / x / volume17 / tcl-editor / part16 / tkColbox.c
Encoding:
C/C++ Source or Header  |  1992-03-18  |  54.4 KB  |  1,797 lines

  1. /* 
  2.  * tkColbox.c --
  3.  *
  4.  *    This module implements colbox widgets for the Tk
  5.  *    toolkit.  A colbox displays a collection of strings,
  6.  *    N per line (where N is the number of columns chosen by
  7.  *      the user), and provides scrolling and selection.
  8.  *      The items in the list are filled by columns, that is, the
  9.  *      first column is filled before the second column, and so on.
  10.  *      Thus an alphabatical list will be alphabetical by columns.
  11.  *      It would be easy to add on option to fill by rows but I have
  12.  *      not done it.
  13.  *
  14.  *    This module was modified to be a `colbox', that is, a listbox
  15.  *      that will display the list in one or more columns.
  16.  *      In addition, a command to return the indexes of the first and
  17.  *      last selected element was added.  this command is called
  18.  *      'sellimits' and causes the abbreviation 'se' for 'select' to
  19.  *      no longer be a valid abbreviation.
  20.  *      Finally I added a resource to prevent losing the X selection
  21.  *      from deselecting the selected elements.  This is to allow several
  22.  *      list boxes to have selections simultaneously.
  23.  *      The modifications were done in July 1991 by
  24.  *           Charles Crowley
  25.  *           Computer Science Department
  26.  *           University of New Mexico
  27.  *           Albuquerque, NM 87131
  28.  *           505-277-3112
  29.  *           crowley@unmvax.cs.unm.edu
  30.  *
  31.  * Copyright 1990 Regents of the University of California.
  32.  * Permission to use, copy, modify, and distribute this
  33.  * software and its documentation for any purpose and without
  34.  * fee is hereby granted, provided that the above copyright
  35.  * notice appear in all copies.  The University of California
  36.  * makes no representations about the suitability of this
  37.  * software for any purpose.  It is provided "as is" without
  38.  * express or implied warranty.
  39.  */
  40.  
  41. #include <stdio.h>
  42. #include <stdlib.h>
  43. #include <string.h>
  44. #include <X11/Xatom.h>
  45. #include "tkConfig.h"
  46. #include "default.h"
  47. #include "tkInt.h"
  48.  
  49. /*
  50.  * One record of the following type is kept for each element
  51.  * associated with a listbox widget:
  52.  */
  53.  
  54. typedef struct Element {
  55.     int textLength;        /* # non-NULL characters in text. */
  56.     int lBearing;        /* Distance from first character's
  57.                  * origin to left edge of character. */
  58.     struct Element *nextPtr;    /* Next in list of all elements of this
  59.                  * listbox, or NULL for last element. */
  60.     char text[4];        /* Characters of this element, NULL-
  61.                  * terminated.  The actual space allocated
  62.                  * here will be as large as needed (> 4,
  63.                  * most likely).  Must be the last field
  64.                  * of the record. */
  65. } Element;
  66.  
  67. #define ElementSize(stringLength) \
  68.     ((unsigned) (sizeof(Element) - 3 + stringLength))
  69.  
  70. #define MAX_COLS    50    /* seems big enough */
  71.  
  72. /*
  73.  * A data structure of the following type is kept for each listbox
  74.  * widget managed by this file:
  75.  */
  76.  
  77. typedef struct {
  78.     Tk_Window tkwin;        /* Window that embodies the listbox.  NULL
  79.                  * means that the window has been destroyed
  80.                  * but the data structures haven't yet been
  81.                  * cleaned up.*/
  82.     Tcl_Interp *interp;        /* Interpreter associated with listbox. */
  83.     int numElements;        /* Total number of elements in this listbox. */
  84.     Element *elementPtr;    /* First in list of elements (NULL if no
  85.                  * elements. */
  86.  
  87.     /*
  88.      * Information used when displaying widget:
  89.      */
  90.  
  91.     Tk_3DBorder normalBorder;    /* Used for drawing border around whole
  92.                  * window, plus used for background. */
  93.     int borderWidth;        /* Width of 3-D border around window. */
  94.     int relief;            /* 3-D effect: TK_RELIEF_RAISED, etc. */
  95.     XFontStruct *fontPtr;    /* Information about text font, or NULL. */
  96.     XColor *fgColorPtr;        /* Text color in normal mode. */
  97.     GC textGC;            /* For drawing normal text. */
  98.     Tk_3DBorder selBorder;    /* Borders and backgrounds for selected
  99.                  * elements. */
  100.     int selBorderWidth;        /* Width of border around selection. */
  101.     XColor *selFgColorPtr;    /* Foreground color for selected elements. */
  102.     GC selTextGC;        /* For drawing selected text. */
  103.     char *geometry;        /* Desired geometry for window.  Malloc'ed. */
  104.     int lineHeight;        /* Number of pixels allocated for each line
  105.                  * in display. */
  106.     int topIndex;        /* Index of top-most element visible in
  107.                  * window (on the left most column). */
  108.     int numLines;        /* Number of lines that fit
  109.                  * in window at one time. */
  110.     int columns;        /* Number of columns to display. Can be at
  111.                                  * most MAX_COLS. */
  112.     int noxsel;            /* If true, then do not deselect elements
  113.                                  * when we lose the X selection. */
  114.     int numInColumn;        /* Number of elements in one column. */
  115.     int redrawFirst;        /* Index (in element list, not on display)
  116.                  * of first element (in the left most column)
  117.                  * to redisplay.  -1 means
  118.                  * no redisplay pending. */
  119.     int redrawLast;        /* Index of last element to redisplay. */
  120.     int scrollLines;        /* If non-zero, then DisplayListbox is
  121.                  * expected to shift the whole picture
  122.                  * down (if > 0) or up (if < 0) this many
  123.                  * lines, prior to redisplaying the stuff
  124.                  * given by redrawFirst and redrawLast. */
  125.  
  126.     /*
  127.      * Information about what's selected, if any.
  128.      */
  129.  
  130.     int selectFirst;        /* Index of first selected element (-1 means
  131.                  * nothing selected. */
  132.     int selectLast;        /* Index of last selected element. */
  133.     int selectAnchor;        /* Fixed end of selection (i.e. element
  134.                  * at which selection was started.) */
  135.  
  136.     /*
  137.      * Information for scanning:
  138.      */
  139.  
  140.     int scanMarkY;        /* Y-position at which scan started (e.g.
  141.                  * button was pressed here). */
  142.     int scanMarkIndex;        /* Index of line that was at top of window
  143.                  * when scan started. */
  144.  
  145.     /*
  146.      * Miscellaneous information:
  147.      */
  148.  
  149.     char *scrollCmd;        /* Command prefix for communicating with
  150.                  * scrollbar(s).  NULL means no command
  151.                  * to issue.  Malloc'ed. */
  152.     int flags;            /* Various flag bits:  see below for
  153.                  * definitions. */
  154. } Listbox;
  155.  
  156. /*
  157.  * Flag bits for buttons:
  158.  *
  159.  * None currently defined.
  160.  */
  161.  
  162. /*
  163.  * Information used for argv parsing:
  164.  */
  165.  
  166. static Tk_ConfigSpec configSpecs[] = {
  167.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  168.     DEF_LISTBOX_BG_COLOR, Tk_Offset(Listbox, normalBorder),
  169.     TK_CONFIG_COLOR_ONLY},
  170.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  171.     DEF_LISTBOX_BG_MONO, Tk_Offset(Listbox, normalBorder),
  172.     TK_CONFIG_MONO_ONLY},
  173.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  174.     (char *) NULL, 0, 0},
  175.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  176.     (char *) NULL, 0, 0},
  177.     {TK_CONFIG_INT, "-borderwidth", "borderWidth", "BorderWidth",
  178.     DEF_LISTBOX_BORDER_WIDTH, Tk_Offset(Listbox, borderWidth), 0},
  179.     {TK_CONFIG_INT, "-columns", "columns", "Columns",
  180.     "1", Tk_Offset(Listbox, columns), 0},
  181.     {TK_CONFIG_INT, "-noxsel", "noxsel", "noxsel",
  182.     "0", Tk_Offset(Listbox, noxsel), 0},
  183.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  184.     (char *) NULL, 0, 0},
  185.     {TK_CONFIG_FONT, "-font", "font", "Font",
  186.     DEF_LISTBOX_FONT, Tk_Offset(Listbox, fontPtr), 0},
  187.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  188.     DEF_LISTBOX_FG, Tk_Offset(Listbox, fgColorPtr), 0},
  189.     {TK_CONFIG_STRING, "-geometry", "geometry", "Geometry",
  190.     DEF_LISTBOX_GEOMETRY, Tk_Offset(Listbox, geometry), 0},
  191.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  192.     DEF_LISTBOX_RELIEF, Tk_Offset(Listbox, relief), 0},
  193.     {TK_CONFIG_STRING, "-scrollcommand", "scrollCommand", "ScrollCommand",
  194.     DEF_LISTBOX_SCROLL_COMMAND, Tk_Offset(Listbox, scrollCmd), 0},
  195.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  196.     DEF_LISTBOX_SELECT_COLOR, Tk_Offset(Listbox, selBorder),
  197.     TK_CONFIG_COLOR_ONLY},
  198.     {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
  199.     DEF_LISTBOX_SELECT_MONO, Tk_Offset(Listbox, selBorder),
  200.     TK_CONFIG_MONO_ONLY},
  201.     {TK_CONFIG_INT, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
  202.     DEF_LISTBOX_SELECT_BD, Tk_Offset(Listbox, selBorderWidth), 0},
  203.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  204.     DEF_LISTBOX_SELECT_FG_COLOR, Tk_Offset(Listbox, selFgColorPtr),
  205.     TK_CONFIG_COLOR_ONLY},
  206.     {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
  207.     DEF_LISTBOX_SELECT_FG_MONO, Tk_Offset(Listbox, selFgColorPtr),
  208.     TK_CONFIG_MONO_ONLY},
  209.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  210.     (char *) NULL, 0, 0}
  211. };
  212.  
  213. /*
  214.  * Flags for GetListboxIndex procedure:
  215.  */
  216.  
  217. #define ZERO_OK            1
  218. #define LAST_PLUS_ONE_OK    2
  219.  
  220. /*
  221.  * Forward declarations for procedures defined later in this file:
  222.  */
  223.  
  224. static void        ChangeListboxView _ANSI_ARGS_((Listbox *listPtr,
  225.                             int index, int lineNum));
  226. static int        ConfigureListbox _ANSI_ARGS_((Tcl_Interp *interp,
  227.                 Listbox *listPtr, int argc, char **argv,
  228.                 int flags));
  229. static void        DeleteEls _ANSI_ARGS_((Listbox *listPtr, int index,
  230.                 int count));
  231. static void        DestroyListbox _ANSI_ARGS_((ClientData clientData));
  232. static void        DisplayListbox _ANSI_ARGS_((ClientData clientData));
  233. static int        GetListboxIndex _ANSI_ARGS_((Tcl_Interp *interp,
  234.                 Listbox *listPtr, char *string, int flags,
  235.                 int *indexPtr));
  236. static void        InsertEls _ANSI_ARGS_((Listbox *listPtr, int index,
  237.                 int argc, char **argv));
  238. static void        ListboxEventProc _ANSI_ARGS_((ClientData clientData,
  239.                 XEvent *eventPtr));
  240. static int        ListboxFetchSelection _ANSI_ARGS_((
  241.                             ClientData clientData, int offset, char *buffer,
  242.                             int maxBytes));
  243. static void        ListboxLostSelection _ANSI_ARGS_((ClientData clientData));
  244. static void        ListboxMouseProc _ANSI_ARGS_((ClientData clientData,
  245.                 XEvent *eventPtr));
  246. static void        ListboxRedrawRange _ANSI_ARGS_((Listbox *listPtr,
  247.                 int first, int last));
  248. static void        ListboxScanTo _ANSI_ARGS_((Listbox *listPtr, int y));
  249. static void        ListboxSelectFrom _ANSI_ARGS_((Listbox *listPtr,
  250.                 int index));
  251. static void        ListboxSelectTo _ANSI_ARGS_((Listbox *listPtr,
  252.                             int index));
  253. static void        ListboxUpdateScrollbar _ANSI_ARGS_((Listbox *listPtr));
  254. static int        ListboxWidgetCmd _ANSI_ARGS_((ClientData clientData,
  255.                 Tcl_Interp *interp, int argc, char **argv));
  256. static int        NearestListboxElement _ANSI_ARGS_((Listbox *listPtr,
  257.                 int x, int y));
  258.  
  259. /*
  260.  *--------------------------------------------------------------
  261.  *
  262.  * Tk_ListboxCmd --
  263.  *
  264.  *    This procedure is invoked to process the "listbox" Tcl
  265.  *    command.  See the user documentation for details on what
  266.  *    it does.
  267.  *
  268.  * Results:
  269.  *    A standard Tcl result.
  270.  *
  271.  * Side effects:
  272.  *    See the user documentation.
  273.  *
  274.  *--------------------------------------------------------------
  275.  */
  276.  
  277. int
  278. Pt_ColboxCmd(clientData, interp, argc, argv)
  279.     ClientData clientData;    /* Main window associated with
  280.                  * interpreter. */
  281.     Tcl_Interp *interp;        /* Current interpreter. */
  282.     int argc;            /* Number of arguments. */
  283.     char **argv;        /* Argument strings. */
  284. {
  285.     register Listbox *listPtr;
  286.     Tk_Window new;
  287.     Tk_Window tkwin = (Tk_Window) clientData;
  288.  
  289.     if (argc < 2) {
  290.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  291.         argv[0], " pathName ?options?\"", (char *) NULL);
  292.     return TCL_ERROR;
  293.     }
  294.  
  295.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  296.     if (new == NULL) {
  297.     return TCL_ERROR;
  298.     }
  299.  
  300.     /*
  301.      * Initialize the fields of the structure that won't be initialized
  302.      * by ConfigureListbox, or that ConfigureListbox requires to be
  303.      * initialized already (e.g. resource pointers).
  304.      */
  305.  
  306.     listPtr = (Listbox *) ckalloc(sizeof(Listbox));
  307.     listPtr->tkwin = new;
  308.     listPtr->interp = interp;
  309.     listPtr->numElements = 0;
  310.     listPtr->numInColumn = 0;
  311.     listPtr->elementPtr = NULL;
  312.     listPtr->normalBorder = NULL;
  313.     listPtr->fontPtr = NULL;
  314.     listPtr->fgColorPtr = NULL;
  315.     listPtr->textGC = None;
  316.     listPtr->selBorder = NULL;
  317.     listPtr->selFgColorPtr = NULL;
  318.     listPtr->selTextGC = NULL;
  319.     listPtr->geometry = NULL;
  320.     listPtr->topIndex = 0;
  321.     listPtr->redrawFirst = -1;
  322.     listPtr->scrollLines = 0;
  323.     listPtr->selectFirst = -1;
  324.     listPtr->selectLast = -2;
  325.     listPtr->scrollCmd = NULL;
  326.     listPtr->flags = 0;
  327.  
  328.     Tk_SetClass(listPtr->tkwin, "Colbox");
  329.     Tk_CreateEventHandler(listPtr->tkwin, ExposureMask|StructureNotifyMask,
  330.         ListboxEventProc, (ClientData) listPtr);
  331.     Tk_CreateEventHandler(listPtr->tkwin, ButtonPressMask|ButtonMotionMask,
  332.         ListboxMouseProc, (ClientData) listPtr);
  333.     Tk_CreateSelHandler(listPtr->tkwin, XA_STRING, ListboxFetchSelection,
  334.         (ClientData) listPtr, XA_STRING);
  335.     Tcl_CreateCommand(interp, Tk_PathName(listPtr->tkwin), ListboxWidgetCmd,
  336.         (ClientData) listPtr, (void (*)()) NULL);
  337.     if (ConfigureListbox(interp, listPtr, argc-2, argv+2, 0) != TCL_OK) {
  338.     goto error;
  339.     }
  340.  
  341.     interp->result = Tk_PathName(listPtr->tkwin);
  342.     return TCL_OK;
  343.  
  344.     error:
  345.     Tk_DestroyWindow(listPtr->tkwin);
  346.     return TCL_ERROR;
  347. }
  348.  
  349. /*
  350.  *--------------------------------------------------------------
  351.  *
  352.  * ListboxWidgetCmd --
  353.  *
  354.  *    This procedure is invoked to process the Tcl command
  355.  *    that corresponds to a widget managed by this module.
  356.  *    See the user documentation for details on what it does.
  357.  *
  358.  * Results:
  359.  *    A standard Tcl result.
  360.  *
  361.  * Side effects:
  362.  *    See the user documentation.
  363.  *
  364.  *--------------------------------------------------------------
  365.  */
  366.  
  367. static int
  368. ListboxWidgetCmd(clientData, interp, argc, argv)
  369.     ClientData clientData;        /* Information about listbox widget. */
  370.     Tcl_Interp *interp;            /* Current interpreter. */
  371.     int argc;                /* Number of arguments. */
  372.     char **argv;            /* Argument strings. */
  373. {
  374.     register Listbox *listPtr = (Listbox *) clientData;
  375.     int result = TCL_OK;
  376.     int length;
  377.     char c;
  378.  
  379.     if (argc < 2) {
  380.     Tcl_AppendResult(interp, "wrong # args: should be \"",
  381.         argv[0], " option ?arg arg ...?\"", (char *) NULL);
  382.     return TCL_ERROR;
  383.     }
  384.     Tk_Preserve((ClientData) listPtr);
  385.     c = argv[1][0];
  386.     length = strlen(argv[1]);
  387.     if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
  388.     if (argc == 2) {
  389.         result = Tk_ConfigureInfo(interp, listPtr->tkwin, configSpecs,
  390.             (char *) listPtr, (char *) NULL, 0);
  391.     } else if (argc == 3) {
  392.         result = Tk_ConfigureInfo(interp, listPtr->tkwin, configSpecs,
  393.             (char *) listPtr, argv[2], 0);
  394.     } else {
  395.         result = ConfigureListbox(interp, listPtr, argc-2, argv+2,
  396.             TK_CONFIG_ARGV_ONLY);
  397.     }
  398.     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) {
  399.     int first, last;
  400.  
  401.     if ((argc < 3) || (argc > 4)) {
  402.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  403.             argv[0], " delete firstIndex ?lastIndex?\"",
  404.             (char *) NULL);
  405.         goto error;
  406.     }
  407.     if (GetListboxIndex(interp, listPtr, argv[2], 0, &first) != TCL_OK) {
  408.         return TCL_ERROR;
  409.     }
  410.     if (argc == 3) {
  411.         last = first;
  412.     } else {
  413.         if (GetListboxIndex(interp, listPtr, argv[3], 0,
  414.             &last) != TCL_OK) {
  415.         goto error;
  416.         }
  417.         if (last < first) {
  418.         Tcl_AppendResult(interp, "bad listbox index \"", argv[3],
  419.             "\"", (char *) NULL);
  420.         goto error;
  421.         }
  422.     }
  423.     DeleteEls(listPtr, first, last+1-first);
  424.     } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
  425.     int index;
  426.     register Element *elPtr;
  427.  
  428.     if (argc != 3) {
  429.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  430.             argv[0], " get index\"", (char *) NULL);
  431.         goto error;
  432.     }
  433.     if (GetListboxIndex(interp, listPtr, argv[2], 0, &index) != TCL_OK) {
  434.         goto error;
  435.     }
  436.     for (elPtr = listPtr->elementPtr; index > 0;
  437.         index--, elPtr = elPtr->nextPtr)
  438.         /*EMPTY*/
  439.         ;
  440.     interp->result = elPtr->text;
  441.     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)) {
  442.     int index;
  443.  
  444.     if (argc < 3) {
  445.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  446.             argv[0], " insert index ?element ?element ...?\"",
  447.             (char *) NULL);
  448.         goto error;
  449.     }
  450.     if (argc > 3) {
  451.         if (GetListboxIndex(interp, listPtr, argv[2],
  452.             ZERO_OK|LAST_PLUS_ONE_OK, &index) != TCL_OK) {
  453.             goto error;
  454.         }
  455.         InsertEls(listPtr, index, argc-3, argv+3);
  456.     }
  457.     } else if ((c == 'n') && (strncmp(argv[1], "nearest", length) == 0)) {
  458.     int index, x, y;
  459.  
  460.     if (argc != 4) {
  461.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  462.             argv[0], " nearest x y\"", (char *) NULL);
  463.         goto error;
  464.     }
  465.     if (Tcl_GetInt(interp, argv[2], &x) != TCL_OK) {
  466.         goto error;
  467.     }
  468.     if (Tcl_GetInt(interp, argv[3], &y) != TCL_OK) {
  469.         goto error;
  470.     }
  471.     index = NearestListboxElement(listPtr, x, y);
  472.     sprintf(interp->result, "%d", index);
  473.     } else if ((c == 's') && (length >= 2)
  474.         && (strncmp(argv[1], "scan", length) == 0)) {
  475.     int y;
  476.  
  477.     if (argc != 4) {
  478.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  479.             argv[0], " scan mark|dragto y\"", (char *) NULL);
  480.         goto error;
  481.     }
  482.     if (Tcl_GetInt(interp, argv[3], &y) != TCL_OK) {
  483.         goto error;
  484.     }
  485.     if ((argv[2][0] == 'm')
  486.         && (strncmp(argv[2], "mark", strlen(argv[2])) == 0)) {
  487.         listPtr->scanMarkY = y;
  488.         listPtr->scanMarkIndex = listPtr->topIndex;
  489.     } else if ((argv[2][0] == 'd')
  490.         && (strncmp(argv[2], "dragto", strlen(argv[2])) == 0)) {
  491.         ListboxScanTo(listPtr, y);
  492.     } else {
  493.         Tcl_AppendResult(interp, "bad scan option \"", argv[2],
  494.             "\":  must be mark or dragto", (char *) NULL);
  495.         goto error;
  496.     }
  497.     } else if ((c == 's') && (length >= 4)
  498.         && (strncmp(argv[1], "sellimits", length) == 0)) {
  499.     sprintf(interp->result, "%d %d", listPtr->selectFirst,
  500.                         listPtr->selectLast);
  501.     } else if ((c == 's') && (length >= 4)
  502.         && (strncmp(argv[1], "select", length) == 0)) {
  503.     int index;
  504.  
  505.     if (argc != 4) {
  506.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  507.             argv[0], " select option index\"", (char *) NULL);
  508.         goto error;
  509.     }
  510.     if (GetListboxIndex(interp, listPtr, argv[3], 0, &index) != TCL_OK) {
  511.         goto error;
  512.     }
  513.     length = strlen(argv[2]);
  514.     c = argv[2][0];
  515.     if ((c == 'a') && (strncmp(argv[2], "adjust", length) == 0)) {
  516.         if (index < (listPtr->selectFirst + listPtr->selectLast)/2) {
  517.         listPtr->selectAnchor = listPtr->selectLast;
  518.         } else {
  519.         listPtr->selectAnchor = listPtr->selectFirst;
  520.         }
  521.         ListboxSelectTo(listPtr, index);
  522.     } else if ((c == 'f') && (strncmp(argv[2], "from", length) == 0)) {
  523.         ListboxSelectFrom(listPtr, index);
  524.     } else if ((c == 't') && (strncmp(argv[2], "to", length) == 0)) {
  525.         ListboxSelectTo(listPtr, index);
  526.     } else {
  527.         Tcl_AppendResult(interp, "bad select option \"", argv[2],
  528.             "\": must be adjust, from, or to", (char *) NULL);
  529.         goto error;
  530.     }
  531.     } else if ((c == 's') && (length >= 2)
  532.         && (strncmp(argv[1], "size", length) == 0)) {
  533.     sprintf(interp->result, "%d", listPtr->numElements);
  534.     } else if ((c == 'v') && (strncmp(argv[1], "view", length) == 0)) {
  535.     int index, lineNum;
  536.  
  537.     if ((argc != 3) && (argc != 4)) {
  538.         Tcl_AppendResult(interp, "wrong # args: should be \"",
  539.             argv[0], " view index ?lineNum?\"", (char *) NULL);
  540.         goto error;
  541.     }
  542.     if (GetListboxIndex(interp, listPtr, argv[2], ZERO_OK, &index)
  543.         != TCL_OK) {
  544.         goto error;
  545.     }
  546.     if (argc == 3) {
  547.         lineNum = 0;
  548.     } else {
  549.         if (Tcl_GetInt(interp, argv[3], &lineNum) != TCL_OK) {
  550.         goto error;
  551.         }
  552.     }
  553.     ChangeListboxView(listPtr, index, lineNum);
  554.     } else {
  555.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  556.         "\": must be configure, delete, get, insert, ",
  557.         "nearest, scan, select, size, or view", (char *) NULL);
  558.     goto error;
  559.     }
  560.     Tk_Release((ClientData) listPtr);
  561.     return result;
  562.  
  563.     error:
  564.     Tk_Release((ClientData) listPtr);
  565.     return TCL_ERROR;
  566. }
  567.  
  568. /*
  569.  *----------------------------------------------------------------------
  570.  *
  571.  * DestroyListbox --
  572.  *
  573.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  574.  *    to clean up the internal structure of a listbox at a safe time
  575.  *    (when no-one is using it anymore).
  576.  *
  577.  * Results:
  578.  *    None.
  579.  *
  580.  * Side effects:
  581.  *    Everything associated with the listbox is freed up.
  582.  *
  583.  *----------------------------------------------------------------------
  584.  */
  585.  
  586. static void
  587. DestroyListbox(clientData)
  588.     ClientData clientData;    /* Info about listbox widget. */
  589. {
  590.     register Listbox *listPtr = (Listbox *) clientData;
  591.     register Element *elPtr, *nextPtr;
  592.  
  593.     for (elPtr = listPtr->elementPtr; elPtr != NULL; ) {
  594.     nextPtr = elPtr->nextPtr;
  595.     ckfree((char *) elPtr);
  596.     elPtr = nextPtr;
  597.     }
  598.     if (listPtr->normalBorder != NULL) {
  599.     Tk_Free3DBorder(listPtr->normalBorder);
  600.     }
  601.     if (listPtr->fontPtr != NULL) {
  602.     Tk_FreeFontStruct(listPtr->fontPtr);
  603.     }
  604.     if (listPtr->fgColorPtr != NULL) {
  605.     Tk_FreeColor(listPtr->fgColorPtr);
  606.     }
  607.     if (listPtr->textGC != None) {
  608.     Tk_FreeGC(listPtr->textGC);
  609.     }
  610.     if (listPtr->selBorder != NULL) {
  611.     Tk_Free3DBorder(listPtr->selBorder);
  612.     }
  613.     if (listPtr->selFgColorPtr != NULL) {
  614.     Tk_FreeColor(listPtr->selFgColorPtr);
  615.     }
  616.     if (listPtr->selTextGC != None) {
  617.     Tk_FreeGC(listPtr->selTextGC);
  618.     }
  619.     if (listPtr->geometry != NULL) {
  620.     ckfree(listPtr->geometry);
  621.     }
  622.     if (listPtr->scrollCmd != NULL) {
  623.     ckfree(listPtr->scrollCmd);
  624.     }
  625.     ckfree((char *) listPtr);
  626. }
  627.  
  628. /*
  629.  *----------------------------------------------------------------------
  630.  *
  631.  * ConfigureListbox --
  632.  *
  633.  *    This procedure is called to process an argv/argc list, plus
  634.  *    the Tk option database, in order to configure (or reconfigure)
  635.  *    a listbox widget.
  636.  *
  637.  * Results:
  638.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  639.  *    returned, then interp->result contains an error message.
  640.  *
  641.  * Side effects:
  642.  *    Configuration information, such as colors, border width,
  643.  *    etc. get set for listPtr;  old resources get freed,
  644.  *    if there were any.
  645.  *
  646.  *----------------------------------------------------------------------
  647.  */
  648.  
  649. static int
  650. ConfigureListbox(interp, listPtr, argc, argv, flags)
  651.     Tcl_Interp *interp;        /* Used for error reporting. */
  652.     register Listbox *listPtr;    /* Information about widget;  may or may
  653.                  * not already have values for some fields. */
  654.     int argc;            /* Number of valid entries in argv. */
  655.     char **argv;        /* Arguments. */
  656.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  657. {
  658.     XGCValues gcValues;
  659.     GC new;
  660.     int width, charWidth, height, fontHeight;
  661.  
  662.     if (Tk_ConfigureWidget(interp, listPtr->tkwin, configSpecs,
  663.         argc, argv, (char *) listPtr, flags) != TCL_OK) {
  664.     return TCL_ERROR;
  665.     }
  666.  
  667.     /*
  668.      * A few options need special processing, such as parsing the
  669.      * geometry and setting the background from a 3-D border.
  670.      */
  671.  
  672.     Tk_SetBackgroundFromBorder(listPtr->tkwin, listPtr->normalBorder);
  673.  
  674.  
  675.     if ( listPtr->columns < 1 ) {
  676.     Tcl_AppendResult(interp, "Number of columns must be >= 1",
  677.         (char *) NULL);
  678.     return TCL_ERROR;
  679.     }
  680.     if ( listPtr->columns > MAX_COLS ) {
  681.     Tcl_AppendResult(interp, "Number of columns must be <= 50",
  682.         (char *) NULL);
  683.     return TCL_ERROR;
  684.     }
  685.     listPtr->numInColumn = ((listPtr->numElements) + (listPtr->columns) - 1)
  686.                             / listPtr->columns;
  687.  
  688.     gcValues.foreground = listPtr->fgColorPtr->pixel;
  689.     gcValues.font = listPtr->fontPtr->fid;
  690.     new = Tk_GetGC(listPtr->tkwin, GCForeground|GCFont, &gcValues);
  691.     if (listPtr->textGC != None) {
  692.     Tk_FreeGC(listPtr->textGC);
  693.     }
  694.     listPtr->textGC = new;
  695.  
  696.     gcValues.foreground = listPtr->selFgColorPtr->pixel;
  697.     gcValues.font = listPtr->fontPtr->fid;
  698.     new = Tk_GetGC(listPtr->tkwin, GCForeground|GCFont, &gcValues);
  699.     if (listPtr->selTextGC != None) {
  700.     Tk_FreeGC(listPtr->selTextGC);
  701.     }
  702.     listPtr->selTextGC = new;
  703.  
  704.     /*
  705.      * Register the desired geometry for the window, and arrange for
  706.      * the window to be redisplayed.
  707.      */
  708.  
  709.     if ((sscanf(listPtr->geometry, "%dx%d", &width, &height) != 2)
  710.         || (width <= 0) || (height <= 0)) {
  711.     Tcl_AppendResult(interp, "bad geometry \"",
  712.         listPtr->geometry, "\"", (char *) NULL);
  713.     return TCL_ERROR;
  714.     }
  715.     fontHeight = listPtr->fontPtr->ascent + listPtr->fontPtr->descent;
  716.     listPtr->lineHeight = fontHeight + 1 + 2*listPtr->selBorderWidth;
  717.     listPtr->numLines = (Tk_Height(listPtr->tkwin) - 2*listPtr->borderWidth)
  718.         / listPtr->lineHeight;
  719.     if (listPtr->numLines < 0) {
  720.     listPtr->numLines = 0;
  721.     }
  722.     charWidth = XTextWidth(listPtr->fontPtr, "0m", 2)/2;
  723.     width = width*charWidth + (15*fontHeight)/10 + 2*listPtr->borderWidth;
  724.     height = height*listPtr->lineHeight + 2*listPtr->borderWidth;
  725.     Tk_GeometryRequest(listPtr->tkwin, width, height);
  726.     Tk_SetInternalBorder(listPtr->tkwin, listPtr->borderWidth);
  727.     ListboxRedrawRange(listPtr, 0, listPtr->numInColumn-1);
  728.     ListboxUpdateScrollbar(listPtr);
  729.     return TCL_OK;
  730. }
  731.  
  732. /*
  733.  *--------------------------------------------------------------
  734.  *
  735.  * DisplayListbox --
  736.  *
  737.  *    This procedure redraws the contents of a listbox window.
  738.  *
  739.  * Results:
  740.  *    None.
  741.  *
  742.  * Side effects:
  743.  *    Information appears on the screen.
  744.  *
  745.  *--------------------------------------------------------------
  746.  */
  747.  
  748. static void
  749. DisplayListbox(clientData)
  750.     ClientData clientData;    /* Information about window. */
  751. {
  752.     register Listbox *listPtr = (Listbox *) clientData;
  753.     register Tk_Window tkwin = listPtr->tkwin;
  754.     GC gc;
  755.     int limit, x, y, margin, height, distance;
  756.     int colWidth, nInCol;
  757.     int xinc, yinc, i, j, col, cols;
  758.     Element *elPtr0, *elPtr[MAX_COLS];
  759.     int counter[MAX_COLS];
  760.     Element nullElement;
  761.  
  762.     if ((listPtr->tkwin == NULL) || !Tk_IsMapped(tkwin) 
  763.         || ((listPtr->redrawFirst == -1) && (listPtr->scrollLines == 0))) {
  764.     goto done;
  765.     }
  766.  
  767.     /*
  768.      * If we're going to redraw the whole box, then clear it first.  This
  769.      * is needed to erase the border, in case its style has changed.
  770.      */
  771.  
  772.     if ((listPtr->redrawFirst == 0)
  773.         && (listPtr->redrawLast == (listPtr->numInColumn-1))) {
  774.     XClearWindow(Tk_Display(tkwin), Tk_WindowId(tkwin));
  775.     listPtr->scrollLines = 0;
  776.     }
  777.  
  778.     /*
  779.      * In order to provide the smoothest possible scrolling and
  780.      * scanning, we use CopyArea operations to avoid redrawing big
  781.      * chunks of the listbox.  First, see if the window has moved
  782.      * so much that none of its current bits are of any use.
  783.      */
  784.  
  785.     if ((listPtr->scrollLines >= listPtr->numLines)
  786.         || (listPtr->scrollLines <= -listPtr->numLines)) {
  787.     ListboxRedrawRange(listPtr, 0, listPtr->numInColumn-1);
  788.     } else if (listPtr->scrollLines != 0) {
  789.  
  790.     /*
  791.      * Copying bits will help.  Figure out how much of the window's
  792.      * new contents can simply be copied into place, then copy them.
  793.      * Also figure which areas of the window must be explicitly
  794.      * redrawn, and arrange for them to be redrawn.
  795.      */
  796.  
  797.     distance = listPtr->scrollLines * listPtr->lineHeight;
  798.     if (listPtr->scrollLines > 0) {
  799.         y = listPtr->borderWidth;
  800.         height = Tk_Height(tkwin) - 2*listPtr->borderWidth - distance;
  801.         ListboxRedrawRange(listPtr, listPtr->topIndex,
  802.             listPtr->topIndex + listPtr->scrollLines - 1);
  803.     } else {
  804.         height = Tk_Height(tkwin) - 2*listPtr->borderWidth + distance;
  805.         y = listPtr->borderWidth - distance;
  806.         ListboxRedrawRange(listPtr, listPtr->topIndex
  807.             + listPtr->numLines + listPtr->scrollLines,
  808.             listPtr->topIndex + listPtr->numLines - 1);
  809.     }
  810.     XCopyArea(Tk_Display(tkwin), Tk_WindowId(tkwin), Tk_WindowId(tkwin),
  811.         listPtr->textGC, listPtr->borderWidth, y,
  812.         Tk_Width(tkwin) - 2*listPtr->borderWidth, height,
  813.         listPtr->borderWidth, y + distance);
  814.  
  815.     /*
  816.      * Some of the bits to be copied may have been obscured by other
  817.      * windows.  Read back the exposure events generated by the
  818.      * copy operation and schedule updates for the obscured areas.
  819.      */
  820.  
  821.     while (1) {
  822.         XEvent event;
  823.  
  824.         XWindowEvent(Tk_Display(tkwin), Tk_WindowId(tkwin), ExposureMask,
  825.             &event);
  826.         if (event.type == NoExpose) {
  827.         break;
  828.         } else if (event.type == GraphicsExpose) {
  829.         ListboxRedrawRange(listPtr,
  830.             NearestListboxElement(listPtr, 0,
  831.                 event.xgraphicsexpose.y),
  832.             NearestListboxElement(listPtr, 0,
  833.                 event.xgraphicsexpose.y
  834.                 + event.xgraphicsexpose.height));
  835.         if (event.xgraphicsexpose.count ==0) {
  836.             break;
  837.         }
  838.         } else if (event.type == Expose) {
  839.  
  840.         /* Expose events here are trouble, because there's no way
  841.          * to tell whether the exposure referred to the pre-copy
  842.          * window or the post-copy window.  This is a rare occurrence,
  843.          * so take the easy way out:  redraw everything.
  844.          */
  845.  
  846.         ListboxRedrawRange(listPtr, 0, listPtr->numInColumn-1);
  847.         }
  848.     }
  849.     }
  850.  
  851.     /*
  852.      * Iterate through all of the elements of the listbox, displaying all
  853.      * those that need redisplay.  Selected elements use a different
  854.      * GC and are raised.
  855.      */
  856.  
  857.     limit = listPtr->topIndex + listPtr->numLines - 1;
  858.     if (limit > listPtr->redrawLast) {
  859.     limit = listPtr->redrawLast;
  860.     }
  861.     if (listPtr->redrawFirst < listPtr->topIndex) {
  862.     listPtr->redrawFirst = listPtr->topIndex;
  863.     }
  864.     margin = listPtr->borderWidth
  865.         + (7*(listPtr->fontPtr->ascent + listPtr->fontPtr->descent))/10;
  866.     colWidth = (Tk_Width(tkwin) - 2*(listPtr->borderWidth)) / listPtr->columns;
  867.     nInCol = listPtr->numInColumn;
  868.     cols = listPtr->columns;
  869.     /* null element for when the last column(s) end early */
  870.     nullElement.textLength = 0;
  871.     nullElement.lBearing = 0;
  872.     nullElement.nextPtr = NULL;
  873.     nullElement.text[0] = '\0';
  874.     
  875.     /* scan down to the first line to redisplay */
  876.     for( i = 0, elPtr0 = listPtr->elementPtr;
  877.             (i < listPtr->redrawFirst) && (elPtr0 != NULL) && (i < limit);
  878.             elPtr0 = elPtr0->nextPtr, ++i )
  879.         /*EMPTY*/
  880.         ;
  881.     counter[0] = i;
  882.     elPtr[0] = elPtr0;
  883.     if( elPtr0 == NULL )
  884.     elPtr[0] = &nullElement;
  885.     /* set up an elPtr and counter for each column */
  886.     for( i = 1; i < cols; ++i ) {
  887.         elPtr[i] = elPtr[i-1];
  888.         for( j = 0; j < nInCol; ++j ) {
  889.             elPtr[i] = elPtr[i]->nextPtr;
  890.         /* this happens when the last column ends early */
  891.         if( elPtr[i] == NULL ) {
  892.             elPtr[i] = &nullElement;
  893.             break;
  894.         }
  895.     }
  896.         counter[i] = counter[i-1] + nInCol;
  897.     if( counter[i] >= listPtr->numElements )
  898.         counter[i] = listPtr->numElements - 1;
  899.     }
  900.     x = listPtr->borderWidth;
  901.     
  902.     /* loop through each row in the display */
  903.     while( (elPtr[0] != NULL) && (counter[0] <= limit) ) {
  904.         y = ((counter[0] - listPtr->topIndex) * listPtr->lineHeight) 
  905.                 + listPtr->borderWidth;
  906.         /* loop through each column in the row */
  907.         for( col = 0; col < cols; ++col ) {
  908.             /* is this a selected element? */
  909.             if ((listPtr->selectFirst >= 0)
  910.                     && (counter[col] >= listPtr->selectFirst)
  911.                     && (counter[col] <= listPtr->selectLast)
  912.                     && (elPtr[col] != NULL) && (elPtr[col] != &nullElement) ) {
  913.                 /* if so draw the background in the selected color */
  914.                 gc = listPtr->selTextGC; /* remember which gc to use for text */
  915.                 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  916.                     listPtr->selBorder, x + col*colWidth, y,
  917.                     colWidth,
  918.                     listPtr->lineHeight, listPtr->selBorderWidth,
  919.                     TK_RELIEF_RAISED);
  920.                 yinc = listPtr->selBorderWidth;
  921.                 xinc = -1;
  922.             } else {
  923.                 /* otherwise draw the background in the normal color */
  924.                 gc = listPtr->textGC; /* remember which gc to use for text */
  925.                 Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  926.                     listPtr->normalBorder, x + col*colWidth, y,
  927.                     colWidth,
  928.                     listPtr->lineHeight, 0, TK_RELIEF_FLAT);
  929.                 yinc = listPtr->selBorderWidth + 1;
  930.                 xinc = 0;
  931.             }
  932.             /* the last column may end early so check for NULL */
  933.             if( elPtr[col] != NULL ) {
  934.                 yinc += listPtr->fontPtr->ascent;
  935.                 xinc += margin - elPtr[col]->lBearing;
  936.                 XDrawString(Tk_Display(tkwin), Tk_WindowId(tkwin), gc,
  937.                     x + xinc + col*colWidth, y+yinc,
  938.                     elPtr[col]->text, elPtr[col]->textLength);
  939.                 elPtr[col] = elPtr[col]->nextPtr;
  940.             }
  941.             ++counter[col];
  942.         }
  943.     }
  944.  
  945.     /*
  946.      * Clear empty space past end of list, if there is any, then redraw
  947.      * the border around the list, if there is one.
  948.      */
  949.  
  950.     if ((listPtr->topIndex + listPtr->numLines) > listPtr->numInColumn) {
  951.     y = ((listPtr->numInColumn - listPtr->topIndex) * listPtr->lineHeight)
  952.         + listPtr->borderWidth;
  953.     } else {
  954.     y = (listPtr->numLines * listPtr->lineHeight) + listPtr->borderWidth;
  955.     }
  956.     height = Tk_Height(tkwin) - y - listPtr->borderWidth;
  957.     if (height > 0) {
  958.     Tk_Fill3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  959.         listPtr->normalBorder, listPtr->borderWidth, y,
  960.         Tk_Width(tkwin) - 2*listPtr->borderWidth, height, 0,
  961.         TK_RELIEF_FLAT);
  962.     }
  963.     if (listPtr->relief != TK_RELIEF_FLAT) {
  964.     Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  965.         listPtr->normalBorder, 0, 0, Tk_Width(tkwin),
  966.         Tk_Height(tkwin), listPtr->borderWidth,
  967.         listPtr->relief);
  968.     } else if (listPtr->borderWidth > 0) {
  969.     /*
  970.      * Non-zero border width but flat:  must clear out the border
  971.      * area on the right, since text could have overflowed into it.
  972.      */
  973.     XClearArea(Tk_Display(tkwin), Tk_WindowId(tkwin),
  974.         Tk_Width(tkwin)-listPtr->borderWidth, 0,
  975.         listPtr->borderWidth, Tk_Height(tkwin), False);
  976.     }
  977.  
  978.     done:
  979.     listPtr->redrawFirst = -1;
  980.     listPtr->scrollLines = 0;
  981. }
  982.  
  983. /*
  984.  *----------------------------------------------------------------------
  985.  *
  986.  * InsertEls --
  987.  *
  988.  *    Add new elements to a listbox widget.
  989.  *
  990.  * Results:
  991.  *    None.
  992.  *
  993.  * Side effects:
  994.  *    New information gets added to listPtr;  it will be redisplayed
  995.  *    soon, but not immediately.
  996.  *
  997.  *----------------------------------------------------------------------
  998.  */
  999.  
  1000. static void
  1001. InsertEls(listPtr, index, argc, argv)
  1002.     register Listbox *listPtr;    /* Listbox that is to get the new
  1003.                  * elements. */
  1004.     int index;            /* Add the new elements before this
  1005.                  * element. */
  1006.     int argc;            /* Number of new elements to add. */
  1007.     char **argv;        /* New elements (one per entry). */
  1008. {
  1009.     register Element *prevPtr, *newPtr;
  1010.     int length, dummy, i;
  1011.     XCharStruct bbox;
  1012.  
  1013.     /*
  1014.      * Find the element before which the new ones will be inserted.
  1015.      */
  1016.  
  1017.     if (index == 0) {
  1018.     prevPtr = NULL;
  1019.     } else {
  1020.     for (prevPtr = listPtr->elementPtr, index--; index > 0; index--) {
  1021.         prevPtr = prevPtr->nextPtr;
  1022.     }
  1023.     }
  1024.  
  1025.     /*
  1026.      * For each new element, create a record, initialize it, and link
  1027.      * it into the list of elements.
  1028.      */
  1029.  
  1030.     for (i = argc ; i > 0; i--, argv++, prevPtr = newPtr) {
  1031.     length = strlen(*argv);
  1032.     newPtr = (Element *) ckalloc(ElementSize(length));
  1033.     newPtr->textLength = length;
  1034.     XTextExtents(listPtr->fontPtr, *argv, 1, &dummy, &dummy, &dummy,
  1035.         &bbox);
  1036.     newPtr->lBearing = bbox.lbearing;
  1037.     if (prevPtr == NULL) {
  1038.         newPtr->nextPtr = listPtr->elementPtr;
  1039.         listPtr->elementPtr = newPtr;
  1040.     } else {
  1041.         newPtr->nextPtr = prevPtr->nextPtr;
  1042.         prevPtr->nextPtr = newPtr;
  1043.     }
  1044.     strcpy(newPtr->text, *argv);
  1045.     }
  1046.     listPtr->numElements += argc;
  1047.     listPtr->numInColumn = ((listPtr->numElements) + (listPtr->columns) - 1)
  1048.                             / (listPtr->columns);
  1049.  
  1050.     /*
  1051.      * Update the selection to account for the  renumbering that has just
  1052.      * occurred.  Then arrange for the new information to be displayed.
  1053.      */
  1054.  
  1055.     if (index <= listPtr->selectFirst) {
  1056.     listPtr->selectFirst += argc;
  1057.     }
  1058.     if (index <= listPtr->selectLast) {
  1059.     listPtr->selectLast += argc;
  1060.     }
  1061.     ListboxRedrawRange(listPtr, index, listPtr->numInColumn-1);
  1062.     ListboxUpdateScrollbar(listPtr);
  1063. }
  1064.  
  1065. /*
  1066.  *----------------------------------------------------------------------
  1067.  *
  1068.  * DeleteEls --
  1069.  *
  1070.  *    Remove one or more elements from a listbox widget.
  1071.  *
  1072.  * Results:
  1073.  *    None.
  1074.  *
  1075.  * Side effects:
  1076.  *    Memory gets freed, the listbox gets modified and (eventually)
  1077.  *    redisplayed.
  1078.  *
  1079.  *----------------------------------------------------------------------
  1080.  */
  1081.  
  1082. static void
  1083. DeleteEls(listPtr, index, count)
  1084.     register Listbox *listPtr;    /* Listbox widget to modify. */
  1085.     int index;            /* Index of first element to delete. */
  1086.     int count;            /* How many elements to delete. */
  1087. {
  1088.     register Element *prevPtr, *elPtr;
  1089.     int i;
  1090.  
  1091.     /*
  1092.      * Find the element just before the ones to delete.
  1093.      */
  1094.  
  1095.     if (index == 0) {
  1096.     prevPtr = NULL;
  1097.     } else {
  1098.     for (i = index-1, prevPtr = listPtr->elementPtr; i > 0; i--) {
  1099.         prevPtr = prevPtr->nextPtr;
  1100.     }
  1101.     }
  1102.  
  1103.     /*
  1104.      * Delete the requested number of elements.
  1105.      */
  1106.  
  1107.     for (i = count; i > 0; i--) {
  1108.     if (prevPtr == NULL) {
  1109.         elPtr = listPtr->elementPtr;
  1110.         listPtr->elementPtr = elPtr->nextPtr;
  1111.     } else {
  1112.         elPtr = prevPtr->nextPtr;
  1113.         prevPtr->nextPtr = elPtr->nextPtr;
  1114.     }
  1115.     ckfree((char *) elPtr);
  1116.     }
  1117.     listPtr->numElements -= count;
  1118.     listPtr->numInColumn = ((listPtr->numElements) + (listPtr->columns) - 1)
  1119.                             / (listPtr->columns);
  1120.  
  1121.     /*
  1122.       * Update the selection and viewing information to reflect the change
  1123.       * in the element numbering, and redisplay to slide information up over
  1124.       * the elements that were deleted.
  1125.      */
  1126.  
  1127.     if (index <= listPtr->selectFirst) {
  1128.     listPtr->selectFirst -= count;
  1129.     if (listPtr->selectFirst < index) {
  1130.         listPtr->selectFirst = index;
  1131.     }
  1132.     }
  1133.     if (index <= listPtr->selectLast) {
  1134.     listPtr->selectLast -= count;
  1135.     if (listPtr->selectLast < index) {
  1136.         listPtr->selectLast = index-1;
  1137.     }
  1138.     }
  1139.     if (listPtr->selectLast < listPtr->selectFirst) {
  1140.     listPtr->selectFirst = -1;
  1141.     }
  1142.     if (index <= listPtr->topIndex) {
  1143.     listPtr->topIndex -= count;
  1144.     if (listPtr->topIndex < index) {
  1145.         listPtr->topIndex = index;
  1146.     }
  1147.     }
  1148.     ListboxRedrawRange(listPtr, index, listPtr->numInColumn-1);
  1149.     ListboxUpdateScrollbar(listPtr);
  1150. }
  1151.  
  1152. /*
  1153.  *--------------------------------------------------------------
  1154.  *
  1155.  * ListboxEventProc --
  1156.  *
  1157.  *    This procedure is invoked by the Tk dispatcher for various
  1158.  *    events on listboxes.
  1159.  *
  1160.  * Results:
  1161.  *    None.
  1162.  *
  1163.  * Side effects:
  1164.  *    When the window gets deleted, internal structures get
  1165.  *    cleaned up.  When it gets exposed, it is redisplayed.
  1166.  *
  1167.  *--------------------------------------------------------------
  1168.  */
  1169.  
  1170. static void
  1171. ListboxEventProc(clientData, eventPtr)
  1172.     ClientData clientData;    /* Information about window. */
  1173.     XEvent *eventPtr;        /* Information about event. */
  1174. {
  1175.     Listbox *listPtr = (Listbox *) clientData;
  1176.  
  1177.     if (eventPtr->type == Expose) {
  1178.     ListboxRedrawRange(listPtr,
  1179.         NearestListboxElement(listPtr, 0,
  1180.             eventPtr->xexpose.y),
  1181.         NearestListboxElement(listPtr, 0,
  1182.             eventPtr->xexpose.y + eventPtr->xexpose.height) );
  1183.     } else if (eventPtr->type == DestroyNotify) {
  1184.     Tcl_DeleteCommand(listPtr->interp, Tk_PathName(listPtr->tkwin));
  1185.     listPtr->tkwin = NULL;
  1186.     if ((listPtr->redrawFirst != -1) || (listPtr->scrollLines != 0)) {
  1187.         Tk_CancelIdleCall(DisplayListbox, (ClientData) listPtr);
  1188.     }
  1189.     Tk_EventuallyFree((ClientData) listPtr, DestroyListbox);
  1190.     } else if (eventPtr->type == ConfigureNotify) {
  1191.     Tk_Preserve((ClientData) listPtr);
  1192.     listPtr->numLines = (Tk_Height(listPtr->tkwin)
  1193.         - 2*listPtr->borderWidth) / listPtr->lineHeight;
  1194.     ListboxRedrawRange(listPtr, 0, listPtr->numInColumn-1);
  1195.     ListboxUpdateScrollbar(listPtr);
  1196.     Tk_Release((ClientData) listPtr);
  1197.     }
  1198. }
  1199.  
  1200. /*
  1201.  *--------------------------------------------------------------
  1202.  *
  1203.  * GetListboxIndex --
  1204.  *
  1205.  *    Parse an index into a listbox and return either its value
  1206.  *    or an error.
  1207.  *
  1208.  * Results:
  1209.  *    A standard Tcl result.  If all went well, then *indexPtr is
  1210.  *    filled in with the index (into listPtr) corresponding to
  1211.  *    string.  Otherwise an error message is left in interp->result.
  1212.  *
  1213.  * Side effects:
  1214.  *    None.
  1215.  *
  1216.  *--------------------------------------------------------------
  1217.  */
  1218.  
  1219. static int
  1220. GetListboxIndex(interp, listPtr, string, flags, indexPtr)
  1221.     Tcl_Interp *interp;        /* For error messages. */
  1222.     Listbox *listPtr;        /* Listbox for which the index is being
  1223.                  * specified. */
  1224.     char *string;        /* Numerical index into listPtr's element
  1225.                  * list, or "end" to refer to last element. */
  1226.     int flags;            /* OR-ed combination of flag bits:  ZERO_OK
  1227.                  * means accept index 0, even if the list
  1228.                  * has no entries;  LAST_PLUS_ONE_OK means
  1229.                  * accept an index equal to the number of
  1230.                  * elements, and treat "end" as this index. */
  1231.     int *indexPtr;        /* Where to store converted relief. */
  1232. {
  1233.     int last;
  1234.  
  1235.     if (flags & LAST_PLUS_ONE_OK) {
  1236.     last = listPtr->numElements;
  1237.     } else {
  1238.     last = listPtr->numElements-1;
  1239.     }
  1240.     if (string[0] == 'e') {
  1241.     if (strncmp(string, "end", strlen(string)) != 0) {
  1242.         *indexPtr = last;
  1243.         badIndex:
  1244.         Tcl_AppendResult(interp, "bad listbox index \"", string,
  1245.             "\"", (char *) NULL);
  1246.         return TCL_ERROR;
  1247.     }
  1248.     *indexPtr = last;
  1249.     if (listPtr->numElements <= 0) {
  1250.         if (flags & ZERO_OK) {
  1251.         *indexPtr = 0;
  1252.         } else {
  1253.         interp->result = "listbox has no entries";
  1254.         return TCL_ERROR;
  1255.         }
  1256.     }
  1257.     } else {
  1258.     if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) {
  1259.         Tcl_SetResult(interp, (char *) NULL, TCL_STATIC);
  1260.         goto badIndex;
  1261.     }
  1262.     if ((*indexPtr < 0) || (*indexPtr > last)) {
  1263.         if ((*indexPtr != 0) || (listPtr->numElements != 0)
  1264.             || !(flags & ZERO_OK)) {
  1265.         goto badIndex;
  1266.         }
  1267.     }
  1268.     }
  1269.     return TCL_OK;
  1270. }
  1271.  
  1272. /*
  1273.  *----------------------------------------------------------------------
  1274.  *
  1275.  * ChangeListboxView --
  1276.  *
  1277.  *    Change the view on a listbox widget.
  1278.  *
  1279.  * Results:
  1280.  *    None.
  1281.  *
  1282.  * Side effects:
  1283.  *    What's displayed on the screen is changed.  If there is a
  1284.  *    scrollbar associated with this widget, then the scrollbar
  1285.  *    is instructed to change its display too.
  1286.  *
  1287.  *----------------------------------------------------------------------
  1288.  */
  1289.  
  1290. static void
  1291. ChangeListboxView(listPtr, index, lineNum)
  1292.     register Listbox *listPtr;        /* Information about widget. */
  1293.     int index;                /* Index of element in listPtr. */
  1294.     int lineNum;            /* Index of line on screen:  make
  1295.                      * element "elIndex" appear at this
  1296.                      * position on screen. */
  1297. {
  1298.     int newTop;
  1299.  
  1300.     if (listPtr->tkwin == NULL) {
  1301.     return;
  1302.     }
  1303.  
  1304.     newTop = index - lineNum;
  1305.     if (newTop >= listPtr->numInColumn) {
  1306.     newTop = listPtr->numInColumn-1;
  1307.     }
  1308.     if (newTop < 0) {
  1309.     newTop = 0;
  1310.     }
  1311.     if (listPtr->topIndex == newTop) {
  1312.     return;
  1313.     }
  1314.     if ((listPtr->scrollLines == 0) && (listPtr->redrawFirst == -1)) {
  1315.     Tk_DoWhenIdle(DisplayListbox, (ClientData) listPtr);
  1316.     }
  1317.     listPtr->scrollLines += listPtr->topIndex - newTop;
  1318.     listPtr->topIndex = newTop;
  1319.  
  1320.     ListboxUpdateScrollbar(listPtr);
  1321. }
  1322.  
  1323. /*
  1324.  *----------------------------------------------------------------------
  1325.  *
  1326.  * ListboxScanTo --
  1327.  *
  1328.  *    Given a y-coordinate (presumably of the curent mouse location)
  1329.  *    drag the view in the window to implement the scan operation.
  1330.  *
  1331.  * Results:
  1332.  *    None.
  1333.  *
  1334.  * Side effects:
  1335.  *    The view in the window may change.
  1336.  *
  1337.  *----------------------------------------------------------------------
  1338.  */
  1339.  
  1340. static void
  1341. ListboxScanTo(listPtr, y)
  1342.     register Listbox *listPtr;        /* Information about widget. */
  1343.     int y;                /* Y-coordinate to use for scan
  1344.                      * operation. */
  1345. {
  1346.     int newTopIndex;
  1347.  
  1348.     /*
  1349.      * Compute new top line for screen by amplifying the difference
  1350.      * between the current position and the place where the scan
  1351.      * started (the "mark" position).  If we run off the top or bottom
  1352.      * of the list, then reset the mark point so that the current
  1353.      * position continues to correspond to the edge of the window.
  1354.      * This means that the picture will start dragging as soon as the
  1355.      * mouse reverses direction (without this reset, might have to slide
  1356.      * mouse a long ways back before the picture starts moving again).
  1357.      */
  1358.  
  1359.     newTopIndex = listPtr->scanMarkIndex
  1360.         - (10*(y - listPtr->scanMarkY))/listPtr->lineHeight;
  1361.     if (newTopIndex >= listPtr->numInColumn) {
  1362.     newTopIndex = listPtr->scanMarkIndex = listPtr->numInColumn-1;
  1363.     listPtr->scanMarkY = y;
  1364.     }
  1365.     if (newTopIndex < 0) {
  1366.     newTopIndex = listPtr->scanMarkIndex = 0;
  1367.     listPtr->scanMarkY = y;
  1368.     } 
  1369.     ChangeListboxView(listPtr, newTopIndex, 0);
  1370. }
  1371.  
  1372. /*
  1373.  *----------------------------------------------------------------------
  1374.  *
  1375.  * NearestListboxElement --
  1376.  *
  1377.  *    Given a y-coordinate inside a listbox, compute the index of
  1378.  *    the element under that y-coordinate (or closest to that
  1379.  *    y-coordinate).
  1380.  *
  1381.  * Results:
  1382.  *    The return value is an index of an element of listPtr.  If
  1383.  *    listPtr has no elements, then 0 is always returned.
  1384.  *
  1385.  * Side effects:
  1386.  *    None.
  1387.  *
  1388.  *----------------------------------------------------------------------
  1389.  */
  1390.  
  1391. static int
  1392. NearestListboxElement(listPtr, x, y)
  1393.     register Listbox *listPtr;        /* Information about widget. */
  1394.     int x;                /* X-coordinate in listPtr's window. */
  1395.     int y;                /* Y-coordinate in listPtr's window. */
  1396. {
  1397.     int index;
  1398.     int colWidth;
  1399.  
  1400.     index = (y - listPtr->borderWidth)/listPtr->lineHeight;
  1401.     if (index >= listPtr->numLines) {
  1402.     index = listPtr->numLines-1;
  1403.     }
  1404.     if (index < 0) {
  1405.     index = 0;
  1406.     }
  1407.     index += listPtr->topIndex;
  1408.     colWidth = (Tk_Width(listPtr->tkwin) - 2*(listPtr->borderWidth))
  1409.                     / (listPtr->columns);
  1410.     index += (x/colWidth) * (listPtr->numInColumn);
  1411.     if (index >= listPtr->numElements) {
  1412.     index = listPtr->numElements-1;
  1413.     }
  1414.     return index;
  1415. }
  1416.  
  1417. /*
  1418.  *----------------------------------------------------------------------
  1419.  *
  1420.  * ListboxSelectFrom --
  1421.  *
  1422.  *    Start a new selection in a listbox.
  1423.  *
  1424.  * Results:
  1425.  *    None.
  1426.  *
  1427.  * Side effects:
  1428.  *    ListPtr claims the selection, and the selection becomes the
  1429.  *    single element given by index.
  1430.  *
  1431.  *----------------------------------------------------------------------
  1432.  */
  1433.  
  1434. static void
  1435. ListboxSelectFrom(listPtr, index)
  1436.     register Listbox *listPtr;        /* Information about widget. */
  1437.     int index;                /* Index of element that is to
  1438.                      * become the new selection. */
  1439. {
  1440.     if (listPtr->selectFirst == -1) {
  1441.     Tk_OwnSelection(listPtr->tkwin, ListboxLostSelection,
  1442.         (ClientData) listPtr);
  1443.     } else {
  1444.     ListboxRedrawRange(listPtr, listPtr->selectFirst, listPtr->selectLast);
  1445.     }
  1446.  
  1447.     listPtr->selectFirst = listPtr->selectLast = index;
  1448.     listPtr->selectAnchor = index;
  1449.     ListboxRedrawRange(listPtr, index, index);
  1450. }
  1451.  
  1452. /*
  1453.  *----------------------------------------------------------------------
  1454.  *
  1455.  * ListboxSelectTo --
  1456.  *
  1457.  *    Modify the selection by moving its un-anchored end.  This could
  1458.  *    make the selection either larger or smaller.
  1459.  *
  1460.  * Results:
  1461.  *    None.
  1462.  *
  1463.  * Side effects:
  1464.  *    The selection changes.
  1465.  *
  1466.  *----------------------------------------------------------------------
  1467.  */
  1468.  
  1469. static void
  1470. ListboxSelectTo(listPtr, index)
  1471.     register Listbox *listPtr;        /* Information about widget. */
  1472.     int index;                /* Index of element that is to
  1473.                      * become the "other" end of the
  1474.                      * selection. */
  1475. {
  1476.     int newFirst, newLast;
  1477.  
  1478.     /*
  1479.      * We should already own the selection, but grab it if we don't.
  1480.      */
  1481.  
  1482.     if (listPtr->selectFirst == -1) {
  1483.     ListboxSelectFrom(listPtr, index);
  1484.     }
  1485.  
  1486.     if (listPtr->selectAnchor < index) {
  1487.     newFirst = listPtr->selectAnchor;
  1488.     newLast = index;
  1489.     } else {
  1490.     newFirst = index;
  1491.     newLast = listPtr->selectAnchor;
  1492.     }
  1493.     if ((listPtr->selectFirst == newFirst)
  1494.         && (listPtr->selectLast == newLast)) {
  1495.     return;
  1496.     }
  1497.     if (listPtr->selectFirst != newFirst) {
  1498.     if (listPtr->selectFirst < newFirst) {
  1499.         ListboxRedrawRange(listPtr, listPtr->selectFirst, newFirst-1);
  1500.     } else {
  1501.         ListboxRedrawRange(listPtr, newFirst, listPtr->selectFirst-1);
  1502.     }
  1503.     listPtr->selectFirst = newFirst;
  1504.     }
  1505.     if (listPtr->selectLast != newLast) {
  1506.     if (listPtr->selectLast < newLast) {
  1507.         ListboxRedrawRange(listPtr, listPtr->selectLast+1, newLast);
  1508.     } else {
  1509.         ListboxRedrawRange(listPtr, newLast+1, listPtr->selectLast);
  1510.     }
  1511.     listPtr->selectLast = newLast;
  1512.     }
  1513. }
  1514.  
  1515. /*
  1516.  *----------------------------------------------------------------------
  1517.  *
  1518.  * ListboxFetchSelection --
  1519.  *
  1520.  *    This procedure is called back by Tk when the selection is
  1521.  *    requested by someone.  It returns part or all of the selection
  1522.  *    in a buffer provided by the caller.
  1523.  *
  1524.  * Results:
  1525.  *    The return value is the number of non-NULL bytes stored
  1526.  *    at buffer.  Buffer is filled (or partially filled) with a
  1527.  *    NULL-terminated string containing part or all of the selection,
  1528.  *    as given by offset and maxBytes.  The selection is returned
  1529.  *    as a Tcl list with one list element for each element in the
  1530.  *    listbox.
  1531.  *
  1532.  * Side effects:
  1533.  *    None.
  1534.  *
  1535.  *----------------------------------------------------------------------
  1536.  */
  1537.  
  1538. static int
  1539. ListboxFetchSelection(clientData, offset, buffer, maxBytes)
  1540.     ClientData clientData;        /* Information about listbox widget. */
  1541.     int offset;                /* Offset within selection of first
  1542.                      * byte to be returned. */
  1543.     char *buffer;            /* Location in which to place
  1544.                      * selection. */
  1545.     int maxBytes;            /* Maximum number of bytes to place
  1546.                      * at buffer, not including terminating
  1547.                      * NULL character. */
  1548. {
  1549.     register Listbox *listPtr = (Listbox *) clientData;
  1550.     register Element *elPtr;
  1551.     char **argv, *selection;
  1552.     int src, dst, length, count, argc;
  1553.  
  1554.     if (listPtr->selectFirst == -1) {
  1555.     return -1;
  1556.     }
  1557.  
  1558.     /*
  1559.      * Use Tcl_Merge to format the listbox elements into a suitable
  1560.      * Tcl list.
  1561.      */
  1562.  
  1563.     argc = listPtr->selectLast - listPtr->selectFirst + 1;
  1564.     argv = (char **) ckalloc((unsigned) (argc*sizeof(char *)));
  1565.     for (src = 0, dst = 0, elPtr = listPtr->elementPtr; ;
  1566.         src++, elPtr = elPtr->nextPtr) {
  1567.     if (src < listPtr->selectFirst) {
  1568.         continue;
  1569.     }
  1570.     if (src > listPtr->selectLast) {
  1571.         break;
  1572.     }
  1573.     argv[dst] = elPtr->text;
  1574.     dst++;
  1575.     }
  1576.     selection = Tcl_Merge(argc, argv);
  1577.  
  1578.     /*
  1579.      * Copy the requested portion of the selection to the buffer.
  1580.      */
  1581.  
  1582.     length = strlen(selection);
  1583.     count = length - offset;
  1584.     if (count <= 0) {
  1585.     count = 0;
  1586.     goto done;
  1587.     }
  1588.     if (count > maxBytes) {
  1589.     count = maxBytes;
  1590.     }
  1591.     memcpy((VOID *) buffer, (VOID *) selection + offset, count);
  1592.     /* WAS: bcopy(selection + offset, buffer, count); */
  1593.  
  1594.     done:
  1595.     buffer[count] = '\0';
  1596.     ckfree(selection);
  1597.     return count;
  1598. }
  1599.  
  1600. /*
  1601.  *----------------------------------------------------------------------
  1602.  *
  1603.  * ListboxLostSelection --
  1604.  *
  1605.  *    This procedure is called back by Tk when the selection is
  1606.  *    grabbed away from a listbox widget.
  1607.  *
  1608.  * Results:
  1609.  *    None.
  1610.  *
  1611.  * Side effects:
  1612.  *      If the variable 'noxsel' is false then
  1613.  *    the existing selection is unhighlighted, and the window is
  1614.  *    marked as not containing a selection.
  1615.  *      Otherwise there is no effect.
  1616.  *
  1617.  *----------------------------------------------------------------------
  1618.  */
  1619.  
  1620. static void
  1621. ListboxLostSelection(clientData)
  1622.     ClientData clientData;        /* Information about listbox widget. */
  1623. {
  1624.     register Listbox *listPtr = (Listbox *) clientData;
  1625.  
  1626.     if( listPtr->noxsel )
  1627.     return;
  1628.     ListboxRedrawRange(listPtr, listPtr->selectFirst, listPtr->selectLast);
  1629.     listPtr->selectFirst = -1;
  1630. }
  1631.  
  1632. /*
  1633.  *----------------------------------------------------------------------
  1634.  *
  1635.  * ListboxMouseProc --
  1636.  *
  1637.  *    This procedure responds to mouse actions to implement selection
  1638.  *    operations and dragging.
  1639.  *
  1640.  * Results:
  1641.  *    None.
  1642.  *
  1643.  * Side effects:
  1644.  *    The selection may change, and the window's view may change.
  1645.  *
  1646.  *----------------------------------------------------------------------
  1647.  */
  1648.  
  1649. static void
  1650. ListboxMouseProc(clientData, eventPtr)
  1651.     ClientData clientData;        /* Information about window. */
  1652.     register XEvent *eventPtr;        /* Information about event. */
  1653. {
  1654.     Listbox *listPtr = (Listbox *) clientData;
  1655.     int index;
  1656.  
  1657.     Tk_Preserve((ClientData) listPtr);
  1658.     if (eventPtr->type == ButtonPress) {
  1659.     if (eventPtr->xbutton.button == 1) {
  1660.         index = NearestListboxElement(listPtr,
  1661.                 eventPtr->xbutton.x, eventPtr->xbutton.y);
  1662.         if (eventPtr->xbutton.state == 0) {
  1663.         ListboxSelectFrom(listPtr, index);
  1664.         } else if (eventPtr->xbutton.state == ShiftMask) {
  1665.         if (index < (listPtr->selectFirst
  1666.             + listPtr->selectLast)/2) {
  1667.             listPtr->selectAnchor = listPtr->selectLast;
  1668.         } else {
  1669.             listPtr->selectAnchor = listPtr->selectFirst;
  1670.         }
  1671.         ListboxSelectTo(listPtr, index);
  1672.         }
  1673.     } else if ((eventPtr->xbutton.button == 3)
  1674.         || (eventPtr->xbutton.state == 0)) {
  1675.         listPtr->scanMarkY = eventPtr->xbutton.y;
  1676.         listPtr->scanMarkIndex = listPtr->topIndex;
  1677.     }
  1678.     } else if (eventPtr->type == MotionNotify) {
  1679.     if ((eventPtr->xmotion.state & ~ShiftMask) == Button1Mask) {
  1680.         index = NearestListboxElement(listPtr,
  1681.                     eventPtr->xmotion.x, eventPtr->xmotion.y);
  1682.         ListboxSelectTo(listPtr, index);
  1683.     } else if (eventPtr->xmotion.state == Button3Mask) {
  1684.         ListboxScanTo(listPtr, eventPtr->xmotion.y);
  1685.     }
  1686.     }
  1687.     Tk_Release((ClientData) listPtr);
  1688. }
  1689.  
  1690. /*
  1691.  *----------------------------------------------------------------------
  1692.  *
  1693.  * ListboxRedrawRange --
  1694.  *
  1695.  *    Ensure that a given range of elements is eventually redrawn on
  1696.  *    the display (if those elements in fact appear on the display).
  1697.  *
  1698.  * Results:
  1699.  *    None.
  1700.  *
  1701.  * Side effects:
  1702.  *    Information gets redisplayed.
  1703.  *
  1704.  *----------------------------------------------------------------------
  1705.  */
  1706.  
  1707. static void
  1708. ListboxRedrawRange(listPtr, first, last)
  1709.     register Listbox *listPtr;        /* Information about widget. */
  1710.     int first;                /* Index of first element in list
  1711.                      * that needs to be redrawn. */
  1712.     int last;                /* Index of last element in list
  1713.                      * that needs to be redrawn.  May
  1714.                      * be less than first;
  1715.                      * these just bracket a range. */
  1716. {
  1717.     int nCol = listPtr->numInColumn;
  1718.     int row_first, row_last;
  1719.     int col_first, col_last;
  1720.  
  1721.     if ((listPtr->tkwin == NULL) || !Tk_IsMapped(listPtr->tkwin)) {
  1722.     return;
  1723.     }
  1724.     /* adjust for multiple columns */
  1725.     if( nCol > 0 ) {
  1726.         row_first = first % nCol;
  1727.         col_first = first / nCol;
  1728.         row_last = last % nCol;
  1729.         col_last = last / nCol;
  1730.         if( col_first == col_last ) {
  1731.             first = row_first;
  1732.             last = row_last;
  1733.         } else if( col_first < col_last ) {
  1734.             first = 0;
  1735.             last = nCol - 1;
  1736.         }
  1737.     }
  1738.  
  1739.     if (listPtr->redrawFirst == -1) {
  1740.     listPtr->redrawFirst = first;
  1741.     listPtr->redrawLast = last;
  1742.     if (listPtr->scrollLines == 0) {
  1743.         Tk_DoWhenIdle(DisplayListbox, (ClientData) listPtr);
  1744.     }
  1745.     } else {
  1746.     if (first < listPtr->redrawFirst) {
  1747.         listPtr->redrawFirst = first;
  1748.     }
  1749.     if (last > listPtr->redrawLast) {
  1750.         listPtr->redrawLast = last;
  1751.     }
  1752.     }
  1753. }
  1754.  
  1755. /*
  1756.  *----------------------------------------------------------------------
  1757.  *
  1758.  * ListboxUpdateScrollbar --
  1759.  *
  1760.  *    This procedure is invoked whenever information has changed in
  1761.  *    a listbox in a way that would invalidate a scrollbar display.
  1762.  *    If there is an associated scrollbar, then this command updates
  1763.  *    it by invoking a Tcl command.
  1764.  *
  1765.  * Results:
  1766.  *    None.
  1767.  *
  1768.  * Side effects:
  1769.  *    A Tcl command is invoked, and an additional command may be
  1770.  *    invoked to process errors in the command.
  1771.  *
  1772.  *----------------------------------------------------------------------
  1773.  */
  1774.  
  1775. static void
  1776. ListboxUpdateScrollbar(listPtr)
  1777.     register Listbox *listPtr;        /* Information about widget. */
  1778. {
  1779.     char string[60];
  1780.     int result, last;
  1781.  
  1782.     if (listPtr->scrollCmd == NULL) {
  1783.     return;
  1784.     }
  1785.     last = listPtr->topIndex + listPtr->numLines;
  1786.     if (last >= listPtr->numInColumn) {
  1787.     last = listPtr->numInColumn-1;
  1788.     }
  1789.     sprintf(string, " %d %d %d %d", listPtr->numInColumn, listPtr->numLines,
  1790.         listPtr->topIndex, last);
  1791.     result = Tcl_VarEval(listPtr->interp, listPtr->scrollCmd, string,
  1792.         (char *) NULL);
  1793.     if (result != TCL_OK) {
  1794.     TkBindError(listPtr->interp);
  1795.     }
  1796. }
  1797.