/* 
 * tkListbox.c --
 *
 *	This module implements listbox widgets for the Tk
 *	toolkit.  A listbox displays a collection of strings,
 *	one per line, and provides scrolling and selection.
 *      Individual elements can be selected/unselected.
 *
 * Multiple selection patch applied
 * Marc Ewing - meathead@acpub.duke.edu
 * Heiko Schroeder - heiko@POOL.Informatik.RWTH-Aachen.DE
 *
 * Copyright (c) 1990-1993 The Regents of the University of California.
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 * 
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */
/*
 * Copyright (c) 1994 Software Research Associates, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  Software Research
 * Associates makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkListbox.c,v 1.69 93/07/15 16:39:21 ouster Exp $ SPRITE (Berkeley)";
#endif

#include "tkConfig.h"
#include "default.h"
#include "tkInt.h"

/*
 * One record of the following type is kept for each element
 * associated with a listbox widget:
 */

typedef struct Element {
    int selected;               /* 1 if this element is selected */
    int textLength;		/* # non-NULL characters in text. */
    int lBearing;		/* Distance from first character's
				 * origin to left edge of character. */
    int pixelWidth;		/* Total width of element in pixels (including
				 * left bearing and right bearing). */
    struct Element *nextPtr;	/* Next in list of all elements of this
				 * listbox, or NULL for last element. */
#ifdef KANJI
    wchar *text;
#else
    char text[4];		/* Characters of this element, NULL-
				 * terminated.  The actual space allocated
				 * here will be as large as needed (> 4,
				 * most likely).  Must be the last field
				 * of the record. */
#endif /* KANJI */
} Element;

#ifndef KANJI
#define ElementSize(stringLength) \
	((unsigned) (sizeof(Element) - 3 + stringLength))
#endif /* !KANJI */

/*
 * A data structure of the following type is kept for each listbox
 * widget managed by this file:
 */

typedef struct {
    Tk_Window tkwin;		/* Window that embodies the listbox.  NULL
				 * means that the window has been destroyed
				 * but the data structures haven't yet been
				 * cleaned up.*/
    Display *display;		/* Display containing widget.  Used, among
				 * other things, so that resources can be
				 * freed even after tkwin has gone away. */
    Tcl_Interp *interp;		/* Interpreter associated with listbox. */
    int numElements;		/* Total number of elements in this listbox. */
    Element *elementPtr;	/* First in list of elements (NULL if no
				 * elements. */

    /*
     * Information used when displaying widget:
     */

    Tk_3DBorder normalBorder;	/* Used for drawing border around whole
				 * window, plus used for background. */
    int borderWidth;		/* Width of 3-D border around window. */
    int relief;			/* 3-D effect: TK_RELIEF_RAISED, etc. */
#ifdef KANJI
    XWSFontSet *fontPtr;
    XFontStruct *asciiFontPtr;
    XFontStruct *kanjiFontPtr;
#else
    XFontStruct *fontPtr;	/* Information about text font, or NULL. */
#endif /* KANJI */
    XColor *fgColorPtr;		/* Text color in normal mode. */
#ifdef KANJI
    XWSGC textGC;
#else
    GC textGC;			/* For drawing normal text. */
#endif /* KANJI */
    Tk_3DBorder selBorder;	/* Borders and backgrounds for selected
				 * elements. */
    int selBorderWidth;		/* Width of border around selection. */
    XColor *selFgColorPtr;	/* Foreground color for selected elements. */
#ifdef KANJI
    XWSGC selTextGC;
#else
    GC selTextGC;		/* For drawing selected text. */
#endif /* KANJI */
    char *geometry;		/* Desired geometry for window.  Malloc'ed. */
    int lineHeight;		/* Number of pixels allocated for each line
				 * in display. */
    int topIndex;		/* Index of top-most element visible in
				 * window. */
    int numLines;		/* Number of lines (elements) that fit
				 * in window at one time. */
    int setGrid;		/* Non-zero means pass gridding information
				 * to window manager. */

    /*
     * Information to support horizontal scrolling:
     */

    int maxWidth;		/* Width (in pixels) of widest string in
				 * listbox. */
    int xScrollUnit;		/* Number of pixels in one "unit" for
				 * horizontal scrolling (window scrolls
				 * horizontally in increments of this size).
				 * This is an average character size. */
    int xOffset;		/* The left edge of each string in the
				 * listbox is offset to the left by this
				 * many pixels (0 means no offset, positive
				 * means there is an offset). */

    /*
     * Information about what's selected, if any.
     */

    int numSelected;            /* Number of elements selected */
    int lastTo;                 /* Index of last select to */
    int selectAnchor;		/* Fixed end of a selection in progress
				 * (i.e. element at which selection was 
				 * started.) */
    int exportSelection;	/* Non-zero means tie internal listbox
				 * to X selection. */
    int multipleSelections;     /* Non-zero means only one area of selection
                                 * may exist. (Old behavior) */

    /*
     * Information for scanning:
     */

    int scanMarkX;		/* X-position at which scan started (e.g.
				 * button was pressed here). */
    int scanMarkY;		/* Y-position at which scan started (e.g.
				 * button was pressed here). */
    int scanMarkXOffset;	/* Value of "xOffset" field when scan
				 * started. */
    int scanMarkYIndex;		/* Index of line that was at top of window
				 * when scan started. */

    /*
     * Miscellaneous information:
     */

    Cursor cursor;		/* Current cursor for window, or None. */
    char *yScrollCmd;		/* Command prefix for communicating with
				 * vertical scrollbar.  NULL means no command
				 * to issue.  Malloc'ed. */
    char *xScrollCmd;		/* Command prefix for communicating with
				 * horizontal scrollbar.  NULL means no command
				 * to issue.  Malloc'ed. */
    int flags;			/* Various flag bits:  see below for
				 * definitions. */
} Listbox;

/*
 * Flag bits for buttons:
 *
 * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler
 *				has already been queued to redraw
 *				this window.
 * UPDATE_V_SCROLLBAR:		Non-zero means vertical scrollbar needs
 *				to be updated.
 * UPDATE_H_SCROLLBAR:		Non-zero means horizontal scrollbar needs
 *				to be updated.
 */

#define REDRAW_PENDING		1
#define UPDATE_V_SCROLLBAR	2
#define UPDATE_H_SCROLLBAR	4

/*
 * Information used for argv parsing:
 */

static Tk_ConfigSpec configSpecs[] = {
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_LISTBOX_BG_COLOR, Tk_Offset(Listbox, normalBorder),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
	DEF_LISTBOX_BG_MONO, Tk_Offset(Listbox, normalBorder),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
	(char *) NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
	(char *) NULL, 0, 0},
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
	DEF_LISTBOX_BORDER_WIDTH, Tk_Offset(Listbox, borderWidth), 0},
    {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
	DEF_LISTBOX_CURSOR, Tk_Offset(Listbox, cursor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection",
	"ExportSelection", DEF_LISTBOX_EXPORT_SELECTION,
	Tk_Offset(Listbox, exportSelection), 0},
    {TK_CONFIG_BOOLEAN, "-multipleselections", "multipleSelections",
        "MultipleSelections", "0",
        Tk_Offset(Listbox, multipleSelections), 0},
    {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
	(char *) NULL, 0, 0},
#ifdef KANJI
    {TK_CONFIG_FONT, "-font", "font", "Font",
	DEF_LISTBOX_FONT, Tk_Offset(Listbox, asciiFontPtr), 0},
    {TK_CONFIG_FONT, "-kanjifont", "kanjiFont", "KanjiFont",
	DEF_LISTBOX_KANJIFONT, Tk_Offset(Listbox, kanjiFontPtr), 0},
#else
    {TK_CONFIG_FONT, "-font", "font", "Font",
	DEF_LISTBOX_FONT, Tk_Offset(Listbox, fontPtr), 0},
#endif /* KANJI */
    {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
	DEF_LISTBOX_FG, Tk_Offset(Listbox, fgColorPtr), 0},
    {TK_CONFIG_STRING, "-geometry", "geometry", "Geometry",
	DEF_LISTBOX_GEOMETRY, Tk_Offset(Listbox, geometry), 0},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
	DEF_LISTBOX_RELIEF, Tk_Offset(Listbox, relief), 0},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
	DEF_LISTBOX_SELECT_COLOR, Tk_Offset(Listbox, selBorder),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground",
	DEF_LISTBOX_SELECT_MONO, Tk_Offset(Listbox, selBorder),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
	DEF_LISTBOX_SELECT_BD, Tk_Offset(Listbox, selBorderWidth), 0},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
	DEF_LISTBOX_SELECT_FG_COLOR, Tk_Offset(Listbox, selFgColorPtr),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
	DEF_LISTBOX_SELECT_FG_MONO, Tk_Offset(Listbox, selFgColorPtr),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
	DEF_LISTBOX_SET_GRID, Tk_Offset(Listbox, setGrid), 0},
    {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
	DEF_LISTBOX_SCROLL_COMMAND, Tk_Offset(Listbox, xScrollCmd),
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
	DEF_LISTBOX_SCROLL_COMMAND, Tk_Offset(Listbox, yScrollCmd),
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
	(char *) NULL, 0, 0}
};

