#include #include #include #include "ui.h" #include "list.h" typedef struct sl SL; typedef struct item ITEM; struct sl { int x; int lwx; } ; /* * ptr is the caller item pointer. flags is per-item flags. * firstsline is the first screen line number for this item (relative * to the list beginning, not to the displayed window). slines is the * number of screen lines this item wants to take up. ox is this * item's index in the list's order[] array; that is, * list->order[item->ox]==item. fox is the item's index in the list's * forder[] array, or -1 if it's not in forder[]. */ struct item { void *ptr; unsigned int flags; #define ITEMF_FILTER_PASS 0x00000001 int firstsline; int slines; int ox; int fox; } ; /* * cookie through emptyline are values set up at list creation, as * modified by calls like list_set_filter(). flags is internal flags, * mostly recording what kinds of recomputation need to be done (but * _FIRST instead records whether no highlight motion has ever * happened, in which case the highlight is managed a little * differently). * * nitems/aitems/items are the vector of items; nfitems records how * many of them pass the filter. order and forder are arrays parallel * to (and in particular allocated to the same array-size as) items; * order holds the ordering of the items according to the cmp * function, so that items[order[0]], items[order[1]], * items[order[2]], etc, are in order. (The items' .ox fields are the * inverse permutation.) forder is the same thing but restricted to * just the items that pass the filter. * * scrlines is the number of screen lines available to the list for * display. nslines/aslines/slines are the vector of SLs, indexed by * (list-relative) screen line number, giving the item index (.x) and * line-within-item (.lwx) for each screen line. escrlines is * `effective scrlines': if there's no scrollbar displayed, it's * scrlines, otherwise, it's scrlines minus the scrollbar line count. * * hl and top are the highlight and top-of-screen-window line numbers. * If the list is in no-highlight mode, hl is -1, but top still holds * the top-of-screen-window line number. */ struct list { void *cookie; int (*filter)(void *, void *); int (*cmp)(void *, void *, void *); int (*lcount)(void *, void *); void (*render)(void *, void *, unsigned int, unsigned int, unsigned int); void (*print)(void *, void *, FILE *); void (*scrollbar)(void *, int, int, int); void *emptyline; unsigned int flags; #define LISTF_REFILTER 0x00000001 #define LISTF_RESORT 0x00000002 #define LISTF_FILTERSORT 0x00000004 #define LISTF_RECOUNT 0x00000008 #define LISTF_RELAYOUT 0x00000010 #define LISTF_FIRST 0x00000020 #define LISTF_SHOW_SB 0x00000040 int nitems; int nfitems; int aitems; ITEM *items; int *order; int *forder; int scrlines; int escrlines; int nslines; int aslines; SL *slines; int hl; int top; int sblines; } ; static int dbgflag = 0; #define DBGFD_UNOPENED (-1) // never opened #define DBGFD_FAILED (-2) // open failed #define DBGFD_NOFD (-3) // FILE * set, fd irrelevant static int dbgfd = DBGFD_UNOPENED; static FILE *dbgf = 0; static void dbg(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void dbg(const char *fmt, ...) { va_list ap; if (! dbgflag) return; switch (dbgfd) { case DBGFD_UNOPENED: dbgfd = open("list.dbg",O_WRONLY|O_APPEND|O_CREAT,0644); if (dbgfd < 0) { dbgfd = DBGFD_FAILED; case DBGFD_FAILED: return; } break; case DBGFD_NOFD: default: break; } if (! dbgf) { dbgf = fdopen(dbgfd,"a"); if (! dbgf) return; setlinebuf(dbgf); fprintf(dbgf,"---------------- DEBUG BEGINNING ----------------\n"); } va_start(ap,fmt); vfprintf(dbgf,fmt,ap); va_end(ap); } static void dbgflush(void) { if (dbgf) fflush(dbgf); } static void dump(LIST *l) { int i; if (! dbgflag) return; dbg("%p: flags",(void*)l); if (l->flags == 0) { dbg(" 0"); } else { if (l->flags & LISTF_REFILTER) dbg(" REFILTER"); if (l->flags & LISTF_RESORT) dbg(" RESORT"); if (l->flags & LISTF_RECOUNT) dbg(" RECOUNT"); if (l->flags & LISTF_RELAYOUT) dbg(" RELAYOUT"); if (l->flags & LISTF_FIRST) dbg(" FIRST"); if (l->flags & ~(LISTF_REFILTER | LISTF_RESORT | LISTF_RECOUNT | LISTF_RELAYOUT | LISTF_FIRST)) { dbg("0x%08x",l->flags&~(LISTF_REFILTER|LISTF_RESORT|LISTF_RECOUNT|LISTF_RELAYOUT|LISTF_FIRST)); } } dbg("\n"); dbg("nitems %d aitems %d\n",l->nitems,l->aitems); for (i=0;initems;i++) { dbg("items[%d]: ptr %p flags",i,l->items[i].ptr); if (l->items[i].flags == 0) { dbg(" 0"); } else { if (l->items[i].flags & ITEMF_FILTER_PASS) dbg(" FILTER_PASS"); if (l->items[i].flags & ~ITEMF_FILTER_PASS) { dbg("0x%08x",l->items[i].flags&~ITEMF_FILTER_PASS); } } dbg(" firstsline %d slines %d: ",l->items[i].firstsline,l->items[i].slines); (*l->print)(l->cookie,l->items[i].ptr,dbgf); dbg("\n"); } dbg("order:"); for (i=0;initems;i++) dbg(" %d",l->order[i]); dbg("\n"); dbg("forder:"); for (i=0;infitems;i++) dbg(" %d",l->forder[i]); dbg("\n"); dbg("scr l=%d/%d, nslines %d aslines %d\n",l->scrlines,l->escrlines,l->nslines,l->aslines); dbg("slines"); for (i=0;inslines;i++) dbg(" %d.%d",l->slines[i].x,l->slines[i].lwx); dbg("\n"); dbg("hl %d top %d\n",l->hl,l->top); dbgflush(); } #define H_L(x) (((x)*2)+1) #define H_R(x) (((x)+1)*2) #define H_U(x) (((x)-1)/2) static void reheap_down(LIST *l, int x, int n) { int lc; int rc; int t; int s; t = l->order[x]; while (1) { lc = H_L(x); if (lc >= n) break; rc = H_R(x); if ((rc >= n) || ((*l->cmp)(l->cookie,l->items[l->order[rc]].ptr,l->items[t].ptr) <= 0)) { if ((*l->cmp)(l->cookie,l->items[l->order[lc]].ptr,l->items[t].ptr) <= 0) { break; } else { s = lc; } } else { if ((*l->cmp)(l->cookie,l->items[l->order[lc]].ptr,l->items[t].ptr) <= 0) { s = rc; } else { s = ((*l->cmp)(l->cookie,l->items[l->order[lc]].ptr,l->items[l->order[rc]].ptr) <= 0) ? rc : lc; } } l->order[x] = l->order[s]; x = s; } l->order[x] = t; } static void maybe_move_top(LIST *l) { if (l->hl >= 0) { if (l->top > l->hl) l->top = l->hl; if (l->top <= l->hl - l->escrlines) l->top = l->hl - l->escrlines + 1; } if (l->top >= l->nslines - l->escrlines) l->top = l->nslines - l->escrlines; if (l->top < 0) l->top = 0; } static void maybe_move_hl(LIST *l) { if (l->hl < 0) return; if (l->hl >= l->top + l->escrlines) l->hl = l->top + l->escrlines - 1; if (l->hl < l->top) l->hl = l->top; } static int adjust_saved(LIST *l, SL orig, const char *what) { int rv; int i; int j; int k; if (orig.x < 0) panic(); if (orig.x >= l->nitems) panic(); if (l->items[orig.x].flags & ITEMF_FILTER_PASS) { rv = l->items[orig.x].firstsline; dbg("mapping %s to %d\n",what,rv); i = orig.lwx; j = l->items[orig.x].slines; dbg("i %d, j %d -> ",i,j); if (i >= j) i = j - 1; if (i < 0) i = 0; rv += i; dbg("%d, %s now %d\n",i,what,rv); } else { dbg("top %s away\n",what); j = l->items[orig.x].ox; while (1) { j ++; if (j >= l->nitems) break; if (l->items[l->order[j]].flags & ITEMF_FILTER_PASS) { k = 0; dbg("%s advanced to %d -> %d\n",what,j,l->order[j]); break; } } if (j >= l->nitems) { j = l->items[orig.x].ox; while (1) { j --; if (j < 0) panic(); if (l->items[l->order[j]].flags & ITEMF_FILTER_PASS) { k = l->items[l->order[j]].slines - 1; dbg("%s backed up to %d -> %d\n",what,j,l->order[j]); break; } } } j = l->order[j]; if ((j < 0) || (j >= l->nitems)) panic(); i = l->items[j].fox; if ((i < 0) || (i >= l->nfitems)) panic(); rv = l->items[j].firstsline; if ((k < 0) || (k >= l->items[j].slines)) panic(); rv += k; dbg("%s set to %d\n",what,rv); } return(rv); } static void update(LIST *l) { int i; int j; int k; int empty; SL hl; SL top; dbg("BEGIN UPDATE\n"); dbg("l->nslines %d, hl %d, top %d\n",l->nslines,l->hl,l->top); if (l->nslines < 1) { empty = 1; } else { empty = 0; if (l->hl < 0) { hl = (SL){.x=-1}; } else { hl = l->slines[l->hl]; } top = l->slines[l->top]; dbg("saved hl %d.%d top %d.%d\n",hl.x,hl.lwx,top.x,top.lwx); } if (l->flags & LISTF_RESORT) { l->flags = (l->flags & ~LISTF_RESORT) | LISTF_FILTERSORT | LISTF_RELAYOUT; for (i=l->nitems-1;i>=0;i--) l->order[i] = i; if (l->nitems > 1) { for (i=H_U(l->nitems-1);i>=0;i--) reheap_down(l,i,l->nitems); for (i=l->nitems-1;i>0;i--) { j = l->order[0]; l->order[0] = l->order[i]; l->order[i] = j; reheap_down(l,0,i); } } for (i=l->nitems-1;i>=0;i--) l->items[i].ox = -1; for (i=l->nitems-1;i>=0;i--) l->items[l->order[i]].ox = i; for (i=l->nitems-1;i>=0;i--) if (l->items[i].ox < 0) panic(); } if (l->flags & LISTF_REFILTER) { l->flags = (l->flags & ~LISTF_REFILTER) | LISTF_FILTERSORT | LISTF_RELAYOUT; for (i=l->nitems-1;i>=0;i--) { if ((*l->filter)(l->cookie,l->items[i].ptr)) { l->items[i].flags |= ITEMF_FILTER_PASS; } else { l->items[i].flags &= ~ITEMF_FILTER_PASS; } } } if (l->flags & LISTF_FILTERSORT) { l->flags = (l->flags & ~LISTF_FILTERSORT) | LISTF_RELAYOUT; j = 0; for (i=0;initems;i++) { k = l->order[i]; if (l->items[k].flags & ITEMF_FILTER_PASS) { l->forder[j] = k; l->items[k].fox = j; j ++; } else { l->items[k].fox = -1; } } l->nfitems = j; } if (l->flags & LISTF_RECOUNT) { l->flags = (l->flags & ~LISTF_RECOUNT) | LISTF_RELAYOUT; for (i=l->nitems-1;i>=0;i--) { if (l->items[i].flags & ITEMF_FILTER_PASS) { j = (*l->lcount)(l->cookie,l->items[i].ptr); if (j < 1) panic(); l->items[i].slines = j; } } } if (l->flags & LISTF_RELAYOUT) { l->flags &= ~LISTF_RELAYOUT; k = 0; for (i=0;infitems;i++) { j = l->forder[i]; l->items[j].firstsline = k; k += l->items[j].slines; } l->nslines = k; if (l->nslines > l->aslines) { l->aslines = l->nslines; free(l->slines); l->slines = malloc(l->aslines*sizeof(*l->slines)); } for (i=l->nslines-1;i>=0;i--) l->slines[i].x = -1; for (i=l->nitems-1;i>=0;i--) { if (l->items[i].flags & ITEMF_FILTER_PASS) { k = l->items[i].firstsline; for (j=l->items[i].slines-1;j>=0;j--) { l->slines[k+j] = (SL){.x=i,.lwx=j}; } } } for (i=l->nslines-1;i>=0;i--) if (l->slines[i].x < 0) panic(); } dbg("nslines now %d\n",l->nslines); if (l->scrollbar && (l->nslines > l->scrlines) && (l->scrlines > l->sblines)) { l->flags |= LISTF_SHOW_SB; l->escrlines = l->scrlines - l->sblines; } else { l->flags &= ~LISTF_SHOW_SB; l->escrlines = l->scrlines; } if ((l->nslines < 1) || empty) { if (l->hl >= 0) l->hl = 0; l->top = 0; dbg("empty either before or after, resetting to top\n"); } else { /* * Because we call maybe_move_top() and maybe_move_hl() inside * list_set_hl_*() and list_scroll(), if the hl is outside the * window here, it must be because of something like line counts * or sort order changing, something for which we want to move top * to bring hl back onto the screen. So we just set hl and top * independently and then call maybe_move_top() to do any movement * necessary. * * At this point, top.x and hl.x are indices into l->items[]. */ l->top = adjust_saved(l,top,"top"); if (hl.x < 0) { l->hl = -1; } else { l->hl = adjust_saved(l,hl,"hl"); } maybe_move_top(l); dbg("maybe_move_top left top %d, hl %d\n",l->top,l->hl); } dbg("END UPDATE\n"); } // ---------------- Below this point are exported APIs. LIST *list_new( void *cookie, int (*filter)(void *, void *), int (*cmp)(void *, void *, void *), int (*lcount)(void *, void *), void (*render)(void *, void *, unsigned int, unsigned int, unsigned int), void (*print)(void *, void *, FILE *), void *emptyline ) { LIST *l; l = malloc(sizeof(LIST)); l->cookie = cookie; l->filter = filter; l->cmp = cmp; l->lcount = lcount; l->render = render; l->print = print; l->scrollbar = 0; l->emptyline = emptyline; l->flags = LISTF_REFILTER | LISTF_RESORT | LISTF_RECOUNT | LISTF_RELAYOUT | LISTF_FIRST; l->nitems = 0; l->order = 0; l->forder = 0; l->aitems = 0; l->items = 0; l->nslines = 0; l->scrlines = 0; l->escrlines = 0; l->aslines = 0; l->slines = 0; l->hl = 0; l->top = 0; return(l); } void list_set_cookie(LIST *l, void *cookie) { if (cookie != l->cookie) { l->cookie = cookie; l->flags |= LISTF_REFILTER | LISTF_RESORT | LISTF_RECOUNT; } } void list_set_filter(LIST *l, int (*filter)(void *, void *)) { if (filter != l->filter) { l->filter = filter; l->flags |= LISTF_REFILTER; } } void list_change_filter(LIST *l, int (*filter)(void *, void *)) { if (filter) l->filter = filter; l->flags |= LISTF_REFILTER; } void list_set_compare(LIST *l, int (*cmp)(void *, void *, void *)) { if (cmp != l->cmp) { l->cmp = cmp; l->flags |= LISTF_RESORT; } } void list_change_compare(LIST *l, int (*cmp)(void *, void *, void *)) { if (cmp) l->cmp = cmp; l->flags |= LISTF_RESORT; } void list_set_linecount(LIST *l, int (*lcount)(void *, void *)) { if (lcount != l->lcount) { l->lcount = lcount; l->flags |= LISTF_RECOUNT; } } void list_change_linecount(LIST *l, int (*lcount)(void *, void *)) { if (lcount) l->lcount = lcount; l->flags |= LISTF_RECOUNT; } void list_set_render(LIST *l, void (*render)(void *, void *, unsigned int, unsigned int, unsigned int)) { l->render = render; } void list_set_empty_item(LIST *l, void *emptyline) { l->emptyline = emptyline; } void list_clear(LIST *l) { dbg("=== list_clear %p\n",(void *)l); l->nitems = 0; l->nslines = 0; l->flags |= LISTF_REFILTER | LISTF_RESORT | LISTF_RECOUNT | LISTF_RELAYOUT; } void list_clear_cb(LIST *l, void (*cb)(void *, void *)) { int i; dbg("=== list_clear_cb %p\n",(void *)l); for (i=l->nitems-1;i>=0;i--) (*cb)(l->cookie,l->items[i].ptr); list_clear(l); } void list_destroy(LIST *l) { dbg("=== list_destroy %p\n",(void *)l); list_clear(l); free(l->items); free(l->slines); free(l); } int list_add1(LIST *l, void *item) { int x; dbg("=== list_add1 %p %p ",(void *)l,item); list_addn(l,&item,1,&x); return(x); } void list_addn(LIST *l, void * const *items, int nitems, int *xv) { int i; int j; void *item; if (dbgflag) { dbg("=== list_addn %p\n",(void *)l); for (i=0;iprint)(l->cookie,items[i],dbgf); dbg("\n"); } dbgflush(); } if (l->nitems+nitems > l->aitems) { l->aitems = l->nitems + nitems; l->items = realloc(l->items,l->aitems*sizeof(*l->items)); l->order = realloc(l->order,l->aitems*sizeof(*l->order)); l->forder = realloc(l->forder,l->aitems*sizeof(*l->forder)); } j = l->nitems; for (i=nitems-1;i>=0;i--) { item = items[i]; l->items[j] = (ITEM){.ptr=item,.flags=0,.firstsline=-1}; xv[i] = j; j ++; } if (j > l->aitems) panic(); l->nitems = j; l->flags |= LISTF_RESORT | LISTF_REFILTER | LISTF_RECOUNT; } void list_set_lines(LIST *l, int lines) { dbg("=== list_set_lines %p %d\n",(void *)l,lines); if (lines < 2) panic(); l->scrlines = lines; l->flags |= LISTF_RELAYOUT; } int list_lines_wanted(LIST *l) { dbg("=== list_lines_wanted %p\n",(void *)l); update(l); dbg(" -> %d\n",l->nslines); return(l->nslines); } int list_cur_top(LIST *l) { dbg("=== list_cur_top %p\n",(void *)l); update(l); dbg(" -> %d\n",l->top); return(l->top); } int list_cur_hl_line(LIST *l) { dbg("=== list_cur_hl_line %p\n",(void *)l); update(l); if (l->nitems < 1) { dbg("=== list_cur_hl_line %p -> empty -> 0\n",(void *)l); return(0); } dbg("=== list_cur_hl_line %p -> %d\n",(void *)l,l->hl); return(l->hl); } void *list_item_hl_ptr(LIST *l) { dbg("=== list_cur_hl_ptr %p\n",(void *)l); update(l); if (l->hl < 0) panic(); if (l->nitems < 1) { dbg("=== list_cur_hl_ptr %p -> empty -> 0\n",(void *)l); return(0); } dbg("=== list_cur_hl_ptr %p -> %p\n",(void *)l,l->items[l->slines[l->hl].x].ptr); return(l->items[l->slines[l->hl].x].ptr); } int list_item_hl_inx(LIST *l) { dbg("=== list_cur_hl_inx %p\n",(void *)l); update(l); if (l->hl < 0) panic(); if (l->nitems < 1) { dbg("=== list_cur_hl_inx %p -> empty -> 0\n",(void *)l); return(0); } dbg("=== list_cur_hl_inx %p -> %d\n",(void *)l,l->slines[l->hl].x); return(l->slines[l->hl].x); } void list_set_hl_line(LIST *l, int hl) { dbg("=== list_set_hl_line %p %d\n",(void *)l,hl); update(l); if (l->nitems < 1) { if (l->hl < 0) l->hl = 0; dbg(" -> empty\n"); return; } l->flags &= ~LISTF_FIRST; if (hl >= l->nslines) hl = l->nslines-1; if (hl < 0) hl = 0; l->hl = hl; dbg(" -> hl %d (top %d)\n",l->hl,l->top); maybe_move_top(l); dbg(" -> adjusted top %d\n",l->top); } void list_set_hl_inx(LIST *l, int x, int lwx) { dbg("=== list_set_hl_inx %p %d.%d\n",(void *)l,x,lwx); update(l); if (l->nitems < 1) panic(); l->flags &= ~LISTF_FIRST; if ((x < 0) || (x >= l->nitems)) panic(); if ((lwx < 0) || (lwx >= l->items[x].slines)) panic(); l->hl = l->items[x].firstsline + lwx; dbg(" -> hl %d (top %d)\n",l->hl,l->top); maybe_move_top(l); dbg(" -> adjusted top %d\n",l->top); } void list_set_no_highlight(LIST *l) { dbg("=== list_set_no_highlight %p\n",(void *)l); l->hl = -1; } void list_move_highlight(LIST *l, int inc) { dbg("=== list_move_highlight %p %d ",(void *)l,inc); if (l->hl < 0) { list_scroll(l,inc); } else { list_set_hl_line(l,l->hl+inc); } } void list_move_highlight_first(LIST *l) { dbg("=== list_move_highlight_first %p ",(void *)l); update(l); if (l->hl < 0) { list_scroll(l,-l->nslines); } else { list_set_hl_line(l,0); } } void list_move_highlight_last(LIST *l) { dbg("=== list_move_highlight_first %p ",(void *)l); update(l); if (l->hl < 0) { list_scroll(l,l->nslines); } else { list_set_hl_line(l,l->nslines-1); } } void list_set_scrollbar(LIST *l, void (*sb)(void *, int, int, int), int sbl) { l->scrollbar = sb; l->sblines = sbl; } void list_scroll(LIST *l, int by) { dbg("=== list_scroll %p %d\n",(void *)l,by); update(l); if (l->nitems < 1) { dbg(" -> empty\n"); return; } l->flags &= ~LISTF_FIRST; l->top += by; dbg(" -> set top to %d\n",l->top); if (l->top > l->nslines-l->escrlines) l->top = l->nslines - l->escrlines; if (l->top < 0) l->top = 0; dbg(" -> clipped top %d (hl %d)\n",l->top,l->hl); maybe_move_hl(l); dbg(" -> adjusted hl %d\n",l->hl); } void list_update(LIST *l) { update(l); } void list_render(LIST *l, int firstline) { int i; int j; dbg("=== list_render %p\n",(void *)l); update(l); dbg("post-update:\n"); dump(l); for (i=l->escrlines-1;i>=0;i--) { j = l->top + i; if (j >= l->nslines) { dbg("rendering empty line, screen line %d\n",firstline+i); (*l->render)(l->cookie,l->emptyline,0,firstline+i,0); } else { dbg("rendering line %p subline %d, screen line %d\n",l->items[l->slines[j].x].ptr,l->slines[j].lwx,firstline+i); (*l->render)(l->cookie,l->items[l->slines[j].x].ptr,l->slines[j].lwx,firstline+i,(j==l->hl)?LIST_HIGHLIGHTED:0); } } if (l->scrollbar) { if (l->flags & LISTF_SHOW_SB) { (*l->scrollbar)(l->cookie,l->top,l->escrlines,l->nslines-l->top-l->escrlines); } else { (*l->scrollbar)(l->cookie,-1,-1,-1); } } dbgflush(); } int list_get_debug(void) { return(dbgflag); } void list_set_debug(int f) { dbgflag = f; } void list_debug_to(FILE *f) { dbgflag = 1; dbgfd = DBGFD_NOFD; dbgf = f; }