home *** CD-ROM | disk | FTP | other *** search
/ InfoMagic Source Code 1993 July / THE_SOURCE_CODE_CD_ROM.iso / languages / tcl / tk3.3b1 / tkListbox.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-06-16  |  53.1 KB  |  1,798 lines

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