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