]> www.wagner.pp.ru Git - oss/ck.git/blob - ckListbox.c
Ck console graphics toolkit
[oss/ck.git] / ckListbox.c
1 /* 
2  * ckListbox.c --
3  *
4  *      This module implements listbox widgets for the
5  *      toolkit.  A listbox displays a collection of strings,
6  *      one per line, and provides scrolling and selection.
7  *
8  * Copyright (c) 1990-1994 The Regents of the University of California.
9  * Copyright (c) 1994-1995 Sun Microsystems, Inc.
10  * Copyright (c) 1995 Christian Werner
11  *
12  * See the file "license.terms" for information on usage and redistribution
13  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14  */
15
16 #include "ckPort.h"
17 #include "ck.h"
18 #include "default.h"
19
20 /*
21  * One record of the following type is kept for each element
22  * associated with a listbox widget:
23  */
24
25 typedef struct Element {
26     int textLength;             /* # non-NULL characters in text. */
27     int textWidth;              /* Total width of element in screen
28                                  * characters. */
29     int selected;               /* 1 means this item is selected, 0 means
30                                  * it isn't. */
31     struct Element *nextPtr;    /* Next in list of all elements of this
32                                  * listbox, or NULL for last element. */
33     char text[4];               /* Characters of this element, NULL-
34                                  * terminated.  The actual space allocated
35                                  * here will be as large as needed (> 4,
36                                  * most likely).  Must be the last field
37                                  * of the record. */
38 } Element;
39
40 #define ElementSize(stringLength) \
41         (sizeof(Element) - 3 + stringLength)
42
43 /*
44  * A data structure of the following type is kept for each listbox
45  * widget managed by this file:
46  */
47
48 typedef struct {
49     CkWindow *winPtr;           /* Window that embodies the listbox.  NULL
50                                  * means that the window has been destroyed
51                                  * but the data structures haven't yet been
52                                  * cleaned up.*/
53     Tcl_Interp *interp;         /* Interpreter associated with listbox. */
54     Tcl_Command widgetCmd;      /* Token for listbox's widget command. */
55     int numElements;            /* Total number of elements in this listbox. */
56     Element *firstPtr;          /* First in list of elements (NULL if no
57                                  * elements). */
58     Element *lastPtr;           /* Last in list of elements (NULL if no
59                                  * elements). */
60
61     /*
62      * Information used when displaying widget:
63      */
64
65     int normalBg;               /* Normal background color. */
66     int normalFg;               /* Normal foreground color. */
67     int normalAttr;             /* Normal video attributes. */
68     int selBg;                  /* Select background color. */
69     int selFg;                  /* Select foreground color. */
70     int selAttr;                /* Select video attributes. */
71     int activeBg;               /* Active background color. */
72     int activeFg;               /* Active foreground color. */
73     int activeAttr;             /* Video attribute for active item. */
74     int width;                  /* Desired width of window, in characters. */
75     int height;                 /* Desired height of window, in lines. */
76     int topIndex;               /* Index of top-most element visible in
77                                  * window. */
78     int fullLines;              /* Number of lines that fit are completely
79                                  * visible in window.  There may be one
80                                  * additional line at the bottom that is
81                                  * partially visible. */
82
83     /*
84      * Information to support horizontal scrolling:
85      */
86
87     int maxWidth;               /* Width of widest string in listbox. */
88     int xOffset;                /* The left edge of each string in the
89                                  * listbox is offset to the left by this
90                                  * many chars (0 means no offset, positive
91                                  * means there is an offset). */
92
93     /*
94      * Information about what's selected or active, if any.
95      */
96
97     Ck_Uid selectMode;          /* Selection style: single, browse, multiple,
98                                  * or extended.  This value isn't used in C
99                                  * code, but the Tcl bindings use it. */
100     int numSelected;            /* Number of elements currently selected. */
101     int selectAnchor;           /* Fixed end of selection (i.e. element
102                                  * at which selection was started.) */
103     int active;                 /* Index of "active" element (the one that
104                                  * has been selected by keyboard traversal).
105                                  * -1 means none. */
106
107     /*
108      * Miscellaneous information:
109      */
110
111     char *takeFocus;            /* Value of -takefocus option;  not used in
112                                  * the C code, but used by keyboard traversal
113                                  * scripts.  Malloc'ed, but may be NULL. */
114     char *yScrollCmd;           /* Command prefix for communicating with
115                                  * vertical scrollbar.  NULL means no command
116                                  * to issue.  Malloc'ed. */
117     char *xScrollCmd;           /* Command prefix for communicating with
118                                  * horizontal scrollbar.  NULL means no command
119                                  * to issue.  Malloc'ed. */
120     int flags;                  /* Various flag bits:  see below for
121                                  * definitions. */
122 } Listbox;
123
124 /*
125  * Flag bits for listboxes:
126  *
127  * REDRAW_PENDING:              Non-zero means a DoWhenIdle handler
128  *                              has already been queued to redraw
129  *                              this window.
130  * UPDATE_V_SCROLLBAR:          Non-zero means vertical scrollbar needs
131  *                              to be updated.
132  * UPDATE_H_SCROLLBAR:          Non-zero means horizontal scrollbar needs
133  *                              to be updated.
134  * GOT_FOCUS:                   Non-zero means this widget currently
135  *                              has the input focus.
136  */
137
138 #define REDRAW_PENDING          1
139 #define UPDATE_V_SCROLLBAR      2
140 #define UPDATE_H_SCROLLBAR      4
141 #define GOT_FOCUS               8
142
143 /*
144  * Information used for argv parsing:
145  */
146
147 static Ck_ConfigSpec configSpecs[] = {
148     {CK_CONFIG_ATTR, "-activeattributes", "activeAttributes",
149         "ActiveAttributes", DEF_LISTBOX_ACTIVE_ATTR_COLOR,
150         Ck_Offset(Listbox, activeAttr), CK_CONFIG_COLOR_ONLY},
151     {CK_CONFIG_ATTR, "-activeattributes", "activeAttributes",
152         "ActiveAttributes", DEF_LISTBOX_ACTIVE_ATTR_MONO,
153         Ck_Offset(Listbox, activeAttr), CK_CONFIG_MONO_ONLY},
154     {CK_CONFIG_COLOR, "-activebackground", "activeBackground", "Foreground",
155         DEF_LISTBOX_ACTIVE_BG_COLOR, Ck_Offset(Listbox, activeBg),
156         CK_CONFIG_COLOR_ONLY},
157     {CK_CONFIG_COLOR, "-activebackground", "activeBackground", "Foreground",
158         DEF_LISTBOX_ACTIVE_BG_MONO, Ck_Offset(Listbox, activeBg),
159         CK_CONFIG_MONO_ONLY},
160     {CK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
161         DEF_LISTBOX_ACTIVE_FG_COLOR, Ck_Offset(Listbox, activeFg),
162         CK_CONFIG_COLOR_ONLY},
163     {CK_CONFIG_COLOR, "-activeforeground", "activeForeground", "Background",
164         DEF_LISTBOX_ACTIVE_FG_MONO, Ck_Offset(Listbox, activeFg),
165         CK_CONFIG_MONO_ONLY},
166     {CK_CONFIG_ATTR, "-attributes", "attributes", "Attributes",
167         DEF_LISTBOX_ATTR, Ck_Offset(Listbox, normalAttr), 0},
168     {CK_CONFIG_COLOR, "-background", "background", "Background",
169         DEF_LISTBOX_BG_COLOR, Ck_Offset(Listbox, normalBg),
170         CK_CONFIG_COLOR_ONLY},
171     {CK_CONFIG_COLOR, "-background", "background", "Background",
172         DEF_LISTBOX_BG_MONO, Ck_Offset(Listbox, normalBg),
173         CK_CONFIG_MONO_ONLY},
174     {CK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
175         (char *) NULL, 0, 0},
176     {CK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
177         (char *) NULL, 0, 0},
178     {CK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
179         (char *) NULL, 0, 0},
180     {CK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
181         DEF_LISTBOX_FG, Ck_Offset(Listbox, normalFg), 0},
182     {CK_CONFIG_INT, "-height", "height", "Height",
183         DEF_LISTBOX_HEIGHT, Ck_Offset(Listbox, height), 0},
184     {CK_CONFIG_ATTR, "-selectattributes", "selectAttributes",
185         "SelectAttributes", DEF_LISTBOX_SELECT_ATTR_COLOR,
186         Ck_Offset(Listbox, selAttr), CK_CONFIG_COLOR_ONLY},
187     {CK_CONFIG_ATTR, "-selectattributes", "selectAttributes",
188         "SelectAttributes", DEF_LISTBOX_SELECT_ATTR_MONO,
189         Ck_Offset(Listbox, selAttr), CK_CONFIG_MONO_ONLY},
190     {CK_CONFIG_COLOR, "-selectbackground", "selectBackground", "Foreground",
191         DEF_LISTBOX_SELECT_BG_COLOR, Ck_Offset(Listbox, selBg),
192         CK_CONFIG_COLOR_ONLY},
193     {CK_CONFIG_COLOR, "-selectbackground", "selectBackground", "Foreground",
194         DEF_LISTBOX_SELECT_BG_MONO, Ck_Offset(Listbox, selBg),
195         CK_CONFIG_MONO_ONLY},
196     {CK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
197         DEF_LISTBOX_SELECT_FG_COLOR, Ck_Offset(Listbox, selFg),
198         CK_CONFIG_COLOR_ONLY},
199     {CK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background",
200         DEF_LISTBOX_SELECT_FG_MONO, Ck_Offset(Listbox, selFg),
201         CK_CONFIG_MONO_ONLY},
202     {CK_CONFIG_UID, "-selectmode", "selectMode", "SelectMode",
203         DEF_LISTBOX_SELECT_MODE, Ck_Offset(Listbox, selectMode), 0},
204     {CK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus",
205         DEF_LISTBOX_TAKE_FOCUS, Ck_Offset(Listbox, takeFocus),
206         CK_CONFIG_NULL_OK},
207     {CK_CONFIG_INT, "-width", "width", "Width",
208         DEF_LISTBOX_WIDTH, Ck_Offset(Listbox, width), 0},
209     {CK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
210         DEF_LISTBOX_SCROLL_COMMAND, Ck_Offset(Listbox, xScrollCmd),
211         CK_CONFIG_NULL_OK},
212     {CK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
213         DEF_LISTBOX_SCROLL_COMMAND, Ck_Offset(Listbox, yScrollCmd),
214         CK_CONFIG_NULL_OK},
215     {CK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
216         (char *) NULL, 0, 0}
217 };
218
219 /*
220  * Forward declarations for procedures defined later in this file:
221  */
222
223 static void             ChangeListboxOffset _ANSI_ARGS_((Listbox *listPtr,
224                             int offset));
225 static void             ChangeListboxView _ANSI_ARGS_((Listbox *listPtr,
226                             int index));
227 static int              ConfigureListbox _ANSI_ARGS_((Tcl_Interp *interp,
228                             Listbox *listPtr, int argc, char **argv,
229                             int flags));
230 static void             DeleteEls _ANSI_ARGS_((Listbox *listPtr, int first,
231                             int last));
232 static void             DestroyListbox _ANSI_ARGS_((ClientData clientData));
233 static void             DisplayListbox _ANSI_ARGS_((ClientData clientData));
234 static int              GetListboxIndex _ANSI_ARGS_((Tcl_Interp *interp,
235                             Listbox *listPtr, char *string, int numElsOK,
236                             int *indexPtr));
237 static void             InsertEls _ANSI_ARGS_((Listbox *listPtr, int index,
238                             int argc, char **argv));
239 static void             ListboxCmdDeletedProc _ANSI_ARGS_((
240                             ClientData clientData));
241 static void             ListboxComputeGeometry _ANSI_ARGS_((Listbox *listPtr));
242 static void             ListboxEventProc _ANSI_ARGS_((ClientData clientData,
243                             CkEvent *eventPtr));
244 static void             ListboxRedrawRange _ANSI_ARGS_((Listbox *listPtr,
245                             int first, int last));
246 static void             ListboxSelect _ANSI_ARGS_((Listbox *listPtr,
247                             int first, int last, int select));
248 static void             ListboxUpdateHScrollbar _ANSI_ARGS_((Listbox *listPtr));
249 static void             ListboxUpdateVScrollbar _ANSI_ARGS_((Listbox *listPtr));
250 static int              ListboxWidgetCmd _ANSI_ARGS_((ClientData clientData,
251                             Tcl_Interp *interp, int argc, char **argv));
252 static int              NearestListboxElement _ANSI_ARGS_((Listbox *listPtr,
253                             int y));
254 \f
255 /*
256  *--------------------------------------------------------------
257  *
258  * Ck_ListboxCmd --
259  *
260  *      This procedure is invoked to process the "listbox" Tcl
261  *      command.  See the user documentation for details on what
262  *      it does.
263  *
264  * Results:
265  *      A standard Tcl result.
266  *
267  * Side effects:
268  *      See the user documentation.
269  *
270  *--------------------------------------------------------------
271  */
272
273 int
274 Ck_ListboxCmd(clientData, interp, argc, argv)
275     ClientData clientData;      /* Main window associated with
276                                  * interpreter. */
277     Tcl_Interp *interp;         /* Current interpreter. */
278     int argc;                   /* Number of arguments. */
279     char **argv;                /* Argument strings. */
280 {
281     register Listbox *listPtr;
282     CkWindow *new;
283     CkWindow *mainPtr = (CkWindow *) clientData;
284
285     if (argc < 2) {
286         Tcl_AppendResult(interp, "wrong # args: should be \"",
287                 argv[0], " pathName ?options?\"", (char *) NULL);
288         return TCL_ERROR;
289     }
290
291     new = Ck_CreateWindowFromPath(interp, mainPtr, argv[1], 0);
292     if (new == NULL) {
293         return TCL_ERROR;
294     }
295
296     /*
297      * Initialize the fields of the structure that won't be initialized
298      * by ConfigureListbox, or that ConfigureListbox requires to be
299      * initialized already (e.g. resource pointers).
300      */
301
302     listPtr = (Listbox *) ckalloc(sizeof(Listbox));
303     listPtr->winPtr = new;
304     listPtr->interp = interp;
305     listPtr->widgetCmd = Tcl_CreateCommand(interp, listPtr->winPtr->pathName,
306         ListboxWidgetCmd, (ClientData) listPtr, ListboxCmdDeletedProc);
307     listPtr->numElements = 0;
308     listPtr->firstPtr = NULL;
309     listPtr->lastPtr = NULL;
310     listPtr->normalBg = 0;
311     listPtr->normalFg = 0;
312     listPtr->normalAttr = 0;
313     listPtr->selBg = 0;
314     listPtr->selFg = 0;
315     listPtr->selAttr = 0;
316     listPtr->activeBg = 0;
317     listPtr->activeFg = 0;
318     listPtr->activeAttr = 0;
319     listPtr->width = 0;
320     listPtr->height = 0;
321     listPtr->topIndex = 0;
322     listPtr->fullLines = 1;
323     listPtr->maxWidth = 0;
324     listPtr->xOffset = 0;
325     listPtr->selectMode = NULL;
326     listPtr->numSelected = 0;
327     listPtr->selectAnchor = 0;
328     listPtr->active = 0;
329     listPtr->takeFocus = NULL;
330     listPtr->xScrollCmd = NULL;
331     listPtr->yScrollCmd = NULL;
332     listPtr->flags = 0;
333
334     Ck_SetClass(listPtr->winPtr, "Listbox");
335     Ck_CreateEventHandler(listPtr->winPtr,
336             CK_EV_EXPOSE | CK_EV_MAP | CK_EV_DESTROY |
337             CK_EV_FOCUSIN | CK_EV_FOCUSOUT,
338             ListboxEventProc, (ClientData) listPtr);
339     if (ConfigureListbox(interp, listPtr, argc-2, argv+2, 0) != TCL_OK) {
340         goto error;
341     }
342
343     interp->result = listPtr->winPtr->pathName;
344     return TCL_OK;
345
346     error:
347     Ck_DestroyWindow(listPtr->winPtr);
348     return TCL_ERROR;
349 }
350 \f
351 /*
352  *--------------------------------------------------------------
353  *
354  * ListboxWidgetCmd --
355  *
356  *      This procedure is invoked to process the Tcl command
357  *      that corresponds to a widget managed by this module.
358  *      See the user documentation for details on what it does.
359  *
360  * Results:
361  *      A standard Tcl result.
362  *
363  * Side effects:
364  *      See the user documentation.
365  *
366  *--------------------------------------------------------------
367  */
368
369 static int
370 ListboxWidgetCmd(clientData, interp, argc, argv)
371     ClientData clientData;              /* Information about listbox widget. */
372     Tcl_Interp *interp;                 /* Current interpreter. */
373     int argc;                           /* Number of arguments. */
374     char **argv;                        /* Argument strings. */
375 {
376     register Listbox *listPtr = (Listbox *) clientData;
377     int result = TCL_OK;
378     size_t length;
379     int c;
380
381     if (argc < 2) {
382         Tcl_AppendResult(interp, "wrong # args: should be \"",
383                 argv[0], " option ?arg arg ...?\"", (char *) NULL);
384         return TCL_ERROR;
385     }
386     Ck_Preserve((ClientData) listPtr);
387     c = argv[1][0];
388     length = strlen(argv[1]);
389     if ((c == 'a') && (strncmp(argv[1], "activate", length) == 0)) {
390         int index;
391
392         if (argc != 3) {
393             Tcl_AppendResult(interp, "wrong # args: should be \"",
394                     argv[0], " activate index\"",
395                     (char *) NULL);
396             goto error;
397         }
398         ListboxRedrawRange(listPtr, listPtr->active, listPtr->active);
399         if (GetListboxIndex(interp, listPtr, argv[2], 0, &index)
400                 != TCL_OK) {
401             goto error;
402         }
403         listPtr->active = index;
404         ListboxRedrawRange(listPtr, listPtr->active, listPtr->active);
405     } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0)
406         && (length >= 2)) {
407         if (argc != 3) {
408             Tcl_AppendResult(interp, "wrong # args: should be \"",
409                     argv[0], " cget option\"",
410                     (char *) NULL);
411             goto error;
412         }
413         result = Ck_ConfigureValue(interp, listPtr->winPtr, configSpecs,
414                 (char *) listPtr, argv[2], 0);
415     } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)
416             && (length >= 2)) {
417         if (argc == 2) {
418             result = Ck_ConfigureInfo(interp, listPtr->winPtr, configSpecs,
419                     (char *) listPtr, (char *) NULL, 0);
420         } else if (argc == 3) {
421             result = Ck_ConfigureInfo(interp, listPtr->winPtr, configSpecs,
422                     (char *) listPtr, argv[2], 0);
423         } else {
424             result = ConfigureListbox(interp, listPtr, argc-2, argv+2,
425                     CK_CONFIG_ARGV_ONLY);
426         }
427     } else if ((c == 'c') && (strncmp(argv[1], "curselection", length) == 0)
428             && (length >= 2)) {
429         int i, count;
430         char index[20];
431         Element *elPtr;
432
433         if (argc != 2) {
434             Tcl_AppendResult(interp, "wrong # args: should be \"",
435                     argv[0], " curselection\"",
436                     (char *) NULL);
437             goto error;
438         }
439         count = 0;
440         for (i = 0, elPtr = listPtr->firstPtr; elPtr != NULL;
441                 i++, elPtr = elPtr->nextPtr) {
442             if (elPtr->selected) {
443                 sprintf(index, "%d", i);
444                 Tcl_AppendElement(interp, index);
445                 count++;
446             }
447         }
448         if (count != listPtr->numSelected) {
449             panic("ListboxWidgetCmd: selection count incorrect");
450         }
451     } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0)) {
452         int first, last;
453
454         if ((argc < 3) || (argc > 4)) {
455             Tcl_AppendResult(interp, "wrong # args: should be \"",
456                     argv[0], " delete firstIndex ?lastIndex?\"",
457                     (char *) NULL);
458             goto error;
459         }
460         if (GetListboxIndex(interp, listPtr, argv[2], 0, &first) != TCL_OK) {
461             goto error;
462         }
463         if (argc == 3) {
464             last = first;
465         } else {
466             if (GetListboxIndex(interp, listPtr, argv[3], 0, &last) != TCL_OK) {
467                 goto error;
468             }
469         }
470         DeleteEls(listPtr, first, last);
471     } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) {
472         int first, last, i;
473         Element *elPtr;
474
475         if ((argc != 3) && (argc != 4)) {
476             Tcl_AppendResult(interp, "wrong # args: should be \"",
477                     argv[0], " get first ?last?\"", (char *) NULL);
478             goto error;
479         }
480         if (GetListboxIndex(interp, listPtr, argv[2], 0, &first) != TCL_OK) {
481             goto error;
482         }
483         if ((argc == 4) && (GetListboxIndex(interp, listPtr, argv[3],
484                 0, &last) != TCL_OK)) {
485             goto error;
486         }
487         for (elPtr = listPtr->firstPtr, i = 0; i < first;
488                 i++, elPtr = elPtr->nextPtr) {
489             /* Empty loop body. */
490         }
491         if (elPtr != NULL) {
492             if (argc == 3) {
493                 interp->result = elPtr->text;
494             } else {
495                 for (  ; i <= last; i++, elPtr = elPtr->nextPtr) {
496                     Tcl_AppendElement(interp, elPtr->text);
497                 }
498             }
499         }
500     } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0)
501             && (length >= 3)) {
502         int index;
503
504         if (argc != 3) {
505             Tcl_AppendResult(interp, "wrong # args: should be \"",
506                     argv[0], " index index\"",
507                     (char *) NULL);
508             goto error;
509         }
510         if (GetListboxIndex(interp, listPtr, argv[2], 1, &index)
511                 != TCL_OK) {
512             goto error;
513         }
514         sprintf(interp->result, "%d", index);
515     } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0)
516             && (length >= 3)) {
517         int index;
518
519         if (argc < 3) {
520             Tcl_AppendResult(interp, "wrong # args: should be \"",
521                     argv[0], " insert index ?element element ...?\"",
522                     (char *) NULL);
523             goto error;
524         }
525         if (GetListboxIndex(interp, listPtr, argv[2], 1, &index)
526                 != TCL_OK) {
527             goto error;
528         }
529         InsertEls(listPtr, index, argc-3, argv+3);
530     } else if ((c == 'n') && (strncmp(argv[1], "nearest", length) == 0)) {
531         int index, y;
532
533         if (argc != 3) {
534             Tcl_AppendResult(interp, "wrong # args: should be \"",
535                     argv[0], " nearest y\"", (char *) NULL);
536             goto error;
537         }
538         if (Tcl_GetInt(interp, argv[2], &y) != TCL_OK) {
539             goto error;
540         }
541         index = NearestListboxElement(listPtr, y);
542         sprintf(interp->result, "%d", index);
543     } else if ((c == 's') && (strncmp(argv[1], "see", length) == 0)
544             && (length >= 3)) {
545         int index, diff;
546         if (argc != 3) {
547             Tcl_AppendResult(interp, "wrong # args: should be \"",
548                     argv[0], " see index\"",
549                     (char *) NULL);
550             goto error;
551         }
552         if (GetListboxIndex(interp, listPtr, argv[2], 0, &index) != TCL_OK) {
553             goto error;
554         }
555         diff = listPtr->topIndex-index;
556         if (diff > 0) {
557             if (diff <= (listPtr->fullLines/3)) {
558                 ChangeListboxView(listPtr, index);
559             } else {
560                 ChangeListboxView(listPtr, index - (listPtr->fullLines-1)/2);
561             }
562         } else {
563             diff = index - (listPtr->topIndex + listPtr->fullLines - 1);
564             if (diff > 0) {
565                 if (diff <= (listPtr->fullLines/3)) {
566                     ChangeListboxView(listPtr, listPtr->topIndex + diff);
567                 } else {
568                     ChangeListboxView(listPtr,
569                             index - (listPtr->fullLines-1)/2);
570                 }
571             }
572         }
573     } else if ((c == 's') && (length >= 3)
574             && (strncmp(argv[1], "selection", length) == 0)) {
575         int first, last;
576
577         if ((argc != 4) && (argc != 5)) {
578             Tcl_AppendResult(interp, "wrong # args: should be \"",
579                     argv[0], " selection option index ?index?\"",
580                     (char *) NULL);
581             goto error;
582         }
583         if (GetListboxIndex(interp, listPtr, argv[3], 0, &first) != TCL_OK) {
584             goto error;
585         }
586         if (argc == 5) {
587             if (GetListboxIndex(interp, listPtr, argv[4], 0, &last) != TCL_OK) {
588                 goto error;
589             }
590         } else {
591             last = first;
592         }
593         length = strlen(argv[2]);
594         c = argv[2][0];
595         if ((c == 'a') && (strncmp(argv[2], "anchor", length) == 0)) {
596             if (argc != 4) {
597                 Tcl_AppendResult(interp, "wrong # args: should be \"",
598                     argv[0], " selection anchor index\"", (char *) NULL);
599                 goto error;
600             }
601             listPtr->selectAnchor = first;
602         } else if ((c == 'c') && (strncmp(argv[2], "clear", length) == 0)) {
603             ListboxSelect(listPtr, first, last, 0);
604         } else if ((c == 'i') && (strncmp(argv[2], "includes", length) == 0)) {
605             int i;
606             Element *elPtr;
607     
608             if (argc != 4) {
609                 Tcl_AppendResult(interp, "wrong # args: should be \"",
610                         argv[0], " selection includes index\"", (char *) NULL);
611                 goto error;
612             }
613             for (elPtr = listPtr->firstPtr, i = 0; i < first;
614                     i++, elPtr = elPtr->nextPtr) {
615                 /* Empty loop body. */
616             }
617             if ((elPtr != NULL) && (elPtr->selected)) {
618                 interp->result = "1";
619             } else {
620                 interp->result = "0";
621             }
622         } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
623             ListboxSelect(listPtr, first, last, 1);
624         } else {
625             Tcl_AppendResult(interp, "bad selection option \"", argv[2],
626                     "\": must be anchor, clear, includes, or set",
627                     (char *) NULL);
628             goto error;
629         }
630     } else if ((c == 's') && (length >= 2)
631             && (strncmp(argv[1], "size", length) == 0)) {
632         if (argc != 2) {
633             Tcl_AppendResult(interp, "wrong # args: should be \"",
634                     argv[0], " size\"", (char *) NULL);
635             goto error;
636         }
637         sprintf(interp->result, "%d", listPtr->numElements);
638     } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) {
639         int index, count, type, windowWidth;
640         int offset = 0;         /* Initialized to stop gcc warnings. */
641         double fraction, fraction2;
642
643         windowWidth = listPtr->winPtr->width;
644         if (argc == 2) {
645             if (listPtr->maxWidth == 0) {
646                 interp->result = "0 1";
647             } else {
648                 fraction = listPtr->xOffset/((double) listPtr->maxWidth);
649                 fraction2 = (listPtr->xOffset + windowWidth)
650                         /((double) listPtr->maxWidth);
651                 if (fraction2 > 1.0) {
652                     fraction2 = 1.0;
653                 }
654                 sprintf(interp->result, "%g %g", fraction, fraction2);
655             }
656         } else if (argc == 3) {
657             if (Tcl_GetInt(interp, argv[2], &index) != TCL_OK) {
658                 goto error;
659             }
660             ChangeListboxOffset(listPtr, index);
661         } else {
662             type = Ck_GetScrollInfo(interp, argc, argv, &fraction, &count);
663             switch (type) {
664                 case CK_SCROLL_ERROR:
665                     goto error;
666                 case CK_SCROLL_MOVETO:
667                     offset = (int) fraction*listPtr->maxWidth;
668                     break;
669                 case CK_SCROLL_PAGES:
670                     offset = listPtr->xOffset + count * windowWidth;
671                     break;
672                 case CK_SCROLL_UNITS:
673                     offset = listPtr->xOffset + count;
674                     break;
675             }
676             ChangeListboxOffset(listPtr, offset);
677         }
678     } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0)) {
679         int index, count, type;
680         double fraction, fraction2;
681
682         if (argc == 2) {
683             if (listPtr->numElements == 0) {
684                 interp->result = "0 1";
685             } else {
686                 fraction = listPtr->topIndex/((double) listPtr->numElements);
687                 fraction2 = (listPtr->topIndex+listPtr->fullLines)
688                         /((double) listPtr->numElements);
689                 if (fraction2 > 1.0) {
690                     fraction2 = 1.0;
691                 }
692                 sprintf(interp->result, "%g %g", fraction, fraction2);
693             }
694         } else if (argc == 3) {
695             if (GetListboxIndex(interp, listPtr, argv[2], 0, &index)
696                     != TCL_OK) {
697                 goto error;
698             }
699             ChangeListboxView(listPtr, index);
700         } else {
701             type = Ck_GetScrollInfo(interp, argc, argv, &fraction, &count);
702             switch (type) {
703                 case CK_SCROLL_ERROR:
704                     goto error;
705                 case CK_SCROLL_MOVETO:
706                     index = (int) (listPtr->numElements * fraction);
707                     break;
708                 case CK_SCROLL_PAGES:
709                     if (listPtr->fullLines > 2) {
710                         index = listPtr->topIndex
711                                 + count * (listPtr->fullLines - 2);
712                     } else {
713                         index = listPtr->topIndex + count;
714                     }
715                     break;
716                 case CK_SCROLL_UNITS:
717                     index = listPtr->topIndex + count;
718                     break;
719             }
720             ChangeListboxView(listPtr, index);
721         }
722     } else {
723         Tcl_AppendResult(interp, "bad option \"", argv[1],
724                 "\": must be activate, cget, configure, ",
725                 "curselection, delete, get, index, insert, nearest, ",
726                 "see, selection, size, ",
727                 "xview, or yview", (char *) NULL);
728         goto error;
729     }
730     Ck_Release((ClientData) listPtr);
731     return result;
732
733     error:
734     Ck_Release((ClientData) listPtr);
735     return TCL_ERROR;
736 }
737 \f
738 /*
739  *----------------------------------------------------------------------
740  *
741  * DestroyListbox --
742  *
743  *      This procedure is invoked by Ck_EventuallyFree or Ck_Release
744  *      to clean up the internal structure of a listbox at a safe time
745  *      (when no-one is using it anymore).
746  *
747  * Results:
748  *      None.
749  *
750  * Side effects:
751  *      Everything associated with the listbox is freed up.
752  *
753  *----------------------------------------------------------------------
754  */
755
756 static void
757 DestroyListbox(clientData)
758     ClientData clientData;      /* Info about listbox widget. */
759 {
760     register Listbox *listPtr = (Listbox *) clientData;
761     register Element *elPtr, *nextPtr;
762
763     /*
764      * Free up all of the list elements.
765      */
766
767     for (elPtr = listPtr->firstPtr; elPtr != NULL; ) {
768         nextPtr = elPtr->nextPtr;
769         ckfree((char *) elPtr);
770         elPtr = nextPtr;
771     }
772
773     Ck_FreeOptions(configSpecs, (char *) listPtr, 0);
774     ckfree((char *) listPtr);
775 }
776 \f
777 /*
778  *----------------------------------------------------------------------
779  *
780  * ListboxCmdDeletedProc --
781  *
782  *      This procedure is invoked when a widget command is deleted.  If
783  *      the widget isn't already in the process of being destroyed,
784  *      this command destroys it.
785  *
786  * Results:
787  *      None.
788  *
789  * Side effects:
790  *      The widget is destroyed.
791  *
792  *----------------------------------------------------------------------
793  */
794
795 static void
796 ListboxCmdDeletedProc(clientData)
797     ClientData clientData;      /* Pointer to widget record for widget. */
798 {
799     Listbox *listPtr = (Listbox *) clientData;
800     CkWindow *winPtr = listPtr->winPtr;
801
802     /*
803      * This procedure could be invoked either because the window was
804      * destroyed and the command was then deleted (in which case winPtr
805      * is NULL) or because the command was deleted, and then this procedure
806      * destroys the widget.
807      */
808
809     if (winPtr != NULL) {
810         listPtr->winPtr = NULL;
811         Ck_DestroyWindow(winPtr);
812     }
813 }
814 \f
815 /*
816  *----------------------------------------------------------------------
817  *
818  * ConfigureListbox --
819  *
820  *      This procedure is called to process an argv/argc list, plus
821  *      the option database, in order to configure (or reconfigure)
822  *      a listbox widget.
823  *
824  * Results:
825  *      The return value is a standard Tcl result.  If TCL_ERROR is
826  *      returned, then interp->result contains an error message.
827  *
828  * Side effects:
829  *      Configuration information, such as colors, border width,
830  *      etc. get set for listPtr;  old resources get freed,
831  *      if there were any.
832  *
833  *----------------------------------------------------------------------
834  */
835
836 static int
837 ConfigureListbox(interp, listPtr, argc, argv, flags)
838     Tcl_Interp *interp;         /* Used for error reporting. */
839     register Listbox *listPtr;  /* Information about widget;  may or may
840                                  * not already have values for some fields. */
841     int argc;                   /* Number of valid entries in argv. */
842     char **argv;                /* Arguments. */
843     int flags;                  /* Flags to pass to Ck_ConfigureWidget. */
844 {
845     if (Ck_ConfigureWidget(interp, listPtr->winPtr, configSpecs,
846             argc, argv, (char *) listPtr, flags) != TCL_OK) {
847         return TCL_ERROR;
848     }
849
850     /*
851      * Register the desired geometry for the window and arrange for
852      * the window to be redisplayed.
853      */
854
855     ListboxComputeGeometry(listPtr);
856     listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
857     ListboxRedrawRange(listPtr, 0, listPtr->numElements-1);
858     return TCL_OK;
859 }
860 \f
861 /*
862  *--------------------------------------------------------------
863  *
864  * DisplayListbox --
865  *
866  *      This procedure redraws the contents of a listbox window.
867  *
868  * Results:
869  *      None.
870  *
871  * Side effects:
872  *      Information appears on the screen.
873  *
874  *--------------------------------------------------------------
875  */
876
877 static void
878 DisplayListbox(clientData)
879     ClientData clientData;      /* Information about window. */
880 {
881     Listbox *listPtr = (Listbox *) clientData;
882     CkWindow *winPtr = listPtr->winPtr;
883     Element *elPtr;
884     int i, limit, y, width, cursorY;
885
886     listPtr->flags &= ~REDRAW_PENDING;
887     if (listPtr->flags & UPDATE_V_SCROLLBAR) {
888         ListboxUpdateVScrollbar(listPtr);
889     }
890     if (listPtr->flags & UPDATE_H_SCROLLBAR) {
891         ListboxUpdateHScrollbar(listPtr);
892     }
893     listPtr->flags &= ~(REDRAW_PENDING|UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR);
894     if ((listPtr->winPtr == NULL) || !(winPtr->flags & CK_MAPPED)) {
895         return;
896     }
897
898     Ck_SetWindowAttr(winPtr, listPtr->normalFg, listPtr->normalBg,
899         listPtr->normalAttr);
900     Ck_ClearToBot(winPtr, 0, 0);
901
902     /*
903      * Iterate through all of the elements of the listbox, displaying each
904      * in turn.  Selected elements use a different fg/bg/attr.
905      */
906
907     limit = listPtr->topIndex + listPtr->fullLines;
908     if (limit > listPtr->numElements) {
909         limit = listPtr->numElements;
910     }
911     width = listPtr->xOffset + winPtr->width;
912     for (elPtr = listPtr->firstPtr, i = 0, y = cursorY = 0;
913             (elPtr != NULL) && (i < limit);
914             elPtr = elPtr->nextPtr, i++) {
915         if (i < listPtr->topIndex) {
916             continue;
917         }
918         if (i == listPtr->active && (listPtr->flags & GOT_FOCUS)) {
919             cursorY = y;
920             Ck_SetWindowAttr(winPtr, listPtr->activeFg, listPtr->activeBg,
921                 listPtr->activeAttr |
922                 (elPtr->selected ? listPtr->selAttr : 0));
923         } else if (elPtr->selected) {
924             Ck_SetWindowAttr(winPtr, listPtr->selFg, listPtr->selBg,
925                 listPtr->selAttr);
926         } else {
927             Ck_SetWindowAttr(winPtr, listPtr->normalFg, listPtr->normalBg,
928                 listPtr->normalAttr);
929         }
930 #if CK_USE_UTF
931         if (listPtr->xOffset < elPtr->textWidth) {
932             char *p = Tcl_UtfAtIndex(elPtr->text, listPtr->xOffset);
933
934             CkDisplayChars(winPtr->mainPtr, winPtr->window, p,
935                 strlen(p), 0, y, 0,
936                 CK_NEWLINES_NOT_SPECIAL | CK_IGNORE_TABS | CK_FILL_UNTIL_EOL);
937         }
938 #else
939         CkDisplayChars(winPtr->mainPtr,
940             winPtr->window, &elPtr->text[listPtr->xOffset],
941             elPtr->textLength - listPtr->xOffset, 0, y, 0,
942             CK_NEWLINES_NOT_SPECIAL | CK_IGNORE_TABS | CK_FILL_UNTIL_EOL);
943 #endif
944         y++;
945     }
946     wmove(winPtr->window, cursorY, 0);
947     Ck_EventuallyRefresh(winPtr);
948 }
949 \f
950 /*
951  *----------------------------------------------------------------------
952  *
953  * ListboxComputeGeometry --
954  *
955  *      This procedure is invoked to recompute geometry information
956  *      such as the sizes of the elements and the overall dimensions
957  *      desired for the listbox.
958  *
959  * Results:
960  *      None.
961  *
962  * Side effects:
963  *      Geometry information is updated and a new requested size is
964  *      registered for the widget.  Internal border and gridding
965  *      information is also set.
966  *
967  *----------------------------------------------------------------------
968  */
969
970 static void
971 ListboxComputeGeometry(listPtr)
972     Listbox *listPtr;           /* Listbox whose geometry is to be
973                                  * recomputed. */
974 {
975     int width, height;
976
977     width = listPtr->width;
978     if (width <= 0) {
979         width = listPtr->maxWidth;
980         if (width < 1) {
981             width = 1;
982         }
983     }
984     height = listPtr->height;
985     if (listPtr->height <= 0) {
986         height = listPtr->numElements;
987         if (height < 1) {
988             height = 1;
989         }
990     }
991     Ck_GeometryRequest(listPtr->winPtr, width, height);
992 }
993 \f
994 /*
995  *----------------------------------------------------------------------
996  *
997  * InsertEls --
998  *
999  *      Add new elements to a listbox widget.
1000  *
1001  * Results:
1002  *      None.
1003  *
1004  * Side effects:
1005  *      New information gets added to listPtr;  it will be redisplayed
1006  *      soon, but not immediately.
1007  *
1008  *----------------------------------------------------------------------
1009  */
1010
1011 static void
1012 InsertEls(listPtr, index, argc, argv)
1013     register Listbox *listPtr;  /* Listbox that is to get the new
1014                                  * elements. */
1015     int index;                  /* Add the new elements before this
1016                                  * element. */
1017     int argc;                   /* Number of new elements to add. */
1018     char **argv;                /* New elements (one per entry). */
1019 {
1020     register Element *prevPtr, *newPtr;
1021     int length, i, oldMaxWidth;
1022
1023     /*
1024      * Find the element before which the new ones will be inserted.
1025      */
1026
1027     if (index <= 0) {
1028         index = 0;
1029     }
1030     if (index > listPtr->numElements) {
1031         index = listPtr->numElements;
1032     }
1033     if (index == 0) {
1034         prevPtr = NULL;
1035     } else if (index == listPtr->numElements) {
1036           prevPtr = listPtr->lastPtr;
1037     } else {
1038         for (prevPtr = listPtr->firstPtr, i = index - 1; i > 0; i--) {
1039             prevPtr = prevPtr->nextPtr;
1040         }
1041     }
1042
1043     /*
1044      * For each new element, create a record, initialize it, and link
1045      * it into the list of elements.
1046      */
1047
1048     oldMaxWidth = listPtr->maxWidth;
1049     for (i = argc ; i > 0; i--, argv++, prevPtr = newPtr) {
1050         length = strlen(*argv);
1051         newPtr = (Element *) ckalloc(ElementSize(length));
1052         newPtr->textLength = length;
1053         strcpy(newPtr->text, *argv);
1054 #if CK_USE_UTF
1055         newPtr->textWidth = Tcl_NumUtfChars(*argv, length);
1056 #else
1057         newPtr->textWidth = newPtr->textLength;
1058 #endif
1059         if (newPtr->textWidth > listPtr->maxWidth) {
1060             listPtr->maxWidth = newPtr->textWidth;
1061         }
1062         newPtr->selected = 0;
1063         if (prevPtr == NULL) {
1064             newPtr->nextPtr = listPtr->firstPtr;
1065             listPtr->firstPtr = newPtr;
1066         } else {
1067             newPtr->nextPtr = prevPtr->nextPtr;
1068             prevPtr->nextPtr = newPtr;
1069         }
1070     }
1071     if ((prevPtr != NULL) && (prevPtr->nextPtr == NULL)) {
1072         listPtr->lastPtr = prevPtr;
1073     }
1074     listPtr->numElements += argc;
1075
1076     /*
1077      * Update the selection and other indexes to account for the
1078      * renumbering that has just occurred.  Then arrange for the new
1079      * information to be displayed.
1080      */
1081
1082     if (index <= listPtr->selectAnchor) {
1083         listPtr->selectAnchor += argc;
1084     }
1085     if (index < listPtr->topIndex) {
1086         listPtr->topIndex += argc;
1087     }
1088     if (index <= listPtr->active) {
1089         listPtr->active += argc;
1090         if ((listPtr->active >= listPtr->numElements)
1091                 && (listPtr->numElements > 0)) {
1092             listPtr->active = listPtr->numElements-1;
1093         }
1094     }
1095     listPtr->flags |= UPDATE_V_SCROLLBAR;
1096     if (listPtr->maxWidth != oldMaxWidth) {
1097         listPtr->flags |= UPDATE_H_SCROLLBAR;
1098     }
1099     ListboxComputeGeometry(listPtr);
1100     ListboxRedrawRange(listPtr, index, listPtr->numElements-1);
1101 }
1102 \f
1103 /*
1104  *----------------------------------------------------------------------
1105  *
1106  * DeleteEls --
1107  *
1108  *      Remove one or more elements from a listbox widget.
1109  *
1110  * Results:
1111  *      None.
1112  *
1113  * Side effects:
1114  *      Memory gets freed, the listbox gets modified and (eventually)
1115  *      redisplayed.
1116  *
1117  *----------------------------------------------------------------------
1118  */
1119
1120 static void
1121 DeleteEls(listPtr, first, last)
1122     register Listbox *listPtr;  /* Listbox widget to modify. */
1123     int first;                  /* Index of first element to delete. */
1124     int last;                   /* Index of last element to delete. */
1125 {
1126     register Element *prevPtr, *elPtr;
1127     int count, i, widthChanged;
1128
1129     /*
1130      * Adjust the range to fit within the existing elements of the
1131      * listbox, and make sure there's something to delete.
1132      */
1133
1134     if (first < 0) {
1135         first = 0;
1136     }
1137     if (last >= listPtr->numElements) {
1138         last = listPtr->numElements-1;
1139     }
1140     count = last + 1 - first;
1141     if (count <= 0) {
1142         return;
1143     }
1144
1145     /*
1146      * Find the element just before the ones to delete.
1147      */
1148
1149     if (first == 0) {
1150         prevPtr = NULL;
1151     } else {
1152         for (i = first-1, prevPtr = listPtr->firstPtr; i > 0; i--) {
1153             prevPtr = prevPtr->nextPtr;
1154         }
1155     }
1156
1157     /*
1158      * Delete the requested number of elements.
1159      */
1160
1161     widthChanged = 0;
1162     for (i = count; i > 0; i--) {
1163         if (prevPtr == NULL) {
1164             elPtr = listPtr->firstPtr;
1165             listPtr->firstPtr = elPtr->nextPtr;
1166             if (listPtr->firstPtr == NULL) {
1167                 listPtr->lastPtr = NULL;
1168             }
1169         } else {
1170             elPtr = prevPtr->nextPtr;
1171             prevPtr->nextPtr = elPtr->nextPtr;
1172             if (prevPtr->nextPtr == NULL) {
1173                 listPtr->lastPtr = prevPtr;
1174             }
1175         }
1176         if (elPtr->textWidth == listPtr->maxWidth) {
1177             widthChanged = 1;
1178         }
1179         if (elPtr->selected) {
1180             listPtr->numSelected -= 1;
1181         }
1182         ckfree((char *) elPtr);
1183     }
1184     listPtr->numElements -= count;
1185
1186     /*
1187      * Update the selection and viewing information to reflect the change
1188      * in the element numbering, and redisplay to slide information up over
1189      * the elements that were deleted.
1190      */
1191
1192     if (first <= listPtr->selectAnchor) {
1193         listPtr->selectAnchor -= count;
1194         if (listPtr->selectAnchor < first) {
1195             listPtr->selectAnchor = first;
1196         }
1197     }
1198     if (first <= listPtr->topIndex) {
1199         listPtr->topIndex -= count;
1200         if (listPtr->topIndex < first) {
1201             listPtr->topIndex = first;
1202         }
1203     }
1204     if (listPtr->topIndex > (listPtr->numElements - listPtr->fullLines)) {
1205         listPtr->topIndex = listPtr->numElements - listPtr->fullLines;
1206         if (listPtr->topIndex < 0) {
1207             listPtr->topIndex = 0;
1208         }
1209     }
1210     if (listPtr->active > last) {
1211         listPtr->active -= count;
1212     } else if (listPtr->active >= first) {
1213         listPtr->active = first;
1214         if ((listPtr->active >= listPtr->numElements)
1215                 && (listPtr->numElements > 0)) {
1216             listPtr->active = listPtr->numElements-1;
1217         }
1218     }
1219     listPtr->flags |= UPDATE_V_SCROLLBAR;
1220     ListboxComputeGeometry(listPtr);
1221     if (widthChanged) {
1222         int maxWidth = 0;
1223
1224         for (elPtr = listPtr->firstPtr; elPtr != NULL; elPtr = elPtr->nextPtr)
1225             if (elPtr->textWidth > maxWidth)
1226                 maxWidth = elPtr->textWidth;
1227         if (maxWidth != listPtr->maxWidth) {
1228             listPtr->maxWidth = maxWidth;
1229             listPtr->flags |= UPDATE_H_SCROLLBAR;
1230             if (listPtr->xOffset + listPtr->width >= listPtr->maxWidth)
1231                 listPtr->xOffset = listPtr->maxWidth - listPtr->width;
1232             if (listPtr->xOffset < 0)
1233                 listPtr->xOffset = 0;
1234         }
1235     }
1236     ListboxRedrawRange(listPtr, first, listPtr->numElements-1);
1237 }
1238 \f
1239 /*
1240  *--------------------------------------------------------------
1241  *
1242  * ListboxEventProc --
1243  *
1244  *      This procedure is invoked by the dispatcher for various
1245  *      events on listboxes.
1246  *
1247  * Results:
1248  *      None.
1249  *
1250  * Side effects:
1251  *      When the window gets deleted, internal structures get
1252  *      cleaned up.  When it gets exposed, it is redisplayed.
1253  *
1254  *--------------------------------------------------------------
1255  */
1256
1257 static void
1258 ListboxEventProc(clientData, eventPtr)
1259     ClientData clientData;      /* Information about window. */
1260     CkEvent *eventPtr;          /* Information about event. */
1261 {
1262     Listbox *listPtr = (Listbox *) clientData;
1263
1264     if (eventPtr->type == CK_EV_DESTROY) {
1265         if (listPtr->winPtr != NULL) {
1266             listPtr->winPtr = NULL;
1267             Tcl_DeleteCommand(listPtr->interp,
1268                 Tcl_GetCommandName(listPtr->interp, listPtr->widgetCmd));
1269         }
1270         if (listPtr->flags & REDRAW_PENDING) {
1271             Tk_CancelIdleCall(DisplayListbox, (ClientData) listPtr);
1272         }
1273         Ck_EventuallyFree((ClientData) listPtr,
1274             (Ck_FreeProc *) DestroyListbox);
1275     } else if (eventPtr->type == CK_EV_EXPOSE) {
1276         listPtr->fullLines = listPtr->winPtr->height;
1277         listPtr->flags |= UPDATE_V_SCROLLBAR|UPDATE_H_SCROLLBAR;
1278         ChangeListboxView(listPtr, listPtr->topIndex);
1279         ChangeListboxOffset(listPtr, listPtr->xOffset);
1280
1281         /*
1282          * Redraw the whole listbox.  It's hard to tell what needs
1283          * to be redrawn (e.g. if the listbox has shrunk then we
1284          * may only need to redraw the borders), so just redraw
1285          * everything for safety.
1286          */
1287
1288         ListboxRedrawRange(listPtr, 0, listPtr->numElements-1);
1289     } else if (eventPtr->type == CK_EV_FOCUSIN) {
1290         listPtr->flags |= GOT_FOCUS;
1291         ListboxRedrawRange(listPtr, listPtr->active, listPtr->active);
1292     } else if (eventPtr->type == CK_EV_FOCUSOUT) {
1293         listPtr->flags &= ~GOT_FOCUS;
1294         ListboxRedrawRange(listPtr, listPtr->active, listPtr->active);
1295     }
1296 }
1297 \f
1298 /*
1299  *--------------------------------------------------------------
1300  *
1301  * GetListboxIndex --
1302  *
1303  *      Parse an index into a listbox and return either its value
1304  *      or an error.
1305  *
1306  * Results:
1307  *      A standard Tcl result.  If all went well, then *indexPtr is
1308  *      filled in with the index (into listPtr) corresponding to
1309  *      string.  Otherwise an error message is left in interp->result.
1310  *
1311  * Side effects:
1312  *      None.
1313  *
1314  *--------------------------------------------------------------
1315  */
1316
1317 static int
1318 GetListboxIndex(interp, listPtr, string, numElsOK, indexPtr)
1319     Tcl_Interp *interp;         /* For error messages. */
1320     Listbox *listPtr;           /* Listbox for which the index is being
1321                                  * specified. */
1322     char *string;               /* Specifies an element in the listbox. */
1323     int numElsOK;               /* 0 means the return value must be less
1324                                  * less than the number of entries in
1325                                  * the listbox;  1 means it may also be
1326                                  * equal to the number of entries. */
1327     int *indexPtr;              /* Where to store converted index. */
1328 {
1329     int c;
1330     size_t length;
1331
1332     length = strlen(string);
1333     c = string[0];
1334     if ((c == 'a') && (strncmp(string, "active", length) == 0)
1335             && (length >= 2)) {
1336         *indexPtr = listPtr->active;
1337     } else if ((c == 'a') && (strncmp(string, "anchor", length) == 0)
1338             && (length >= 2)) {
1339         *indexPtr = listPtr->selectAnchor;
1340     } else if ((c == 'e') && (strncmp(string, "end", length) == 0)) {
1341         *indexPtr = listPtr->numElements;
1342     } else if (c == '@') {
1343         int x, y;
1344         char *p, *end;
1345
1346         p = string+1;
1347         x = strtol(p, &end, 0);
1348         if ((end == p) || (*end != ',')) {
1349             goto badIndex;
1350         }
1351         p = end+1;
1352         y = strtol(p, &end, 0);
1353         if ((end == p) || (*end != 0)) {
1354             goto badIndex;
1355         }
1356         *indexPtr = NearestListboxElement(listPtr, y);
1357     } else {
1358         if (Tcl_GetInt(interp, string, indexPtr) != TCL_OK) {
1359             Tcl_ResetResult(interp);
1360             goto badIndex;
1361         }
1362     }
1363     if (numElsOK) {
1364         if (*indexPtr > listPtr->numElements) {
1365             *indexPtr = listPtr->numElements;
1366         }
1367     } else if (*indexPtr >= listPtr->numElements) {
1368         *indexPtr = listPtr->numElements-1;
1369     }
1370     if (*indexPtr < 0) {
1371         *indexPtr = 0;
1372     }
1373     return TCL_OK;
1374
1375     badIndex:
1376     Tcl_AppendResult(interp, "bad listbox index \"", string,
1377             "\":  must be active, anchor, end, @x,y, or a number",
1378             (char *) NULL);
1379     return TCL_ERROR;
1380 }
1381 \f
1382 /*
1383  *----------------------------------------------------------------------
1384  *
1385  * ChangeListboxView --
1386  *
1387  *      Change the view on a listbox widget so that a given element
1388  *      is displayed at the top.
1389  *
1390  * Results:
1391  *      None.
1392  *
1393  * Side effects:
1394  *      What's displayed on the screen is changed.  If there is a
1395  *      scrollbar associated with this widget, then the scrollbar
1396  *      is instructed to change its display too.
1397  *
1398  *----------------------------------------------------------------------
1399  */
1400
1401 static void
1402 ChangeListboxView(listPtr, index)
1403     register Listbox *listPtr;          /* Information about widget. */
1404     int index;                          /* Index of element in listPtr
1405                                          * that should now appear at the
1406                                          * top of the listbox. */
1407 {
1408     if (index >= (listPtr->numElements - listPtr->fullLines)) {
1409         index = listPtr->numElements - listPtr->fullLines;
1410     }
1411     if (index < 0) {
1412         index = 0;
1413     }
1414     if (listPtr->topIndex != index) {
1415         listPtr->topIndex = index;
1416         if (!(listPtr->flags & REDRAW_PENDING)) {
1417             Tk_DoWhenIdle(DisplayListbox, (ClientData) listPtr);
1418             listPtr->flags |= REDRAW_PENDING;
1419         }
1420         listPtr->flags |= UPDATE_V_SCROLLBAR;
1421     }
1422 }
1423 \f
1424 /*
1425  *----------------------------------------------------------------------
1426  *
1427  * ChangListboxOffset --
1428  *
1429  *      Change the horizontal offset for a listbox.
1430  *
1431  * Results:
1432  *      None.
1433  *
1434  * Side effects:
1435  *      The listbox may be redrawn to reflect its new horizontal
1436  *      offset.
1437  *
1438  *----------------------------------------------------------------------
1439  */
1440
1441 static void
1442 ChangeListboxOffset(listPtr, offset)
1443     register Listbox *listPtr;          /* Information about widget. */
1444     int offset;                         /* Desired new "xOffset" for
1445                                          * listbox. */
1446 {
1447     int maxOffset;
1448
1449     /*
1450      * Make sure that the new offset is within the allowable range, and
1451      * round it off to an even multiple of xScrollUnit.
1452      */
1453
1454     maxOffset = listPtr->maxWidth - listPtr->winPtr->width;
1455     if (offset > maxOffset) {
1456         offset = maxOffset;
1457     }
1458     if (offset < 0) {
1459         offset = 0;
1460     }
1461     listPtr->xOffset = offset;
1462     listPtr->flags |= UPDATE_H_SCROLLBAR;
1463     ListboxRedrawRange(listPtr, 0, listPtr->numElements);
1464 }
1465 \f
1466 /*
1467  *----------------------------------------------------------------------
1468  *
1469  * NearestListboxElement --
1470  *
1471  *      Given a y-coordinate inside a listbox, compute the index of
1472  *      the element under that y-coordinate (or closest to that
1473  *      y-coordinate).
1474  *
1475  * Results:
1476  *      The return value is an index of an element of listPtr.  If
1477  *      listPtr has no elements, then 0 is always returned.
1478  *
1479  * Side effects:
1480  *      None.
1481  *
1482  *----------------------------------------------------------------------
1483  */
1484
1485 static int
1486 NearestListboxElement(listPtr, y)
1487     register Listbox *listPtr;          /* Information about widget. */
1488     int y;                              /* Y-coordinate in listPtr's window. */
1489 {
1490     int index;
1491
1492     index = y;
1493     if (index >= listPtr->fullLines) {
1494         index = listPtr->fullLines - 1;
1495     }
1496     if (index < 0) {
1497         index = 0;
1498     }
1499     index += listPtr->topIndex;
1500     if (index >= listPtr->numElements) {
1501         index = listPtr->numElements-1;
1502     }
1503     return index;
1504 }
1505 \f
1506 /*
1507  *----------------------------------------------------------------------
1508  *
1509  * ListboxSelect --
1510  *
1511  *      Select or deselect one or more elements in a listbox..
1512  *
1513  * Results:
1514  *      None.
1515  *
1516  * Side effects:
1517  *      All of the elements in the range between first and last are
1518  *      marked as either selected or deselected, depending on the
1519  *      "select" argument.  Any items whose state changes are redisplayed.
1520  *      The selection is claimed from X when the number of selected
1521  *      elements changes from zero to non-zero.
1522  *
1523  *----------------------------------------------------------------------
1524  */
1525
1526 static void
1527 ListboxSelect(listPtr, first, last, select)
1528     register Listbox *listPtr;          /* Information about widget. */
1529     int first;                          /* Index of first element to
1530                                          * select or deselect. */
1531     int last;                           /* Index of last element to
1532                                          * select or deselect. */
1533     int select;                         /* 1 means select items, 0 means
1534                                          * deselect them. */
1535 {
1536     int i, firstRedisplay, lastRedisplay, increment, oldCount;
1537     Element *elPtr;
1538
1539     if (last < first) {
1540         i = first;
1541         first = last;
1542         last = i;
1543     }
1544     if (first >= listPtr->numElements) {
1545         return;
1546     }
1547     oldCount = listPtr->numSelected;
1548     firstRedisplay = -1;
1549     increment = select ? 1 : -1;
1550     for (i = 0, elPtr = listPtr->firstPtr; i < first;
1551             i++, elPtr = elPtr->nextPtr) {
1552         /* Empty loop body. */
1553     }
1554     for ( ; i <= last; i++, elPtr = elPtr->nextPtr) {
1555         if (elPtr->selected == select) {
1556             continue;
1557         }
1558         listPtr->numSelected += increment;
1559         elPtr->selected = select;
1560         if (firstRedisplay < 0) {
1561             firstRedisplay = i;
1562         }
1563         lastRedisplay = i;
1564     }
1565     if (firstRedisplay >= 0) {
1566         ListboxRedrawRange(listPtr, first, last);
1567     }
1568 }
1569 \f
1570 /*
1571  *----------------------------------------------------------------------
1572  *
1573  * ListboxRedrawRange --
1574  *
1575  *      Ensure that a given range of elements is eventually redrawn on
1576  *      the display (if those elements in fact appear on the display).
1577  *
1578  * Results:
1579  *      None.
1580  *
1581  * Side effects:
1582  *      Information gets redisplayed.
1583  *
1584  *----------------------------------------------------------------------
1585  */
1586
1587 static void
1588 ListboxRedrawRange(listPtr, first, last)
1589     register Listbox *listPtr;          /* Information about widget. */
1590     int first;                          /* Index of first element in list
1591                                          * that needs to be redrawn. */
1592     int last;                           /* Index of last element in list
1593                                          * that needs to be redrawn.  May
1594                                          * be less than first;
1595                                          * these just bracket a range. */
1596 {
1597     if ((listPtr->winPtr == NULL) || !(listPtr->winPtr->flags & CK_MAPPED)
1598             || (listPtr->flags & REDRAW_PENDING)) {
1599         return;
1600     }
1601     Tk_DoWhenIdle(DisplayListbox, (ClientData) listPtr);
1602     listPtr->flags |= REDRAW_PENDING;
1603 }
1604 \f
1605 /*
1606  *----------------------------------------------------------------------
1607  *
1608  * ListboxUpdateVScrollbar --
1609  *
1610  *      This procedure is invoked whenever information has changed in
1611  *      a listbox in a way that would invalidate a vertical scrollbar
1612  *      display.  If there is an associated scrollbar, then this command
1613  *      updates it by invoking a Tcl command.
1614  *
1615  * Results:
1616  *      None.
1617  *
1618  * Side effects:
1619  *      A Tcl command is invoked, and an additional command may be
1620  *      invoked to process errors in the command.
1621  *
1622  *----------------------------------------------------------------------
1623  */
1624
1625 static void
1626 ListboxUpdateVScrollbar(listPtr)
1627     register Listbox *listPtr;          /* Information about widget. */
1628 {
1629     char string[100];
1630     double first, last;
1631     int result;
1632
1633     if (listPtr->yScrollCmd == NULL) {
1634         return;
1635     }
1636     if (listPtr->numElements == 0) {
1637         first = 0.0;
1638         last = 1.0;
1639     } else {
1640         first = listPtr->topIndex/((double) listPtr->numElements);
1641         last = (listPtr->topIndex+listPtr->fullLines)
1642                 /((double) listPtr->numElements);
1643         if (last > 1.0) {
1644             last = 1.0;
1645         }
1646     }
1647     sprintf(string, " %g %g", first, last);
1648     result = Tcl_VarEval(listPtr->interp, listPtr->yScrollCmd, string,
1649             (char *) NULL);
1650     if (result != TCL_OK) {
1651         Tcl_AddErrorInfo(listPtr->interp,
1652                 "\n    (vertical scrolling command executed by listbox)");
1653         Tk_BackgroundError(listPtr->interp);
1654     }
1655 }
1656 \f
1657 /*
1658  *----------------------------------------------------------------------
1659  *
1660  * ListboxUpdateHScrollbar --
1661  *
1662  *      This procedure is invoked whenever information has changed in
1663  *      a listbox in a way that would invalidate a horizontal scrollbar
1664  *      display.  If there is an associated horizontal scrollbar, then
1665  *      this command updates it by invoking a Tcl command.
1666  *
1667  * Results:
1668  *      None.
1669  *
1670  * Side effects:
1671  *      A Tcl command is invoked, and an additional command may be
1672  *      invoked to process errors in the command.
1673  *
1674  *----------------------------------------------------------------------
1675  */
1676
1677 static void
1678 ListboxUpdateHScrollbar(listPtr)
1679     register Listbox *listPtr;          /* Information about widget. */
1680 {
1681     char string[60];
1682     int result, windowWidth;
1683     double first, last;
1684
1685     if (listPtr->xScrollCmd == NULL) {
1686         return;
1687     }
1688     windowWidth = listPtr->winPtr->width;
1689     if (listPtr->maxWidth == 0) {
1690         first = 0;
1691         last = 1.0;
1692     } else {
1693         first = listPtr->xOffset/((double) listPtr->maxWidth);
1694         last = (listPtr->xOffset + windowWidth)
1695                 /((double) listPtr->maxWidth);
1696         if (last > 1.0) {
1697             last = 1.0;
1698         }
1699     }
1700     sprintf(string, " %g %g", first, last);
1701     result = Tcl_VarEval(listPtr->interp, listPtr->xScrollCmd, string,
1702             (char *) NULL);
1703     if (result != TCL_OK) {
1704         Tcl_AddErrorInfo(listPtr->interp,
1705                 "\n    (horizontal scrolling command executed by listbox)");
1706         Tk_BackgroundError(listPtr->interp);
1707     }
1708 }