1 /*
2  * Copyright (c) 2002-2015 Hypertriton, Inc. <http://hypertriton.com/>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
18  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
23  * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include <agar/core/core.h>
27 #include <agar/gui/tlist.h>
28 #include <agar/gui/primitive.h>
29 
30 #include <string.h>
31 #include <stdarg.h>
32 
33 static void MouseButtonDown(AG_Event *);
34 static void KeyDown(AG_Event *);
35 static void KeyUp(AG_Event *);
36 
37 static void FreeItem(AG_Tlist *, AG_TlistItem *);
38 static void SelectItem(AG_Tlist *, AG_TlistItem *);
39 static void DeselectItem(AG_Tlist *, AG_TlistItem *);
40 static void UpdateItemIcon(AG_Tlist *, AG_TlistItem *, AG_Surface *);
41 
42 #ifndef AG_TLIST_PADDING
43 #define AG_TLIST_PADDING 2	/* Label padding (pixels) */
44 #endif
45 
46 AG_Tlist *
AG_TlistNew(void * parent,Uint flags)47 AG_TlistNew(void *parent, Uint flags)
48 {
49 	AG_Tlist *tl;
50 
51 	tl = Malloc(sizeof(AG_Tlist));
52 	AG_ObjectInit(tl, &agTlistClass);
53 	tl->flags |= flags;
54 
55 	if (flags & AG_TLIST_HFILL) { AG_ExpandHoriz(tl); }
56 	if (flags & AG_TLIST_VFILL) { AG_ExpandVert(tl); }
57 
58 	AG_ObjectAttach(parent, tl);
59 	return (tl);
60 }
61 
62 AG_Tlist *
AG_TlistNewPolled(void * parent,Uint flags,AG_EventFn fn,const char * fmt,...)63 AG_TlistNewPolled(void *parent, Uint flags, AG_EventFn fn, const char *fmt, ...)
64 {
65 	AG_Tlist *tl;
66 	AG_Event *ev;
67 
68 	tl = AG_TlistNew(parent, flags);
69 	AG_ObjectLock(tl);
70 	tl->flags |= AG_TLIST_POLL;
71 	ev = AG_SetEvent(tl, "tlist-poll", fn, NULL);
72 	AG_EVENT_GET_ARGS(ev, fmt);
73 	AG_ObjectUnlock(tl);
74 	AG_RedrawOnTick(tl, 1000);
75 	return (tl);
76 }
77 
78 static __inline__ void
UpdatePolled(AG_Tlist * tl)79 UpdatePolled(AG_Tlist *tl)
80 {
81 	if ((tl->flags & AG_TLIST_POLL) &&
82 	    (tl->flags & AG_TLIST_REFRESH)) {
83 		tl->flags &= ~(AG_TLIST_REFRESH);
84 		AG_PostEvent(NULL, tl, "tlist-poll", NULL);
85 	}
86 }
87 
88 static int
SelectionVisible(AG_Tlist * tl)89 SelectionVisible(AG_Tlist *tl)
90 {
91 	AG_TlistItem *it;
92 	int y = 0, i = 0;
93 
94 	UpdatePolled(tl);
95 
96 	TAILQ_FOREACH(it, &tl->items, items) {
97 		if (i++ < tl->rOffs)
98 			continue;
99 		if (y > HEIGHT(tl) - tl->item_h)
100 			break;
101 
102 		if (it->selected) {
103 			return (1);
104 		}
105 		y += tl->item_h;
106 	}
107 	return (0);
108 }
109 
110 static void
ScrollToSelection(AG_Tlist * tl)111 ScrollToSelection(AG_Tlist *tl)
112 {
113 	AG_TlistItem *it;
114 	int m = 0;
115 
116 	TAILQ_FOREACH(it, &tl->items, items) {
117 		if (!it->selected) {
118 			m++;
119 			continue;
120 		}
121 		tl->rOffs = (tl->rOffs > m) ? m :
122 		    MAX(0, m - tl->nvisitems + 1);
123 		AG_Redraw(tl);
124 		return;
125 	}
126 }
127 
128 static void
DecrementSelection(AG_Tlist * tl,int inc)129 DecrementSelection(AG_Tlist *tl, int inc)
130 {
131 	AG_TlistItem *it, *itPrev;
132 	int i;
133 
134 	for (i = 0; i < inc; i++) {
135 		TAILQ_FOREACH(it, &tl->items, items) {
136 			if (!it->selected) {
137 				continue;
138 			}
139 			itPrev = TAILQ_PREV(it, ag_tlist_itemq, items);
140 			if (itPrev != NULL) {
141 				DeselectItem(tl, it);
142 				SelectItem(tl, itPrev);
143 			}
144 			break;
145 		}
146 		if (it == NULL && (it = TAILQ_FIRST(&tl->items)) != NULL)
147 			SelectItem(tl, it);
148 	}
149 	if (!SelectionVisible(tl))
150 		ScrollToSelection(tl);
151 }
152 
153 static void
IncrementSelection(AG_Tlist * tl,int inc)154 IncrementSelection(AG_Tlist *tl, int inc)
155 {
156 	AG_TlistItem *it, *itNext;
157 	int i;
158 
159 	for (i = 0; i < inc; i++) {
160 		TAILQ_FOREACH(it, &tl->items, items) {
161 			if (!it->selected) {
162 				continue;
163 			}
164 			itNext = TAILQ_NEXT(it, items);
165 			if (itNext != NULL) {
166 				DeselectItem(tl, it);
167 				SelectItem(tl, itNext);
168 			}
169 			break;
170 		}
171 		if (it == NULL && (it = TAILQ_FIRST(&tl->items)) != NULL)
172 			SelectItem(tl, it);
173 	}
174 	if (!SelectionVisible(tl))
175 		ScrollToSelection(tl);
176 }
177 
178 static Uint32
DoubleClickTimeout(AG_Timer * to,AG_Event * event)179 DoubleClickTimeout(AG_Timer *to, AG_Event *event)
180 {
181 	AG_Tlist *tl = AG_SELF();
182 
183 	tl->dblClicked = NULL;
184 	return (0);
185 }
186 
187 static void
OnFocusLoss(AG_Event * event)188 OnFocusLoss(AG_Event *event)
189 {
190 	AG_Tlist *tl = AG_SELF();
191 
192 	AG_DelTimer(tl, &tl->moveTo);
193 	AG_DelTimer(tl, &tl->dblClickTo);
194 }
195 
196 /* Timer for updates in AG_TLIST_POLL mode. */
197 static Uint32
PollRefreshTimeout(AG_Timer * to,AG_Event * event)198 PollRefreshTimeout(AG_Timer *to, AG_Event *event)
199 {
200 	AG_Tlist *tl = AG_SELF();
201 
202 	tl->flags |= AG_TLIST_REFRESH;
203 	AG_Redraw(tl);
204 	return (to->ival);
205 }
206 
207 static void
OnFontChange(AG_Event * event)208 OnFontChange(AG_Event *event)
209 {
210 	AG_Tlist *tl = AG_SELF();
211 	AG_TlistItem *it;
212 
213 	TAILQ_FOREACH(it, &tl->items, items) {
214 		if (it->icon != -1) {
215 			AG_WidgetUnmapSurface(tl, it->icon);
216 			it->icon = -1;
217 		}
218 		if (it->label != -1) {
219 			AG_WidgetUnmapSurface(tl, it->label);
220 			it->label = -1;
221 		}
222 	}
223 	AG_TlistSetItemHeight(tl, WIDGET(tl)->font->height + AG_TLIST_PADDING);
224 	AG_TlistSetIconWidth(tl, tl->item_h + 1);
225 }
226 
227 static void
OnShow(AG_Event * event)228 OnShow(AG_Event *event)
229 {
230 	AG_Tlist *tl = AG_SELF();
231 
232 	if (tl->flags & AG_TLIST_POLL) {
233 		tl->flags |= AG_TLIST_REFRESH;
234 		AG_AddTimer(tl, &tl->refreshTo, 125, PollRefreshTimeout, NULL);
235 	}
236 }
237 
238 /* Timer for moving keyboard selection. */
239 static Uint32
MoveTimeout(AG_Timer * to,AG_Event * event)240 MoveTimeout(AG_Timer *to, AG_Event *event)
241 {
242 	AG_Tlist *tl = AG_SELF();
243 	int incr = AG_INT(1);
244 
245 	if (incr < 0) {
246 		DecrementSelection(tl, -incr);
247 	} else {
248 		IncrementSelection(tl, incr);
249 	}
250 	return (agKbdRepeat);
251 }
252 
253 static void
Init(void * obj)254 Init(void *obj)
255 {
256 	AG_Tlist *tl = obj;
257 
258 	WIDGET(tl)->flags |= AG_WIDGET_FOCUSABLE|AG_WIDGET_USE_TEXT;
259 
260 	tl->flags = 0;
261 	tl->selected = NULL;
262 	tl->wSpace = 4;
263 	tl->item_h = agTextFontHeight + AG_TLIST_PADDING;
264 	tl->icon_w = tl->item_h + 1;
265 	tl->dblClicked = NULL;
266 	tl->nitems = 0;
267 	tl->nvisitems = 0;
268 	tl->compare_fn = AG_TlistComparePtrs;
269 	tl->wHint = 0;
270 	tl->hHint = tl->item_h + 2;
271 	tl->popupEv = NULL;
272 	tl->changedEv = NULL;
273 	tl->dblClickEv = NULL;
274 	tl->wheelTicks = 0;
275 	tl->r = AG_RECT(0,0,0,0);
276 	tl->rOffs = 0;
277 	TAILQ_INIT(&tl->items);
278 	TAILQ_INIT(&tl->selitems);
279 	TAILQ_INIT(&tl->popups);
280 
281 	AG_InitTimer(&tl->moveTo, "move", 0);
282 	AG_InitTimer(&tl->refreshTo, "refresh", 0);
283 	AG_InitTimer(&tl->dblClickTo, "dblClick", 0);
284 
285 	tl->sbar = AG_ScrollbarNew(tl, AG_SCROLLBAR_VERT, AG_SCROLLBAR_EXCL);
286 	AG_SetInt(tl->sbar, "min", 0);
287 	AG_BindInt(tl->sbar, "max", &tl->nitems);
288 	AG_BindInt(tl->sbar, "visible", &tl->nvisitems);
289 	AG_BindInt(tl->sbar, "value", &tl->rOffs);
290 	AG_WidgetSetFocusable(tl->sbar, 0);
291 
292 	AG_AddEvent(tl, "font-changed", OnFontChange, NULL);
293 	AG_AddEvent(tl, "widget-shown", OnShow, NULL);
294 	AG_AddEvent(tl, "widget-hidden", OnFocusLoss, NULL);
295 	AG_SetEvent(tl, "widget-lostfocus", OnFocusLoss, NULL);
296 	AG_SetEvent(tl, "mouse-button-down", MouseButtonDown, NULL);
297 	AG_SetEvent(tl, "key-down", KeyDown, NULL);
298 	AG_SetEvent(tl, "key-up", KeyUp, NULL);
299 
300 	AG_BindPointer(tl, "selected", &tl->selected);
301 
302 #ifdef AG_DEBUG
303 	AG_BindInt(tl, "nitems", &tl->nitems);
304 	AG_BindInt(tl, "nvisitems", &tl->nvisitems);
305 #endif /* AG_DEBUG */
306 }
307 
308 void
AG_TlistSizeHint(AG_Tlist * tl,const char * text,int nitems)309 AG_TlistSizeHint(AG_Tlist *tl, const char *text, int nitems)
310 {
311 	AG_ObjectLock(tl);
312 	AG_TextSize(text, &tl->wHint, NULL);
313 	tl->hHint = (tl->item_h+2)*nitems;
314 	AG_ObjectUnlock(tl);
315 }
316 
317 void
AG_TlistSizeHintPixels(AG_Tlist * tl,int w,int nitems)318 AG_TlistSizeHintPixels(AG_Tlist *tl, int w, int nitems)
319 {
320 	AG_ObjectLock(tl);
321 	tl->wHint = w;
322 	tl->hHint = (tl->item_h+2)*nitems;
323 	AG_ObjectUnlock(tl);
324 }
325 
326 /*
327  * Set the default size hint to accomodate the largest text label in
328  * the current list of items, and the given number of items.
329  */
330 void
AG_TlistSizeHintLargest(AG_Tlist * tl,int nitems)331 AG_TlistSizeHintLargest(AG_Tlist *tl, int nitems)
332 {
333 	AG_TlistItem *it;
334 	int w;
335 
336 	AG_ObjectLock(tl);
337 	UpdatePolled(tl);
338 	tl->wHint = 0;
339 	AG_TLIST_FOREACH(it, tl) {
340 		AG_TextSize(it->text, &w, NULL);
341 		if (w > tl->wHint) { tl->wHint = w; }
342 	}
343 	tl->wHint += tl->icon_w*4;
344 	tl->hHint = (tl->item_h+2)*nitems;
345 	AG_ObjectUnlock(tl);
346 }
347 
348 static void
Destroy(void * p)349 Destroy(void *p)
350 {
351 	AG_Tlist *tl = p;
352 	AG_TlistItem *it, *nit;
353 	AG_TlistPopup *tp, *ntp;
354 
355 	for (it = TAILQ_FIRST(&tl->selitems);
356 	     it != TAILQ_END(&tl->selitems);
357 	     it = nit) {
358 		nit = TAILQ_NEXT(it, selitems);
359 		FreeItem(tl, it);
360 	}
361 	for (it = TAILQ_FIRST(&tl->items);
362 	     it != TAILQ_END(&tl->items);
363 	     it = nit) {
364 		nit = TAILQ_NEXT(it, items);
365 		FreeItem(tl, it);
366 	}
367 	for (tp = TAILQ_FIRST(&tl->popups);
368 	     tp != TAILQ_END(&tl->popups);
369 	     tp = ntp) {
370 		ntp = TAILQ_NEXT(tp, popups);
371 		AG_ObjectDestroy(tp->menu);
372 		Free(tp);
373 	}
374 }
375 
376 static void
SizeRequest(void * obj,AG_SizeReq * r)377 SizeRequest(void *obj, AG_SizeReq *r)
378 {
379 	AG_Tlist *tl = obj;
380 	AG_SizeReq rBar;
381 
382 	AG_WidgetSizeReq(tl->sbar, &rBar);
383 	r->w = tl->icon_w + tl->wSpace*2 + tl->wHint + rBar.w;
384 	r->h = tl->hHint;
385 }
386 
387 static int
SizeAllocate(void * obj,const AG_SizeAlloc * a)388 SizeAllocate(void *obj, const AG_SizeAlloc *a)
389 {
390 	AG_Tlist *tl = obj;
391 	AG_SizeReq rBar;
392 	AG_SizeAlloc aBar;
393 
394 	AG_WidgetSizeReq(tl->sbar, &rBar);
395 	if (a->w < rBar.w*2) {
396 		rBar.w = MAX(0, a->w/2);
397 	}
398 	aBar.w = rBar.w;
399 	aBar.h = a->h;
400 	aBar.x = a->w - rBar.w;
401 	aBar.y = 0;
402 	AG_WidgetSizeAlloc(tl->sbar, &aBar);
403 	tl->wRow = a->w - aBar.w;
404 
405 	tl->r.w = tl->wRow;
406 	tl->r.h = a->h;
407 
408 	/* Limit vertical scrollbar parameters */
409 	tl->nvisitems = a->h/tl->item_h;
410 	if (tl->rOffs+tl->nvisitems >= tl->nitems) {
411 		tl->rOffs = MAX(0, tl->nitems - tl->nvisitems);
412 	}
413 	return (0);
414 }
415 
416 static void
DrawSubnodeIndicator(void * wid,AG_Rect r,int isExpanded)417 DrawSubnodeIndicator(void *wid, AG_Rect r, int isExpanded)
418 {
419 	AG_Color C;
420 
421 	AG_DrawRectBlended(wid,
422 	    AG_RECT(r.x-1, r.y, r.w+2, r.h),
423 	    AG_ColorRGBA(0,0,0,64),
424 	    AG_ALPHA_SRC);
425 
426 	C = AG_ColorRGBA(255,255,255,100);
427 	if (isExpanded) {
428 		AG_DrawMinus(wid,
429 		    AG_RECT(r.x+2, r.y+2, r.w-4, r.h-4),
430 		    C, AG_ALPHA_SRC);
431 	} else {
432 		AG_DrawPlus(wid,
433 		    AG_RECT(r.x+2, r.y+2, r.w-4, r.h-4),
434 		    C, AG_ALPHA_SRC);
435 	}
436 }
437 
438 static void
Draw(void * obj)439 Draw(void *obj)
440 {
441 	AG_Tlist *tl = obj;
442 	AG_TlistItem *it;
443 	int y = 0, i = 0, selSeen = 0, selPos = 1;
444 
445 	AG_DrawBox(tl, tl->r, -1, WCOLOR(tl,AG_COLOR));
446 	AG_WidgetDraw(tl->sbar);
447 	AG_PushClipRect(tl, tl->r);
448 
449 	UpdatePolled(tl);
450 
451 	TAILQ_FOREACH(it, &tl->items, items) {
452 		int x = 2 + it->depth*tl->icon_w;
453 
454 		if (i++ < tl->rOffs) {
455 			if (it->selected) {
456 				selPos = -1;
457 			}
458 			continue;
459 		}
460 		if (y > HEIGHT(tl) - tl->item_h)
461 			break;
462 
463 		if (it->selected) {
464 		    	AG_Rect rSel;
465 			rSel.x = x + tl->icon_w + 2;
466 			rSel.y = y;
467 			rSel.w = tl->wRow - x - tl->icon_w - 3;
468 			rSel.h = tl->item_h + 1;
469 			AG_DrawRect(tl, rSel, WCOLOR_SEL(tl,AG_COLOR));
470 			selSeen = 1;
471 		}
472 		if (it->iconsrc != NULL) {
473 			if (it->icon == -1) {
474 				AG_Surface *scaled = NULL;
475 
476 				if (AG_ScaleSurface(it->iconsrc,
477 				    tl->icon_w, tl->item_h, &scaled) == -1) {
478 					AG_FatalError(NULL);
479 				}
480 				it->icon = AG_WidgetMapSurface(tl, scaled);
481 			}
482 			AG_WidgetBlitSurface(tl, it->icon, x, y);
483 		}
484 		if (it->flags & AG_TLIST_HAS_CHILDREN) {
485 			DrawSubnodeIndicator(tl,
486 			    AG_RECT(x,
487 			            y,
488 				    tl->icon_w,
489 				    tl->item_h),
490 			    (it->flags & AG_TLIST_VISIBLE_CHILDREN));
491 		}
492 		if (it->label == -1) {
493 			AG_TextColor(it->selected ?
494 			             WCOLOR_SEL(tl,AG_TEXT_COLOR) :
495 				     WCOLOR(tl,AG_TEXT_COLOR));
496 			it->label = AG_WidgetMapSurface(tl,
497 			    AG_TextRender(it->text));
498 		}
499 
500 		if ((y + tl->item_h) < HEIGHT(tl)-1)
501 			AG_DrawLineH(tl, 1, tl->wRow-2, (y + tl->item_h),
502 			    WCOLOR(tl,AG_LINE_COLOR));
503 
504 		AG_WidgetBlitSurface(tl, it->label,
505 		    x + tl->icon_w + tl->wSpace,
506 		    y + AG_TLIST_PADDING);
507 		y += tl->item_h;
508 	}
509 	if (!selSeen && (tl->flags & AG_TLIST_SCROLLTOSEL)) {
510 		if (selPos == -1) {
511 			tl->rOffs--;
512 		} else {
513 			tl->rOffs++;
514 		}
515 	} else {
516 		tl->flags &= ~(AG_TLIST_SCROLLTOSEL);
517 	}
518 	AG_PopClipRect(tl);
519 }
520 
521 static void
FreeItem(AG_Tlist * tl,AG_TlistItem * it)522 FreeItem(AG_Tlist *tl, AG_TlistItem *it)
523 {
524 	if (it->label != -1) {
525 		AG_WidgetUnmapSurface(tl, it->label);
526 	}
527 	if (it->flags & AG_TLIST_DYNICON && it->iconsrc != NULL) {
528 		AG_SurfaceFree(it->iconsrc);
529 	}
530 	if (it->icon != -1) {
531 		AG_WidgetUnmapSurface(tl, it->icon);
532 	}
533 	Free(it);
534 }
535 
536 /* Remove a tlist item. */
537 void
AG_TlistDel(AG_Tlist * tl,AG_TlistItem * it)538 AG_TlistDel(AG_Tlist *tl, AG_TlistItem *it)
539 {
540 	AG_ObjectLock(tl);
541 	TAILQ_REMOVE(&tl->items, it, items);
542 	tl->nitems--;
543 	FreeItem(tl, it);
544 
545 	/* Update the scrollbar range and offset accordingly. */
546 	if (tl->rOffs+tl->nvisitems > tl->nitems) {
547 		tl->rOffs = MAX(0, tl->nitems - tl->nvisitems);
548 	}
549 	AG_ObjectUnlock(tl);
550 	AG_Redraw(tl);
551 }
552 
553 /* Remove duplicate items from the list. */
554 void
AG_TlistUniq(AG_Tlist * tl)555 AG_TlistUniq(AG_Tlist *tl)
556 {
557 	AG_TlistItem *it, *it2;
558 
559 	AG_ObjectLock(tl);
560 restart:							/* XXX */
561 	TAILQ_FOREACH(it, &tl->items, items) {
562 		TAILQ_FOREACH(it2, &tl->items, items) {
563 			if (it != it2 &&
564 			    tl->compare_fn(it, it2) != 0) {
565 				AG_TlistDel(tl, it);
566 				goto restart;
567 			}
568 		}
569 	}
570 	AG_ObjectUnlock(tl);
571 }
572 
573 /* Clear the items on the list, save the selections if polling. */
574 void
AG_TlistClear(AG_Tlist * tl)575 AG_TlistClear(AG_Tlist *tl)
576 {
577 	AG_TlistItem *it, *nit;
578 
579 	AG_ObjectLock(tl);
580 
581 	for (it = TAILQ_FIRST(&tl->items);
582 	     it != TAILQ_END(&tl->items);
583 	     it = nit) {
584 		nit = TAILQ_NEXT(it, items);
585 		if ((!(tl->flags & AG_TLIST_NOSELSTATE) && it->selected) ||
586 		      (it->flags & AG_TLIST_HAS_CHILDREN)) {
587 			TAILQ_INSERT_HEAD(&tl->selitems, it, selitems);
588 		} else {
589 			FreeItem(tl, it);
590 		}
591 	}
592 	TAILQ_INIT(&tl->items);
593 	tl->nitems = 0;
594 	AG_ObjectUnlock(tl);
595 
596 	AG_Redraw(tl);
597 }
598 
599 /* Generic string compare routine. */
600 int
AG_TlistCompareStrings(const AG_TlistItem * it1,const AG_TlistItem * it2)601 AG_TlistCompareStrings(const AG_TlistItem *it1,
602     const AG_TlistItem *it2)
603 {
604 	return (it1->text != NULL && it2->text != NULL &&
605 	        strcmp(it1->text, it2->text) == 0);
606 }
607 
608 /* Generic pointer compare routine. */
609 int
AG_TlistComparePtrs(const AG_TlistItem * it1,const AG_TlistItem * it2)610 AG_TlistComparePtrs(const AG_TlistItem *it1, const AG_TlistItem *it2)
611 {
612 	return (it1->p1 == it2->p1);
613 }
614 
615 /* Generic pointer+class compare routine. */
616 int
AG_TlistComparePtrsAndClasses(const AG_TlistItem * it1,const AG_TlistItem * it2)617 AG_TlistComparePtrsAndClasses(const AG_TlistItem *it1,
618     const AG_TlistItem *it2)
619 {
620 	return ((it1->p1 == it2->p1) &&
621 	        (it1->cat != NULL && it2->cat!= NULL &&
622 		 (strcmp(it1->cat, it2->cat) == 0)));
623 }
624 
625 /* Set an alternate compare function for items. */
626 void
AG_TlistSetCompareFn(AG_Tlist * tl,int (* fn)(const AG_TlistItem *,const AG_TlistItem *))627 AG_TlistSetCompareFn(AG_Tlist *tl,
628     int (*fn)(const AG_TlistItem *, const AG_TlistItem *))
629 {
630 	AG_ObjectLock(tl);
631 	tl->compare_fn = fn;
632 	AG_ObjectUnlock(tl);
633 }
634 
635 /* Set the update rate for polled displays in ms (-1 = update explicitely). */
636 void
AG_TlistSetRefresh(AG_Tlist * tl,int ms)637 AG_TlistSetRefresh(AG_Tlist *tl, int ms)
638 {
639 	AG_ObjectLock(tl);
640 	if (ms == -1) {
641 		AG_DelTimer(tl, &tl->refreshTo);
642 	} else {
643 		AG_AddTimer(tl, &tl->refreshTo, ms, PollRefreshTimeout, NULL);
644 	}
645 	AG_ObjectUnlock(tl);
646 }
647 
648 /* Restore previous item selection state. */
649 void
AG_TlistRestore(AG_Tlist * tl)650 AG_TlistRestore(AG_Tlist *tl)
651 {
652 	AG_TlistItem *sit, *cit, *nsit;
653 
654 	AG_ObjectLock(tl);
655 
656 	for (sit = TAILQ_FIRST(&tl->selitems);
657 	     sit != TAILQ_END(&tl->selitems);
658 	     sit = nsit) {
659 		nsit = TAILQ_NEXT(sit, selitems);
660 		TAILQ_FOREACH(cit, &tl->items, items) {
661 			if (!tl->compare_fn(sit, cit)) {
662 				continue;
663 			}
664 			if (!(tl->flags & AG_TLIST_NOSELSTATE)) {
665 				cit->selected = sit->selected;
666 			}
667 			if (sit->flags & AG_TLIST_VISIBLE_CHILDREN) {
668 				cit->flags |= AG_TLIST_VISIBLE_CHILDREN;
669 			} else {
670 				cit->flags &= ~(AG_TLIST_VISIBLE_CHILDREN);
671 			}
672 		}
673 		FreeItem(tl, sit);
674 	}
675 	TAILQ_INIT(&tl->selitems);
676 
677 	AG_ObjectUnlock(tl);
678 }
679 
680 /*
681  * Allocate a new tlist item.
682  * XXX allocate from a pool, especially for polled items.
683  */
684 static __inline__ AG_TlistItem *
AllocItem(AG_Tlist * tl,AG_Surface * iconsrc)685 AllocItem(AG_Tlist *tl, AG_Surface *iconsrc)
686 {
687 	AG_TlistItem *it;
688 
689 	it = Malloc(sizeof(AG_TlistItem));
690 	it->selected = 0;
691 	it->cat = "";
692 	it->depth = 0;
693 	it->flags = 0;
694 	it->icon = -1;
695 	it->label = -1;
696 	UpdateItemIcon(tl, it, iconsrc);
697 	return (it);
698 }
699 
700 /* The Tlist must be locked. */
701 static __inline__ void
InsertItem(AG_Tlist * tl,AG_TlistItem * it,int ins_head)702 InsertItem(AG_Tlist *tl, AG_TlistItem *it, int ins_head)
703 {
704 	if (ins_head) {
705 		TAILQ_INSERT_HEAD(&tl->items, it, items);
706 	} else {
707 		TAILQ_INSERT_TAIL(&tl->items, it, items);
708 	}
709 	tl->nitems++;
710 
711 	AG_Redraw(tl);
712 }
713 
714 /* Add an item to the tail of the list (user pointer) */
715 AG_TlistItem *
AG_TlistAddPtr(AG_Tlist * tl,AG_Surface * iconsrc,const char * text,void * p1)716 AG_TlistAddPtr(AG_Tlist *tl, AG_Surface *iconsrc, const char *text,
717     void *p1)
718 {
719 	AG_TlistItem *it;
720 
721 	AG_ObjectLock(tl);
722 	it = AllocItem(tl, iconsrc);
723 	it->p1 = p1;
724 	Strlcpy(it->text, text, sizeof(it->text));
725 	InsertItem(tl, it, 0);
726 	AG_ObjectUnlock(tl);
727 	return (it);
728 }
729 
730 /* Add an item to the tail of the list (format string) */
731 AG_TlistItem *
AG_TlistAdd(AG_Tlist * tl,AG_Surface * iconsrc,const char * fmt,...)732 AG_TlistAdd(AG_Tlist *tl, AG_Surface *iconsrc, const char *fmt, ...)
733 {
734 	AG_TlistItem *it;
735 	va_list args;
736 
737 	AG_ObjectLock(tl);
738 	it = AllocItem(tl, iconsrc);
739 	it->p1 = NULL;
740 	va_start(args, fmt);
741 	Vsnprintf(it->text, sizeof(it->text), fmt, args);
742 	va_end(args);
743 	InsertItem(tl, it, 0);
744 	AG_ObjectUnlock(tl);
745 	return (it);
746 }
747 
748 /* Add an item to the tail of the list (plain string) */
749 AG_TlistItem *
AG_TlistAddS(AG_Tlist * tl,AG_Surface * iconsrc,const char * text)750 AG_TlistAddS(AG_Tlist *tl, AG_Surface *iconsrc, const char *text)
751 {
752 	AG_TlistItem *it;
753 
754 	AG_ObjectLock(tl);
755 	it = AllocItem(tl, iconsrc);
756 	it->p1 = NULL;
757 	Strlcpy(it->text, text, sizeof(it->text));
758 	InsertItem(tl, it, 0);
759 	AG_ObjectUnlock(tl);
760 	return (it);
761 }
762 
763 /* Add an item to the head of the list (format string) */
764 AG_TlistItem *
AG_TlistAddHead(AG_Tlist * tl,AG_Surface * iconsrc,const char * fmt,...)765 AG_TlistAddHead(AG_Tlist *tl, AG_Surface *iconsrc, const char *fmt, ...)
766 {
767 	AG_TlistItem *it;
768 	va_list args;
769 
770 	AG_ObjectLock(tl);
771 	it = AllocItem(tl, iconsrc);
772 	it->p1 = NULL;
773 	va_start(args, fmt);
774 	Vsnprintf(it->text, sizeof(it->text), fmt, args);
775 	va_end(args);
776 	InsertItem(tl, it, 1);
777 	AG_ObjectUnlock(tl);
778 	return (it);
779 }
780 
781 /* Add an item to the head of the list (plain string) */
782 AG_TlistItem *
AG_TlistAddHeadS(AG_Tlist * tl,AG_Surface * iconsrc,const char * text)783 AG_TlistAddHeadS(AG_Tlist *tl, AG_Surface *iconsrc, const char *text)
784 {
785 	AG_TlistItem *it;
786 
787 	AG_ObjectLock(tl);
788 	it = AllocItem(tl, iconsrc);
789 	it->p1 = NULL;
790 	Strlcpy(it->text, text, sizeof(it->text));
791 	InsertItem(tl, it, 1);
792 	AG_ObjectUnlock(tl);
793 	return (it);
794 }
795 
796 /* Add an item to the head of the list (user pointer) */
797 AG_TlistItem *
AG_TlistAddPtrHead(AG_Tlist * tl,AG_Surface * icon,const char * text,void * p1)798 AG_TlistAddPtrHead(AG_Tlist *tl, AG_Surface *icon, const char *text,
799     void *p1)
800 {
801 	AG_TlistItem *it;
802 
803 	AG_ObjectLock(tl);
804 	it = AllocItem(tl, icon);
805 	it->p1 = p1;
806 	Strlcpy(it->text, text, sizeof(it->text));
807 	InsertItem(tl, it, 1);
808 	AG_ObjectUnlock(tl);
809 	return (it);
810 }
811 
812 /* Select an item based on its pointer value. */
813 AG_TlistItem *
AG_TlistSelectPtr(AG_Tlist * tl,void * p)814 AG_TlistSelectPtr(AG_Tlist *tl, void *p)
815 {
816 	AG_TlistItem *it;
817 
818 	AG_ObjectLock(tl);
819 	UpdatePolled(tl);
820 	if ((tl->flags & AG_TLIST_MULTI) == 0) {
821 		AG_TlistDeselectAll(tl);
822 	}
823 	TAILQ_FOREACH(it, &tl->items, items) {
824 		if (it->p1 == p) {
825 			SelectItem(tl, it);
826 			break;
827 		}
828 	}
829 	AG_ObjectUnlock(tl);
830 	return (it);
831 }
832 
833 /* Select an item based on text. */
834 AG_TlistItem *
AG_TlistSelectText(AG_Tlist * tl,const char * text)835 AG_TlistSelectText(AG_Tlist *tl, const char *text)
836 {
837 	AG_TlistItem *it;
838 
839 	AG_ObjectLock(tl);
840 	UpdatePolled(tl);
841 	if ((tl->flags & AG_TLIST_MULTI) == 0) {
842 		AG_TlistDeselectAll(tl);
843 	}
844 	TAILQ_FOREACH(it, &tl->items, items) {
845 		if (it->text[0] == text[0] &&
846 		    strcmp(it->text, text) == 0) {
847 			SelectItem(tl, it);
848 			break;
849 		}
850 	}
851 	AG_ObjectUnlock(tl);
852 	return (it);
853 }
854 
855 /* Set the selection flag on an item. */
856 void
AG_TlistSelect(AG_Tlist * tl,AG_TlistItem * it)857 AG_TlistSelect(AG_Tlist *tl, AG_TlistItem *it)
858 {
859 	AG_ObjectLock(tl);
860 	if ((tl->flags & AG_TLIST_MULTI) == 0) {
861 		AG_TlistDeselectAll(tl);
862 	}
863 	SelectItem(tl, it);
864 	AG_ObjectUnlock(tl);
865 }
866 
867 /* Clear the selection flag on an item. */
868 void
AG_TlistDeselect(AG_Tlist * tl,AG_TlistItem * it)869 AG_TlistDeselect(AG_Tlist *tl, AG_TlistItem *it)
870 {
871 	AG_ObjectLock(tl);
872 	DeselectItem(tl, it);
873 	AG_ObjectUnlock(tl);
874 }
875 
876 /* Set the selection flag on all items. */
877 void
AG_TlistSelectAll(AG_Tlist * tl)878 AG_TlistSelectAll(AG_Tlist *tl)
879 {
880 	AG_TlistItem *it;
881 
882 	AG_ObjectLock(tl);
883 	TAILQ_FOREACH(it, &tl->items, items) {
884 		SelectItem(tl, it);
885 	}
886 	AG_ObjectUnlock(tl);
887 }
888 
889 /* Unset the selection flag on all items. */
890 void
AG_TlistDeselectAll(AG_Tlist * tl)891 AG_TlistDeselectAll(AG_Tlist *tl)
892 {
893 	AG_TlistItem *it;
894 
895 	AG_ObjectLock(tl);
896 	TAILQ_FOREACH(it, &tl->items, items) {
897 		DeselectItem(tl, it);
898 	}
899 	AG_ObjectUnlock(tl);
900 }
901 
902 /* The Tlist must be locked. */
903 static void
SelectItem(AG_Tlist * tl,AG_TlistItem * it)904 SelectItem(AG_Tlist *tl, AG_TlistItem *it)
905 {
906 	AG_Variable *selectedb;
907 	void **sel_ptr;
908 
909 	selectedb = AG_GetVariable(tl, "selected", &sel_ptr);
910 	*sel_ptr = it->p1;
911 	if (!it->selected) {
912 		it->selected = 1;
913 		if (tl->changedEv != NULL) {
914 			AG_PostEventByPtr(NULL, tl, tl->changedEv, "%p,%i",
915 			    it, 1);
916 		}
917 		AG_PostEvent(NULL, tl, "tlist-changed", "%p, %i", it, 1);
918 	}
919 	AG_PostEvent(NULL, tl, "tlist-selected", "%p", it);
920 	AG_UnlockVariable(selectedb);
921 	AG_Redraw(tl);
922 }
923 
924 /* The Tlist must be locked. */
925 static void
DeselectItem(AG_Tlist * tl,AG_TlistItem * it)926 DeselectItem(AG_Tlist *tl, AG_TlistItem *it)
927 {
928 	AG_Variable *selectedb;
929 	void **sel_ptr;
930 
931 	selectedb = AG_GetVariable(tl, "selected", &sel_ptr);
932 	*sel_ptr = NULL;
933 	if (it->selected) {
934 		it->selected = 0;
935 		if (tl->changedEv != NULL) {
936 			AG_PostEventByPtr(NULL, tl, tl->changedEv, "%p,%i",
937 			    it, 0);
938 		}
939 		AG_PostEvent(NULL, tl, "tlist-changed", "%p, %i", it, 0);
940 	}
941 	AG_UnlockVariable(selectedb);
942 	AG_Redraw(tl);
943 }
944 
945 static void
PopupMenu(AG_Tlist * tl,AG_TlistPopup * tp,int x,int y)946 PopupMenu(AG_Tlist *tl, AG_TlistPopup *tp, int x, int y)
947 {
948 	AG_Menu *m = tp->menu;
949 
950 #if 0
951 	if (AG_ParentWindow(tl) == NULL)
952 		AG_FatalError("AG_Tlist: %s is unattached", OBJECT(tl)->name);
953 #endif
954 	if (tp->panel != NULL) {
955 		AG_MenuCollapse(tp->item);
956 		tp->panel = NULL;
957 	}
958 	m->itemSel = tp->item;
959 	tp->panel = AG_MenuExpand(tl, tp->item, x+4, y+4);
960 }
961 
962 static void
MouseButtonDown(AG_Event * event)963 MouseButtonDown(AG_Event *event)
964 {
965 	AG_Tlist *tl = AG_SELF();
966 	int button = AG_INT(1);
967 	int x = AG_INT(2);
968 	int y = AG_INT(3);
969 	AG_TlistItem *ti;
970 	int tind;
971 
972 	tind = tl->rOffs + y/tl->item_h + 1;
973 
974 	/* XXX use array */
975 	if ((ti = AG_TlistFindByIndex(tl, tind)) == NULL)
976 		return;
977 
978 	if (!AG_WidgetIsFocused(tl))
979 		AG_WidgetFocus(tl);
980 
981 	switch (button) {
982 	case AG_MOUSE_WHEELUP:
983 		tl->rOffs -= AG_WidgetScrollDelta(&tl->wheelTicks);
984 		if (tl->rOffs < 0) {
985 			tl->rOffs = 0;
986 		}
987 		AG_Redraw(tl);
988 		break;
989 	case AG_MOUSE_WHEELDOWN:
990 		tl->rOffs += AG_WidgetScrollDelta(&tl->wheelTicks);
991 		if (tl->rOffs > (tl->nitems - tl->nvisitems)) {
992 			tl->rOffs = MAX(0, tl->nitems - tl->nvisitems);
993 		}
994 		AG_Redraw(tl);
995 		break;
996 	case AG_MOUSE_LEFT:
997 		/* Expand the children if the user clicked on the [+] sign. */
998 		if (ti->flags & AG_TLIST_HAS_CHILDREN) {
999 			if (x >= ti->depth*tl->icon_w &&
1000 			    x <= (ti->depth+1)*tl->icon_w) {
1001 				if (ti->flags & AG_TLIST_VISIBLE_CHILDREN) {
1002 					ti->flags &= ~AG_TLIST_VISIBLE_CHILDREN;
1003 				} else {
1004 					ti->flags |=  AG_TLIST_VISIBLE_CHILDREN;
1005 				}
1006 				tl->flags |= AG_TLIST_REFRESH;
1007 				AG_Redraw(tl);
1008 				return;
1009 			}
1010 		}
1011 
1012 		if (ti->flags & AG_TLIST_NO_SELECT) {
1013 			return;
1014 		}
1015 		/*
1016 		 * Handle range selections.
1017 		 */
1018 		if ((tl->flags & AG_TLIST_MULTI) &&
1019 		    (AG_GetModState(tl) & AG_KEYMOD_SHIFT)) {
1020 			AG_TlistItem *oitem;
1021 			int oind = -1, i = 0, nitems = 0;
1022 
1023 			TAILQ_FOREACH(oitem, &tl->items, items) {
1024 				if (oitem->selected) {
1025 					oind = i;
1026 				}
1027 				i++;
1028 				nitems++;
1029 			}
1030 			if (oind == -1) {
1031 				return;
1032 			}
1033 			if (oind < tind) {			  /* Forward */
1034 				i = 0;
1035 				TAILQ_FOREACH(oitem, &tl->items, items) {
1036 					if (i == tind)
1037 						break;
1038 					if (i > oind) {
1039 						SelectItem(tl, oitem);
1040 					}
1041 					i++;
1042 				}
1043 			} else if (oind >= tind) {		  /* Backward */
1044 				i = nitems;
1045 				TAILQ_FOREACH_REVERSE(oitem, &tl->items,
1046 				    ag_tlist_itemq, items) {
1047 					if (i <= oind)
1048 						SelectItem(tl, oitem);
1049 					if (i == tind)
1050 						break;
1051 					i--;
1052 				}
1053 			}
1054 			break;
1055 		}
1056 		/*
1057 		 * Handle single selections.
1058 		 */
1059 		if ((tl->flags & AG_TLIST_MULTITOGGLE) ||
1060 		    ((tl->flags & AG_TLIST_MULTI) &&
1061 		     (AG_GetModState(tl) & AG_KEYMOD_CTRL))) {
1062 			if (ti->selected) {
1063 				DeselectItem(tl, ti);
1064 			} else {
1065 				SelectItem(tl, ti);
1066 			}
1067 			break;
1068 		}
1069 
1070 		AG_TlistDeselectAll(tl);
1071 		SelectItem(tl, ti);
1072 
1073 		/* Handle double clicks. */
1074 		/* XXX compare the args as well as p1 */
1075 		if (tl->dblClicked != NULL && tl->dblClicked == ti->p1) {
1076 			AG_DelTimer(tl, &tl->dblClickTo);
1077 			if (tl->dblClickEv != NULL) {
1078 				AG_PostEventByPtr(NULL, tl, tl->dblClickEv,
1079 				    "%p", ti);
1080 			}
1081 			AG_PostEvent(NULL, tl, "tlist-dblclick", "%p", ti);
1082 			tl->dblClicked = NULL;
1083 		} else {
1084 			tl->dblClicked = ti->p1;
1085 			AG_AddTimer(tl, &tl->dblClickTo, agMouseDblclickDelay,
1086 			    DoubleClickTimeout, NULL);
1087 		}
1088 		break;
1089 	case AG_MOUSE_RIGHT:
1090 		if (ti->flags & AG_TLIST_NO_POPUP) {
1091 			return;
1092 		}
1093 		if (tl->popupEv != NULL) {
1094 			AG_PostEventByPtr(NULL, tl, tl->popupEv, NULL);
1095 		} else if (ti->cat != NULL) {
1096 			AG_TlistPopup *tp;
1097 
1098 			if (!(tl->flags &
1099 			    (AG_TLIST_MULTITOGGLE|AG_TLIST_MULTI)) ||
1100 			    !(AG_GetModState(tl) & (AG_KEYMOD_CTRL|AG_KEYMOD_SHIFT))) {
1101 				AG_TlistDeselectAll(tl);
1102 				SelectItem(tl, ti);
1103 			}
1104 			TAILQ_FOREACH(tp, &tl->popups, popups) {
1105 				if (strcmp(tp->iclass, ti->cat) == 0)
1106 					break;
1107 			}
1108 			if (tp != NULL) {
1109 				PopupMenu(tl, tp, x,y);
1110 				return;
1111 			}
1112 		}
1113 		break;
1114 	}
1115 }
1116 
1117 static void
KeyDown(AG_Event * event)1118 KeyDown(AG_Event *event)
1119 {
1120 	AG_Tlist *tl = AG_SELF();
1121 	int keysym = AG_INT(1);
1122 	void *ti;
1123 
1124 	switch (keysym) {
1125 	case AG_KEY_UP:
1126 		DecrementSelection(tl, 1);
1127 		AG_AddTimer(tl, &tl->moveTo, agKbdDelay, MoveTimeout, "%i", -1);
1128 		break;
1129 	case AG_KEY_DOWN:
1130 		IncrementSelection(tl, 1);
1131 		AG_AddTimer(tl, &tl->moveTo, agKbdDelay, MoveTimeout, "%i", +1);
1132 		break;
1133 	case AG_KEY_PAGEUP:
1134 		DecrementSelection(tl, agPageIncrement);
1135 		AG_AddTimer(tl, &tl->moveTo, agKbdDelay, MoveTimeout, "%i", -agPageIncrement);
1136 		break;
1137 	case AG_KEY_PAGEDOWN:
1138 		IncrementSelection(tl, agPageIncrement);
1139 		AG_AddTimer(tl, &tl->moveTo, agKbdDelay, MoveTimeout, "%i", +agPageIncrement);
1140 		break;
1141 	case AG_KEY_RETURN:
1142 		if ((ti = AG_TlistSelectedItemPtr(tl)) != NULL) {
1143 			AG_PostEvent(NULL, tl, "tlist-return", "%p", ti);
1144 		}
1145 		break;
1146 	}
1147 	tl->lastKeyDown = keysym;
1148 }
1149 
1150 static void
KeyUp(AG_Event * event)1151 KeyUp(AG_Event *event)
1152 {
1153 	AG_Tlist *tl = AG_SELF();
1154 	int keysym = AG_INT(1);
1155 
1156 	switch (keysym) {
1157 	case AG_KEY_UP:
1158 	case AG_KEY_DOWN:
1159 	case AG_KEY_PAGEUP:
1160 	case AG_KEY_PAGEDOWN:
1161 		if (keysym == tl->lastKeyDown) {
1162 			AG_DelTimer(tl, &tl->moveTo);
1163 		}
1164 		break;
1165 	}
1166 }
1167 
1168 /*
1169  * Return the item at the given index. Result is only valid as long as
1170  * the Tlist is locked.
1171  */
1172 AG_TlistItem *
AG_TlistFindByIndex(AG_Tlist * tl,int index)1173 AG_TlistFindByIndex(AG_Tlist *tl, int index)
1174 {
1175 	AG_TlistItem *it;
1176 	int i = 0;
1177 
1178 	AG_ObjectLock(tl);
1179 	TAILQ_FOREACH(it, &tl->items, items) {
1180 		if (++i == index) {
1181 			AG_ObjectUnlock(tl);
1182 			return (it);
1183 		}
1184 	}
1185 	AG_ObjectUnlock(tl);
1186 	return (NULL);
1187 }
1188 
1189 /*
1190  * Return the first selected item. Result is only valid as long as the Tlist
1191  * is locked.
1192  */
1193 AG_TlistItem *
AG_TlistSelectedItem(AG_Tlist * tl)1194 AG_TlistSelectedItem(AG_Tlist *tl)
1195 {
1196 	AG_TlistItem *it;
1197 
1198 	AG_ObjectLock(tl);
1199 	TAILQ_FOREACH(it, &tl->items, items) {
1200 		if (it->selected) {
1201 			AG_ObjectUnlock(tl);
1202 			return (it);
1203 		}
1204 	}
1205 	AG_ObjectUnlock(tl);
1206 	return (NULL);
1207 }
1208 
1209 /*
1210  * Return the user pointer of the first selected item. Result is only valid
1211  * as long as the Tlist is locked.
1212  */
1213 void *
AG_TlistSelectedItemPtr(AG_Tlist * tl)1214 AG_TlistSelectedItemPtr(AG_Tlist *tl)
1215 {
1216 	AG_TlistItem *it;
1217 	void *rv;
1218 
1219 	AG_ObjectLock(tl);
1220 	TAILQ_FOREACH(it, &tl->items, items) {
1221 		if (it->selected) {
1222 			rv = it->p1;
1223 			AG_ObjectUnlock(tl);
1224 			return (rv);
1225 		}
1226 	}
1227 	AG_ObjectUnlock(tl);
1228 	return (NULL);
1229 }
1230 
1231 /*
1232  * Return the pointer associated with the first selected item. Result is only
1233  * valid as long as the Tlist is locked.
1234  */
1235 void *
AG_TlistFindPtr(AG_Tlist * tl)1236 AG_TlistFindPtr(AG_Tlist *tl)
1237 {
1238 	AG_TlistItem *it;
1239 	void *rv;
1240 
1241 	AG_ObjectLock(tl);
1242 	TAILQ_FOREACH(it, &tl->items, items) {
1243 		if (it->selected) {
1244 			rv = it->p1;
1245 			AG_ObjectUnlock(tl);
1246 			return (rv);
1247 		}
1248 	}
1249 	AG_ObjectUnlock(tl);
1250 	return (NULL);
1251 }
1252 
1253 /*
1254  * Return the first item matching a text string. Result is only valid as long
1255  * as the Tlist is locked.
1256  */
1257 AG_TlistItem *
AG_TlistFindText(AG_Tlist * tl,const char * text)1258 AG_TlistFindText(AG_Tlist *tl, const char *text)
1259 {
1260 	AG_TlistItem *it;
1261 
1262 	AG_ObjectLock(tl);
1263 	TAILQ_FOREACH(it, &tl->items, items) {
1264 		if (strcmp(it->text, text) == 0) {
1265 			AG_ObjectUnlock(tl);
1266 			return (it);
1267 		}
1268 	}
1269 	AG_ObjectUnlock(tl);
1270 	return (NULL);
1271 }
1272 
1273 /*
1274  * Return the first item on the list. Result is only valid as long as
1275  * the Tlist is locked.
1276  */
1277 AG_TlistItem *
AG_TlistFirstItem(AG_Tlist * tl)1278 AG_TlistFirstItem(AG_Tlist *tl)
1279 {
1280 	AG_TlistItem *it;
1281 
1282 	AG_ObjectLock(tl);
1283 	it = TAILQ_FIRST(&tl->items);
1284 	AG_ObjectUnlock(tl);
1285 	return (it);
1286 }
1287 
1288 /*
1289  * Return the last item on the list. Result is only valid as long as
1290  * the Tlist is locked.
1291  */
1292 AG_TlistItem *
AG_TlistLastItem(AG_Tlist * tl)1293 AG_TlistLastItem(AG_Tlist *tl)
1294 {
1295 	AG_TlistItem *it;
1296 
1297 	AG_ObjectLock(tl);
1298 	it = TAILQ_LAST(&tl->items, ag_tlist_itemq);
1299 	AG_ObjectUnlock(tl);
1300 	return (it);
1301 }
1302 
1303 /* Set the height to use for item display. */
1304 void
AG_TlistSetItemHeight(AG_Tlist * tl,int ih)1305 AG_TlistSetItemHeight(AG_Tlist *tl, int ih)
1306 {
1307 	AG_TlistItem *it;
1308 
1309 	AG_ObjectLock(tl);
1310 	tl->item_h = ih;
1311 	TAILQ_FOREACH(it, &tl->items, items) {
1312 		if (it->icon != -1) {
1313 			AG_Surface *scaled = NULL;
1314 
1315 			if (AG_ScaleSurface(it->iconsrc,
1316 			    tl->item_h, tl->item_h, &scaled) == -1) {
1317 				AG_FatalError(NULL);
1318 			}
1319 			AG_WidgetReplaceSurface(tl, it->icon, scaled);
1320 		}
1321 	}
1322 	AG_ObjectUnlock(tl);
1323 	AG_Redraw(tl);
1324 }
1325 
1326 /* Set the width to use for item icons. */
1327 void
AG_TlistSetIconWidth(AG_Tlist * tl,int iw)1328 AG_TlistSetIconWidth(AG_Tlist *tl, int iw)
1329 {
1330 	AG_TlistItem *it;
1331 
1332 	AG_ObjectLock(tl);
1333 	tl->icon_w = iw;
1334 	TAILQ_FOREACH(it, &tl->items, items) {
1335 		if (it->icon != -1) {
1336 			AG_Surface *scaled = NULL;
1337 
1338 			if (AG_ScaleSurface(it->iconsrc,
1339 			    tl->item_h, tl->item_h, &scaled) == -1) {
1340 				AG_FatalError(NULL);
1341 			}
1342 			AG_WidgetReplaceSurface(tl, it->icon, scaled);
1343 		}
1344 	}
1345 	AG_ObjectUnlock(tl);
1346 	AG_Redraw(tl);
1347 }
1348 
1349 /* Update the icon associated with an item. The Tlist must be locked. */
1350 static void
UpdateItemIcon(AG_Tlist * tl,AG_TlistItem * it,AG_Surface * iconsrc)1351 UpdateItemIcon(AG_Tlist *tl, AG_TlistItem *it, AG_Surface *iconsrc)
1352 {
1353 	if (it->flags & AG_TLIST_DYNICON) {
1354 		if (it->iconsrc != NULL) {
1355 			AG_SurfaceFree(it->iconsrc);
1356 		}
1357 		if (iconsrc != NULL) {
1358 			it->iconsrc = AG_SurfaceDup(iconsrc);
1359 		} else {
1360 			it->iconsrc = NULL;
1361 		}
1362 	} else {
1363 		it->iconsrc = iconsrc;
1364 	}
1365 
1366 	if (it->icon != -1) {
1367 		AG_WidgetUnmapSurface(tl, it->icon);
1368 		it->icon = -1;
1369 	}
1370 }
1371 
1372 void
AG_TlistSetIcon(AG_Tlist * tl,AG_TlistItem * it,AG_Surface * iconsrc)1373 AG_TlistSetIcon(AG_Tlist *tl, AG_TlistItem *it, AG_Surface *iconsrc)
1374 {
1375 	AG_ObjectLock(tl);
1376 	it->flags |= AG_TLIST_DYNICON;
1377 	UpdateItemIcon(tl, it, iconsrc);
1378 	AG_ObjectUnlock(tl);
1379 	AG_Redraw(tl);
1380 }
1381 
1382 void
AG_TlistSetDblClickFn(AG_Tlist * tl,AG_EventFn fn,const char * fmt,...)1383 AG_TlistSetDblClickFn(AG_Tlist *tl, AG_EventFn fn, const char *fmt, ...)
1384 {
1385 	AG_ObjectLock(tl);
1386 	tl->dblClickEv = AG_SetVoidFn(tl, fn, NULL);
1387 	AG_EVENT_GET_ARGS(tl->dblClickEv, fmt);
1388 	AG_ObjectUnlock(tl);
1389 }
1390 
1391 void
AG_TlistSetPopupFn(AG_Tlist * tl,AG_EventFn fn,const char * fmt,...)1392 AG_TlistSetPopupFn(AG_Tlist *tl, AG_EventFn fn, const char *fmt, ...)
1393 {
1394 	AG_ObjectLock(tl);
1395 	tl->popupEv = AG_SetVoidFn(tl, fn, NULL);
1396 	AG_EVENT_GET_ARGS(tl->popupEv, fmt);
1397 	AG_ObjectUnlock(tl);
1398 }
1399 
1400 void
AG_TlistSetChangedFn(AG_Tlist * tl,AG_EventFn fn,const char * fmt,...)1401 AG_TlistSetChangedFn(AG_Tlist *tl, AG_EventFn fn, const char *fmt, ...)
1402 {
1403 	AG_ObjectLock(tl);
1404 	tl->changedEv = AG_SetVoidFn(tl, fn, NULL);
1405 	AG_EVENT_GET_ARGS(tl->changedEv, fmt);
1406 	AG_ObjectUnlock(tl);
1407 }
1408 
1409 /* Create a new popup menu for items of the given class. */
1410 AG_MenuItem *
AG_TlistSetPopup(AG_Tlist * tl,const char * iclass)1411 AG_TlistSetPopup(AG_Tlist *tl, const char *iclass)
1412 {
1413 	AG_TlistPopup *tp;
1414 
1415 	tp = Malloc(sizeof(AG_TlistPopup));
1416 	tp->iclass = iclass;
1417 	tp->panel = NULL;
1418 	tp->menu = AG_MenuNew(NULL, 0);
1419 	tp->item = tp->menu->root;		/* XXX redundant */
1420 
1421 	/* AG_MenuExpand() need a window pointer in AG_Menu */
1422 	WIDGET(tp->menu)->window = WIDGET(tl)->window;
1423 
1424 	AG_ObjectLock(tl);
1425 	TAILQ_INSERT_TAIL(&tl->popups, tp, popups);
1426 	AG_ObjectUnlock(tl);
1427 	return (tp->item);
1428 }
1429 
1430 /* Scroll to the beginning of the list. */
1431 void
AG_TlistScrollToStart(AG_Tlist * tl)1432 AG_TlistScrollToStart(AG_Tlist *tl)
1433 {
1434 	tl->rOffs = 0;
1435 	AG_Redraw(tl);
1436 }
1437 
1438 /* Scroll to the end of the list. */
1439 void
AG_TlistScrollToEnd(AG_Tlist * tl)1440 AG_TlistScrollToEnd(AG_Tlist *tl)
1441 {
1442 	tl->rOffs = MAX(0, tl->nitems - tl->nvisitems);
1443 	AG_Redraw(tl);
1444 }
1445 
1446 static int
CompareText(const void * p1,const void * p2)1447 CompareText(const void *p1, const void *p2)
1448 {
1449 	const AG_TlistItem *it1 = *(const AG_TlistItem **)p1;
1450 	const AG_TlistItem *it2 = *(const AG_TlistItem **)p2;
1451 
1452 	return strcoll(it1->text, it2->text);
1453 }
1454 
1455 int
AG_TlistSort(AG_Tlist * tl)1456 AG_TlistSort(AG_Tlist *tl)
1457 {
1458 	AG_TlistItem *it, **items;
1459 	Uint i = 0;
1460 
1461 	if ((items = TryMalloc(tl->nitems*sizeof(AG_TlistItem *))) == NULL) {
1462 		return (-1);
1463 	}
1464 	TAILQ_FOREACH(it, &tl->items, items) {
1465 		items[i++] = it;
1466 	}
1467 	qsort(items, tl->nitems, sizeof(AG_TlistItem *), CompareText);
1468 	TAILQ_INIT(&tl->items);
1469 	for (i = 0; i < tl->nitems; i++) {
1470 		TAILQ_INSERT_TAIL(&tl->items, items[i], items);
1471 	}
1472 	free(items);
1473 	AG_Redraw(tl);
1474 	return (0);
1475 }
1476 
1477 AG_WidgetClass agTlistClass = {
1478 	{
1479 		"Agar(Widget:Tlist)",
1480 		sizeof(AG_Tlist),
1481 		{ 0,0 },
1482 		Init,
1483 		NULL,		/* free */
1484 		Destroy,
1485 		NULL,		/* load */
1486 		NULL,		/* save */
1487 		NULL		/* edit */
1488 	},
1489 	Draw,
1490 	SizeRequest,
1491 	SizeAllocate
1492 };
1493