/*
 * 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.
 *
 * Copyright (c) 1990-1994 The Regents of the University of California.
 * Copyright (c) 1994-1997 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

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

#ifdef WIN32
#include "tkWinInt.h"
#endif

typedef struct {
    Tk_OptionTable listboxOptionTable;
				/* Table defining configuration options
				 * available for the listbox. */
    Tk_OptionTable itemAttrOptionTable;
				/* Table defining configuration options
				 * available for listbox items. */
} ListboxOptionTables;

/*
 * 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. */
    Tcl_Command widgetCmd;	/* Token for listbox's widget command. */
    Tk_OptionTable optionTable;	/* Table that defines configuration options
				 * available for this widget. */
    Tk_OptionTable itemAttrOptionTable;
				/* Table that defines configuration options
				 * available for listbox items. */
    char *listVarName;		/* List variable name */
    Tcl_Obj *listObj;		/* Pointer to the list object being used */
    int nElements;		/* Holds the current count of elements */
    Tcl_HashTable *selection;	/* Tracks selection */
    Tcl_HashTable *itemAttrTable;
				/* Tracks item attributes */

    /*
     * 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. */
    int highlightWidth;		/* Width in pixels of highlight to draw around
				 * widget when it has the focus. <= 0 means
				 * don't draw a highlight. */
    XColor *highlightBgColorPtr;
				/* Color for drawing traversal highlight area
				 * when highlight is off. */
    XColor *highlightColorPtr;	/* Color for drawing traversal highlight. */
    int inset;			/* Total width of all borders, including
				 * traversal highlight and 3-D border.
				 * Indicates how much interior stuff must be
				 * offset from outside edges to leave room for
				 * borders. */
    Tk_Font tkfont;		/* Information about text font, or NULL. */
    XColor *fgColorPtr;		/* Text color in normal mode. */
    XColor *dfgColorPtr;	/* Text color in disabled mode. */
    GC textGC;			/* For drawing normal text. */
    Tk_3DBorder selBorder;	/* Borders and backgrounds for selected
				 * elements. */
    int selBorderWidth;		/* Width of border around selection. */
    XColor *selFgColorPtr;	/* Foreground color for selected elements. */
    GC selTextGC;		/* For drawing selected text. */
    int width;			/* Desired width of window, in characters. */
    int height;			/* Desired height of window, in lines. */
    int lineHeight;		/* Number of pixels allocated for each line in
				 * display. */
    int topIndex;		/* Index of top-most element visible in
				 * window. */
    int fullLines;		/* Number of lines that fit are completely
				 * visible in window. There may be one
				 * additional line at the bottom that is
				 * partially visible. */
    int partialLine;		/* 0 means that the window holds exactly
				 * fullLines lines. 1 means that there is one
				 * additional line that is partially
				 * visble. */
    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 or active, if any.
     */

    Tk_Uid selectMode;		/* Selection style: single, browse, multiple,
				 * or extended. This value isn't used in C
				 * code, but the Tcl bindings use it. */
    int numSelected;		/* Number of elements currently selected. */
    int selectAnchor;		/* Fixed end of selection (i.e. element at
				 * which selection was started.) */
    int exportSelection;	/* Non-zero means tie internal listbox to X
				 * selection. */
    int active;			/* Index of "active" element (the one that has
				 * been selected by keyboard traversal). -1
				 * means none. */
    int activeStyle;		/* style in which to draw the active element.
				 * One of: underline, none, dotbox */

    /*
     * 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:
     */

    Tk_Cursor cursor;		/* Current cursor for window, or None. */
    char *takeFocus;		/* Value of -takefocus option; not used in the
				 * C code, but used by keyboard traversal
				 * scripts. Malloc'ed, but may be NULL. */
    char *yScrollCmd;		/* Command prefix for communicating with
				 * vertical scrollbar. NULL means no command
				 * to issue. Malloc'ed. */
    char *xScrollCmd;		/* Command prefix for communicating with
				 * horizontal scrollbar. NULL means no command
				 * to issue. Malloc'ed. */
    int state;			/* Listbox state. */
    Pixmap gray;		/* Pixmap for displaying disabled text. */
    int flags;			/* Various flag bits: see below for
				 * definitions. */
} Listbox;

/*
 * How to encode the keys for the hash tables used to store what items are
 * selected and what the attributes are.
 */

#define KEY(i)		((char *) INT2PTR(i))

/*
 * ItemAttr structures are used to store item configuration information for
 * the items in a listbox
 */

typedef struct {
    Tk_3DBorder border;		/* Used for drawing background around text */
    Tk_3DBorder selBorder;	/* Used for selected text */
    XColor *fgColor;		/* Text color in normal mode. */
    XColor *selFgColor;		/* Text color in selected mode. */
} ItemAttr;

/*
 * Flag bits for listboxes:
 *
 * REDRAW_PENDING:		Non-zero means a DoWhenIdle handler has
 *				already been queued to redraw this window.
 * UPDATE_V_SCROLLBAR:		Non-zero means vertical scrollbar needs to be
 *				updated.
 * UPDATE_H_SCROLLBAR:		Non-zero means horizontal scrollbar needs to
 *				be updated.
 * GOT_FOCUS:			Non-zero means this widget currently has the
 *				input focus.
 * MAXWIDTH_IS_STALE:		Stored maxWidth may be out-of-date
 * LISTBOX_DELETED:		This listbox has been effectively destroyed.
 */

#define REDRAW_PENDING		1
#define UPDATE_V_SCROLLBAR	2
#define UPDATE_H_SCROLLBAR	4
#define GOT_FOCUS		8
#define MAXWIDTH_IS_STALE	16
#define LISTBOX_DELETED		32

/*
 * The following enum is used to define a type for the -state option of the
 * Listbox widget. These values are used as indices into the string table
 * below.
 */

enum state {
    STATE_DISABLED, STATE_NORMAL
};

static const char *const stateStrings[] = {
    "disabled", "normal", NULL
};

enum activeStyle {
    ACTIVE_STYLE_DOTBOX, ACTIVE_STYLE_NONE, ACTIVE_STYLE_UNDERLINE
};

static const char *const activeStyleStrings[] = {
    "dotbox", "none", "underline", NULL
};

/*
 * The optionSpecs table defines the valid configuration options for the
 * listbox widget.
 */

static const Tk_OptionSpec optionSpecs[] = {
    {TK_OPTION_STRING_TABLE, "-activestyle", "activeStyle", "ActiveStyle",
	DEF_LISTBOX_ACTIVE_STYLE, -1, Tk_Offset(Listbox, activeStyle),
	0, activeStyleStrings, 0},
    {TK_OPTION_BORDER, "-background", "background", "Background",
	 DEF_LISTBOX_BG_COLOR, -1, Tk_Offset(Listbox, normalBorder),
	 0, DEF_LISTBOX_BG_MONO, 0},
    {TK_OPTION_SYNONYM, "-bd", NULL, NULL,
	 NULL, 0, -1, 0, "-borderwidth", 0},
    {TK_OPTION_SYNONYM, "-bg", NULL, NULL,
	 NULL, 0, -1, 0, "-background", 0},
    {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
	 DEF_LISTBOX_BORDER_WIDTH, -1, Tk_Offset(Listbox, borderWidth),
	 0, 0, 0},
    {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
	 DEF_LISTBOX_CURSOR, -1, Tk_Offset(Listbox, cursor),
	 TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_COLOR, "-disabledforeground", "disabledForeground",
	 "DisabledForeground", DEF_LISTBOX_DISABLED_FG, -1,
	 Tk_Offset(Listbox, dfgColorPtr), TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
	 "ExportSelection", DEF_LISTBOX_EXPORT_SELECTION, -1,
	 Tk_Offset(Listbox, exportSelection), 0, 0, 0},
    {TK_OPTION_SYNONYM, "-fg", "foreground", NULL,
	 NULL, 0, -1, 0, "-foreground", 0},
    {TK_OPTION_FONT, "-font", "font", "Font",
	 DEF_LISTBOX_FONT, -1, Tk_Offset(Listbox, tkfont), 0, 0, 0},
    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
	 DEF_LISTBOX_FG, -1, Tk_Offset(Listbox, fgColorPtr), 0, 0, 0},
    {TK_OPTION_INT, "-height", "height", "Height",
	 DEF_LISTBOX_HEIGHT, -1, Tk_Offset(Listbox, height), 0, 0, 0},
    {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
	 "HighlightBackground", DEF_LISTBOX_HIGHLIGHT_BG, -1,
	 Tk_Offset(Listbox, highlightBgColorPtr), 0, 0, 0},
    {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
	 DEF_LISTBOX_HIGHLIGHT, -1, Tk_Offset(Listbox, highlightColorPtr),
	 0, 0, 0},
    {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
	 "HighlightThickness", DEF_LISTBOX_HIGHLIGHT_WIDTH, -1,
	 Tk_Offset(Listbox, highlightWidth), 0, 0, 0},
    {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
	 DEF_LISTBOX_RELIEF, -1, Tk_Offset(Listbox, relief), 0, 0, 0},
    {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
	 DEF_LISTBOX_SELECT_COLOR, -1, Tk_Offset(Listbox, selBorder),
	 0, DEF_LISTBOX_SELECT_MONO, 0},
    {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth",
	 "BorderWidth", DEF_LISTBOX_SELECT_BD, -1,
	 Tk_Offset(Listbox, selBorderWidth), 0, 0, 0},
    {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
	 DEF_LISTBOX_SELECT_FG_COLOR, -1, Tk_Offset(Listbox, selFgColorPtr),
	 TK_CONFIG_NULL_OK, DEF_LISTBOX_SELECT_FG_MONO, 0},
    {TK_OPTION_STRING, "-selectmode", "selectMode", "SelectMode",
	 DEF_LISTBOX_SELECT_MODE, -1, Tk_Offset(Listbox, selectMode),
	 TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_BOOLEAN, "-setgrid", "setGrid", "SetGrid",
	 DEF_LISTBOX_SET_GRID, -1, Tk_Offset(Listbox, setGrid), 0, 0, 0},
    {TK_OPTION_STRING_TABLE, "-state", "state", "State",
	DEF_LISTBOX_STATE, -1, Tk_Offset(Listbox, state),
	0, stateStrings, 0},
    {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
	 DEF_LISTBOX_TAKE_FOCUS, -1, Tk_Offset(Listbox, takeFocus),
	 TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_INT, "-width", "width", "Width",
	 DEF_LISTBOX_WIDTH, -1, Tk_Offset(Listbox, width), 0, 0, 0},
    {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
	 DEF_LISTBOX_SCROLL_COMMAND, -1, Tk_Offset(Listbox, xScrollCmd),
	 TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
	 DEF_LISTBOX_SCROLL_COMMAND, -1, Tk_Offset(Listbox, yScrollCmd),
	 TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_STRING, "-listvariable", "listVariable", "Variable",
	 DEF_LISTBOX_LIST_VARIABLE, -1, Tk_Offset(Listbox, listVarName),
	 TK_OPTION_NULL_OK, 0, 0},
    {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, 0, 0}
};

/*
 * The itemAttrOptionSpecs table defines the valid configuration options for
 * listbox items
 */

static const Tk_OptionSpec itemAttrOptionSpecs[] = {
    {TK_OPTION_BORDER, "-background", "background", "Background",
     NULL, -1, Tk_Offset(ItemAttr, border),
     TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT,
     DEF_LISTBOX_BG_MONO, 0},
    {TK_OPTION_SYNONYM, "-bg", NULL, NULL,
     NULL, 0, -1, 0, "-background", 0},
    {TK_OPTION_SYNONYM, "-fg", "foreground", NULL,
     NULL, 0, -1, 0, "-foreground", 0},
    {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
     NULL, -1, Tk_Offset(ItemAttr, fgColor),
     TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT, 0, 0},
    {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
     NULL, -1, Tk_Offset(ItemAttr, selBorder),
     TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT,
     DEF_LISTBOX_SELECT_MONO, 0},
    {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
     NULL, -1, Tk_Offset(ItemAttr, selFgColor),
     TK_OPTION_NULL_OK|TK_OPTION_DONT_SET_DEFAULT,
     DEF_LISTBOX_SELECT_FG_MONO, 0},
    {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, 0, 0}
};

