/* * Copyright (c) 2002-2009 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include "tlist.h" #include "primitive.h" #include #include static void MouseButtonDown(AG_Event *); static void KeyDown(AG_Event *); static void KeyUp(AG_Event *); static void FreeItem(AG_Tlist *, AG_TlistItem *); static void SelectItem(AG_Tlist *, AG_TlistItem *); static void DeselectItem(AG_Tlist *, AG_TlistItem *); static void PopupMenu(AG_Tlist *, AG_TlistPopup *); static void UpdateItemIcon(AG_Tlist *, AG_TlistItem *, AG_Surface *); static void UpdateListScrollbar(AG_Tlist *); static void ScrollbarChanged(AG_Event *); AG_Tlist * AG_TlistNew(void *parent, Uint flags) { AG_Tlist *tl; tl = Malloc(sizeof(AG_Tlist)); AG_ObjectInit(tl, &agTlistClass); tl->flags |= flags; if (flags & AG_TLIST_HFILL) { AG_ExpandHoriz(tl); } if (flags & AG_TLIST_VFILL) { AG_ExpandVert(tl); } AG_ObjectAttach(parent, tl); return (tl); } AG_Tlist * AG_TlistNewPolled(void *parent, Uint flags, AG_EventFn fn, const char *fmt, ...) { AG_Tlist *tl; AG_Event *ev; tl = AG_TlistNew(parent, flags); AG_ObjectLock(tl); tl->flags |= AG_TLIST_POLL; ev = AG_SetEvent(tl, "tlist-poll", fn, NULL); AG_EVENT_GET_ARGS(ev, fmt); AG_ObjectUnlock(tl); return (tl); } static __inline__ void UpdatePolled(AG_Tlist *tl) { if ((tl->flags & AG_TLIST_POLL) && (tl->flags & AG_TLIST_REFRESH)) { tl->flags &= ~(AG_TLIST_REFRESH); AG_PostEvent(NULL, tl, "tlist-poll", NULL); } } static int SelectionVisible(AG_Tlist *tl) { AG_TlistItem *it; int y = 0, i = 0; int offset; UpdatePolled(tl); offset = AG_GetInt(tl->sbar, "value"); TAILQ_FOREACH(it, &tl->items, items) { if (i++ < offset) continue; if (y > HEIGHT(tl) - tl->item_h) break; if (it->selected) { return (1); } y += tl->item_h; } return (0); } static void ScrollToSelection(AG_Tlist *tl) { AG_TlistItem *it; int m = 0; int offset = AG_GetInt(tl->sbar, "value"); TAILQ_FOREACH(it, &tl->items, items) { if (!it->selected) { m++; continue; } if (offset > m) { AG_SetInt(tl->sbar, "value", m); } else { offset = m - tl->nvisitems + 1; if (offset < 0) { offset = 0; } AG_SetInt(tl->sbar, "value", offset); } return; } } static void DecrementSelection(AG_Tlist *tl, int inc) { AG_TlistItem *it, *itPrev; int i; for (i = 0; i < inc; i++) { TAILQ_FOREACH(it, &tl->items, items) { if (!it->selected) { continue; } itPrev = TAILQ_PREV(it, ag_tlist_itemq, items); if (itPrev != NULL) { DeselectItem(tl, it); SelectItem(tl, itPrev); } break; } if (it == NULL && (it = TAILQ_FIRST(&tl->items)) != NULL) SelectItem(tl, it); } if (!SelectionVisible(tl)) ScrollToSelection(tl); } static void IncrementSelection(AG_Tlist *tl, int inc) { AG_TlistItem *it, *itNext; int i; for (i = 0; i < inc; i++) { TAILQ_FOREACH(it, &tl->items, items) { if (!it->selected) { continue; } itNext = TAILQ_NEXT(it, items); if (itNext != NULL) { DeselectItem(tl, it); SelectItem(tl, itNext); } break; } if (it == NULL && (it = TAILQ_FIRST(&tl->items)) != NULL) SelectItem(tl, it); } if (!SelectionVisible(tl)) ScrollToSelection(tl); } static void DoubleClickTimeout(AG_Event *event) { AG_Tlist *tl = AG_SELF(); tl->dblclicked = NULL; } static void LostFocus(AG_Event *event) { AG_Tlist *tl = AG_SELF(); AG_DelTimeout(tl, &tl->incTo); AG_DelTimeout(tl, &tl->decTo); AG_CancelEvent(tl, "dblclick-expire"); } static void Shown(AG_Event *event) { AG_Tlist *tl = AG_SELF(); if (tl->flags & AG_TLIST_POLL) { tl->flags |= AG_TLIST_REFRESH; AG_ScheduleTimeout(tl, &tl->refreshTo, 125); } } static Uint32 DecrementTimeout(void *obj, Uint32 ival, void *arg) { AG_Tlist *tl = obj; Uint8 *ks; int numkeys; ks = SDL_GetKeyState(&numkeys); DecrementSelection(tl, ks[SDLK_PAGEUP] ? agPageIncrement : 1); return (agKbdRepeat); } static Uint32 IncrementTimeout(void *obj, Uint32 ival, void *arg) { AG_Tlist *tl = obj; Uint8 *ks; int numkeys; ks = SDL_GetKeyState(&numkeys); IncrementSelection(tl, ks[SDLK_PAGEDOWN] ? agPageIncrement : 1); return (agKbdRepeat); } static Uint32 RefreshTimeout(void *obj, Uint32 ival, void *arg) { AG_Tlist *tl = obj; tl->flags |= AG_TLIST_REFRESH; return (ival); } static void Init(void *obj) { AG_Tlist *tl = obj; WIDGET(tl)->flags |= AG_WIDGET_FOCUSABLE; AG_BindPointer(tl, "selected", &tl->selected); tl->flags = 0; tl->selected = NULL; tl->wSpace = 4; tl->item_h = agTextFontHeight+2; tl->icon_w = 16; tl->dblclicked = NULL; tl->nitems = 0; tl->nvisitems = 0; tl->compare_fn = AG_TlistComparePtrs; tl->wHint = 0; tl->hHint = tl->item_h + 2; tl->popupEv = NULL; tl->changedEv = NULL; tl->dblClickEv = NULL; tl->wheelTicks = 0; tl->r = AG_RECT(0,0,0,0); TAILQ_INIT(&tl->items); TAILQ_INIT(&tl->selitems); TAILQ_INIT(&tl->popups); tl->sbar = AG_ScrollbarNew(tl, AG_SCROLLBAR_VERT, 0); AG_WidgetSetFocusable(tl->sbar, 0); AG_SetEvent(tl->sbar, "scrollbar-changed", ScrollbarChanged, "%p", tl); AG_SetEvent(tl, "window-mousebuttondown", MouseButtonDown, NULL); AG_SetEvent(tl, "window-keydown", KeyDown, NULL); AG_SetEvent(tl, "window-keyup", KeyUp, NULL); AG_SetEvent(tl, "dblclick-expire", DoubleClickTimeout, NULL); AG_SetEvent(tl, "widget-lostfocus", LostFocus, NULL); AG_SetEvent(tl, "widget-hidden", LostFocus, NULL); AG_SetEvent(tl, "widget-shown", Shown, NULL); AG_SetTimeout(&tl->decTo, DecrementTimeout, NULL, 0); AG_SetTimeout(&tl->incTo, IncrementTimeout, NULL, 0); AG_SetTimeout(&tl->refreshTo, RefreshTimeout, NULL, 0); } void AG_TlistSizeHint(AG_Tlist *tl, const char *text, int nitems) { AG_ObjectLock(tl); AG_TextSize(text, &tl->wHint, NULL); tl->hHint = (tl->item_h+2)*nitems; AG_ObjectUnlock(tl); } void AG_TlistSizeHintPixels(AG_Tlist *tl, int w, int nitems) { AG_ObjectLock(tl); tl->wHint = w; tl->hHint = (tl->item_h+2)*nitems; AG_ObjectUnlock(tl); } /* * Set the default size hint to accomodate the largest text label in * the current list of items, and the given number of items. */ void AG_TlistSizeHintLargest(AG_Tlist *tl, int nitems) { AG_TlistItem *it; int w; AG_ObjectLock(tl); tl->wHint = 0; AG_TLIST_FOREACH(it, tl) { AG_TextSize(it->text, &w, NULL); if (w > tl->wHint) { tl->wHint = w; } } tl->hHint = (tl->item_h+2)*nitems; AG_ObjectUnlock(tl); } static void Destroy(void *p) { AG_Tlist *tl = p; AG_TlistItem *it, *nit; AG_TlistPopup *tp, *ntp; for (it = TAILQ_FIRST(&tl->selitems); it != TAILQ_END(&tl->selitems); it = nit) { nit = TAILQ_NEXT(it, selitems); FreeItem(tl, it); } for (it = TAILQ_FIRST(&tl->items); it != TAILQ_END(&tl->items); it = nit) { nit = TAILQ_NEXT(it, items); FreeItem(tl, it); } for (tp = TAILQ_FIRST(&tl->popups); tp != TAILQ_END(&tl->popups); tp = ntp) { ntp = TAILQ_NEXT(tp, popups); AG_ObjectDestroy(tp->menu); Free(tp); } } static void SizeRequest(void *obj, AG_SizeReq *r) { AG_Tlist *tl = obj; AG_SizeReq rBar; AG_WidgetSizeReq(tl->sbar, &rBar); r->w = tl->icon_w + tl->wSpace*2 + tl->wHint + rBar.w; r->h = tl->hHint; } static int SizeAllocate(void *obj, const AG_SizeAlloc *a) { AG_Tlist *tl = obj; AG_SizeReq rBar; AG_SizeAlloc aBar; AG_WidgetSizeReq(tl->sbar, &rBar); if (a->w < rBar.w*2) { rBar.w = MAX(0, a->w/2); } aBar.w = rBar.w; aBar.h = a->h; aBar.x = a->w - rBar.w; aBar.y = 0; AG_WidgetSizeAlloc(tl->sbar, &aBar); tl->wRow = a->w - aBar.w; tl->r.w = tl->wRow; tl->r.h = a->h; UpdateListScrollbar(tl); return (0); } static void Draw(void *obj) { AG_Tlist *tl = obj; AG_TlistItem *it; int y = 0, i = 0, selSeen = 0, selPos = 1; int offset; STYLE(tl)->ListBackground(tl, tl->r); AG_WidgetDraw(tl->sbar); AG_PushClipRect(tl, tl->r); UpdatePolled(tl); offset = AG_GetInt(tl->sbar, "value"); TAILQ_FOREACH(it, &tl->items, items) { int x = 2 + it->depth*tl->icon_w; if (i++ < offset) { if (it->selected) { selPos = -1; } continue; } if (y > HEIGHT(tl) - tl->item_h) break; STYLE(tl)->ListItemBackground(tl, AG_RECT(x + tl->icon_w + 2, y, tl->wRow - x - tl->icon_w - 3, tl->item_h), it->selected); if (it->selected) selSeen = 1; if (it->iconsrc != NULL) { if (it->icon == -1) { AG_Surface *scaled = NULL; if (AG_ScaleSurface(it->iconsrc, tl->icon_w, tl->item_h, &scaled) == -1) { AG_FatalError(NULL); } it->icon = AG_WidgetMapSurface(tl, scaled); } AG_WidgetBlitSurface(tl, it->icon, x, y); } if (it->flags & AG_TLIST_HAS_CHILDREN) { STYLE(tl)->TreeSubnodeIndicator(tl, AG_RECT(x, y, tl->icon_w, tl->item_h), (it->flags & AG_TLIST_VISIBLE_CHILDREN)); } if (it->label == -1) { AG_TextColor(TLIST_TXT_COLOR); it->label = AG_WidgetMapSurface(tl, AG_TextRender(it->text)); } AG_WidgetBlitSurface(tl, it->label, x + tl->icon_w + tl->wSpace, y + tl->item_h/2 - WSURFACE(tl,it->label)->h/2); y += tl->item_h; if (y < HEIGHT(tl)-1) { AG_DrawLineH(tl, 1, tl->wRow-2, y, AG_COLOR(TLIST_LINE_COLOR)); } } if (!selSeen && (tl->flags & AG_TLIST_SCROLLTOSEL)) { AG_Variable *offsetb; int *offset; offsetb = AG_GetVariable(tl->sbar, "value", &offset); if (selPos == -1) { (*offset)--; } else { (*offset)++; } AG_UnlockVariable(offsetb); } else { tl->flags &= ~(AG_TLIST_SCROLLTOSEL); } AG_PopClipRect(); } /* * Adjust the scrollbar offset according to the number of visible items. * Tlist must be locked. */ static void UpdateListScrollbar(AG_Tlist *tl) { AG_Variable *maxb, *offsetb; int *max, *offset; int noffset; tl->nvisitems = HEIGHT(tl)/tl->item_h; maxb = AG_GetVariable(tl->sbar, "max", &max); offsetb = AG_GetVariable(tl->sbar, "value", &offset); noffset = *offset; *max = tl->nitems - tl->nvisitems; if (noffset > *max) noffset = *max; if (noffset < 0) noffset = 0; if (*offset != noffset) *offset = noffset; if (tl->nitems > 0 && tl->nvisitems > 0 && tl->nvisitems < tl->nitems) { AG_ScrollbarSetBarSize(tl->sbar, tl->nvisitems*(HEIGHT(tl->sbar) - tl->sbar->wButton*2) / tl->nitems); } else { AG_ScrollbarSetBarSize(tl->sbar, -1); /* Full range */ } AG_UnlockVariable(offsetb); AG_UnlockVariable(maxb); } static void FreeItem(AG_Tlist *tl, AG_TlistItem *it) { if (it->label != -1) { AG_WidgetUnmapSurface(tl, it->label); } if (it->flags & AG_TLIST_DYNICON && it->iconsrc != NULL) { AG_SurfaceFree(it->iconsrc); } if (it->icon != -1) { AG_WidgetUnmapSurface(tl, it->icon); } Free(it); } void AG_TlistDel(AG_Tlist *tl, AG_TlistItem *it) { AG_Variable *offsetb; int *offset; int nitems; AG_ObjectLock(tl); TAILQ_REMOVE(&tl->items, it, items); nitems = --tl->nitems; FreeItem(tl, it); /* Update the scrollbar range and offset accordingly. */ AG_SetInt(tl->sbar, "max", nitems); offsetb = AG_GetVariable(tl->sbar, "value", &offset); if (*offset > nitems) { *offset = nitems; } else if (nitems > 0 && *offset < nitems) { /* XXX ugly */ *offset = 0; } AG_UnlockVariable(offsetb); AG_ObjectUnlock(tl); } /* Clear the items on the list, save the selections if polling. */ void AG_TlistClear(AG_Tlist *tl) { AG_TlistItem *it, *nit; AG_ObjectLock(tl); for (it = TAILQ_FIRST(&tl->items); it != TAILQ_END(&tl->items); it = nit) { nit = TAILQ_NEXT(it, items); if ((!(tl->flags & AG_TLIST_NOSELSTATE) && it->selected) || (it->flags & AG_TLIST_HAS_CHILDREN)) { TAILQ_INSERT_HEAD(&tl->selitems, it, selitems); } else { FreeItem(tl, it); } } TAILQ_INIT(&tl->items); tl->nitems = 0; /* Preserve the offset value, for polling. */ AG_SetInt(tl->sbar, "max", 0); AG_ObjectUnlock(tl); } /* Generic string compare routine. */ int AG_TlistCompareStrings(const AG_TlistItem *it1, const AG_TlistItem *it2) { return (it1->text != NULL && it2->text != NULL && strcmp(it1->text, it2->text) == 0); } /* Generic pointer compare routine. */ int AG_TlistComparePtrs(const AG_TlistItem *it1, const AG_TlistItem *it2) { return (it1->p1 == it2->p1); } /* Generic pointer+class compare routine. */ int AG_TlistComparePtrsAndClasses(const AG_TlistItem *it1, const AG_TlistItem *it2) { return ((it1->p1 == it2->p1) && (it1->cat != NULL && it2->cat!= NULL && (strcmp(it1->cat, it2->cat) == 0))); } /* Set an alternate compare function for items. */ void AG_TlistSetCompareFn(AG_Tlist *tl, int (*fn)(const AG_TlistItem *, const AG_TlistItem *)) { AG_ObjectLock(tl); tl->compare_fn = fn; AG_ObjectUnlock(tl); } /* Set the update rate for polled displays in ms (-1 = update explicitely). */ void AG_TlistSetRefresh(AG_Tlist *tl, int ms) { AG_ObjectLock(tl); if (ms == -1) { AG_DelTimeout(tl, &tl->refreshTo); } else { AG_ScheduleTimeout(tl, &tl->refreshTo, ms); } AG_ObjectUnlock(tl); } /* Restore previous item selection state. */ void AG_TlistRestore(AG_Tlist *tl) { AG_TlistItem *sit, *cit, *nsit; AG_ObjectLock(tl); for (sit = TAILQ_FIRST(&tl->selitems); sit != TAILQ_END(&tl->selitems); sit = nsit) { nsit = TAILQ_NEXT(sit, selitems); TAILQ_FOREACH(cit, &tl->items, items) { if (!tl->compare_fn(sit, cit)) { continue; } if (!(tl->flags & AG_TLIST_NOSELSTATE)) { cit->selected = sit->selected; } if (sit->flags & AG_TLIST_VISIBLE_CHILDREN) { cit->flags |= AG_TLIST_VISIBLE_CHILDREN; } else { cit->flags &= ~(AG_TLIST_VISIBLE_CHILDREN); } } FreeItem(tl, sit); } TAILQ_INIT(&tl->selitems); UpdateListScrollbar(tl); AG_ObjectUnlock(tl); } /* * Allocate a new tlist item. * XXX allocate from a pool, especially for polled items. */ static __inline__ AG_TlistItem * AllocItem(AG_Tlist *tl, AG_Surface *iconsrc) { AG_TlistItem *it; it = Malloc(sizeof(AG_TlistItem)); it->selected = 0; it->cat = ""; it->depth = 0; it->flags = 0; it->icon = -1; it->label = -1; UpdateItemIcon(tl, it, iconsrc); return (it); } /* The Tlist must be locked. */ static __inline__ void InsertItem(AG_Tlist *tl, AG_TlistItem *it, int ins_head) { if (ins_head) { TAILQ_INSERT_HEAD(&tl->items, it, items); } else { TAILQ_INSERT_TAIL(&tl->items, it, items); } AG_SetInt(tl->sbar, "max", ++tl->nitems); } /* Add an item to the list. */ AG_TlistItem * AG_TlistAddPtr(AG_Tlist *tl, AG_Surface *iconsrc, const char *text, void *p1) { AG_TlistItem *it; AG_ObjectLock(tl); it = AllocItem(tl, iconsrc); it->p1 = p1; Strlcpy(it->text, text, sizeof(it->text)); InsertItem(tl, it, 0); AG_ObjectUnlock(tl); return (it); } AG_TlistItem * AG_TlistAdd(AG_Tlist *tl, AG_Surface *iconsrc, const char *fmt, ...) { AG_TlistItem *it; va_list args; AG_ObjectLock(tl); it = AllocItem(tl, iconsrc); it->p1 = NULL; va_start(args, fmt); Vsnprintf(it->text, sizeof(it->text), fmt, args); va_end(args); InsertItem(tl, it, 0); AG_ObjectUnlock(tl); return (it); } AG_TlistItem * AG_TlistAddPtrHead(AG_Tlist *tl, AG_Surface *icon, const char *text, void *p1) { AG_TlistItem *it; AG_ObjectLock(tl); it = AllocItem(tl, icon); it->p1 = p1; Strlcpy(it->text, text, sizeof(it->text)); InsertItem(tl, it, 1); AG_ObjectUnlock(tl); return (it); } /* Select an item based on its pointer value. */ AG_TlistItem * AG_TlistSelectPtr(AG_Tlist *tl, void *p) { AG_TlistItem *it; AG_ObjectLock(tl); UpdatePolled(tl); if ((tl->flags & AG_TLIST_MULTI) == 0) { AG_TlistDeselectAll(tl); } TAILQ_FOREACH(it, &tl->items, items) { if (it->p1 == p) { SelectItem(tl, it); break; } } AG_ObjectUnlock(tl); return (it); } /* Select an item based on text. */ AG_TlistItem * AG_TlistSelectText(AG_Tlist *tl, const char *text) { AG_TlistItem *it; AG_ObjectLock(tl); UpdatePolled(tl); if ((tl->flags & AG_TLIST_MULTI) == 0) { AG_TlistDeselectAll(tl); } TAILQ_FOREACH(it, &tl->items, items) { if (it->text[0] == text[0] && strcmp(it->text, text) == 0) { SelectItem(tl, it); break; } } AG_ObjectUnlock(tl); return (it); } /* Set the selection flag on an item. */ void AG_TlistSelect(AG_Tlist *tl, AG_TlistItem *it) { AG_ObjectLock(tl); if ((tl->flags & AG_TLIST_MULTI) == 0) { AG_TlistDeselectAll(tl); } SelectItem(tl, it); AG_ObjectUnlock(tl); } /* Clear the selection flag on an item. */ void AG_TlistDeselect(AG_Tlist *tl, AG_TlistItem *it) { AG_ObjectLock(tl); DeselectItem(tl, it); AG_ObjectUnlock(tl); } /* Set the selection flag on all items. */ void AG_TlistSelectAll(AG_Tlist *tl) { AG_TlistItem *it; AG_ObjectLock(tl); TAILQ_FOREACH(it, &tl->items, items) { SelectItem(tl, it); } AG_ObjectUnlock(tl); } /* Unset the selection flag on all items. */ void AG_TlistDeselectAll(AG_Tlist *tl) { AG_TlistItem *it; AG_ObjectLock(tl); TAILQ_FOREACH(it, &tl->items, items) { DeselectItem(tl, it); } AG_ObjectUnlock(tl); } /* The Tlist must be locked. */ static void SelectItem(AG_Tlist *tl, AG_TlistItem *it) { AG_Variable *selectedb; void **sel_ptr; selectedb = AG_GetVariable(tl, "selected", &sel_ptr); *sel_ptr = it->p1; if (!it->selected) { it->selected = 1; if (tl->changedEv != NULL) { AG_PostEvent(NULL, tl, tl->changedEv->name, "%p,%i", it, 1); } AG_PostEvent(NULL, tl, "tlist-changed", "%p, %i", it, 1); } AG_PostEvent(NULL, tl, "tlist-selected", "%p", it); AG_UnlockVariable(selectedb); } /* The Tlist must be locked. */ static void DeselectItem(AG_Tlist *tl, AG_TlistItem *it) { AG_Variable *selectedb; void **sel_ptr; selectedb = AG_GetVariable(tl, "selected", &sel_ptr); *sel_ptr = NULL; if (it->selected) { it->selected = 0; if (tl->changedEv != NULL) { AG_PostEvent(NULL, tl, tl->changedEv->name, "%p,%i", it, 0); } AG_PostEvent(NULL, tl, "tlist-changed", "%p, %i", it, 0); } AG_UnlockVariable(selectedb); } static void MouseButtonDown(AG_Event *event) { AG_Tlist *tl = AG_SELF(); int button = AG_INT(1); int x = AG_INT(2); int y = AG_INT(3); AG_TlistItem *ti; int tind; tind = (AG_GetInt(tl->sbar, "value") + y/tl->item_h) + 1; /* XXX use array */ if ((ti = AG_TlistFindByIndex(tl, tind)) == NULL) return; switch (button) { case SDL_BUTTON_WHEELUP: { AG_Variable *offsb; int *offs; offsb = AG_GetVariable(tl->sbar, "value", &offs); (*offs) -= AG_WidgetScrollDelta(&tl->wheelTicks); if (*offs < 0) { *offs = 0; } AG_UnlockVariable(offsb); } break; case SDL_BUTTON_WHEELDOWN: { AG_Variable *offsb; int *offs; offsb = AG_GetVariable(tl->sbar, "value", &offs); (*offs) += AG_WidgetScrollDelta(&tl->wheelTicks); if (*offs > (tl->nitems - tl->nvisitems)) { *offs = tl->nitems - tl->nvisitems; } AG_UnlockVariable(offsb); } break; case SDL_BUTTON_LEFT: /* Expand the children if the user clicked on the [+] sign. */ if (ti->flags & AG_TLIST_HAS_CHILDREN) { if (x >= ti->depth*tl->icon_w && x <= (ti->depth+1)*tl->icon_w) { if (ti->flags & AG_TLIST_VISIBLE_CHILDREN) { ti->flags &= ~AG_TLIST_VISIBLE_CHILDREN; } else { ti->flags |= AG_TLIST_VISIBLE_CHILDREN; } tl->flags |= AG_TLIST_REFRESH; return; } } if (ti->flags & AG_TLIST_NO_SELECT) { return; } /* * Handle range selections. */ if ((tl->flags & AG_TLIST_MULTI) && (SDL_GetModState() & KMOD_SHIFT)) { AG_TlistItem *oitem; int oind = -1, i = 0, nitems = 0; TAILQ_FOREACH(oitem, &tl->items, items) { if (oitem->selected) { oind = i; } i++; nitems++; } if (oind == -1) { return; } if (oind < tind) { /* Forward */ i = 0; TAILQ_FOREACH(oitem, &tl->items, items) { if (i == tind) break; if (i > oind) { SelectItem(tl, oitem); } i++; } } else if (oind >= tind) { /* Backward */ i = nitems; TAILQ_FOREACH_REVERSE(oitem, &tl->items, ag_tlist_itemq, items) { if (i <= oind) SelectItem(tl, oitem); if (i == tind) break; i--; } } break; } /* * Handle single selections. */ if ((tl->flags & AG_TLIST_MULTITOGGLE) || ((tl->flags & AG_TLIST_MULTI) && (SDL_GetModState() & KMOD_CTRL))) { if (ti->selected) { DeselectItem(tl, ti); } else { SelectItem(tl, ti); } break; } AG_WidgetFocus(tl); AG_TlistDeselectAll(tl); SelectItem(tl, ti); /* Handle double clicks. */ /* XXX compare the args as well as p1 */ if (tl->dblclicked != NULL && tl->dblclicked == ti->p1) { AG_CancelEvent(tl, "dblclick-expire"); if (tl->dblClickEv != NULL) { AG_PostEvent(NULL, tl, tl->dblClickEv->name, "%p", ti); } AG_PostEvent(NULL, tl, "tlist-dblclick", "%p", ti); tl->dblclicked = NULL; } else { tl->dblclicked = ti->p1; AG_SchedEvent(NULL, tl, agMouseDblclickDelay, "dblclick-expire", NULL); } break; case SDL_BUTTON_RIGHT: if (ti->flags & AG_TLIST_NO_POPUP) { return; } if (tl->popupEv != NULL) { AG_PostEvent(NULL, tl, tl->popupEv->name, NULL); } else if (ti->cat != NULL) { AG_TlistPopup *tp; AG_WidgetFocus(tl); if (!(tl->flags & (AG_TLIST_MULTITOGGLE|AG_TLIST_MULTI)) || !(SDL_GetModState() & (KMOD_CTRL|KMOD_SHIFT))) { AG_TlistDeselectAll(tl); SelectItem(tl, ti); } TAILQ_FOREACH(tp, &tl->popups, popups) { if (strcmp(tp->iclass, ti->cat) == 0) break; } if (tp != NULL) { PopupMenu(tl, tp); return; } } break; } } static void KeyDown(AG_Event *event) { AG_Tlist *tl = AG_SELF(); int keysym = AG_INT(1); switch (keysym) { case SDLK_UP: DecrementSelection(tl, 1); AG_DelTimeout(tl, &tl->incTo); AG_ScheduleTimeout(tl, &tl->decTo, agKbdDelay); break; case SDLK_DOWN: IncrementSelection(tl, 1); AG_DelTimeout(tl, &tl->decTo); AG_ScheduleTimeout(tl, &tl->incTo, agKbdDelay); break; case SDLK_PAGEUP: DecrementSelection(tl, agPageIncrement); AG_DelTimeout(tl, &tl->incTo); AG_ScheduleTimeout(tl, &tl->decTo, agKbdDelay); break; case SDLK_PAGEDOWN: IncrementSelection(tl, agPageIncrement); AG_DelTimeout(tl, &tl->decTo); AG_ScheduleTimeout(tl, &tl->incTo, agKbdDelay); break; } } static void KeyUp(AG_Event *event) { AG_Tlist *tl = AG_SELF(); int keysym = AG_INT(1); switch (keysym) { case SDLK_UP: case SDLK_PAGEUP: AG_DelTimeout(tl, &tl->decTo); break; case SDLK_DOWN: case SDLK_PAGEDOWN: AG_DelTimeout(tl, &tl->incTo); break; } } static void ScrollbarChanged(AG_Event *event) { AG_Tlist *tl = AG_PTR(1); AG_ObjectLock(tl); UpdateListScrollbar(tl); AG_ObjectUnlock(tl); } /* * Return the item at the given index. Result is only valid as long as * the Tlist is locked. */ AG_TlistItem * AG_TlistFindByIndex(AG_Tlist *tl, int index) { AG_TlistItem *it; int i = 0; AG_ObjectLock(tl); TAILQ_FOREACH(it, &tl->items, items) { if (++i == index) { AG_ObjectUnlock(tl); return (it); } } AG_ObjectUnlock(tl); return (NULL); } /* * Return the first selected item. Result is only valid as long as the Tlist * is locked. */ AG_TlistItem * AG_TlistSelectedItem(AG_Tlist *tl) { AG_TlistItem *it; AG_ObjectLock(tl); TAILQ_FOREACH(it, &tl->items, items) { if (it->selected) { AG_ObjectUnlock(tl); return (it); } } AG_ObjectUnlock(tl); return (NULL); } /* * Return the user pointer of the first selected item. Result is only valid * as long as the Tlist is locked. */ void * AG_TlistSelectedItemPtr(AG_Tlist *tl) { AG_TlistItem *it; void *rv; AG_ObjectLock(tl); TAILQ_FOREACH(it, &tl->items, items) { if (it->selected) { rv = it->p1; AG_ObjectUnlock(tl); return (rv); } } AG_ObjectUnlock(tl); return (NULL); } /* * Return the pointer associated with the first selected item. Result is only * valid as long as the Tlist is locked. */ void * AG_TlistFindPtr(AG_Tlist *tl) { AG_TlistItem *it; void *rv; AG_ObjectLock(tl); TAILQ_FOREACH(it, &tl->items, items) { if (it->selected) { rv = it->p1; AG_ObjectUnlock(tl); return (rv); } } AG_ObjectUnlock(tl); return (NULL); } /* * Return the first item matching a text string. Result is only valid as long * as the Tlist is locked. */ AG_TlistItem * AG_TlistFindText(AG_Tlist *tl, const char *text) { AG_TlistItem *it; AG_ObjectLock(tl); TAILQ_FOREACH(it, &tl->items, items) { if (strcmp(it->text, text) == 0) { AG_ObjectUnlock(tl); return (it); } } AG_ObjectUnlock(tl); return (NULL); } /* * Return the first item on the list. Result is only valid as long as * the Tlist is locked. */ AG_TlistItem * AG_TlistFirstItem(AG_Tlist *tl) { AG_TlistItem *it; AG_ObjectLock(tl); it = TAILQ_FIRST(&tl->items); AG_ObjectUnlock(tl); return (it); } /* * Return the last item on the list. Result is only valid as long as * the Tlist is locked. */ AG_TlistItem * AG_TlistLastItem(AG_Tlist *tl) { AG_TlistItem *it; AG_ObjectLock(tl); it = TAILQ_LAST(&tl->items, ag_tlist_itemq); AG_ObjectUnlock(tl); return (it); } /* Set the height to use for item display. */ void AG_TlistSetItemHeight(AG_Tlist *tl, int ih) { AG_TlistItem *it; AG_ObjectLock(tl); tl->item_h = ih; TAILQ_FOREACH(it, &tl->items, items) { if (it->icon != -1) { AG_Surface *scaled = NULL; if (AG_ScaleSurface(it->iconsrc, tl->item_h, tl->item_h, &scaled) == -1) { AG_FatalError(NULL); } AG_WidgetReplaceSurface(tl, it->icon, scaled); } } AG_ObjectUnlock(tl); } /* Update the icon associated with an item. The Tlist must be locked. */ static void UpdateItemIcon(AG_Tlist *tl, AG_TlistItem *it, AG_Surface *iconsrc) { if (it->flags & AG_TLIST_DYNICON) { if (it->iconsrc != NULL) { AG_SurfaceFree(it->iconsrc); } if (iconsrc != NULL) { it->iconsrc = AG_DupSurface(iconsrc); } else { it->iconsrc = NULL; } } else { it->iconsrc = iconsrc; } if (it->icon != -1) { AG_WidgetUnmapSurface(tl, it->icon); it->icon = -1; } } void AG_TlistSetIcon(AG_Tlist *tl, AG_TlistItem *it, AG_Surface *iconsrc) { AG_ObjectLock(tl); it->flags |= AG_TLIST_DYNICON; UpdateItemIcon(tl, it, iconsrc); AG_ObjectUnlock(tl); } void AG_TlistSetDblClickFn(AG_Tlist *tl, void (*ev)(AG_Event *), const char *fmt, ...) { AG_ObjectLock(tl); tl->dblClickEv = AG_SetEvent(tl, NULL, ev, NULL); AG_EVENT_GET_ARGS(tl->dblClickEv, fmt); AG_ObjectUnlock(tl); } void AG_TlistSetPopupFn(AG_Tlist *tl, void (*ev)(AG_Event *), const char *fmt, ...) { AG_ObjectLock(tl); tl->popupEv = AG_SetEvent(tl, NULL, ev, NULL); AG_EVENT_GET_ARGS(tl->popupEv, fmt); AG_ObjectUnlock(tl); } void AG_TlistSetChangedFn(AG_Tlist *tl, void (*ev)(AG_Event *), const char *fmt, ...) { AG_ObjectLock(tl); tl->changedEv = AG_SetEvent(tl, NULL, ev, NULL); AG_EVENT_GET_ARGS(tl->changedEv, fmt); AG_ObjectUnlock(tl); } /* Create a new popup menu for items of the given class. */ AG_MenuItem * AG_TlistSetPopup(AG_Tlist *tl, const char *iclass) { AG_TlistPopup *tp; tp = Malloc(sizeof(AG_TlistPopup)); tp->iclass = iclass; tp->panel = NULL; tp->menu = AG_MenuNew(NULL, 0); tp->item = tp->menu->root; /* XXX redundant */ AG_ObjectLock(tl); TAILQ_INSERT_TAIL(&tl->popups, tp, popups); AG_ObjectUnlock(tl); return (tp->item); } /* Scroll to the beginning of the list. */ void AG_TlistScrollToStart(AG_Tlist *tl) { AG_SetInt(tl->sbar, "value", 0); } /* Scroll to the end of the list. */ void AG_TlistScrollToEnd(AG_Tlist *tl) { AG_SetInt(tl->sbar, "value", tl->nitems - tl->nvisitems); } static void PopupMenu(AG_Tlist *tl, AG_TlistPopup *tp) { AG_Menu *m = tp->menu; int x, y; #if 0 if (AG_WidgetParentWindow(tl) == NULL) AG_FatalError("AG_Tlist: %s is unattached", OBJECT(tl)->name); #endif AG_MouseGetState(&x, &y); if (tp->panel != NULL) { AG_MenuCollapse(m, tp->item); tp->panel = NULL; } #if 0 if (m->itemSel != NULL) { AG_MenuCollapse(m, m->itemSel); } #endif m->itemSel = tp->item; tp->panel = AG_MenuExpand(m, tp->item, x+4, y+4); } AG_WidgetClass agTlistClass = { { "Agar(Widget:Tlist)", sizeof(AG_Tlist), { 0,0 }, Init, NULL, /* free */ Destroy, NULL, /* load */ NULL, /* save */ NULL /* edit */ }, Draw, SizeRequest, SizeAllocate };