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