/*
 * Forward declarations for procedures defined later in this file:
 */

static void		ChangeListboxOffset _ANSI_ARGS_((Listbox *listPtr,
			    int offset));
static void		ChangeListboxView _ANSI_ARGS_((Listbox *listPtr,
			    int index));
static int		ConfigureListbox _ANSI_ARGS_((Tcl_Interp *interp,
			    Listbox *listPtr, int argc, char **argv,
			    int flags));
static void		DeleteEls _ANSI_ARGS_((Listbox *listPtr, int first,
			    int last));
extern void		DestroyListbox _ANSI_ARGS_((ClientData clientData));
extern void		DisplayListbox _ANSI_ARGS_((ClientData clientData));
static int		GetListboxIndex _ANSI_ARGS_((Tcl_Interp *interp,
			    Listbox *listPtr, char *string, int endAfter,
			    int *indexPtr));
static void		InsertEls _ANSI_ARGS_((Listbox *listPtr, int index,
			    int argc, char **argv));
static void		ListboxComputeWidths _ANSI_ARGS_((Listbox *listPtr,
			    int fontChanged));
extern void		ListboxEventProc _ANSI_ARGS_((ClientData clientData,
			    XEvent *eventPtr));
extern int		ListboxFetchSelection _ANSI_ARGS_((
			    ClientData clientData, int offset, char *buffer,
			    int maxBytes));
#ifdef KANJI
extern int		ListboxFetchSelectionCtext _ANSI_ARGS_((
			    ClientData clientData, int offset, char *buffer,
			    int maxBytes));
#endif /* KANJI */
extern void		ListboxLostSelection _ANSI_ARGS_((
			    ClientData clientData));
static void		ListboxRedrawRange _ANSI_ARGS_((Listbox *listPtr,
			    int first, int last));
static void		ListboxScanTo _ANSI_ARGS_((Listbox *listPtr,
			    int x, int y));
static void		ListboxSetRange _ANSI_ARGS_((Listbox *listPtr,
			    int index1, int index2, int m));
static void		ListboxUnselectRange _ANSI_ARGS_((Listbox *listPtr,
			    int index1, int index2));
static void		ListboxSelectRange _ANSI_ARGS_((Listbox *listPtr,
			    int index1, int index2));
static void		ListboxInvertRange _ANSI_ARGS_((Listbox *listPtr,
			    int index1, int index2));
static void             ListboxSelectAdjust _ANSI_ARGS_((Listbox *listPtr,
                            int index));
static void		ListboxSelectFrom _ANSI_ARGS_((Listbox *listPtr,
			    int index));
static void		ListboxSelectTo _ANSI_ARGS_((Listbox *listPtr,
			    int index));
static void		ListboxUpdateHScrollbar _ANSI_ARGS_((Listbox *listPtr));
static void		ListboxUpdateVScrollbar _ANSI_ARGS_((Listbox *listPtr));
extern int		ListboxWidgetCmd _ANSI_ARGS_((ClientData clientData,
			    Tcl_Interp *interp, int argc, char **argv));
static int		NearestListboxElement _ANSI_ARGS_((Listbox *listPtr,
			    int y));

/*
 *--------------------------------------------------------------
 *
 * Tk_ListboxCmd --
 *
 *	This procedure is invoked to process the "listbox" Tcl
 *	command.  See the user documentation for details on what
 *	it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
Tk_ListboxCmd(clientData, interp, argc, argv)
    ClientData clientData;	/* Main window associated with
				 * interpreter. */
    Tcl_Interp *interp;		/* Current interpreter. */
    int argc;			/* Number of arguments. */
    char **argv;		/* Argument strings. */
{
    register Listbox *listPtr;
    Tk_Window new;
    Tk_Window tkwin = (Tk_Window) clientData;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " pathName ?options?\"", (char *) NULL);
	return TCL_ERROR;
    }

    new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
    if (new == NULL) {
	return TCL_ERROR;
    }

    /*
     * Initialize the fields of the structure that won't be initialized
     * by ConfigureListbox, or that ConfigureListbox requires to be
     * initialized already (e.g. resource pointers).
     */

    listPtr = (Listbox *) ckalloc(sizeof(Listbox));
    listPtr->tkwin = new;
    listPtr->display = Tk_Display(new);
    listPtr->interp = interp;
    listPtr->numElements = 0;
    listPtr->numSelected = 0;
    listPtr->lastTo = 0;
    listPtr->elementPtr = NULL;
    listPtr->normalBorder = NULL;
    listPtr->borderWidth = 0;
    listPtr->relief = TK_RELIEF_RAISED;
    listPtr->fontPtr = NULL;
#ifdef KANJI
    listPtr->asciiFontPtr = NULL;
    listPtr->kanjiFontPtr = NULL;
#endif /* KANJI */
    listPtr->fgColorPtr = NULL;
#ifdef KANJI
    listPtr->textGC = NULL;
#else
    listPtr->textGC = None;
#endif /* KANJI */
    listPtr->selBorder = NULL;
    listPtr->selBorderWidth = 0;
    listPtr->selFgColorPtr = None;
    listPtr->selTextGC = None;
    listPtr->geometry = NULL;
    listPtr->lineHeight = 0;
    listPtr->topIndex = 0;
    listPtr->numLines = 0;
    listPtr->setGrid = 0;
    listPtr->maxWidth = 0;
    listPtr->xScrollUnit = 0;
    listPtr->xOffset = 0;
    listPtr->selectAnchor = 0;
    listPtr->exportSelection = 1;
    listPtr->scanMarkX = 0;
    listPtr->scanMarkY = 0;
    listPtr->scanMarkXOffset = 0;
    listPtr->scanMarkYIndex = 0;
    listPtr->cursor = None;
    listPtr->xScrollCmd = NULL;
    listPtr->yScrollCmd = NULL;
    listPtr->flags = 0;

    Tk_SetClass(listPtr->tkwin, "Listbox");
    Tk_CreateEventHandler(listPtr->tkwin, ExposureMask|StructureNotifyMask,
	    ListboxEventProc, (ClientData) listPtr);
    Tk_CreateSelHandler(listPtr->tkwin, XA_STRING, ListboxFetchSelection,
	    (ClientData) listPtr, XA_STRING);
#ifdef KANJI
    {
	Atom textatom = Tk_InternAtom(tkwin, "TEXT");
	Atom ctextatom = Tk_InternAtom(tkwin, "COMPOUND_TEXT");
	Tk_CreateSelHandler(listPtr->tkwin, textatom,
			    ListboxFetchSelectionCtext,
			    (ClientData) listPtr, ctextatom);
	Tk_CreateSelHandler(listPtr->tkwin, ctextatom,
			    ListboxFetchSelectionCtext,
			    (ClientData) listPtr, ctextatom);
    }
#endif /* KANJI */
    Tcl_CreateCommand(interp, Tk_PathName(listPtr->tkwin), ListboxWidgetCmd,
	    (ClientData) listPtr, (void (*)()) NULL);
    if (ConfigureListbox(interp, listPtr, argc-2, argv+2, 0) != TCL_OK) {
	goto error;
    }

    interp->result = Tk_PathName(listPtr->tkwin);
    return TCL_OK;

    error:
    Tk_DestroyWindow(listPtr->tkwin);
    return TCL_ERROR;
}

/*
 *--------------------------------------------------------------
 *
 * ListboxWidgetCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to a widget managed by this module.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */

int
ListboxWidgetCmd(clientData, interp, argc, argv)
    ClientData clientData;		/* Information about listbox widget. */
    Tcl_Interp *interp;			/* Current interpreter. */
    int argc;				/* Number of arguments. */
    char **argv;			/* Argument strings. */
{
    register Listbox *listPtr = (Listbox *) clientData;
    int result = TCL_OK;
    int length;
    char c;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"",
		argv[0], " option ?arg arg ...?\"", (char *) NULL);
	return TCL_ERROR;
    }
    Tk_Preserve((ClientData) listPtr);
    c = argv[1][0];
    length = strlen(argv[1]);
    if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
	    && (length >= 2)) {
	if (argc == 2) {
	    result = Tk_ConfigureInfo(interp, listPtr->tkwin, configSpecs,
		    (char *) listPtr, (char *) NULL, 0);
	} else if (argc == 3) {
	    result = Tk_ConfigureInfo(interp, listPtr->tkwin, configSpecs,
		    (char *) listPtr, argv[2], 0);
	} else {
	    result = ConfigureListbox(interp, listPtr, argc-2, argv+2,
		    TK_CONFIG_ARGV_ONLY);
	}
    } else if ((c == 'c') && (strncmp(argv[1], "curselection", length) == 0)
	    && (length >= 2)) {
	int i;
	char index[20];
	register Element *elPtr;

	if (argc != 2) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " curselection\"",
		    (char *) NULL);
	    goto error;
	}
        if (listPtr->numSelected != 0) {
            elPtr = listPtr->elementPtr;
            for (i = 0, elPtr = listPtr->elementPtr; i < listPtr->numElements; 
                 i++, elPtr = elPtr->nextPtr) {
                if (elPtr->selected == 1) {
                    sprintf(index, "%d", i);
                    Tcl_AppendElement(interp, index);
                }
	     }
	}
    } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) {
	int first, last;

	if ((argc < 3) || (argc > 4)) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " delete firstIndex ?lastIndex?\"",
		    (char *) NULL);
	    goto error;
	}
	if (GetListboxIndex(interp, listPtr, argv[2], 0, &first) != TCL_OK) {
	    goto error;
	}
	if (argc == 3) {
	    last = first;
	} else {
	    if (GetListboxIndex(interp, listPtr, argv[3], 0, &last) != TCL_OK) {
		goto error;
	    }
	}
	DeleteEls(listPtr, first, last);
    } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
	int index;
	register Element *elPtr;

	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " get index\"", (char *) NULL);
	    goto error;
	}
	if (GetListboxIndex(interp, listPtr, argv[2], 0, &index) != TCL_OK) {
	    goto error;
	}
	if (index < 0) {
	    index = 0;
	}
	if (index >= listPtr->numElements) {
	    index = listPtr->numElements-1;
	}
	for (elPtr = listPtr->elementPtr; index > 0;
		index--, elPtr = elPtr->nextPtr) {
	    /* Empty loop body. */
	}
	if (elPtr != NULL) {
#ifdef KANJI
	    interp->result = Tk_DecodeWStr(elPtr->text);
#else
	    interp->result = elPtr->text;
#endif /* KANJI */
	}
    } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)) {
	int index;

	if (argc < 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " insert index ?element element ...?\"",
		    (char *) NULL);
	    goto error;
	}
	if (GetListboxIndex(interp, listPtr, argv[2], 1, &index)
		!= TCL_OK) {
	    goto error;
	}
	InsertEls(listPtr, index, argc-3, argv+3);
    } else if ((c == 'n') && (strncmp(argv[1], "nearest", length) == 0)) {
	int index, y;

	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " nearest y\"", (char *) NULL);
	    goto error;
	}
	if (Tcl_GetInt(interp, argv[2], &y) != TCL_OK) {
	    goto error;
	}
	index = NearestListboxElement(listPtr, y);
	sprintf(interp->result, "%d", index);
    } else if ((c == 's') && (length >=7)
            && (strncmp(argv[1], "selected", length) == 0)) {
	sprintf(interp->result, "%d", listPtr->numSelected);
    } else if ((c == 's') && (length >= 2)
	    && (strncmp(argv[1], "scan", length) == 0)) {
	int x, y;

	if (argc != 5) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " scan mark|dragto x y\"", (char *) NULL);
	    goto error;
	}
	if ((Tcl_GetInt(interp, argv[3], &x) != TCL_OK)
		|| (Tcl_GetInt(interp, argv[4], &y) != TCL_OK)) {
	    goto error;
	}
	if ((argv[2][0] == 'm')
		&& (strncmp(argv[2], "mark", strlen(argv[2])) == 0)) {
	    listPtr->scanMarkX = x;
	    listPtr->scanMarkY = y;
	    listPtr->scanMarkXOffset = listPtr->xOffset;
	    listPtr->scanMarkYIndex = listPtr->topIndex;
	} else if ((argv[2][0] == 'd')
		&& (strncmp(argv[2], "dragto", strlen(argv[2])) == 0)) {
	    ListboxScanTo(listPtr, x, y);
	} else {
	    Tcl_AppendResult(interp, "bad scan option \"", argv[2],
		    "\":  must be mark or dragto", (char *) NULL);
	    goto error;
	}
    } else if ((c == 's') && (length >= 2)
	    && (strncmp(argv[1], "select", length) == 0)) {
	int index;
	int x, y;

	if (argc < 3) {
	    Tcl_AppendResult(interp, "too few args: should be \"",
		    argv[0], " select option ?index?\"", (char *) NULL);
	    goto error;
	}
	length = strlen(argv[2]);
	c = argv[2][0];
	if ((c == 'c') && (argv[2] != NULL)
		&& (strncmp(argv[2], "clear", length) == 0)) {
	    if (argc > 5) {
		Tcl_AppendResult(interp, "wrong # args: should be \"",
			argv[0], " select clear ?index? ?index?\"", (char *) NULL);
		goto error;
	    }
	    if (argc == 3) {
		ListboxUnselectRange(listPtr, 0, listPtr->numElements);
	    } else {
		if (GetListboxIndex(interp, listPtr, argv[3], 0, &x) != TCL_OK) {
		    goto error;
		} else if (argc == 4) {
		    ListboxUnselectRange(listPtr, x, x);
		} else {
		    if (GetListboxIndex(interp, listPtr, argv[4], 0, &y) != TCL_OK) {
			goto error;
		    } else {
			ListboxUnselectRange(listPtr, x, y);
		    }
		}
	    }
	    goto done;
	}
	if ((c == 's') && (argv[2] != NULL)
		&& (strncmp(argv[2], "set", length) == 0)) {
	    if (argc > 5) {
		Tcl_AppendResult(interp, "wrong # args: should be \"",
			argv[0], " select set ?index? ?index?\"", (char *) NULL);
		goto error;
	    }
	    if (argc == 3) {
		ListboxSelectRange(listPtr, 0, listPtr->numElements);
	    } else {
		if (GetListboxIndex(interp, listPtr, argv[3], 0, &x) != TCL_OK) {
		    goto error;
		} else if (argc == 4) {
		    ListboxSelectRange(listPtr, x, x);
		} else {
		    if (GetListboxIndex(interp, listPtr, argv[4], 0, &y) != TCL_OK) {
			goto error;
		    } else {
			ListboxSelectRange(listPtr, x, y);
		    }
		}
	    }
	    goto done;
	}
	if ((c == 'i') && (argv[2] != NULL)
		&& (strncmp(argv[2], "invert", length) == 0)) {
	    if (argc > 5) {
		Tcl_AppendResult(interp, "wrong # args: should be \"",
			argv[0], " select invert ?index? ?index?\"", (char *) NULL);
		goto error;
	    }
	    if (argc == 3) {
		ListboxInvertRange(listPtr, 0, listPtr->numElements);
	    } else {
		if (GetListboxIndex(interp, listPtr, argv[3], 0, &x) != TCL_OK) {
		    goto error;
		} else if (argc == 4) {
		    ListboxInvertRange(listPtr, x, x);
		} else {
		    if (GetListboxIndex(interp, listPtr, argv[4], 0, &y) != TCL_OK) {
			goto error;
		    } else {
			ListboxInvertRange(listPtr, x, y);
		    }
		}
	    }
	    goto done;
	}
	if (argc != 4) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " select option index\"", (char *) NULL);
	    goto error;
	}
	if ((c == 'a') && (strncmp(argv[2], "adjust", length) == 0)) {
	    if (GetListboxIndex(interp, listPtr, argv[3], 1, &index)
		    != TCL_OK) {
		goto error;
	    }
	    ListboxSelectAdjust(listPtr, index);
	} else if ((c == 'f') && (strncmp(argv[2], "from", length) == 0)) {
	    if (GetListboxIndex(interp, listPtr, argv[3], 0, &index)
		    != TCL_OK) {
		goto error;
	    }
	    ListboxSelectFrom(listPtr, index);
	} else if ((c == 't') && (strncmp(argv[2], "to", length) == 0)) {
	    if (GetListboxIndex(interp, listPtr, argv[3], 1, &index)
		    != TCL_OK) {
		goto error;
	    }
	    ListboxSelectTo(listPtr, index);
        } else if ((c == 't') && (strncmp(argv[2], "test", length) == 0)) {
            register Element *elPtr;
  
	    if (GetListboxIndex(interp, listPtr, argv[3], 1, &index)
		    != TCL_OK) {
		goto error;
	    }
            elPtr = listPtr->elementPtr;
            while (index--) {
                elPtr = elPtr->nextPtr;
            }
            sprintf(interp->result, "%d", elPtr->selected);
	} else {
	    Tcl_AppendResult(interp, "bad select option \"", argv[2],
		    "\": must be adjust, clear, from, or to", (char *) NULL);
	    goto error;
	}
    } else if ((c == 's') && (length >= 2)
	    && (strncmp(argv[1], "size", length) == 0)) {
	sprintf(interp->result, "%d", listPtr->numElements);
    } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) {
	int index;

	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " xview index\"", (char *) NULL);
	    goto error;
	}
	if (Tcl_GetInt(interp, argv[2], &index) != TCL_OK) {
	    goto error;
	}
	ChangeListboxOffset(listPtr, index*listPtr->xScrollUnit);
    } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)) {
	int index;

	if (argc != 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
		    argv[0], " yview index\"", (char *) NULL);
	    goto error;
	}
	if (GetListboxIndex(interp, listPtr, argv[2], 0, &index) != TCL_OK) {
	    goto error;
	}
	ChangeListboxView(listPtr, index);
    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
		"\": must be configure, curselection, delete, get, ",
		"insert, nearest, scan, select, size, ",
		"xview, or yview", (char *) NULL);
	goto error;
    }
    done:
    Tk_Release((ClientData) listPtr);
    return result;

    error:
    Tk_Release((ClientData) listPtr);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyListbox --
 *
 *	This procedure is invoked by Tk_EventuallyFree or Tk_Release
 *	to clean up the internal structure of a listbox at a safe time
 *	(when no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the listbox is freed up.
 *
 *----------------------------------------------------------------------
 */

void
DestroyListbox(clientData)
    ClientData clientData;	/* Info about listbox widget. */
{
    register Listbox *listPtr = (Listbox *) clientData;
    register Element *elPtr, *nextPtr;

    /*
     * Free up all of the list elements.
     */

    for (elPtr = listPtr->elementPtr; elPtr != NULL; ) {
	nextPtr = elPtr->nextPtr;
#ifdef KANJI
	Tk_FreeWStr(elPtr->text);
#endif /* KANJI */
	ckfree((char *) elPtr);
	elPtr = nextPtr;
    }

    /*
     * Free up all the stuff that requires special handling, then
     * let Tk_FreeOptions handle all the standard option-related
     * stuff.
     */

#ifdef KANJI
    if (listPtr->fontPtr != NULL ) {
	Tk_FreeFontSet(listPtr->fontPtr);
    }
#endif /* KANJI */
#ifdef KANJI
    if (listPtr->textGC != NULL) {
	Tk_FreeGCSet(listPtr->display, listPtr->textGC);
    }
    if (listPtr->selTextGC != NULL) {
	Tk_FreeGCSet(listPtr->display, listPtr->selTextGC);
    }
#else
    if (listPtr->textGC != None) {
	Tk_FreeGC(listPtr->display, listPtr->textGC);
    }
    if (listPtr->selTextGC != None) {
	Tk_FreeGC(listPtr->display, listPtr->selTextGC);
    }
#endif /* KANJI */
    Tk_FreeOptions(configSpecs, (char *) listPtr, listPtr->display, 0);
    ckfree((char *) listPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureListbox --
 *
 *	This procedure is called to process an argv/argc list, plus
 *	the Tk option database, in order to configure (or reconfigure)
 *	a listbox widget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as colors, border width,
 *	etc. get set for listPtr;  old resources get freed,
 *	if there were any.
 *
 *----------------------------------------------------------------------
 */

static int
ConfigureListbox(interp, listPtr, argc, argv, flags)
    Tcl_Interp *interp;		/* Used for error reporting. */
    register Listbox *listPtr;	/* Information about widget;  may or may
				 * not already have values for some fields. */
    int argc;			/* Number of valid entries in argv. */
    char **argv;		/* Arguments. */
    int flags;			/* Flags to pass to Tk_ConfigureWidget. */
{
    XGCValues gcValues;
#ifdef KANJI
    XWSGC new;
#else
    GC new;
#endif /* KANJI */
    int width, height, fontHeight, oldExport;
    int pixelWidth, pixelHeight;

    oldExport = listPtr->exportSelection;
    if (Tk_ConfigureWidget(interp, listPtr->tkwin, configSpecs,
	    argc, argv, (char *) listPtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }

    /*
     * A few options need special processing, such as parsing the
     * geometry and setting the background from a 3-D border.
     */

    Tk_SetBackgroundFromBorder(listPtr->tkwin, listPtr->normalBorder);

#ifdef KANJI
    listPtr->fontPtr = Tk_GetFontSet(listPtr->asciiFontPtr, listPtr->kanjiFontPtr);
#endif /* KANJI */

    gcValues.foreground = listPtr->fgColorPtr->pixel;
#ifndef KANJI
    gcValues.font = listPtr->fontPtr->fid;
#endif /* !KANJI */
    gcValues.graphics_exposures = False;
#ifdef KANJI
    new = Tk_GetGCSet(listPtr->tkwin, GCForeground|GCFont|GCGraphicsExposures,
	    &gcValues, listPtr->fontPtr);
    if (listPtr->textGC != NULL) {
	Tk_FreeGCSet(listPtr->display, listPtr->textGC);
    }
#else
    new = Tk_GetGC(listPtr->tkwin, GCForeground|GCFont|GCGraphicsExposures,
	    &gcValues);
    if (listPtr->textGC != None) {
	Tk_FreeGC(listPtr->display, listPtr->textGC);
    }
#endif /* KANJI */
    listPtr->textGC = new;

    gcValues.foreground = listPtr->selFgColorPtr->pixel;
#ifndef KANJI
    gcValues.font = listPtr->fontPtr->fid;
#endif /* KANJI */
#ifdef KANJI
    new = Tk_GetGCSet(listPtr->tkwin, GCForeground|GCFont, &gcValues,
	    listPtr->fontPtr);
    if (listPtr->selTextGC != NULL) {
	Tk_FreeGCSet(listPtr->display, listPtr->selTextGC);
    }
#else
    new = Tk_GetGC(listPtr->tkwin, GCForeground|GCFont, &gcValues);
    if (listPtr->selTextGC != None) {
	Tk_FreeGC(listPtr->display, listPtr->selTextGC);
    }
#endif /* KANJI */
    listPtr->selTextGC = new;

    /*
     * Claim the selection if we've suddenly started exporting it.
     */

    if (listPtr->exportSelection && (!oldExport)
	    && (listPtr->numSelected != 0)) {
	Tk_OwnSelection(listPtr->tkwin, ListboxLostSelection,
		(ClientData) listPtr);
    }

    /*
     * Register the desired geometry for the window, and arrange for
     * the window to be redisplayed.
     */

    if ((sscanf(listPtr->geometry, "%dx%d", &width, &height) != 2)
	    || (width <= 0) || (height <= 0)) {
	Tcl_AppendResult(interp, "bad geometry \"",
		listPtr->geometry, "\"", (char *) NULL);
	return TCL_ERROR;
    }
    fontHeight = listPtr->fontPtr->ascent + listPtr->fontPtr->descent;
    listPtr->lineHeight = fontHeight + 1 + 2*listPtr->selBorderWidth;
    listPtr->numLines = (Tk_Height(listPtr->tkwin) - 2*listPtr->borderWidth)
	    / listPtr->lineHeight;
    if (listPtr->numLines < 0) {
	listPtr->numLines = 0;
    }
    ListboxComputeWidths(listPtr, 1);
    pixelWidth = width*listPtr->xScrollUnit + 2*listPtr->borderWidth
	    + 2*listPtr->selBorderWidth;
    pixelHeight = height*listPtr->lineHeight + 2*listPtr->borderWidth;
    Tk_GeometryRequest(listPtr->tkwin, pixelWidth, pixelHeight);
    Tk_SetInternalBorder(listPtr->tkwin, listPtr->borderWidth);
    if (listPtr->setGrid) {
	Tk_SetGrid(listPtr->tkwin, width, height, listPtr->xScrollUnit,
	    listPtr->lineHeight);
    }
    listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
    ListboxRedrawRange(listPtr, 0, listPtr->numElements-1);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * DisplayListbox --
 *
 *	This procedure redraws the contents of a listbox window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information appears on the screen.
 *
 *--------------------------------------------------------------
 */

void
DisplayListbox(clientData)
    ClientData clientData;	/* Information about window. */
{
    register Listbox *listPtr = (Listbox *) clientData;
    register Tk_Window tkwin = listPtr->tkwin;
    register Element *elPtr;
#ifdef KANJI
    XWSGC gc;
#else
    GC gc;
#endif /* KANJI */
    int i, limit, x, y, margin;
    Pixmap pixmap;

    listPtr->flags &= ~REDRAW_PENDING;
    if (listPtr->flags & UPDATE_V_SCROLLBAR) {
	ListboxUpdateVScrollbar(listPtr);
    }
    if (listPtr->flags & UPDATE_H_SCROLLBAR) {
	ListboxUpdateHScrollbar(listPtr);
    }
    listPtr->flags &= ~(REDRAW_PENDING|UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR);
    if ((listPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
	return;
    }

    /*
     * Redrawing is done in a temporary pixmap that is allocated
     * here and freed at the end of the procedure.  All drawing is
     * done to the pixmap, and the pixmap is copied to the screen
     * at the end of the procedure.  This provides the smoothest
     * possible visual effects (no flashing on the screen).
     */

    pixmap = XCreatePixmap(listPtr->display, Tk_WindowId(tkwin),
	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
    Tk_Fill3DRectangle(listPtr->display, pixmap, listPtr->normalBorder,
	    0, 0, Tk_Width(tkwin), Tk_Height(tkwin), listPtr->borderWidth,
	    listPtr->relief);

    /*
     * Iterate through all of the elements of the listbox, displaying each
     * in turn.  Selected elements use a different GC and have a raised
     * background.
     */

    limit = listPtr->topIndex + listPtr->numLines - 1;
    if (limit >= listPtr->numElements) {
	limit = listPtr->numElements-1;
    }
    margin = listPtr->selBorderWidth + listPtr->xScrollUnit/2;
    for (elPtr = listPtr->elementPtr, i = 0; (elPtr != NULL) && (i <= limit);
	    elPtr = elPtr->nextPtr, i++) {
	if (i < listPtr->topIndex) {
	    continue;
	}
	x = listPtr->borderWidth;
	y = ((i - listPtr->topIndex) * listPtr->lineHeight) 
		+ listPtr->borderWidth;
	gc = listPtr->textGC;
	if (elPtr->selected == 1) {
	    gc = listPtr->selTextGC;
	    Tk_Fill3DRectangle(listPtr->display, pixmap,
		    listPtr->selBorder, x, y,
		    Tk_Width(tkwin) - 2*listPtr->borderWidth,
		    listPtr->lineHeight, listPtr->selBorderWidth,
		    TK_RELIEF_RAISED);
	}
	y += listPtr->fontPtr->ascent + listPtr->selBorderWidth;
	x += margin - elPtr->lBearing - listPtr->xOffset;
#ifdef KANJI
	TkWSDrawString(listPtr->display, pixmap, gc, x, y,
		elPtr->text, elPtr->textLength);
#else
	XDrawString(listPtr->display, pixmap, gc, x, y,
		elPtr->text, elPtr->textLength);
#endif /* KANJI */
    }

    /*
     * Redraw the border for the listbox to make sure that it's on top
     * of any of the text of the listbox entries.
     */

    Tk_Draw3DRectangle(listPtr->display, pixmap,
	    listPtr->normalBorder, 0, 0, Tk_Width(tkwin),
	    Tk_Height(tkwin), listPtr->borderWidth,
	    listPtr->relief);
#ifdef KANJI
    XCopyArea(listPtr->display, pixmap, Tk_WindowId(tkwin),
	    listPtr->textGC->fe[0].gc, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
	    0, 0);
#else
    XCopyArea(listPtr->display, pixmap, Tk_WindowId(tkwin),
	    listPtr->textGC, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
	    0, 0);
#endif /* KANJI */
    XFreePixmap(listPtr->display, pixmap);
}

/*
 *----------------------------------------------------------------------
 *
 * InsertEls --
 *
 *	Add new elements to a listbox widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	New information gets added to listPtr;  it will be redisplayed
 *	soon, but not immediately.
 *
 *----------------------------------------------------------------------
 */

static void
InsertEls(listPtr, index, argc, argv)
    register Listbox *listPtr;	/* Listbox that is to get the new
				 * elements. */
    int index;			/* Add the new elements before this
				 * element. */
    int argc;			/* Number of new elements to add. */
    char **argv;		/* New elements (one per entry). */
{
    register Element *prevPtr, *newPtr;
    int length, dummy, i, oldMaxWidth;
    XCharStruct bbox;
    int selected;

    /*
     * Find the element before which the new ones will be inserted.
     */

    if (index <= 0) {
	index = 0;
    }
    if (index > listPtr->numElements) {
	index = listPtr->numElements;
    }
    if (index == 0) {
	prevPtr = NULL;
    } else {
	for (prevPtr = listPtr->elementPtr, i = index - 1; i > 0; i--) {
	    prevPtr = prevPtr->nextPtr;
	}
    }

    selected = (prevPtr) && (prevPtr->selected) && 
	       (prevPtr->nextPtr) && (prevPtr->nextPtr->selected);

    /*
     * For each new element, create a record, initialize it, and link
     * it into the list of elements.
     */

    oldMaxWidth = listPtr->maxWidth;
    for (i = argc ; i > 0; i--, argv++, prevPtr = newPtr) {
#ifdef KANJI
	newPtr = (Element *)ckalloc((unsigned)sizeof(Element));
	newPtr->text = Tk_GetWStr(listPtr->interp, *argv);
	newPtr->textLength = Tcl_WStrlen(newPtr->text);
#else
	length = strlen(*argv);
	newPtr = (Element *) ckalloc(ElementSize(length));
	newPtr->selected = selected;
	newPtr->textLength = length;
	strcpy(newPtr->text, *argv);
#endif /* KANJI */
#ifdef KANJI
	TkWSTextExtents(listPtr->textGC, newPtr->text, newPtr->textLength,
		&dummy, &dummy, &bbox);
#else
	XTextExtents(listPtr->fontPtr, newPtr->text, newPtr->textLength,
		&dummy, &dummy, &dummy, &bbox);
#endif /* KANJI */
	newPtr->lBearing = bbox.lbearing;
	newPtr->pixelWidth = bbox.rbearing - bbox.lbearing;
	if (newPtr->pixelWidth > listPtr->maxWidth) {
	    listPtr->maxWidth = newPtr->pixelWidth;
	}

	if (prevPtr == NULL) {
	    newPtr->nextPtr = listPtr->elementPtr;
	    listPtr->elementPtr = newPtr;
	} else {
	    newPtr->nextPtr = prevPtr->nextPtr;
	    prevPtr->nextPtr = newPtr;
	}
    }
    listPtr->numElements += argc;
    if (selected != 0) {
	listPtr->numSelected += argc;
    }

    /*
     * Arrange for the new information to be displayed.
     */

    listPtr->flags |= UPDATE_V_SCROLLBAR;
    if (listPtr->maxWidth != oldMaxWidth) {
	listPtr->flags |= UPDATE_H_SCROLLBAR;
    }
    ListboxRedrawRange(listPtr, index, listPtr->numElements-1);
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteEls --
 *
 *	Remove one or more elements from a listbox widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory gets freed, the listbox gets modified and (eventually)
 *	redisplayed.
 *
 *----------------------------------------------------------------------
 */

static void
DeleteEls(listPtr, first, last)
    register Listbox *listPtr;	/* Listbox widget to modify. */
    int first;			/* Index of first element to delete. */
    int last;			/* Index of last element to delete. */
{
    register Element *prevPtr, *elPtr;
    int count, i, widthChanged;

    /*
     * Adjust the range to fit within the existing elements of the
     * listbox, and make sure there's something to delete.
     */

    if (first <= 0) {
	first = 0;
    } else if (first >= listPtr->numElements) {
	first = listPtr->numElements-1;
    }
    if (last >= listPtr->numElements) {
	last = listPtr->numElements-1;
    }
    count = last + 1 - first;
    if (count <= 0) {
	return;
    }

    /*
     * Find the element just before the ones to delete.
     */

    if (first == 0) {
	prevPtr = NULL;
    } else {
	for (i = first-1, prevPtr = listPtr->elementPtr; i > 0; i--) {
	    prevPtr = prevPtr->nextPtr;
	}
    }

    /*
     * Delete the requested number of elements.
     */

    widthChanged = 0;
    for (i = count; i > 0; i--) {
	if (prevPtr == NULL) {
	    elPtr = listPtr->elementPtr;
	    listPtr->elementPtr = elPtr->nextPtr;
	} else {
	    elPtr = prevPtr->nextPtr;
	    prevPtr->nextPtr = elPtr->nextPtr;
	}
	if (elPtr->pixelWidth == listPtr->maxWidth) {
	    widthChanged = 1;
	}
        listPtr->numSelected -= elPtr->selected;
#ifdef KANJI
	Tk_FreeWStr(elPtr->text);
#endif /* KANJI */
	ckfree((char *) elPtr);
    }
    listPtr->numElements -= count;

    /*
     * Update the selection and viewing information to reflect the change
     * in the element numbering, and redisplay to slide information up over
     * the elements that were deleted.
     */

    if (first <= listPtr->topIndex) {
	listPtr->topIndex -= count;
	if (listPtr->topIndex < first) {
	    listPtr->topIndex = first;
	}
    }
    listPtr->flags |= UPDATE_V_SCROLLBAR;
    if (widthChanged) {
	ListboxComputeWidths(listPtr, 0);
	listPtr->flags |= UPDATE_H_SCROLLBAR;
    }
    ListboxRedrawRange(listPtr, first, listPtr->numElements-1);
}

/*
 *--------------------------------------------------------------
 *
 * ListboxEventProc --
 *
 *	This procedure is invoked by the Tk dispatcher for various
 *	events on listboxes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	When the window gets deleted, internal structures get
 *	cleaned up.  When it gets exposed, it is redisplayed.
 *
 *--------------------------------------------------------------
 */

void
ListboxEventProc(clientData, eventPtr)
    ClientData clientData;	/* Information about window. */
    XEvent *eventPtr;		/* Information about event. */
{
    Listbox *listPtr = (Listbox *) clientData;

    if (eventPtr->type == Expose) {
	ListboxRedrawRange(listPtr,
		NearestListboxElement(listPtr, eventPtr->xexpose.y),
		NearestListboxElement(listPtr, eventPtr->xexpose.y
		+ eventPtr->xexpose.height));
    } else if (eventPtr->type == DestroyNotify) {
	Tcl_DeleteCommand(listPtr->interp, Tk_PathName(listPtr->tkwin));
	listPtr->tkwin = NULL;
	if (listPtr->flags & REDRAW_PENDING) {
	    Tk_CancelIdleCall(DisplayListbox, (ClientData) listPtr);
	}
	Tk_EventuallyFree((ClientData) listPtr, DestroyListbox);
    } else if (eventPtr->type == ConfigureNotify) {
	Tk_Preserve((ClientData) listPtr);
	listPtr->numLines = (Tk_Height(listPtr->tkwin)
		- 2*listPtr->borderWidth) / listPtr->lineHeight;
	listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
	ListboxRedrawRange(listPtr, 0, listPtr->numElements-1);
	Tk_Release((ClientData) listPtr);
    }
}

/*
 *--------------------------------------------------------------
 *
 * GetListboxIndex --
 *
 *	Parse an index into a listbox and return either its value
 *	or an error.
 *
 * Results:
 *	A standard Tcl result.  If all went well, then *indexPtr is
 *	filled in with the index (into listPtr) corresponding to
 *	string.  Otherwise an error message is left in interp->result.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

static int
GetListboxIndex(interp, listPtr, string, endAfter, indexPtr)
    Tcl_Interp *interp;		/* For error messages. */
    Listbox *listPtr;		/* Listbox for which the index is being
				 * specified. */
    char *string;		/* Numerical index into listPtr's element
				 * list, or "end" to refer to last element. */
    int endAfter;		/* 0 means "end" refers to the index of the
				 * last element, 1 means it refers to the
				 * element after the last one. */
    int *indexPtr;		/* Where to store converted index. */
{
    if (string[0] == 'e') {
	if (strncmp(string, "end", strlen(string)) != 0) {
	    badIndex:
	    Tcl_AppendResult(interp, "bad listbox index \"", string,
		    "\"", (char *) NULL);
	    return TCL_ERROR;
	}
	*indexPtr = listPtr->numElements;
	if (!endAfter) {
	    *indexPtr -= 1;
	}
	if (listPtr->numElements <= 0) {
	    *indexPtr = 0;
	}
    } else {
	if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) {
	    Tcl_ResetResult(interp);
	    goto badIndex;
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ChangeListboxView --
 *
 *	Change the view on a listbox widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	What's displayed on the screen is changed.  If there is a
 *	scrollbar associated with this widget, then the scrollbar
 *	is instructed to change its display too.
 *
 *----------------------------------------------------------------------
 */

static void
ChangeListboxView(listPtr, index)
    register Listbox *listPtr;		/* Information about widget. */
    int index;				/* Index of element in listPtr. */
{
    if (index >= listPtr->numElements) {
	index = listPtr->numElements-1;
    }
    if (index < 0) {
	index = 0;
    }
    if (listPtr->topIndex != index) {
	listPtr->topIndex = index;
	if (!(listPtr->flags & REDRAW_PENDING)) {
	    Tk_DoWhenIdle(DisplayListbox, (ClientData) listPtr);
	    listPtr->flags |= REDRAW_PENDING;
	}
	listPtr->flags |= UPDATE_V_SCROLLBAR;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ChangListboxOffset --
 *
 *	Change the horizontal offset for a listbox.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The listbox may be redrawn to reflect its new horizontal
 *	offset.
 *
 *----------------------------------------------------------------------
 */

static void
ChangeListboxOffset(listPtr, offset)
    register Listbox *listPtr;		/* Information about widget. */
    int offset;				/* Desired new "xOffset" for
					 * listbox. */
{
    int maxOffset;

    /*
     * Make sure that the new offset is within the allowable range, and
     * round it off to an even multiple of xScrollUnit.
     */

    maxOffset = listPtr->maxWidth + (listPtr->xScrollUnit-1)
	    - (Tk_Width(listPtr->tkwin) - 2*listPtr->borderWidth
	    - 2*listPtr->selBorderWidth - listPtr->xScrollUnit);
    if (offset > maxOffset) {
	offset = maxOffset;
    }
    if (offset < 0) {
	offset = 0;
    }
    offset -= offset%listPtr->xScrollUnit;
    if (offset != listPtr->xOffset) {
	listPtr->xOffset = offset;
	listPtr->flags |= UPDATE_H_SCROLLBAR;
	ListboxRedrawRange(listPtr, 0, listPtr->numElements);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxScanTo --
 *
 *	Given a point (presumably of the curent mouse location)
 *	drag the view in the window to implement the scan operation.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The view in the window may change.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxScanTo(listPtr, x, y)
    register Listbox *listPtr;		/* Information about widget. */
    int x;				/* X-coordinate to use for scan
					 * operation. */
    int y;				/* Y-coordinate to use for scan
					 * operation. */
{
    int newTopIndex, newOffset;

    /*
     * Compute new top line for screen by amplifying the difference
     * between the current position and the place where the scan
     * started (the "mark" position).  If we run off the top or bottom
     * of the list, then reset the mark point so that the current
     * position continues to correspond to the edge of the window.
     * This means that the picture will start dragging as soon as the
     * mouse reverses direction (without this reset, might have to slide
     * mouse a long ways back before the picture starts moving again).
     */

    newTopIndex = listPtr->scanMarkYIndex
	    - (10*(y - listPtr->scanMarkY))/listPtr->lineHeight;
    if (newTopIndex >= listPtr->numElements) {
	newTopIndex = listPtr->scanMarkYIndex = listPtr->numElements-1;
	listPtr->scanMarkY = y;
    } else if (newTopIndex < 0) {
	newTopIndex = listPtr->scanMarkYIndex = 0;
	listPtr->scanMarkY = y;
    }
    ChangeListboxView(listPtr, newTopIndex);

    /*
     * Compute new left edge for display in a similar fashion by amplifying
     * the difference between the current position and the place where the
     * scan started.
     */

    newOffset = listPtr->scanMarkXOffset - (10*(x - listPtr->scanMarkX));
    if (newOffset >= listPtr->maxWidth) {
	newOffset = listPtr->scanMarkXOffset = listPtr->maxWidth;
	listPtr->scanMarkX = x;
    } else if (newOffset < 0) {
	newOffset = listPtr->scanMarkXOffset = 0;
	listPtr->scanMarkX = x;
    }
    ChangeListboxOffset(listPtr, newOffset);
}

/*
 *----------------------------------------------------------------------
 *
 * NearestListboxElement --
 *
 *	Given a y-coordinate inside a listbox, compute the index of
 *	the element under that y-coordinate (or closest to that
 *	y-coordinate).
 *
 * Results:
 *	The return value is an index of an element of listPtr.  If
 *	listPtr has no elements, then 0 is always returned.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
NearestListboxElement(listPtr, y)
    register Listbox *listPtr;		/* Information about widget. */
    int y;				/* Y-coordinate in listPtr's window. */
{
    int index;

    index = (y - listPtr->borderWidth)/listPtr->lineHeight;
    if (index >= listPtr->numLines) {
	index = listPtr->numLines-1;
    }
    if (index < 0) {
	index = 0;
    }
    index += listPtr->topIndex;
    if (index >= listPtr->numElements) {
	index = listPtr->numElements-1;
    }
    return index;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxSelectAdjust --
 *
 *      Adjust the selection
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      ListPtr claims the selection.  The selection is adjusted
 *      to include elements between index and the selected element
 *      closest to index.  If there is no selection, elements 0
 *      through index are selected.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxSelectAdjust(listPtr, index)
    Listbox *listPtr;
    int index;
{
    int up, down, nearest, i;
    Element *elPtr, *uPtr, *iPtr;

    /*
     * Make sure the index is within the proper range for the listbox.
     */

    if (listPtr->numElements == 0) {
	return;
    }
    if (index >= listPtr->numElements) {
	index = listPtr->numElements-1;
    }
    if (index < 0) {
	index = 0;
    }
    
    if ((listPtr->numSelected == 0) && (listPtr->exportSelection)) {
	Tk_OwnSelection(listPtr->tkwin, ListboxLostSelection,
		(ClientData) listPtr);
    }

    if (listPtr->numSelected == 0) {
	nearest = 0;
	elPtr = listPtr->elementPtr;
	i = index + 1;
	goto skip;
    }

    elPtr = listPtr->elementPtr;
    i = 0;
    up = down = -1;
    uPtr = iPtr = NULL;
    while (elPtr != NULL) {
	if (i == index) {
	    iPtr = elPtr;
	}
	if (elPtr->selected) {
	    if (i <= index) {
		uPtr = elPtr;
		up = i;
	    } else {
		down = i;
		break;
	    }
	}
	i++;
	elPtr = elPtr->nextPtr;
    }

    if (up == -1) {
	if (down == -1) {
	    nearest = 0;
	    elPtr = listPtr->elementPtr;
	    i = index + 1;
	} else {
	    nearest = down;
	    elPtr = iPtr;
	    i = down - index + 1;
	}
    } else {
	if (down == -1) {
	    nearest = up;
	    elPtr = uPtr;
	    i = index - up + 1;
	} else {
	    if ((index - up) <= (down - index)) {
		nearest = up;
		elPtr = uPtr;
		i = index - up + 1;
	    } else {
		nearest = down;
		elPtr = iPtr;
		i = down - index + 1;
	    }
	}
    }

  skip:

    while (i) {
	listPtr->numSelected += 1 - elPtr->selected;
	elPtr->selected = 1;
	elPtr = elPtr->nextPtr;
	i--;
    }

    listPtr->selectAnchor = nearest;
    listPtr->lastTo = index;
    ListboxRedrawRange(listPtr, nearest, index);
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxSelectFrom --
 *
 *	Start a new selection in a listbox.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	ListPtr claims the selection, and a selection starts at index.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxSelectFrom(listPtr, index)
    register Listbox *listPtr;		/* Information about widget. */
    int index;				/* Index of element that is to
					 * become the new selection. */
{
    int i;
    Element *elPtr;

    /*
     * Make sure the index is within the proper range for the listbox.
     */

    if (listPtr->numElements == 0) {
	return;
    }
    if (index >= listPtr->numElements) {
	index = listPtr->numElements-1;
    }
    if (index < 0) {
	index = 0;
    }

    if ((listPtr->numSelected == 0) && (listPtr->exportSelection)) {
	Tk_OwnSelection(listPtr->tkwin, ListboxLostSelection,
		(ClientData) listPtr);
    }

    /* Allow only one selected area in "compatibility mode" */
    if (!listPtr->multipleSelections && listPtr->numSelected > 0) {
       ListboxUnselectRange(listPtr, 0, listPtr->numElements);
    }

    elPtr = listPtr->elementPtr;
    i = index;
    while (i) {
	elPtr = elPtr->nextPtr;
	i--;
    }

    listPtr->numSelected += 1 - elPtr->selected;
    elPtr->selected = 1;
    listPtr->selectAnchor = index;
    listPtr->lastTo = index;
    ListboxRedrawRange(listPtr, index, index);
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxSelectTo --
 *
 *	Modify the selection by moving its un-anchored end.  This could
 *	make the selection either larger or smaller.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The selection changes.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxSelectTo(listPtr, index)
    register Listbox *listPtr;		/* Information about widget. */
    int index;				/* Index of element that is to
					 * become the "other" end of the
					 * selection. */
{
    register Element *elPtr;
    int i, count;

    /*
     * Make sure the index is within the proper range for the listbox.
     */

    if (listPtr->numElements == 0) {
	return;
    }
    if (index >= listPtr->numElements) {
	index = listPtr->numElements-1;
    }
    if (index < 0) {
	index = 0;
    }

    /*
     * We should already own the selection, but grab it if we don't.
     */

    if (listPtr->numSelected == 0) {
	ListboxSelectFrom(listPtr, index);
	goto done;
    }

    if (index > listPtr->selectAnchor) {
	i = listPtr->selectAnchor;
	count = index - i + 1;
    } else {
	i = index;
	count = listPtr->selectAnchor - index + 1;
    }
    elPtr = listPtr->elementPtr;
    while (i) {
	elPtr = elPtr->nextPtr;
	i--;
    }

    ListboxUnselectRange(listPtr, listPtr->selectAnchor, listPtr->lastTo);

    while (count) {
	listPtr->numSelected += 1 - elPtr->selected;
	elPtr->selected = 1;
	elPtr = elPtr->nextPtr;
	count--;
    }

    listPtr->lastTo = index;
  done:
    ListboxRedrawRange(listPtr, listPtr->selectAnchor, index);
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxFetchSelection --
 *
 *	This procedure is called back by Tk when the selection is
 *	requested by someone.  It returns part or all of the selection
 *	in a buffer provided by the caller.
 *
 * Results:
 *	The return value is the number of non-NULL bytes stored
 *	at buffer.  Buffer is filled (or partially filled) with a
 *	NULL-terminated string containing part or all of the selection,
 *	as given by offset and maxBytes.  The selection is returned
 *	as a Tcl list with one list element for each element in the
 *	listbox.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
ListboxFetchSelection(clientData, offset, buffer, maxBytes)
    ClientData clientData;		/* Information about listbox widget. */
    int offset;				/* Offset within selection of first
					 * byte to be returned. */
    char *buffer;			/* Location in which to place
					 * selection. */
    int maxBytes;			/* Maximum number of bytes to place
					 * at buffer, not including terminating
					 * NULL character. */
{
    register Listbox *listPtr = (Listbox *) clientData;
    register Element *elPtr;
    char **argv, *selection;
    int length, count, argc;
#ifdef KANJI
    char *emptystr = "";
#endif /* KANJI */

    if ((listPtr->numSelected == 0) || !listPtr->exportSelection) {
	return -1;
    }

    /*
     * Use Tcl_Merge to format the listbox elements into a suitable
     * Tcl list.
     */

    count = listPtr->numElements;
    argv = (char **) ckalloc((unsigned) (count*sizeof(char *)));
    for (argc = 0, elPtr = listPtr->elementPtr; count > 0;
   	    count--, elPtr = elPtr->nextPtr) {
   	if (elPtr->selected == 1) {
#ifdef KANJI
	    argv[argc] = Tk_WStrToString(elPtr->text, -1);
	    if (argv[argc] == NULL) argv[argc] = emptystr;
#else
	    argv[argc] = elPtr->text;
#endif /* KANJI */
	    argc++;
	}
    }
    selection = Tcl_Merge(argc, argv);

    /*
     * Copy the requested portion of the selection to the buffer.
     */

    length = strlen(selection);
    count = length - offset;
    if (count <= 0) {
	count = 0;
	goto done;
    }
    if (count > maxBytes) {
	count = maxBytes;
    }
    memcpy((VOID *) buffer, (VOID *) (selection + offset), count);

    done:
    buffer[count] = '\0';
    ckfree(selection);
#ifdef KANJI
    {	int i;
	for (i = 0; i < argc; i++) {
	    if (argv[i] != emptystr) ckfree(argv[i]);
	}
    }
#endif /* KANJI */
    ckfree((char *) argv);
    return count;
}

#ifdef KANJI
/*
 *----------------------------------------------------------------------
 *
 * ListboxFetchSelectionCtext --
 *
 *	This procedure is similar to ListboxFetchSelection except
 *	it converts the selection to COMPOUND_TEXT before
 *	passing it to the requester.
 *
 * Results:
 *	See ListboxFetchSelection.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

int
ListboxFetchSelectionCtext(clientData, offset, buffer, maxBytes)
    ClientData clientData;		/* Information about listbox widget. */
    int offset;				/* Offset within selection of first
					 * byte to be returned. */
    char *buffer;			/* Location in which to place
					 * selection. */
    int maxBytes;			/* Maximum number of bytes to place
					 * at buffer, not including terminating
					 * NULL character. */
{
    register Listbox *listPtr = (Listbox *) clientData;
    register Element *elPtr;
    char **argv, *selection;
    int length, count, argc;
    char *emptystr = "";

    if ((listPtr->numSelected == 0) || !listPtr->exportSelection) {
	return -1;
    }

    /*
     * Use Tcl_Merge to format the listbox elements into a suitable
     * Tcl list.
     */

    count = listPtr->numElements;
    argv = (char **) ckalloc((unsigned) (count*sizeof(char *)));
    for (argc = 0, elPtr = listPtr->elementPtr; count > 0;
	    count--, elPtr = elPtr->nextPtr) {
	if (elPtr->selected == 1) {
	    argv[argc] = Tk_WStrToCtext(elPtr->text, -1);
	    if (argv[argc] == NULL) argv[argc] = emptystr;
	    argc++;
	}
    }
    selection = Tcl_Merge(argc, argv);

    /*
     * Copy the requested portion of the selection to the buffer.
     */

    length = strlen(selection);
    count = length - offset;
    if (count <= 0) {
	count = 0;
	goto done;
    }
    if (count > maxBytes) {
	count = maxBytes;
    }
    memcpy((VOID *) buffer, (VOID *) (selection + offset), count);

    done:
    buffer[count] = '\0';
    ckfree(selection);
    {	int i;
	for (i = 0; i < argc; i++) {
	    if (argv[i] != emptystr) ckfree(argv[i]);
	}
    }
    ckfree((char *) argv);
    return count;
}
#endif /* KANJI */

/*
 *----------------------------------------------------------------------
 *
 * ListboxSelectRange
 *
 *      Select a range of elements.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The range of elements is selected.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxSelectRange(listPtr, index1, index2)
    Listbox *listPtr;
    int index1, index2;
{
    ListboxSetRange(listPtr, index1, index2, 1);
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxUnselectRange
 *
 *      Unselect a range of elements.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The range of elements is unselected.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxUnselectRange(listPtr, index1, index2)
    Listbox *listPtr;
    int index1, index2;
{
    ListboxSetRange(listPtr, index1, index2, 0);
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxInvertRange
 *
 *      Invert the selected field of a range of elements.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The range of elements is selected/unselected.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxInvertRange(listPtr, index1, index2)
    Listbox *listPtr;
    int index1, index2;
{
    ListboxSetRange(listPtr, index1, index2, 2);
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxSetRange
 *
 *      Set the selected field of a range of elements.
 *      m=2 means invert the fields.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The range of elements is (un)selected.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxSetRange(listPtr, index1, index2, m)
    Listbox *listPtr;
    int index1, index2, m;
{
    int count, x;
    Element *elPtr;

    /*
     * Make sure the indices are within the proper range for the listbox.
     */

    if (listPtr->numElements == 0) {
	return;
    }
    if (index1 >= listPtr->numElements) {
	index1 = listPtr->numElements-1;
    }
    if (index2 >= listPtr->numElements) {
	index2 = listPtr->numElements-1;
    }
    if (index1 < 0) {
	index1 = 0;
    }
    if (index2 < 0) {
	index2 = 0;
    }
    
    if (index1 > index2) {
	count = index1;
	index1 = index2;
	index2 = count;
    }

    elPtr = listPtr->elementPtr;
    count = 0;
    while (count <= index2) {
	if (count >= index1) {
	    x = elPtr->selected;
	    elPtr->selected = (m == 2) ? (!(elPtr->selected)) : m;
	    listPtr->numSelected += elPtr->selected - x;
	}
	elPtr = elPtr->nextPtr;
	count++;
    }
    ListboxRedrawRange(listPtr, index1, index2);
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxLostSelection --
 *
 *	This procedure is called back by Tk when the selection is
 *	grabbed away from a listbox widget.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The existing selection is unhighlighted, and the window is
 *	marked as not containing a selection.
 *
 *----------------------------------------------------------------------
 */

void
ListboxLostSelection(clientData)
    ClientData clientData;		/* Information about listbox widget. */
{
    register Listbox *listPtr = (Listbox *) clientData;

    if (listPtr->exportSelection) {
	ListboxUnselectRange(listPtr, 0, listPtr->numElements);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxRedrawRange --
 *
 *	Ensure that a given range of elements is eventually redrawn on
 *	the display (if those elements in fact appear on the display).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information gets redisplayed.
 *
 *----------------------------------------------------------------------
 */

	/* ARGSUSED */
static void
ListboxRedrawRange(listPtr, first, last)
    register Listbox *listPtr;		/* Information about widget. */
    int first;				/* Index of first element in list
					 * that needs to be redrawn. */
    int last;				/* Index of last element in list
					 * that needs to be redrawn.  May
					 * be less than first;
					 * these just bracket a range. */
{
    if ((listPtr->tkwin == NULL) || !Tk_IsMapped(listPtr->tkwin)
	    || (listPtr->flags & REDRAW_PENDING)) {
	return;
    }
    Tk_DoWhenIdle(DisplayListbox, (ClientData) listPtr);
    listPtr->flags |= REDRAW_PENDING;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxUpdateVScrollbar --
 *
 *	This procedure is invoked whenever information has changed in
 *	a listbox in a way that would invalidate a vertical scrollbar
 *	display.  If there is an associated scrollbar, then this command
 *	updates it by invoking a Tcl command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A Tcl command is invoked, and an additional command may be
 *	invoked to process errors in the command.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxUpdateVScrollbar(listPtr)
    register Listbox *listPtr;		/* Information about widget. */
{
    char string[60];
    int result, last;

    if (listPtr->yScrollCmd == NULL) {
	return;
    }
    last = listPtr->topIndex + listPtr->numLines - 1;
    if (last >= listPtr->numElements) {
	last = listPtr->numElements-1;
    }
    if (last < listPtr->topIndex) {
	last = listPtr->topIndex;
    }
    sprintf(string, " %d %d %d %d", listPtr->numElements, listPtr->numLines,
	    listPtr->topIndex, last);
    result = Tcl_VarEval(listPtr->interp, listPtr->yScrollCmd, string,
	    (char *) NULL);
    if (result != TCL_OK) {
	Tcl_AddErrorInfo(listPtr->interp,
		"\n    (vertical scrolling command executed by listbox)");
	Tk_BackgroundError(listPtr->interp);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxUpdateHScrollbar --
 *
 *	This procedure is invoked whenever information has changed in
 *	a listbox in a way that would invalidate a horizontal scrollbar
 *	display.  If there is an associated horizontal scrollbar, then
 *	this command updates it by invoking a Tcl command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A Tcl command is invoked, and an additional command may be
 *	invoked to process errors in the command.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxUpdateHScrollbar(listPtr)
    register Listbox *listPtr;		/* Information about widget. */
{
    char string[60];
    int result, totalUnits, windowUnits, first, last;

    if (listPtr->xScrollCmd == NULL) {
	return;
    }
    totalUnits = 1 + (listPtr->maxWidth-1)/listPtr->xScrollUnit;
    windowUnits = 1 + (Tk_Width(listPtr->tkwin)
	    - 2*(listPtr->borderWidth + listPtr->selBorderWidth)-1)
	    /listPtr->xScrollUnit;
    first = listPtr->xOffset/listPtr->xScrollUnit;
    last = first + windowUnits - 1;
    if (last < first) {
	last = first;
    }
    sprintf(string, " %d %d %d %d", totalUnits, windowUnits, first, last);
    result = Tcl_VarEval(listPtr->interp, listPtr->xScrollCmd, string,
	    (char *) NULL);
    if (result != TCL_OK) {
	Tcl_AddErrorInfo(listPtr->interp,
		"\n    (horizontal scrolling command executed by listbox)");
	Tk_BackgroundError(listPtr->interp);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxComputeWidths --
 *
 *	This procedure is invoked to completely recompute width
 *	information used for displaying listboxes and for horizontal
 *	scrolling.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If "fontChanged" is non-zero then the widths of the individual
 *	elements are all recomputed.  In addition, listPtr->maxWidth is
 *	recomputed.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxComputeWidths(listPtr, fontChanged)
    Listbox *listPtr;		/* Listbox whose geometry is to be
				 * recomputed. */
    int fontChanged;		/* Non-zero means the font may have changed
				 * so per-element width information also
				 * has to be computed. */
{
    register Element *elPtr;
    int dummy;
    XCharStruct bbox;

#ifdef KANJI
    listPtr->xScrollUnit = XTextWidth(listPtr->fontPtr->asciiFont, "0", 1);
#else
    listPtr->xScrollUnit = XTextWidth(listPtr->fontPtr, "0", 1);
#endif /* KANJI */
    listPtr->maxWidth = 0;
    for (elPtr = listPtr->elementPtr; elPtr != NULL; elPtr = elPtr->nextPtr) {
	if (fontChanged) {
#ifdef KANJI
	    TkWSTextExtents(listPtr->textGC, elPtr->text, elPtr->textLength,
		    &dummy, &dummy, &bbox);
#else
	    XTextExtents(listPtr->fontPtr, elPtr->text, elPtr->textLength,
		    &dummy, &dummy, &dummy, &bbox);
#endif /* KANJI */
	    elPtr->lBearing = bbox.lbearing;
	    elPtr->pixelWidth = bbox.rbearing - bbox.lbearing;
	}
	if (elPtr->pixelWidth > listPtr->maxWidth) {
	    listPtr->maxWidth = elPtr->pixelWidth;
	}
    }
}