/*
 * The following tables define the listbox widget commands (and sub- commands)
 * and map the indexes into the string tables into enumerated types used to
 * dispatch the listbox widget command.
 */

static const char *const commandNames[] = {
    "activate", "bbox", "cget", "configure", "curselection", "delete", "get",
    "index", "insert", "itemcget", "itemconfigure", "nearest", "scan",
    "see", "selection", "size", "xview", "yview", NULL
};
enum command {
    COMMAND_ACTIVATE, COMMAND_BBOX, COMMAND_CGET, COMMAND_CONFIGURE,
    COMMAND_CURSELECTION, COMMAND_DELETE, COMMAND_GET, COMMAND_INDEX,
    COMMAND_INSERT, COMMAND_ITEMCGET, COMMAND_ITEMCONFIGURE,
    COMMAND_NEAREST, COMMAND_SCAN, COMMAND_SEE, COMMAND_SELECTION,
    COMMAND_SIZE, COMMAND_XVIEW, COMMAND_YVIEW
};

static const char *const selCommandNames[] = {
    "anchor", "clear", "includes", "set", NULL
};
enum selcommand {
    SELECTION_ANCHOR, SELECTION_CLEAR, SELECTION_INCLUDES, SELECTION_SET
};

static const char *const scanCommandNames[] = {
    "mark", "dragto", NULL
};
enum scancommand {
    SCAN_MARK, SCAN_DRAGTO
};

static const char *const indexNames[] = {
    "active", "anchor", "end", NULL
};
enum indices {
    INDEX_ACTIVE, INDEX_ANCHOR, INDEX_END
};

/*
 * Declarations for procedures defined later in this file.
 */

static void		ChangeListboxOffset(Listbox *listPtr, int offset);
static void		ChangeListboxView(Listbox *listPtr, int index);
static int		ConfigureListbox(Tcl_Interp *interp, Listbox *listPtr,
			    int objc, Tcl_Obj *const objv[], int flags);
static int		ConfigureListboxItem(Tcl_Interp *interp,
			    Listbox *listPtr, ItemAttr *attrs, int objc,
			    Tcl_Obj *const objv[], int index);
static int		ListboxDeleteSubCmd(Listbox *listPtr,
			    int first, int last);
static void		DestroyListbox(char *memPtr);
static void		DestroyListboxOptionTables(ClientData clientData,
			    Tcl_Interp *interp);
static void		DisplayListbox(ClientData clientData);
static int		GetListboxIndex(Tcl_Interp *interp, Listbox *listPtr,
			    Tcl_Obj *index, int endIsSize, int *indexPtr);
static int		ListboxInsertSubCmd(Listbox *listPtr,
			    int index, int objc, Tcl_Obj *const objv[]);
static void		ListboxCmdDeletedProc(ClientData clientData);
static void		ListboxComputeGeometry(Listbox *listPtr,
			    int fontChanged, int maxIsStale, int updateGrid);
static void		ListboxEventProc(ClientData clientData,
			    XEvent *eventPtr);
static int		ListboxFetchSelection(ClientData clientData,
			    int offset, char *buffer, int maxBytes);
static void		ListboxLostSelection(ClientData clientData);
static void		EventuallyRedrawRange(Listbox *listPtr,
			    int first, int last);
static void		ListboxScanTo(Listbox *listPtr, int x, int y);
static int		ListboxSelect(Listbox *listPtr,
			    int first, int last, int select);
static void		ListboxUpdateHScrollbar(Listbox *listPtr);
static void		ListboxUpdateVScrollbar(Listbox *listPtr);
static int		ListboxWidgetObjCmd(ClientData clientData,
			    Tcl_Interp *interp, int objc,
			    Tcl_Obj *const objv[]);
static int		ListboxBboxSubCmd(Tcl_Interp *interp,
			    Listbox *listPtr, int index);
static int		ListboxSelectionSubCmd(Tcl_Interp *interp,
			    Listbox *listPtr, int objc, Tcl_Obj *const objv[]);
static int		ListboxXviewSubCmd(Tcl_Interp *interp,
			    Listbox *listPtr, int objc, Tcl_Obj *const objv[]);
static int		ListboxYviewSubCmd(Tcl_Interp *interp,
			    Listbox *listPtr, int objc, Tcl_Obj *const objv[]);
static ItemAttr *	ListboxGetItemAttributes(Tcl_Interp *interp,
			    Listbox *listPtr, int index);
static void		ListboxWorldChanged(ClientData instanceData);
static int		NearestListboxElement(Listbox *listPtr, int y);
static char *		ListboxListVarProc(ClientData clientData,
			    Tcl_Interp *interp, const char *name1,
			    const char *name2, int flags);
static void		MigrateHashEntries(Tcl_HashTable *table,
			    int first, int last, int offset);

/*
 * The structure below defines button class behavior by means of procedures
 * that can be invoked from generic window code.
 */

static const Tk_ClassProcs listboxClass = {
    sizeof(Tk_ClassProcs),	/* size */
    ListboxWorldChanged,	/* worldChangedProc */
    NULL,			/* createProc */
    NULL			/* modalProc */
};

/*
 *--------------------------------------------------------------
 *
 * Tk_ListboxObjCmd --
 *
 *	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_ListboxObjCmd(
    ClientData clientData,	/* NULL. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Argument objects. */
{
    register Listbox *listPtr;
    Tk_Window tkwin;
    ListboxOptionTables *optionTables;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "pathName ?-option value ...?");
	return TCL_ERROR;
    }

    tkwin = Tk_CreateWindowFromPath(interp, Tk_MainWindow(interp),
	    Tcl_GetString(objv[1]), NULL);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }

    optionTables = Tcl_GetAssocData(interp, "ListboxOptionTables", NULL);
    if (optionTables == NULL) {
	/*
	 * We haven't created the option tables for this widget class yet. Do
	 * it now and save the a pointer to them as the ClientData for the
	 * command, so future invocations will have access to it.
	 */

	optionTables = ckalloc(sizeof(ListboxOptionTables));

	/*
	 * Set up an exit handler to free the optionTables struct.
	 */

	Tcl_SetAssocData(interp, "ListboxOptionTables",
		DestroyListboxOptionTables, optionTables);

	/*
	 * Create the listbox option table and the listbox item option table.
	 */

	optionTables->listboxOptionTable =
		Tk_CreateOptionTable(interp, optionSpecs);
	optionTables->itemAttrOptionTable =
		Tk_CreateOptionTable(interp, itemAttrOptionSpecs);
    }

    /*
     * 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			 = ckalloc(sizeof(Listbox));
    memset(listPtr, 0, sizeof(Listbox));

    listPtr->tkwin		 = tkwin;
    listPtr->display		 = Tk_Display(tkwin);
    listPtr->interp		 = interp;
    listPtr->widgetCmd		 = Tcl_CreateObjCommand(interp,
	    Tk_PathName(listPtr->tkwin), ListboxWidgetObjCmd, listPtr,
	    ListboxCmdDeletedProc);
    listPtr->optionTable	 = optionTables->listboxOptionTable;
    listPtr->itemAttrOptionTable = optionTables->itemAttrOptionTable;
    listPtr->selection		 = ckalloc(sizeof(Tcl_HashTable));
    Tcl_InitHashTable(listPtr->selection, TCL_ONE_WORD_KEYS);
    listPtr->itemAttrTable	 = ckalloc(sizeof(Tcl_HashTable));
    Tcl_InitHashTable(listPtr->itemAttrTable, TCL_ONE_WORD_KEYS);
    listPtr->relief		 = TK_RELIEF_RAISED;
    listPtr->textGC		 = None;
    listPtr->selFgColorPtr	 = None;
    listPtr->selTextGC		 = None;
    listPtr->fullLines		 = 1;
    listPtr->xScrollUnit	 = 1;
    listPtr->exportSelection	 = 1;
    listPtr->cursor		 = None;
    listPtr->state		 = STATE_NORMAL;
    listPtr->gray		 = None;

    /*
     * Keep a hold of the associated tkwin until we destroy the listbox,
     * otherwise Tk might free it while we still need it.
     */

    Tcl_Preserve(listPtr->tkwin);

    Tk_SetClass(listPtr->tkwin, "Listbox");
    Tk_SetClassProcs(listPtr->tkwin, &listboxClass, listPtr);
    Tk_CreateEventHandler(listPtr->tkwin,
	    ExposureMask|StructureNotifyMask|FocusChangeMask,
	    ListboxEventProc, listPtr);
    Tk_CreateSelHandler(listPtr->tkwin, XA_PRIMARY, XA_STRING,
	    ListboxFetchSelection, listPtr, XA_STRING);
    if (Tk_InitOptions(interp, (char *)listPtr,
	    optionTables->listboxOptionTable, tkwin) != TCL_OK) {
	Tk_DestroyWindow(listPtr->tkwin);
	return TCL_ERROR;
    }

    if (ConfigureListbox(interp, listPtr, objc-2, objv+2, 0) != TCL_OK) {
	Tk_DestroyWindow(listPtr->tkwin);
	return TCL_ERROR;
    }

    Tcl_SetObjResult(interp, TkNewWindowObj(listPtr->tkwin));
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxWidgetObjCmd --
 *
 *	This Tcl_Obj based procedure is invoked to process the Tcl command
 *	that corresponds to a widget managed by this module. See the user
 *	documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxWidgetObjCmd(
    ClientData clientData,	/* Information about listbox widget. */
    Tcl_Interp *interp,		/* Current interpreter. */
    int objc,			/* Number of arguments. */
    Tcl_Obj *const objv[])	/* Arguments as Tcl_Obj's. */
{
    register Listbox *listPtr = clientData;
    int cmdIndex, index;
    int result = TCL_OK;
    Tcl_Obj *objPtr;

    if (objc < 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
	return TCL_ERROR;
    }

    /*
     * Parse the command by looking up the second argument in the list of
     * valid subcommand names
     */

    result = Tcl_GetIndexFromObj(interp, objv[1], commandNames,
	    "option", 0, &cmdIndex);
    if (result != TCL_OK) {
	return result;
    }

    Tcl_Preserve(listPtr);

    /*
     * The subcommand was valid, so continue processing.
     */

    switch (cmdIndex) {
    case COMMAND_ACTIVATE:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "index");
	    result = TCL_ERROR;
	    break;
	}
	result = GetListboxIndex(interp, listPtr, objv[2], 0, &index);
	if (result != TCL_OK) {
	    break;
	}

	if (!(listPtr->state & STATE_NORMAL)) {
	    break;
	}

	if (index >= listPtr->nElements) {
	    index = listPtr->nElements-1;
	}
	if (index < 0) {
	    index = 0;
	}
	listPtr->active = index;
	EventuallyRedrawRange(listPtr, listPtr->active, listPtr->active);
	result = TCL_OK;
	break;

    case COMMAND_BBOX:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "index");
	    result = TCL_ERROR;
	    break;
	}
	result = GetListboxIndex(interp, listPtr, objv[2], 0, &index);
	if (result != TCL_OK) {
	    break;
	}

	result = ListboxBboxSubCmd(interp, listPtr, index);
	break;

    case COMMAND_CGET:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "option");
	    result = TCL_ERROR;
	    break;
	}

	objPtr = Tk_GetOptionValue(interp, (char *) listPtr,
		listPtr->optionTable, objv[2], listPtr->tkwin);
	if (objPtr == NULL) {
	    result = TCL_ERROR;
	    break;
	}
	Tcl_SetObjResult(interp, objPtr);
	result = TCL_OK;
	break;

    case COMMAND_CONFIGURE:
	if (objc <= 3) {
	    objPtr = Tk_GetOptionInfo(interp, (char *) listPtr,
		    listPtr->optionTable,
		    (objc == 3) ? objv[2] : NULL, listPtr->tkwin);
	    if (objPtr == NULL) {
		result = TCL_ERROR;
		break;
	    }
	    Tcl_SetObjResult(interp, objPtr);
	    result = TCL_OK;
	} else {
	    result = ConfigureListbox(interp, listPtr, objc-2, objv+2, 0);
	}
	break;

    case COMMAND_CURSELECTION: {
	int i;

	if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, NULL);
	    result = TCL_ERROR;
	    break;
	}

	/*
	 * Of course, it would be more efficient to use the Tcl_HashTable
	 * search functions (Tcl_FirstHashEntry, Tcl_NextHashEntry), but then
	 * the result wouldn't be in sorted order. So instead we loop through
	 * the indices in order, adding them to the result if they are
	 * selected.
	 */

	objPtr = Tcl_NewObj();
	for (i = 0; i < listPtr->nElements; i++) {
	    if (Tcl_FindHashEntry(listPtr->selection, KEY(i))) {
		Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewIntObj(i));
	    }
	}
	Tcl_SetObjResult(interp, objPtr);
	result = TCL_OK;
	break;
    }

    case COMMAND_DELETE: {
	int first, last;

	if ((objc < 3) || (objc > 4)) {
	    Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?");
	    result = TCL_ERROR;
	    break;
	}

	result = GetListboxIndex(interp, listPtr, objv[2], 0, &first);
	if (result != TCL_OK) {
	    break;
	}

	if (!(listPtr->state & STATE_NORMAL)) {
	    break;
	}

	if (first < listPtr->nElements) {
	    /*
	     * if a "last index" was given, get it now; otherwise, use the
	     * first index as the last index.
	     */

	    if (objc == 4) {
		result = GetListboxIndex(interp, listPtr, objv[3], 0, &last);
		if (result != TCL_OK) {
		    break;
		}
	    } else {
		last = first;
	    }
	    if (last >= listPtr->nElements) {
		last = listPtr->nElements - 1;
	    }
	    result = ListboxDeleteSubCmd(listPtr, first, last);
	} else {
	    result = TCL_OK;
	}
	break;
    }

    case COMMAND_GET: {
	int first, last, listLen;
	Tcl_Obj **elemPtrs;

	if (objc != 3 && objc != 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "firstIndex ?lastIndex?");
	    result = TCL_ERROR;
	    break;
	}
	result = GetListboxIndex(interp, listPtr, objv[2], 0, &first);
	if (result != TCL_OK) {
	    break;
	}
	last = first;
	if (objc == 4) {
	    result = GetListboxIndex(interp, listPtr, objv[3], 0, &last);
	    if (result != TCL_OK) {
		break;
	    }
	}
	if (first >= listPtr->nElements) {
	    result = TCL_OK;
	    break;
	}
	if (last >= listPtr->nElements) {
	    last = listPtr->nElements - 1;
	}
	if (first < 0) {
	    first = 0;
	}
	if (first > last) {
	    result = TCL_OK;
	    break;
	}
	result = Tcl_ListObjGetElements(interp, listPtr->listObj, &listLen,
		&elemPtrs);
	if (result != TCL_OK) {
	    break;
	}
	if (objc == 3) {
	    /*
	     * One element request - we return a string
	     */

	    Tcl_SetObjResult(interp, elemPtrs[first]);
	} else {
	    Tcl_SetObjResult(interp,
		    Tcl_NewListObj(last-first+1, elemPtrs+first));
	}
	result = TCL_OK;
	break;
    }

    case COMMAND_INDEX:
	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "index");
	    result = TCL_ERROR;
	    break;
	}
	result = GetListboxIndex(interp, listPtr, objv[2], 1, &index);
	if (result != TCL_OK) {
	    break;
	}
	Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
	result = TCL_OK;
	break;

    case COMMAND_INSERT:
	if (objc < 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "index ?element ...?");
	    result = TCL_ERROR;
	    break;
	}

	result = GetListboxIndex(interp, listPtr, objv[2], 1, &index);
	if (result != TCL_OK) {
	    break;
	}

	if (!(listPtr->state & STATE_NORMAL)) {
	    break;
	}

	result = ListboxInsertSubCmd(listPtr, index, objc-3, objv+3);
	break;

    case COMMAND_ITEMCGET: {
	ItemAttr *attrPtr;

	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 2, objv, "index option");
	    result = TCL_ERROR;
	    break;
	}

	result = GetListboxIndex(interp, listPtr, objv[2], 0, &index);
	if (result != TCL_OK) {
	    break;
	}

	if (index < 0 || index >= listPtr->nElements) {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "item number \"%s\" out of range",
		    Tcl_GetString(objv[2])));
	    Tcl_SetErrorCode(interp, "TK", "LISTBOX", "ITEM_INDEX", NULL);
	    result = TCL_ERROR;
	    break;
	}

	attrPtr = ListboxGetItemAttributes(interp, listPtr, index);

	objPtr = Tk_GetOptionValue(interp, (char *) attrPtr,
		listPtr->itemAttrOptionTable, objv[3], listPtr->tkwin);
	if (objPtr == NULL) {
	    result = TCL_ERROR;
	    break;
	}
	Tcl_SetObjResult(interp, objPtr);
	result = TCL_OK;
	break;
    }

    case COMMAND_ITEMCONFIGURE: {
	ItemAttr *attrPtr;

	if (objc < 3) {
	    Tcl_WrongNumArgs(interp, 2, objv,
		    "index ?-option? ?value? ?-option value ...?");
	    result = TCL_ERROR;
	    break;
	}

	result = GetListboxIndex(interp, listPtr, objv[2], 0, &index);
	if (result != TCL_OK) {
	    break;
	}

	if (index < 0 || index >= listPtr->nElements) {
	    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
		    "item number \"%s\" out of range",
		    Tcl_GetString(objv[2])));
	    Tcl_SetErrorCode(interp, "TK", "LISTBOX", "ITEM_INDEX", NULL);
	    result = TCL_ERROR;
	    break;
	}

	attrPtr = ListboxGetItemAttributes(interp, listPtr, index);
	if (objc <= 4) {
	    objPtr = Tk_GetOptionInfo(interp, (char *) attrPtr,
		    listPtr->itemAttrOptionTable,
		    (objc == 4) ? objv[3] : NULL, listPtr->tkwin);
	    if (objPtr == NULL) {
		result = TCL_ERROR;
		break;
	    }
	    Tcl_SetObjResult(interp, objPtr);
	    result = TCL_OK;
	} else {
	    result = ConfigureListboxItem(interp, listPtr, attrPtr,
		    objc-3, objv+3, index);
	}
	break;
    }

    case COMMAND_NEAREST: {
	int y;

	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "y");
	    result = TCL_ERROR;
	    break;
	}

	result = Tcl_GetIntFromObj(interp, objv[2], &y);
	if (result != TCL_OK) {
	    break;
	}
	index = NearestListboxElement(listPtr, y);
	Tcl_SetObjResult(interp, Tcl_NewIntObj(index));
	result = TCL_OK;
	break;
    }

    case COMMAND_SCAN: {
	int x, y, scanCmdIndex;

	if (objc != 5) {
	    Tcl_WrongNumArgs(interp, 2, objv, "mark|dragto x y");
	    result = TCL_ERROR;
	    break;
	}

	if (Tcl_GetIntFromObj(interp, objv[3], &x) != TCL_OK
		|| Tcl_GetIntFromObj(interp, objv[4], &y) != TCL_OK) {
	    result = TCL_ERROR;
	    break;
	}

	result = Tcl_GetIndexFromObj(interp, objv[2], scanCommandNames,
		"option", 0, &scanCmdIndex);
	if (result != TCL_OK) {
	    break;
	}
	switch (scanCmdIndex) {
	case SCAN_MARK:
	    listPtr->scanMarkX = x;
	    listPtr->scanMarkY = y;
	    listPtr->scanMarkXOffset = listPtr->xOffset;
	    listPtr->scanMarkYIndex = listPtr->topIndex;
	    break;
	case SCAN_DRAGTO:
	    ListboxScanTo(listPtr, x, y);
	    break;
	}
	result = TCL_OK;
	break;
    }

    case COMMAND_SEE: {
	int diff;

	if (objc != 3) {
	    Tcl_WrongNumArgs(interp, 2, objv, "index");
	    result = TCL_ERROR;
	    break;
	}
	result = GetListboxIndex(interp, listPtr, objv[2], 0, &index);
	if (result != TCL_OK) {
	    break;
	}
	if (index >= listPtr->nElements) {
	    index = listPtr->nElements - 1;
	}
	if (index < 0) {
	    index = 0;
	}
	diff = listPtr->topIndex - index;
	if (diff > 0) {
	    if (diff <= listPtr->fullLines / 3) {
		ChangeListboxView(listPtr, index);
	    } else {
		ChangeListboxView(listPtr, index - (listPtr->fullLines-1)/2);
	    }
	} else {
	    diff = index - (listPtr->topIndex + listPtr->fullLines - 1);
	    if (diff > 0) {
		if (diff <= listPtr->fullLines / 3) {
		    ChangeListboxView(listPtr, listPtr->topIndex + diff);
		} else {
		    ChangeListboxView(listPtr, index-(listPtr->fullLines-1)/2);
		}
	    }
	}
	result = TCL_OK;
	break;
    }

    case COMMAND_SELECTION:
	result = ListboxSelectionSubCmd(interp, listPtr, objc, objv);
	break;
    case COMMAND_SIZE:
	if (objc != 2) {
	    Tcl_WrongNumArgs(interp, 2, objv, NULL);
	    result = TCL_ERROR;
	    break;
	}
	Tcl_SetObjResult(interp, Tcl_NewIntObj(listPtr->nElements));
	result = TCL_OK;
	break;
    case COMMAND_XVIEW:
	result = ListboxXviewSubCmd(interp, listPtr, objc, objv);
	break;
    case COMMAND_YVIEW:
	result = ListboxYviewSubCmd(interp, listPtr, objc, objv);
	break;
    }
    Tcl_Release(listPtr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxBboxSubCmd --
 *
 *	This procedure is invoked to process a listbox bbox request. See the
 *	user documentation for more information.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	For valid indices, places the bbox of the requested element in the
 *	interpreter's result.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxBboxSubCmd(
    Tcl_Interp *interp,		/* Pointer to the calling Tcl interpreter */
    Listbox *listPtr,		/* Information about the listbox */
    int index)			/* Index of the element to get bbox info on */
{
    int lastVisibleIndex;

    /*
     * Determine the index of the last visible item in the listbox.
     */

    lastVisibleIndex = listPtr->topIndex + listPtr->fullLines
	    + listPtr->partialLine;
    if (listPtr->nElements < lastVisibleIndex) {
	lastVisibleIndex = listPtr->nElements;
    }

    /*
     * Only allow bbox requests for indices that are visible.
     */

    if ((listPtr->topIndex <= index) && (index < lastVisibleIndex)) {
	Tcl_Obj *el, *results[4];
	const char *stringRep;
	int pixelWidth, stringLen, x, y, result;
	Tk_FontMetrics fm;

	/*
	 * Compute the pixel width of the requested element.
	 */

	result = Tcl_ListObjIndex(interp, listPtr->listObj, index, &el);
	if (result != TCL_OK) {
	    return result;
	}

	stringRep = Tcl_GetStringFromObj(el, &stringLen);
	Tk_GetFontMetrics(listPtr->tkfont, &fm);
	pixelWidth = Tk_TextWidth(listPtr->tkfont, stringRep, stringLen);

	x = listPtr->inset + listPtr->selBorderWidth - listPtr->xOffset;
	y = ((index - listPtr->topIndex)*listPtr->lineHeight)
		+ listPtr->inset + listPtr->selBorderWidth;
	results[0] = Tcl_NewIntObj(x);
	results[1] = Tcl_NewIntObj(y);
	results[2] = Tcl_NewIntObj(pixelWidth);
	results[3] = Tcl_NewIntObj(fm.linespace);
	Tcl_SetObjResult(interp, Tcl_NewListObj(4, results));
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxSelectionSubCmd --
 *
 *	This procedure is invoked to process the selection sub command for
 *	listbox widgets.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	May set the interpreter's result field.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxSelectionSubCmd(
    Tcl_Interp *interp,		/* Pointer to the calling Tcl interpreter */
    Listbox *listPtr,		/* Information about the listbox */
    int objc,			/* Number of arguments in the objv array */
    Tcl_Obj *const objv[])	/* Array of arguments to the procedure */
{
    int selCmdIndex, first, last;
    int result = TCL_OK;

    if (objc != 4 && objc != 5) {
	Tcl_WrongNumArgs(interp, 2, objv, "option index ?index?");
	return TCL_ERROR;
    }
    result = GetListboxIndex(interp, listPtr, objv[3], 0, &first);
    if (result != TCL_OK) {
	return result;
    }
    last = first;
    if (objc == 5) {
	result = GetListboxIndex(interp, listPtr, objv[4], 0, &last);
	if (result != TCL_OK) {
	    return result;
	}
    }
    result = Tcl_GetIndexFromObj(interp, objv[2], selCommandNames,
	    "option", 0, &selCmdIndex);
    if (result != TCL_OK) {
	return result;
    }

    /*
     * Only allow 'selection includes' to respond if disabled. [Bug #632514]
     */

    if ((listPtr->state == STATE_DISABLED)
	    && (selCmdIndex != SELECTION_INCLUDES)) {
	return TCL_OK;
    }

    switch (selCmdIndex) {
    case SELECTION_ANCHOR:
	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 3, objv, "index");
	    return TCL_ERROR;
	}
	if (first >= listPtr->nElements) {
	    first = listPtr->nElements - 1;
	}
	if (first < 0) {
	    first = 0;
	}
	listPtr->selectAnchor = first;
	result = TCL_OK;
	break;
    case SELECTION_CLEAR:
	result = ListboxSelect(listPtr, first, last, 0);
	break;
    case SELECTION_INCLUDES:
	if (objc != 4) {
	    Tcl_WrongNumArgs(interp, 3, objv, "index");
	    return TCL_ERROR;
	}
	Tcl_SetObjResult(interp, Tcl_NewBooleanObj(
		Tcl_FindHashEntry(listPtr->selection, KEY(first)) != NULL));
	result = TCL_OK;
	break;
    case SELECTION_SET:
	result = ListboxSelect(listPtr, first, last, 1);
	break;
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxXviewSubCmd --
 *
 *	Process the listbox "xview" subcommand.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	May change the listbox viewing area; may set the interpreter's result.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxXviewSubCmd(
    Tcl_Interp *interp,		/* Pointer to the calling Tcl interpreter */
    Listbox *listPtr,		/* Information about the listbox */
    int objc,			/* Number of arguments in the objv array */
    Tcl_Obj *const objv[])	/* Array of arguments to the procedure */
{
    int index, count, windowWidth, windowUnits;
    int offset = 0;		/* Initialized to stop gcc warnings. */
    double fraction;

    windowWidth = Tk_Width(listPtr->tkwin)
	    - 2*(listPtr->inset + listPtr->selBorderWidth);
    if (objc == 2) {
	Tcl_Obj *results[2];

	if (listPtr->maxWidth == 0) {
	    results[0] = Tcl_NewDoubleObj(0.0);
	    results[1] = Tcl_NewDoubleObj(1.0);
	} else {
	    double fraction2;

	    fraction = listPtr->xOffset / (double) listPtr->maxWidth;
	    fraction2 = (listPtr->xOffset + windowWidth)
		    / (double) listPtr->maxWidth;
	    if (fraction2 > 1.0) {
		fraction2 = 1.0;
	    }
	    results[0] = Tcl_NewDoubleObj(fraction);
	    results[1] = Tcl_NewDoubleObj(fraction2);
	}
	Tcl_SetObjResult(interp, Tcl_NewListObj(2, results));
    } else if (objc == 3) {
	if (Tcl_GetIntFromObj(interp, objv[2], &index) != TCL_OK) {
	    return TCL_ERROR;
	}
	ChangeListboxOffset(listPtr, index*listPtr->xScrollUnit);
    } else {
	switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count)) {
	case TK_SCROLL_ERROR:
	    return TCL_ERROR;
	case TK_SCROLL_MOVETO:
	    offset = (int) (fraction*listPtr->maxWidth + 0.5);
	    break;
	case TK_SCROLL_PAGES:
	    windowUnits = windowWidth / listPtr->xScrollUnit;
	    if (windowUnits > 2) {
		offset = listPtr->xOffset
			+ count*listPtr->xScrollUnit*(windowUnits-2);
	    } else {
		offset = listPtr->xOffset + count*listPtr->xScrollUnit;
	    }
	    break;
	case TK_SCROLL_UNITS:
	    offset = listPtr->xOffset + count*listPtr->xScrollUnit;
	    break;
	}
	ChangeListboxOffset(listPtr, offset);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxYviewSubCmd --
 *
 *	Process the listbox "yview" subcommand.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	May change the listbox viewing area; may set the interpreter's result.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxYviewSubCmd(
    Tcl_Interp *interp,		/* Pointer to the calling Tcl interpreter */
    Listbox *listPtr,		/* Information about the listbox */
    int objc,			/* Number of arguments in the objv array */
    Tcl_Obj *const objv[])	/* Array of arguments to the procedure */
{
    int index, count;
    double fraction;

    if (objc == 2) {
	Tcl_Obj *results[2];

	if (listPtr->nElements == 0) {
	    results[0] = Tcl_NewDoubleObj(0.0);
	    results[1] = Tcl_NewDoubleObj(1.0);
	} else {
	    double fraction2, numEls = (double) listPtr->nElements;

	    fraction = listPtr->topIndex / numEls;
	    fraction2 = (listPtr->topIndex+listPtr->fullLines) / numEls;
	    if (fraction2 > 1.0) {
		fraction2 = 1.0;
	    }
	    results[0] = Tcl_NewDoubleObj(fraction);
	    results[1] = Tcl_NewDoubleObj(fraction2);
	}
	Tcl_SetObjResult(interp, Tcl_NewListObj(2, results));
    } else if (objc == 3) {
	if (GetListboxIndex(interp, listPtr, objv[2], 0, &index) != TCL_OK) {
	    return TCL_ERROR;
	}
	ChangeListboxView(listPtr, index);
    } else {
	switch (Tk_GetScrollInfoObj(interp, objc, objv, &fraction, &count)) {
	case TK_SCROLL_MOVETO:
	    index = (int) (listPtr->nElements*fraction + 0.5);
	    break;
	case TK_SCROLL_PAGES:
	    if (listPtr->fullLines > 2) {
		index = listPtr->topIndex + count*(listPtr->fullLines-2);
	    } else {
		index = listPtr->topIndex + count;
	    }
	    break;
	case TK_SCROLL_UNITS:
	    index = listPtr->topIndex + count;
	    break;
	case TK_SCROLL_ERROR:
	default:
	    return TCL_ERROR;
	}
	ChangeListboxView(listPtr, index);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxGetItemAttributes --
 *
 *	Returns a pointer to the ItemAttr record for a given index, creating
 *	one if it does not already exist.
 *
 * Results:
 *	Pointer to an ItemAttr record.
 *
 * Side effects:
 *	Memory may be allocated for the ItemAttr record.
 *
 *----------------------------------------------------------------------
 */

static ItemAttr *
ListboxGetItemAttributes(
    Tcl_Interp *interp,		/* Pointer to the calling Tcl interpreter */
    Listbox *listPtr,		/* Information about the listbox */
    int index)			/* Index of the item to retrieve attributes
				 * for. */
{
    int isNew;
    Tcl_HashEntry *entry;
    ItemAttr *attrs;

    entry = Tcl_CreateHashEntry(listPtr->itemAttrTable, KEY(index), &isNew);
    if (isNew) {
	attrs = ckalloc(sizeof(ItemAttr));
	attrs->border = NULL;
	attrs->selBorder = NULL;
	attrs->fgColor = NULL;
	attrs->selFgColor = NULL;
	Tk_InitOptions(interp, (char *)attrs, listPtr->itemAttrOptionTable,
		listPtr->tkwin);
	Tcl_SetHashValue(entry, attrs);
    } else {
	attrs = Tcl_GetHashValue(entry);
    }
    return attrs;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyListbox --
 *
 *	This procedure is invoked by Tcl_EventuallyFree or Tcl_Release to
 *	clean up the internal structure of a listbox at a safe time (when
 *	no-one is using it anymore).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the listbox is freed up.
 *
 *----------------------------------------------------------------------
 */

static void
DestroyListbox(
    char *memPtr)		/* Info about listbox widget. */
{
    register Listbox *listPtr = (Listbox *) memPtr;
    Tcl_HashEntry *entry;
    Tcl_HashSearch search;

    /*
     * If we have an internal list object, free it.
     */

    if (listPtr->listObj != NULL) {
	Tcl_DecrRefCount(listPtr->listObj);
	listPtr->listObj = NULL;
    }

    if (listPtr->listVarName != NULL) {
	Tcl_UntraceVar(listPtr->interp, listPtr->listVarName,
		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
		ListboxListVarProc, listPtr);
    }

    /*
     * Free the selection hash table.
     */

    Tcl_DeleteHashTable(listPtr->selection);
    ckfree(listPtr->selection);

    /*
     * Free the item attribute hash table.
     */

    for (entry = Tcl_FirstHashEntry(listPtr->itemAttrTable, &search);
	    entry != NULL; entry = Tcl_NextHashEntry(&search)) {
	ckfree(Tcl_GetHashValue(entry));
    }
    Tcl_DeleteHashTable(listPtr->itemAttrTable);
    ckfree(listPtr->itemAttrTable);

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

    if (listPtr->textGC != None) {
	Tk_FreeGC(listPtr->display, listPtr->textGC);
    }
    if (listPtr->selTextGC != None) {
	Tk_FreeGC(listPtr->display, listPtr->selTextGC);
    }
    if (listPtr->gray != None) {
	Tk_FreeBitmap(Tk_Display(listPtr->tkwin), listPtr->gray);
    }

    Tk_FreeConfigOptions((char *) listPtr, listPtr->optionTable,
	    listPtr->tkwin);
    Tcl_Release(listPtr->tkwin);
    listPtr->tkwin = NULL;
    ckfree(listPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyListboxOptionTables --
 *
 *	This procedure is registered as an exit callback when the listbox
 *	command is first called. It cleans up the OptionTables structure
 *	allocated by that command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Frees memory.
 *
 *----------------------------------------------------------------------
 */

static void
DestroyListboxOptionTables(
    ClientData clientData,	/* Pointer to the OptionTables struct */
    Tcl_Interp *interp)		/* Pointer to the calling interp */
{
    ckfree(clientData);
    return;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureListbox --
 *
 *	This procedure is called to process an objv/objc 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 the interp's 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(
    Tcl_Interp *interp,		/* Used for error reporting. */
    register Listbox *listPtr,	/* Information about widget; may or may not
				 * already have values for some fields. */
    int objc,			/* Number of valid entries in argv. */
    Tcl_Obj *const objv[],	/* Arguments. */
    int flags)			/* Flags to pass to Tk_ConfigureWidget. */
{
    Tk_SavedOptions savedOptions;
    Tcl_Obj *oldListObj = NULL;
    Tcl_Obj *errorResult = NULL;
    int oldExport, error;

    oldExport = listPtr->exportSelection;
    if (listPtr->listVarName != NULL) {
	Tcl_UntraceVar(interp, listPtr->listVarName,
		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
		ListboxListVarProc, listPtr);
    }

    for (error = 0; error <= 1; error++) {
	if (!error) {
	    /*
	     * First pass: set options to new values.
	     */

	    if (Tk_SetOptions(interp, (char *) listPtr,
		    listPtr->optionTable, objc, objv,
		    listPtr->tkwin, &savedOptions, NULL) != TCL_OK) {
		continue;
	    }
	} else {
	    /*
	     * Second pass: restore options to old values.
	     */

	    errorResult = Tcl_GetObjResult(interp);
	    Tcl_IncrRefCount(errorResult);
	    Tk_RestoreSavedOptions(&savedOptions);
	}

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

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

	if (listPtr->highlightWidth < 0) {
	    listPtr->highlightWidth = 0;
	}
	listPtr->inset = listPtr->highlightWidth + listPtr->borderWidth;

	/*
	 * Claim the selection if we've suddenly started exporting it and
	 * there is a selection to export.
	 */

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

	/*
	 * Verify the current status of the list var.
	 * PREVIOUS STATE | NEW STATE  | ACTION
	 * ---------------+------------+----------------------------------
	 * no listvar     | listvar    | If listvar does not exist, create it
	 *				 and copy the internal list obj's
	 *				 content to the new var. If it does
	 *				 exist, toss the internal list obj.
	 *
	 * listvar	  | no listvar | Copy old listvar content to the
	 *				 internal list obj
	 *
	 * listvar	  | listvar    | no special action
	 *
	 * no listvar     | no listvar | no special action
	 */

	oldListObj = listPtr->listObj;
	if (listPtr->listVarName != NULL) {
	    Tcl_Obj *listVarObj = Tcl_GetVar2Ex(interp, listPtr->listVarName,
		    NULL, TCL_GLOBAL_ONLY);
	    int dummy;

	    if (listVarObj == NULL) {
		listVarObj = (oldListObj ? oldListObj : Tcl_NewObj());
		if (Tcl_SetVar2Ex(interp, listPtr->listVarName, NULL,
			listVarObj, TCL_GLOBAL_ONLY|TCL_LEAVE_ERR_MSG)
			== NULL) {
		    if (oldListObj == NULL) {
			Tcl_DecrRefCount(listVarObj);
		    }
		    continue;
		}
	    }

	    /*
	     * Make sure the object is a good list object.
	     */

	    if (Tcl_ListObjLength(listPtr->interp, listVarObj, &dummy)
		    != TCL_OK) {
		Tcl_AppendResult(listPtr->interp,
			": invalid -listvariable value", NULL);
		continue;
	    }

	    listPtr->listObj = listVarObj;
	    Tcl_TraceVar(listPtr->interp, listPtr->listVarName,
		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
		    ListboxListVarProc, listPtr);
	} else if (listPtr->listObj == NULL) {
	    listPtr->listObj = Tcl_NewObj();
	}
	Tcl_IncrRefCount(listPtr->listObj);
	if (oldListObj != NULL) {
	    Tcl_DecrRefCount(oldListObj);
	}
	break;
    }
    if (!error) {
	Tk_FreeSavedOptions(&savedOptions);
    }

    /*
     * Make sure that the list length is correct.
     */

    Tcl_ListObjLength(listPtr->interp, listPtr->listObj, &listPtr->nElements);

    if (error) {
	Tcl_SetObjResult(interp, errorResult);
	Tcl_DecrRefCount(errorResult);
	return TCL_ERROR;
    }
    ListboxWorldChanged(listPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureListboxItem --
 *
 *	This procedure is called to process an objv/objc list, plus the Tk
 *	option database, in order to configure (or reconfigure) a listbox
 *	item.
 *
 * Results:
 *	The return value is a standard Tcl result. If TCL_ERROR is returned,
 *	then the interp's result contains an error message.
 *
 * Side effects:
 *	Configuration information, such as colors, border width, etc. get set
 *	for a listbox item; old resources get freed, if there were any.
 *
 *----------------------------------------------------------------------
 */

static int
ConfigureListboxItem(
    Tcl_Interp *interp,		/* Used for error reporting. */
    register Listbox *listPtr,	/* Information about widget; may or may not
				 * already have values for some fields. */
    ItemAttr *attrs,		/* Information about the item to configure */
    int objc,			/* Number of valid entries in argv. */
    Tcl_Obj *const objv[],	/* Arguments. */
    int index)			/* Index of the listbox item being configure */
{
    Tk_SavedOptions savedOptions;

    if (Tk_SetOptions(interp, (char *)attrs,
	    listPtr->itemAttrOptionTable, objc, objv, listPtr->tkwin,
	    &savedOptions, NULL) != TCL_OK) {
	Tk_RestoreSavedOptions(&savedOptions);
	return TCL_ERROR;
    }
    Tk_FreeSavedOptions(&savedOptions);

    /*
     * Redraw this index - ListboxWorldChanged would need to be called if item
     * attributes were checked in the "world".
     */

    EventuallyRedrawRange(listPtr, index, index);
    return TCL_OK;
}

/*
 *---------------------------------------------------------------------------
 *
 * ListboxWorldChanged --
 *
 *	This procedure is called when the world has changed in some way and
 *	the widget needs to recompute all its graphics contexts and determine
 *	its new geometry.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Listbox will be relayed out and redisplayed.
 *
 *---------------------------------------------------------------------------
 */

static void
ListboxWorldChanged(
    ClientData instanceData)	/* Information about widget. */
{
    XGCValues gcValues;
    GC gc;
    unsigned long mask;
    Listbox *listPtr = instanceData;

    if (listPtr->state & STATE_NORMAL) {
	gcValues.foreground = listPtr->fgColorPtr->pixel;
	gcValues.graphics_exposures = False;
	mask = GCForeground | GCFont | GCGraphicsExposures;
    } else if (listPtr->dfgColorPtr != NULL) {
	gcValues.foreground = listPtr->dfgColorPtr->pixel;
	gcValues.graphics_exposures = False;
	mask = GCForeground | GCFont | GCGraphicsExposures;
    } else {
	gcValues.foreground = listPtr->fgColorPtr->pixel;
	mask = GCForeground | GCFont;
	if (listPtr->gray == None) {
	    listPtr->gray = Tk_GetBitmap(NULL, listPtr->tkwin, "gray50");
	}
	if (listPtr->gray != None) {
	    gcValues.fill_style = FillStippled;
	    gcValues.stipple = listPtr->gray;
	    mask |= GCFillStyle | GCStipple;
	}
    }

    gcValues.font = Tk_FontId(listPtr->tkfont);
    gc = Tk_GetGC(listPtr->tkwin, mask, &gcValues);
    if (listPtr->textGC != None) {
	Tk_FreeGC(listPtr->display, listPtr->textGC);
    }
    listPtr->textGC = gc;

    if (listPtr->selFgColorPtr != NULL) {
	gcValues.foreground = listPtr->selFgColorPtr->pixel;
    }
    gcValues.font = Tk_FontId(listPtr->tkfont);
    mask = GCForeground | GCFont;
    gc = Tk_GetGC(listPtr->tkwin, mask, &gcValues);
    if (listPtr->selTextGC != None) {
	Tk_FreeGC(listPtr->display, listPtr->selTextGC);
    }
    listPtr->selTextGC = gc;

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

    ListboxComputeGeometry(listPtr, 1, 1, 1);
    listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
    EventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
}

/*
 *--------------------------------------------------------------
 *
 * DisplayListbox --
 *
 *	This procedure redraws the contents of a listbox window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Information appears on the screen.
 *
 *--------------------------------------------------------------
 */

static void
DisplayListbox(
    ClientData clientData)	/* Information about window. */
{
    register Listbox *listPtr = clientData;
    register Tk_Window tkwin = listPtr->tkwin;
    GC gc;
    int i, limit, x, y, prevSelected, freeGC, stringLen;
    Tk_FontMetrics fm;
    Tcl_Obj *curElement;
    Tcl_HashEntry *entry;
    const char *stringRep;
    ItemAttr *attrs;
    Tk_3DBorder selectedBg;
    XGCValues gcValues;
    unsigned long mask;
    int left, right;		/* Non-zero values here indicate that the left
				 * or right edge of the listbox is
				 * off-screen. */
    Pixmap pixmap;

    listPtr->flags &= ~REDRAW_PENDING;
    if (listPtr->flags & LISTBOX_DELETED) {
	return;
    }

    if (listPtr->flags & MAXWIDTH_IS_STALE) {
	ListboxComputeGeometry(listPtr, 0, 1, 0);
	listPtr->flags &= ~MAXWIDTH_IS_STALE;
	listPtr->flags |= UPDATE_H_SCROLLBAR;
    }

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

#ifndef TK_NO_DOUBLE_BUFFERING
    /*
     * 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 = Tk_GetPixmap(listPtr->display, Tk_WindowId(tkwin),
	    Tk_Width(tkwin), Tk_Height(tkwin), Tk_Depth(tkwin));
#else
    pixmap = Tk_WindowId(tkwin);
#endif /* TK_NO_DOUBLE_BUFFERING */
    Tk_Fill3DRectangle(tkwin, pixmap, listPtr->normalBorder, 0, 0,
	    Tk_Width(tkwin), Tk_Height(tkwin), 0, TK_RELIEF_FLAT);

    /*
     * Display each item in the listbox.
     */

    limit = listPtr->topIndex + listPtr->fullLines + listPtr->partialLine - 1;
    if (limit >= listPtr->nElements) {
	limit = listPtr->nElements-1;
    }
    left = right = 0;
    if (listPtr->xOffset > 0) {
	left = listPtr->selBorderWidth+1;
    }
    if ((listPtr->maxWidth - listPtr->xOffset) > (Tk_Width(listPtr->tkwin)
	    - 2*(listPtr->inset + listPtr->selBorderWidth))) {
	right = listPtr->selBorderWidth+1;
    }
    prevSelected = 0;

    for (i = listPtr->topIndex; i <= limit; i++) {
	int width = Tk_Width(tkwin);	/* zeroth approx to silence warning */

	x = listPtr->inset;
	y = ((i - listPtr->topIndex) * listPtr->lineHeight) + listPtr->inset;
	gc = listPtr->textGC;
	freeGC = 0;

	/*
	 * Lookup this item in the item attributes table, to see if it has
	 * special foreground/background colors.
	 */

	entry = Tcl_FindHashEntry(listPtr->itemAttrTable, KEY(i));

	/*
	 * If the listbox is enabled, items may be drawn differently; they may
	 * be drawn selected, or they may have special foreground or
	 * background colors.
	 */

	if (listPtr->state & STATE_NORMAL) {
	    if (Tcl_FindHashEntry(listPtr->selection, KEY(i))) {
		/*
		 * Selected items are drawn differently.
		 */

		gc = listPtr->selTextGC;
		width = Tk_Width(tkwin) - 2*listPtr->inset;
		selectedBg = listPtr->selBorder;

		/*
		 * If there is attribute information for this item, adjust the
		 * drawing accordingly.
		 */

		if (entry != NULL) {
		    attrs = Tcl_GetHashValue(entry);

		    /*
		     * Default GC has the values from the widget at large.
		     */

		    if (listPtr->selFgColorPtr) {
			gcValues.foreground = listPtr->selFgColorPtr->pixel;
		    } else {
			gcValues.foreground = listPtr->fgColorPtr->pixel;
		    }
		    gcValues.font = Tk_FontId(listPtr->tkfont);
		    gcValues.graphics_exposures = False;
		    mask = GCForeground | GCFont | GCGraphicsExposures;

		    if (attrs->selBorder != NULL) {
			selectedBg = attrs->selBorder;
		    }

		    if (attrs->selFgColor != NULL) {
			gcValues.foreground = attrs->selFgColor->pixel;
			gc = Tk_GetGC(listPtr->tkwin, mask, &gcValues);
			freeGC = 1;
		    }
		}

		Tk_Fill3DRectangle(tkwin, pixmap, selectedBg, x, y,
			width, listPtr->lineHeight, 0, TK_RELIEF_FLAT);

		/*
		 * Draw beveled edges around the selection, if there are
		 * visible edges next to this element. Special considerations:
		 *
		 * 1. The left and right bevels may not be visible if
		 *	horizontal scrolling is enabled (the "left" & "right"
		 *	variables are zero to indicate that the corresponding
		 *	bevel is visible).
		 * 2. Top and bottom bevels are only drawn if this is the
		 *	first or last seleted item.
		 * 3. If the left or right bevel isn't visible, then the
		 *	"left" & "right" vars, computed above, have non-zero
		 *	values that extend the top and bottom bevels so that
		 *	the mitered corners are off-screen.
		 */

		/* Draw left bevel */
		if (left == 0) {
		    Tk_3DVerticalBevel(tkwin, pixmap, selectedBg,
			    x, y, listPtr->selBorderWidth, listPtr->lineHeight,
			    1, TK_RELIEF_RAISED);
		}
		/* Draw right bevel */
		if (right == 0) {
		    Tk_3DVerticalBevel(tkwin, pixmap, selectedBg,
			    x + width - listPtr->selBorderWidth, y,
			    listPtr->selBorderWidth, listPtr->lineHeight,
			    0, TK_RELIEF_RAISED);
		}
		/* Draw top bevel */
		if (!prevSelected) {
		    Tk_3DHorizontalBevel(tkwin, pixmap, selectedBg,
			    x-left, y, width+left+right,
			    listPtr->selBorderWidth,
			    1, 1, 1, TK_RELIEF_RAISED);
		}
		/* Draw bottom bevel */
		if (i + 1 == listPtr->nElements ||
			!Tcl_FindHashEntry(listPtr->selection, KEY(i + 1))) {
		    Tk_3DHorizontalBevel(tkwin, pixmap, selectedBg, x-left,
			    y + listPtr->lineHeight - listPtr->selBorderWidth,
			    width+left+right, listPtr->selBorderWidth, 0, 0, 0,
			    TK_RELIEF_RAISED);
		}
		prevSelected = 1;
	    } else {
		/*
		 * If there is an item attributes record for this item, draw
		 * the background box and set the foreground color accordingly
		 */

		if (entry != NULL) {
		    attrs = Tcl_GetHashValue(entry);
		    gcValues.foreground = listPtr->fgColorPtr->pixel;
		    gcValues.font = Tk_FontId(listPtr->tkfont);
		    gcValues.graphics_exposures = False;
		    mask = GCForeground | GCFont | GCGraphicsExposures;

		    /*
		     * If the item has its own background color, draw it now.
		     */

		    if (attrs->border != NULL) {
			width = Tk_Width(tkwin) - 2*listPtr->inset;
			Tk_Fill3DRectangle(tkwin, pixmap, attrs->border, x, y,
				width, listPtr->lineHeight, 0, TK_RELIEF_FLAT);
		    }

		    /*
		     * If the item has its own foreground, use it to override
		     * the value in the gcValues structure.
		     */

		    if ((listPtr->state & STATE_NORMAL)
			    && attrs->fgColor != NULL) {
			gcValues.foreground = attrs->fgColor->pixel;
			gc = Tk_GetGC(listPtr->tkwin, mask, &gcValues);
			freeGC = 1;
		    }
		}
		prevSelected = 0;
	    }
	}

	/*
	 * Draw the actual text of this item.
	 */

	Tk_GetFontMetrics(listPtr->tkfont, &fm);
	y += fm.ascent + listPtr->selBorderWidth;
	x = listPtr->inset + listPtr->selBorderWidth - listPtr->xOffset;
	Tcl_ListObjIndex(listPtr->interp, listPtr->listObj, i, &curElement);
	stringRep = Tcl_GetStringFromObj(curElement, &stringLen);
	Tk_DrawChars(listPtr->display, pixmap, gc, listPtr->tkfont,
		stringRep, stringLen, x, y);

	/*
	 * If this is the active element, apply the activestyle to it.
	 */

	if ((i == listPtr->active) && (listPtr->flags & GOT_FOCUS)) {
	    if (listPtr->activeStyle == ACTIVE_STYLE_UNDERLINE) {
		/*
		 * Underline the text.
		 */

		Tk_UnderlineChars(listPtr->display, pixmap, gc,
			listPtr->tkfont, stringRep, x, y, 0, stringLen);
	    } else if (listPtr->activeStyle == ACTIVE_STYLE_DOTBOX) {
#ifdef WIN32
		/*
		 * This provides for exact default look and feel on Windows.
		 */

		TkWinDCState state;
		HDC dc;
		RECT rect;

		dc = TkWinGetDrawableDC(listPtr->display, pixmap, &state);
		rect.left = listPtr->inset;
		rect.top = ((i - listPtr->topIndex) * listPtr->lineHeight)
			+ listPtr->inset;
		rect.right = rect.left + width;
		rect.bottom = rect.top + listPtr->lineHeight;
		DrawFocusRect(dc, &rect);
		TkWinReleaseDrawableDC(pixmap, dc, &state);
#else /* !WIN32 */
		/*
		 * Draw a dotted box around the text.
		 */

		x = listPtr->inset;
		y = ((i - listPtr->topIndex) * listPtr->lineHeight)
			+ listPtr->inset;
		width = Tk_Width(tkwin) - 2*listPtr->inset - 1;

		gcValues.line_style = LineOnOffDash;
		gcValues.line_width = listPtr->selBorderWidth;
		if (gcValues.line_width <= 0) {
		    gcValues.line_width  = 1;
		}
		gcValues.dash_offset = 0;
		gcValues.dashes = 1;

		/*
		 * You would think the XSetDashes was necessary, but it
		 * appears that the default dotting for just saying we want
		 * dashes appears to work correctly.
		 static char dashList[] = { 1 };
		 static int dashLen = sizeof(dashList);
		 XSetDashes(listPtr->display, gc, 0, dashList, dashLen);
		 */

		mask = GCLineWidth | GCLineStyle | GCDashList | GCDashOffset;
		XChangeGC(listPtr->display, gc, mask, &gcValues);
		XDrawRectangle(listPtr->display, pixmap, gc, x, y,
			(unsigned) width, (unsigned) listPtr->lineHeight - 1);
		if (!freeGC) {
		    /*
		     * Don't bother changing if it is about to be freed.
		     */

		    gcValues.line_style = LineSolid;
		    XChangeGC(listPtr->display, gc, GCLineStyle, &gcValues);
		}
#endif /* WIN32 */
	    }
	}

	if (freeGC) {
	    Tk_FreeGC(listPtr->display, gc);
	}
    }

    /*
     * 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(tkwin, pixmap, listPtr->normalBorder,
	    listPtr->highlightWidth, listPtr->highlightWidth,
	    Tk_Width(tkwin) - 2*listPtr->highlightWidth,
	    Tk_Height(tkwin) - 2*listPtr->highlightWidth,
	    listPtr->borderWidth, listPtr->relief);
    if (listPtr->highlightWidth > 0) {
	GC fgGC, bgGC;

	bgGC = Tk_GCForColor(listPtr->highlightBgColorPtr, pixmap);
	if (listPtr->flags & GOT_FOCUS) {
	    fgGC = Tk_GCForColor(listPtr->highlightColorPtr, pixmap);
	    TkpDrawHighlightBorder(tkwin, fgGC, bgGC,
		    listPtr->highlightWidth, pixmap);
	} else {
	    TkpDrawHighlightBorder(tkwin, bgGC, bgGC,
		    listPtr->highlightWidth, pixmap);
	}
    }
#ifndef TK_NO_DOUBLE_BUFFERING
    XCopyArea(listPtr->display, pixmap, Tk_WindowId(tkwin),
	    listPtr->textGC, 0, 0, (unsigned) Tk_Width(tkwin),
	    (unsigned) Tk_Height(tkwin), 0, 0);
    Tk_FreePixmap(listPtr->display, pixmap);
#endif /* TK_NO_DOUBLE_BUFFERING */
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxComputeGeometry --
 *
 *	This procedure is invoked to recompute geometry information such as
 *	the sizes of the elements and the overall dimensions desired for the
 *	listbox.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Geometry information is updated and a new requested size is registered
 *	for the widget. Internal border and gridding information is also set.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxComputeGeometry(
    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. */
    int maxIsStale,		/* Non-zero means the "maxWidth" field may no
				 * longer be up-to-date and must be
				 * recomputed. If fontChanged is 1 then this
				 * must be 1. */
    int updateGrid)		/* Non-zero means call Tk_SetGrid or
				 * Tk_UnsetGrid to update gridding for the
				 * window. */
{
    int width, height, pixelWidth, pixelHeight, textLength, i, result;
    Tk_FontMetrics fm;
    Tcl_Obj *element;
    const char *text;

    if (fontChanged || maxIsStale) {
	listPtr->xScrollUnit = Tk_TextWidth(listPtr->tkfont, "0", 1);
	if (listPtr->xScrollUnit == 0) {
	    listPtr->xScrollUnit = 1;
	}
	listPtr->maxWidth = 0;
	for (i = 0; i < listPtr->nElements; i++) {
	    /*
	     * Compute the pixel width of the current element.
	     */

	    result = Tcl_ListObjIndex(listPtr->interp, listPtr->listObj, i,
		    &element);
	    if (result != TCL_OK) {
		continue;
	    }
	    text = Tcl_GetStringFromObj(element, &textLength);
	    Tk_GetFontMetrics(listPtr->tkfont, &fm);
	    pixelWidth = Tk_TextWidth(listPtr->tkfont, text, textLength);
	    if (pixelWidth > listPtr->maxWidth) {
		listPtr->maxWidth = pixelWidth;
	    }
	}
    }

    Tk_GetFontMetrics(listPtr->tkfont, &fm);
    listPtr->lineHeight = fm.linespace + 1 + 2*listPtr->selBorderWidth;
    width = listPtr->width;
    if (width <= 0) {
	width = (listPtr->maxWidth + listPtr->xScrollUnit - 1)
		/ listPtr->xScrollUnit;
	if (width < 1) {
	    width = 1;
	}
    }
    pixelWidth = width*listPtr->xScrollUnit + 2*listPtr->inset
	    + 2*listPtr->selBorderWidth;
    height = listPtr->height;
    if (listPtr->height <= 0) {
	height = listPtr->nElements;
	if (height < 1) {
	    height = 1;
	}
    }
    pixelHeight = height*listPtr->lineHeight + 2*listPtr->inset;
    Tk_GeometryRequest(listPtr->tkwin, pixelWidth, pixelHeight);
    Tk_SetInternalBorder(listPtr->tkwin, listPtr->inset);
    if (updateGrid) {
	if (listPtr->setGrid) {
	    Tk_SetGrid(listPtr->tkwin, width, height, listPtr->xScrollUnit,
		    listPtr->lineHeight);
	} else {
	    Tk_UnsetGrid(listPtr->tkwin);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxInsertSubCmd --
 *
 *	This procedure is invoked to handle the listbox "insert" subcommand.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	New elements are added to the listbox pointed to by listPtr; a refresh
 *	callback is registered for the listbox.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxInsertSubCmd(
    register Listbox *listPtr,	/* Listbox that is to get the new elements. */
    int index,			/* Add the new elements before this
				 * element. */
    int objc,			/* Number of new elements to add. */
    Tcl_Obj *const objv[])	/* New elements (one per entry). */
{
    int i, oldMaxWidth, pixelWidth, result, length;
    Tcl_Obj *newListObj;
    const char *stringRep;

    oldMaxWidth = listPtr->maxWidth;
    for (i = 0; i < objc; i++) {
	/*
	 * Check if any of the new elements are wider than the current widest;
	 * if so, update our notion of "widest."
	 */

	stringRep = Tcl_GetStringFromObj(objv[i], &length);
	pixelWidth = Tk_TextWidth(listPtr->tkfont, stringRep, length);
	if (pixelWidth > listPtr->maxWidth) {
	    listPtr->maxWidth = pixelWidth;
	}
    }

    /*
     * Adjust selection and attribute information for every index after the
     * first index.
     */

    MigrateHashEntries(listPtr->selection, index, listPtr->nElements-1, objc);
    MigrateHashEntries(listPtr->itemAttrTable, index, listPtr->nElements-1,
	    objc);

    /*
     * If the object is shared, duplicate it before writing to it.
     */

    if (Tcl_IsShared(listPtr->listObj)) {
	newListObj = Tcl_DuplicateObj(listPtr->listObj);
    } else {
	newListObj = listPtr->listObj;
    }
    result = Tcl_ListObjReplace(listPtr->interp, newListObj, index, 0,
	    objc, objv);
    if (result != TCL_OK) {
	return result;
    }

    /*
     * Replace the current object and set attached listvar, if any. This may
     * error if listvar points to a var in a deleted namespace, but we ignore
     * those errors. If the namespace is recreated, it will auto-sync with the
     * current value. [Bug 1424513]
     */

    Tcl_IncrRefCount(newListObj);
    Tcl_DecrRefCount(listPtr->listObj);
    listPtr->listObj = newListObj;
    if (listPtr->listVarName != NULL) {
	Tcl_SetVar2Ex(listPtr->interp, listPtr->listVarName, NULL,
		listPtr->listObj, TCL_GLOBAL_ONLY);
    }

    /*
     * Get the new list length.
     */

    Tcl_ListObjLength(listPtr->interp, listPtr->listObj, &listPtr->nElements);

    /*
     * Update the "special" indices (anchor, topIndex, active) to account for
     * the renumbering that just occurred. Then arrange for the new
     * information to be displayed.
     */

    if (index <= listPtr->selectAnchor) {
	listPtr->selectAnchor += objc;
    }
    if (index < listPtr->topIndex) {
	listPtr->topIndex += objc;
    }
    if (index <= listPtr->active) {
	listPtr->active += objc;
	if ((listPtr->active >= listPtr->nElements) &&
		(listPtr->nElements > 0)) {
	    listPtr->active = listPtr->nElements-1;
	}
    }
    listPtr->flags |= UPDATE_V_SCROLLBAR;
    if (listPtr->maxWidth != oldMaxWidth) {
	listPtr->flags |= UPDATE_H_SCROLLBAR;
    }
    ListboxComputeGeometry(listPtr, 0, 0, 0);
    EventuallyRedrawRange(listPtr, index, listPtr->nElements-1);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxDeleteSubCmd --
 *
 *	Process a listbox "delete" subcommand by removing one or more elements
 *	from a listbox widget.
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	The listbox will be modified and (eventually) redisplayed.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxDeleteSubCmd(
    register Listbox *listPtr,	/* Listbox widget to modify. */
    int first,			/* Index of first element to delete. */
    int last)			/* Index of last element to delete. */
{
    int count, i, widthChanged, length, result, pixelWidth;
    Tcl_Obj *newListObj, *element;
    const char *stringRep;
    Tcl_HashEntry *entry;

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

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

    /*
     * Foreach deleted index we must:
     * a) remove selection information,
     * b) check the width of the element; if it is equal to the max, set
     *    widthChanged to 1, because it may be the only element with that
     *    width.
     */

    widthChanged = 0;
    for (i = first; i <= last; i++) {
	/*
	 * Remove selection information.
	 */

	entry = Tcl_FindHashEntry(listPtr->selection, KEY(i));
	if (entry != NULL) {
	    listPtr->numSelected--;
	    Tcl_DeleteHashEntry(entry);
	}

	entry = Tcl_FindHashEntry(listPtr->itemAttrTable, KEY(i));
	if (entry != NULL) {
	    ckfree(Tcl_GetHashValue(entry));
	    Tcl_DeleteHashEntry(entry);
	}

	/*
	 * Check width of the element. We only have to check if widthChanged
	 * has not already been set to 1, because we only need one maxWidth
	 * element to disappear for us to have to recompute the width
	 */

	if (widthChanged == 0) {
	    Tcl_ListObjIndex(listPtr->interp, listPtr->listObj, i, &element);
	    stringRep = Tcl_GetStringFromObj(element, &length);
	    pixelWidth = Tk_TextWidth(listPtr->tkfont, stringRep, length);
	    if (pixelWidth == listPtr->maxWidth) {
		widthChanged = 1;
	    }
	}
    }

    /*
     * Adjust selection and attribute info for indices after lastIndex.
     */

    MigrateHashEntries(listPtr->selection, last+1,
	    listPtr->nElements-1, count*-1);
    MigrateHashEntries(listPtr->itemAttrTable, last+1,
	    listPtr->nElements-1, count*-1);

    /*
     * Delete the requested elements.
     */

    if (Tcl_IsShared(listPtr->listObj)) {
	newListObj = Tcl_DuplicateObj(listPtr->listObj);
    } else {
	newListObj = listPtr->listObj;
    }
    result = Tcl_ListObjReplace(listPtr->interp,
	    newListObj, first, count, 0, NULL);
    if (result != TCL_OK) {
	return result;
    }

    /*
     * Replace the current object and set attached listvar, if any. This may
     * error if listvar points to a var in a deleted namespace, but we ignore
     * those errors. If the namespace is recreated, it will auto-sync with the
     * current value. [Bug 1424513]
     */

    Tcl_IncrRefCount(newListObj);
    Tcl_DecrRefCount(listPtr->listObj);
    listPtr->listObj = newListObj;
    if (listPtr->listVarName != NULL) {
	Tcl_SetVar2Ex(listPtr->interp, listPtr->listVarName, NULL,
		listPtr->listObj, TCL_GLOBAL_ONLY);
    }

    /*
     * Get the new list length.
     */

    Tcl_ListObjLength(listPtr->interp, listPtr->listObj, &listPtr->nElements);

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

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

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

    if (eventPtr->type == Expose) {
	EventuallyRedrawRange(listPtr,
		NearestListboxElement(listPtr, eventPtr->xexpose.y),
		NearestListboxElement(listPtr, eventPtr->xexpose.y
		+ eventPtr->xexpose.height));
    } else if (eventPtr->type == DestroyNotify) {
	if (!(listPtr->flags & LISTBOX_DELETED)) {
	    listPtr->flags |= LISTBOX_DELETED;
	    Tcl_DeleteCommandFromToken(listPtr->interp, listPtr->widgetCmd);
	    if (listPtr->setGrid) {
		Tk_UnsetGrid(listPtr->tkwin);
	    }
	    if (listPtr->flags & REDRAW_PENDING) {
		Tcl_CancelIdleCall(DisplayListbox, clientData);
	    }
	    Tcl_EventuallyFree(clientData, DestroyListbox);
	}
    } else if (eventPtr->type == ConfigureNotify) {
	int vertSpace;

	vertSpace = Tk_Height(listPtr->tkwin) - 2*listPtr->inset;
	listPtr->fullLines = vertSpace / listPtr->lineHeight;
	if ((listPtr->fullLines*listPtr->lineHeight) < vertSpace) {
	    listPtr->partialLine = 1;
	} else {
	    listPtr->partialLine = 0;
	}
	listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
	ChangeListboxView(listPtr, listPtr->topIndex);
	ChangeListboxOffset(listPtr, listPtr->xOffset);

	/*
	 * Redraw the whole listbox. It's hard to tell what needs to be
	 * redrawn (e.g. if the listbox has shrunk then we may only need to
	 * redraw the borders), so just redraw everything for safety.
	 */

	EventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
    } else if (eventPtr->type == FocusIn) {
	if (eventPtr->xfocus.detail != NotifyInferior) {
	    listPtr->flags |= GOT_FOCUS;
	    EventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
	}
    } else if (eventPtr->type == FocusOut) {
	if (eventPtr->xfocus.detail != NotifyInferior) {
	    listPtr->flags &= ~GOT_FOCUS;
	    EventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxCmdDeletedProc --
 *
 *	This procedure is invoked when a widget command is deleted. If the
 *	widget isn't already in the process of being destroyed, this command
 *	destroys it.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The widget is destroyed.
 *
 *----------------------------------------------------------------------
 */

static void
ListboxCmdDeletedProc(
    ClientData clientData)	/* Pointer to widget record for widget. */
{
    Listbox *listPtr = clientData;

    /*
     * This procedure could be invoked either because the window was destroyed
     * and the command was then deleted (in which case tkwin is NULL) or
     * because the command was deleted, and then this procedure destroys the
     * widget.
     */

    if (!(listPtr->flags & LISTBOX_DELETED)) {
	Tk_DestroyWindow(listPtr->tkwin);
    }
}

/*
 *--------------------------------------------------------------
 *
 * GetListboxIndex --
 *
 *	Parse an index into a listbox and return either its value or an error.
 *
 * Results:
 *	A standard Tcl result. If all went well, then *indexPtr is filled in
 *	with the index (into listPtr) corresponding to string. Otherwise an
 *	error message is left in the interp's result.
 *
 * Side effects:
 *	None.
 *
 *--------------------------------------------------------------
 */

static int
GetListboxIndex(
    Tcl_Interp *interp,		/* For error messages. */
    Listbox *listPtr,		/* Listbox for which the index is being
				 * specified. */
    Tcl_Obj *indexObj,		/* Specifies an element in the listbox. */
    int endIsSize,		/* If 1, "end" refers to the number of entries
				 * in the listbox. If 0, "end" refers to 1
				 * less than the number of entries. */
    int *indexPtr)		/* Where to store converted index. */
{
    int result, index;
    const char *stringRep;

    /*
     * First see if the index is one of the named indices.
     */

    result = Tcl_GetIndexFromObj(NULL, indexObj, indexNames, "", 0, &index);
    if (result == TCL_OK) {
	switch (index) {
	case INDEX_ACTIVE:
	    /* "active" index */
	    *indexPtr = listPtr->active;
	    break;
	case INDEX_ANCHOR:
	    /* "anchor" index */
	    *indexPtr = listPtr->selectAnchor;
	    break;
	case INDEX_END:
	    /* "end" index */
	    if (endIsSize) {
		*indexPtr = listPtr->nElements;
	    } else {
		*indexPtr = listPtr->nElements - 1;
	    }
	    break;
	}
	return TCL_OK;
    }

    /*
     * The index didn't match any of the named indices; maybe it's an @x,y
     */

    stringRep = Tcl_GetString(indexObj);
    if (stringRep[0] == '@') {
	/* @x,y index */
	int y;
	const char *start;
	char *end;

	start = stringRep + 1;
	y = strtol(start, &end, 0);
	if ((start == end) || (*end != ',')) {
	    goto badIndex;
	}
	start = end+1;
	y = strtol(start, &end, 0);
	if ((start == end) || (*end != '\0')) {
	    goto badIndex;
	}
	*indexPtr = NearestListboxElement(listPtr, y);
	return TCL_OK;
    }

    /*
     * Maybe the index is just an integer.
     */

    if (Tcl_GetIntFromObj(interp, indexObj, indexPtr) == TCL_OK) {
	return TCL_OK;
    }

    /*
     * Everything failed, nothing matched. Throw up an error message.
     */

  badIndex:
    Tcl_SetObjResult(interp, Tcl_ObjPrintf(
	    "bad listbox index \"%s\": must be active, anchor, end, @x,y,"
	    " or a number", Tcl_GetString(indexObj)));
    Tcl_SetErrorCode(interp, "TK", "VALUE", "LISTBOX_INDEX", NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * ChangeListboxView --
 *
 *	Change the view on a listbox widget so that a given element is
 *	displayed at the top.
 *
 * 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(
    register Listbox *listPtr,	/* Information about widget. */
    int index)			/* Index of element in listPtr that should now
				 * appear at the top of the listbox. */
{
    if (index >= (listPtr->nElements - listPtr->fullLines)) {
	index = listPtr->nElements - listPtr->fullLines;
    }
    if (index < 0) {
	index = 0;
    }
    if (listPtr->topIndex != index) {
	listPtr->topIndex = index;
	EventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
	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(
    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.
     *
     * Add half a scroll unit to do entry/text-like synchronization. [Bug
     * #225025]
     */

    offset += listPtr->xScrollUnit / 2;
    maxOffset = listPtr->maxWidth - (Tk_Width(listPtr->tkwin) -
	    2*listPtr->inset - 2*listPtr->selBorderWidth)
	    + listPtr->xScrollUnit - 1;
    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;
	EventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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(
    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, maxIndex, maxOffset;

    maxIndex = listPtr->nElements - listPtr->fullLines;
    maxOffset = listPtr->maxWidth + (listPtr->xScrollUnit - 1)
	    - (Tk_Width(listPtr->tkwin) - 2*listPtr->inset
	    - 2*listPtr->selBorderWidth - listPtr->xScrollUnit);

    /*
     * 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 > maxIndex) {
	newTopIndex = listPtr->scanMarkYIndex = maxIndex;
	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 > maxOffset) {
	newOffset = listPtr->scanMarkXOffset = maxOffset;
	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(
    register Listbox *listPtr,	/* Information about widget. */
    int y)			/* Y-coordinate in listPtr's window. */
{
    int index;

    index = (y - listPtr->inset) / listPtr->lineHeight;
    if (index >= (listPtr->fullLines + listPtr->partialLine)) {
	index = listPtr->fullLines + listPtr->partialLine - 1;
    }
    if (index < 0) {
	index = 0;
    }
    index += listPtr->topIndex;
    if (index >= listPtr->nElements) {
	index = listPtr->nElements-1;
    }
    return index;
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxSelect --
 *
 *	Select or deselect one or more elements in a listbox..
 *
 * Results:
 *	Standard Tcl result.
 *
 * Side effects:
 *	All of the elements in the range between first and last are marked as
 *	either selected or deselected, depending on the "select" argument. Any
 *	items whose state changes are redisplayed. The selection is claimed
 *	from X when the number of selected elements changes from zero to
 *	non-zero.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxSelect(
    register Listbox *listPtr,	/* Information about widget. */
    int first,			/* Index of first element to select or
				 * deselect. */
    int last,			/* Index of last element to select or
				 * deselect. */
    int select)			/* 1 means select items, 0 means deselect
				 * them. */
{
    int i, firstRedisplay, oldCount, isNew;
    Tcl_HashEntry *entry;

    if (last < first) {
	i = first;
	first = last;
	last = i;
    }
    if ((last < 0) || (first >= listPtr->nElements)) {
	return TCL_OK;
    }
    if (first < 0) {
	first = 0;
    }
    if (last >= listPtr->nElements) {
	last = listPtr->nElements - 1;
    }
    oldCount = listPtr->numSelected;
    firstRedisplay = -1;

    /*
     * For each index in the range, find it in our selection hash table. If
     * it's not there but should be, add it. If it's there but shouldn't be,
     * remove it.
     */

    for (i = first; i <= last; i++) {
	entry = Tcl_FindHashEntry(listPtr->selection, KEY(i));
	if (entry != NULL) {
	    if (!select) {
		Tcl_DeleteHashEntry(entry);
		listPtr->numSelected--;
		if (firstRedisplay < 0) {
		    firstRedisplay = i;
		}
	    }
	} else {
	    if (select) {
		entry = Tcl_CreateHashEntry(listPtr->selection, KEY(i),
			&isNew);
		Tcl_SetHashValue(entry, NULL);
		listPtr->numSelected++;
		if (firstRedisplay < 0) {
		    firstRedisplay = i;
		}
	    }
	}
    }

    if (firstRedisplay >= 0) {
	EventuallyRedrawRange(listPtr, first, last);
    }
    if ((oldCount == 0) && (listPtr->numSelected > 0)
	    && listPtr->exportSelection) {
	Tk_OwnSelection(listPtr->tkwin, XA_PRIMARY,
		ListboxLostSelection, listPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * 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.
 *
 *----------------------------------------------------------------------
 */

static int
ListboxFetchSelection(
    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 = clientData;
    Tcl_DString selection;
    int length, count, needNewline, stringLen, i;
    Tcl_Obj *curElement;
    const char *stringRep;
    Tcl_HashEntry *entry;

    if (!listPtr->exportSelection) {
	return -1;
    }

    /*
     * Use a dynamic string to accumulate the contents of the selection.
     */

    needNewline = 0;
    Tcl_DStringInit(&selection);
    for (i = 0; i < listPtr->nElements; i++) {
	entry = Tcl_FindHashEntry(listPtr->selection, KEY(i));
	if (entry != NULL) {
	    if (needNewline) {
		Tcl_DStringAppend(&selection, "\n", 1);
	    }
	    Tcl_ListObjIndex(listPtr->interp, listPtr->listObj, i,
		    &curElement);
	    stringRep = Tcl_GetStringFromObj(curElement, &stringLen);
	    Tcl_DStringAppend(&selection, stringRep, stringLen);
	    needNewline = 1;
	}
    }

    length = Tcl_DStringLength(&selection);
    if (length == 0) {
	return -1;
    }

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

    count = length - offset;
    if (count <= 0) {
	count = 0;
    } else {
	if (count > maxBytes) {
	    count = maxBytes;
	}
	memcpy(buffer, Tcl_DStringValue(&selection) + offset, (size_t) count);
    }
    buffer[count] = '\0';
    Tcl_DStringFree(&selection);
    return count;
}

/*
 *----------------------------------------------------------------------
 *
 * 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.
 *
 *----------------------------------------------------------------------
 */

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

    if ((listPtr->exportSelection) && (listPtr->nElements > 0)) {
	ListboxSelect(listPtr, 0, listPtr->nElements-1, 0);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * EventuallyRedrawRange --
 *
 *	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.
 *
 *----------------------------------------------------------------------
 */

static void
EventuallyRedrawRange(
    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. */
{
    /*
     * We don't have to register a redraw callback if one is already pending,
     * or if the window doesn't exist, or if the window isn't mapped.
     */

    if ((listPtr->flags & REDRAW_PENDING)
	    || (listPtr->flags & LISTBOX_DELETED)
	    || !Tk_IsMapped(listPtr->tkwin)) {
	return;
    }
    listPtr->flags |= REDRAW_PENDING;
    Tcl_DoWhenIdle(DisplayListbox, listPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * 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(
    register Listbox *listPtr)	/* Information about widget. */
{
    char firstStr[TCL_DOUBLE_SPACE], lastStr[TCL_DOUBLE_SPACE];
    double first, last;
    int result;
    Tcl_Interp *interp;

    if (listPtr->yScrollCmd == NULL) {
	return;
    }
    if (listPtr->nElements == 0) {
	first = 0.0;
	last = 1.0;
    } else {
	first = listPtr->topIndex / (double) listPtr->nElements;
	last = (listPtr->topIndex + listPtr->fullLines)
		/ (double) listPtr->nElements;
	if (last > 1.0) {
	    last = 1.0;
	}
    }
    Tcl_PrintDouble(NULL, first, firstStr);
    Tcl_PrintDouble(NULL, last, lastStr);

    /*
     * We must hold onto the interpreter from the listPtr because the data at
     * listPtr might be freed as a result of the Tcl_VarEval.
     */

    interp = listPtr->interp;
    Tcl_Preserve(interp);
    result = Tcl_VarEval(interp, listPtr->yScrollCmd, " ", firstStr, " ",
	    lastStr, NULL);
    if (result != TCL_OK) {
	Tcl_AddErrorInfo(interp,
		"\n    (vertical scrolling command executed by listbox)");
	Tcl_BackgroundException(interp, result);
    }
    Tcl_Release(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(
    register Listbox *listPtr)	/* Information about widget. */
{
    char firstStr[TCL_DOUBLE_SPACE], lastStr[TCL_DOUBLE_SPACE];
    int result, windowWidth;
    double first, last;
    Tcl_Interp *interp;

    if (listPtr->xScrollCmd == NULL) {
	return;
    }

    windowWidth = Tk_Width(listPtr->tkwin)
	    - 2*(listPtr->inset + listPtr->selBorderWidth);
    if (listPtr->maxWidth == 0) {
	first = 0;
	last = 1.0;
    } else {
	first = listPtr->xOffset / (double) listPtr->maxWidth;
	last = (listPtr->xOffset + windowWidth) / (double) listPtr->maxWidth;
	if (last > 1.0) {
	    last = 1.0;
	}
    }
    Tcl_PrintDouble(NULL, first, firstStr);
    Tcl_PrintDouble(NULL, last, lastStr);

    /*
     * We must hold onto the interpreter because the data referred to at
     * listPtr might be freed as a result of the call to Tcl_VarEval.
     */

    interp = listPtr->interp;
    Tcl_Preserve(interp);
    result = Tcl_VarEval(interp, listPtr->xScrollCmd, " ", firstStr, " ",
	    lastStr, NULL);
    if (result != TCL_OK) {
	Tcl_AddErrorInfo(interp,
		"\n    (horizontal scrolling command executed by listbox)");
	Tcl_BackgroundException(interp, result);
    }
    Tcl_Release(interp);
}

/*
 *----------------------------------------------------------------------
 *
 * ListboxListVarProc --
 *
 *	Called whenever the trace on the listbox list var fires.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static char *
ListboxListVarProc(
    ClientData clientData,	/* Information about button. */
    Tcl_Interp *interp,		/* Interpreter containing variable. */
    const char *name1,		/* Not used. */
    const char *name2,		/* Not used. */
    int flags)			/* Information about what happened. */
{
    Listbox *listPtr = clientData;
    Tcl_Obj *oldListObj, *varListObj;
    int oldLength, i;
    Tcl_HashEntry *entry;

    /*
     * Bwah hahahaha! Puny mortal, you can't unset a -listvar'd variable!
     */

    if (flags & TCL_TRACE_UNSETS) {
	if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
	    Tcl_SetVar2Ex(interp, listPtr->listVarName, NULL,
		    listPtr->listObj, TCL_GLOBAL_ONLY);
	    Tcl_TraceVar(interp, listPtr->listVarName,
		    TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
		    ListboxListVarProc, clientData);
	    return NULL;
	}
    } else {
	oldListObj = listPtr->listObj;
	varListObj = Tcl_GetVar2Ex(listPtr->interp, listPtr->listVarName,
		NULL, TCL_GLOBAL_ONLY);

	/*
	 * Make sure the new value is a good list; if it's not, disallow the
	 * change - the fact that it is a listvar means that it must always be
	 * a valid list - and return an error message.
	 */

	if (Tcl_ListObjLength(listPtr->interp, varListObj, &i) != TCL_OK) {
	    Tcl_SetVar2Ex(interp, listPtr->listVarName, NULL, oldListObj,
		    TCL_GLOBAL_ONLY);
	    return (char *) "invalid listvar value";
	}

	listPtr->listObj = varListObj;

	/*
	 * Incr the obj ref count so it doesn't vanish if the var is unset.
	 */

	Tcl_IncrRefCount(listPtr->listObj);

	/*
	 * Clean up the ref to our old list obj.
	 */

	Tcl_DecrRefCount(oldListObj);
    }

    /*
     * If the list length has decreased, then we should clean up selection and
     * attributes information for elements past the end of the new list
     */

    oldLength = listPtr->nElements;
    Tcl_ListObjLength(listPtr->interp, listPtr->listObj, &listPtr->nElements);
    if (listPtr->nElements < oldLength) {
	for (i = listPtr->nElements; i < oldLength; i++) {
	    /*
	     * Clean up selection.
	     */

	    entry = Tcl_FindHashEntry(listPtr->selection, KEY(i));
	    if (entry != NULL) {
		listPtr->numSelected--;
		Tcl_DeleteHashEntry(entry);
	    }

	    /*
	     * Clean up attributes.
	     */

	    entry = Tcl_FindHashEntry(listPtr->itemAttrTable, KEY(i));
	    if (entry != NULL) {
		ckfree(Tcl_GetHashValue(entry));
		Tcl_DeleteHashEntry(entry);
	    }
	}
    }

    if (oldLength != listPtr->nElements) {
	listPtr->flags |= UPDATE_V_SCROLLBAR;
	if (listPtr->topIndex > (listPtr->nElements - listPtr->fullLines)) {
	    listPtr->topIndex = listPtr->nElements - listPtr->fullLines;
	    if (listPtr->topIndex < 0) {
		listPtr->topIndex = 0;
	    }
	}
    }

    /*
     * The computed maxWidth may have changed as a result of this operation.
     * However, we don't want to recompute it every time this trace fires
     * (imagine the user doing 1000 lappends to the listvar). Therefore, set
     * the MAXWIDTH_IS_STALE flag, which will cause the width to be recomputed
     * next time the list is redrawn.
     */

    listPtr->flags |= MAXWIDTH_IS_STALE;

    EventuallyRedrawRange(listPtr, 0, listPtr->nElements-1);
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * MigrateHashEntries --
 *
 *	Given a hash table with entries keyed by a single integer value, move
 *	all entries in a given range by a fixed amount, so that if in the
 *	original table there was an entry with key n and the offset was i, in
 *	the new table that entry would have key n + i.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Rekeys some hash table entries.
 *
 *----------------------------------------------------------------------
 */

static void
MigrateHashEntries(
    Tcl_HashTable *table,
    int first,
    int last,
    int offset)
{
    int i, isNew;
    Tcl_HashEntry *entry;
    ClientData clientData;

    if (offset == 0) {
	return;
    }

    /*
     * It's more efficient to do one if/else and nest the for loops inside,
     * although we could avoid some code duplication if we nested the if/else
     * inside the for loops.
     */

    if (offset > 0) {
	for (i = last; i >= first; i--) {
	    entry = Tcl_FindHashEntry(table, KEY(i));
	    if (entry != NULL) {
		clientData = Tcl_GetHashValue(entry);
		Tcl_DeleteHashEntry(entry);
		entry = Tcl_CreateHashEntry(table, KEY(i + offset), &isNew);
		Tcl_SetHashValue(entry, clientData);
	    }
	}
    } else {
	for (i = first; i <= last; i++) {
	    entry = Tcl_FindHashEntry(table, KEY(i));
	    if (entry != NULL) {
		clientData = Tcl_GetHashValue(entry);
		Tcl_DeleteHashEntry(entry);
		entry = Tcl_CreateHashEntry(table, KEY(i + offset), &isNew);
		Tcl_SetHashValue(entry, clientData);
	    }
	}
    }
    return;
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */