Index: table.c =================================================================== --- table.c (revision 8866) +++ table.c (working copy) @@ -369,8 +369,8 @@ return (0); } -static __inline__ void -PrintCell(AG_TableCell *c, char *buf, size_t bufsz) +void +AG_TablePrintCell(const AG_TableCell *c, char *buf, size_t bufsz) { switch (c->type) { case AG_CELL_INT: @@ -454,23 +454,14 @@ case AG_CELL_FN_TXT: c->fnTxt(c->data.p, buf, bufsz); break; - case AG_CELL_FN_SU: - case AG_CELL_FN_SU_NODUP: - Strlcpy(buf, "", bufsz); - break; case AG_CELL_POINTER: Snprintf(buf, bufsz, c->fmt, c->data.p); break; - case AG_CELL_NULL: - if (c->fmt[0] == '\0') { - Strlcpy(buf, "", bufsz); - } else { - Strlcpy(buf, c->fmt, bufsz); + default: + if (bufsz > 0) { + buf[0] = '\0'; } break; - case AG_CELL_WIDGET: - Strlcpy(buf, "", bufsz); - break; } } @@ -523,7 +514,7 @@ } break; default: - PrintCell(c, txt, sizeof(txt)); + AG_TablePrintCell(c, txt, sizeof(txt)); break; } AG_TextColor(agColors[TEXT_COLOR]); @@ -841,36 +832,18 @@ } } -/* - * Add an entry to a column pool. - * Table must be locked. - */ -int -AG_TablePoolAdd(AG_Table *t, int m, int n) +static __inline__ Uint +HashPrevCell(AG_Table *t, const AG_TableCell *c) { - AG_TableCol *tc = &t->cols[n]; - - tc->pool = Realloc(tc->pool, (tc->mpool+1)*sizeof(AG_TableCell)); - memcpy(&tc->pool[tc->mpool], &t->cells[m][n], sizeof(AG_TableCell)); - return (tc->mpool++); -} + char buf[AG_TABLE_HASHBUF_MAX]; + Uint h; + Uchar *p; -/* - * Cleanup a column pool. - * Table must be locked. - */ -void -AG_TablePoolFree(AG_Table *t, int n) -{ - AG_TableCol *tc = &t->cols[n]; - int m; - - for (m = 0; m < tc->mpool; m++) { - AG_TableFreeCell(t, &tc->pool[m]); + AG_TablePrintCell(c, buf, sizeof(buf)); + for (h = 0, p = (Uchar *)buf; *p != '\0'; p++) { + h = 31*h + *p; } - Free(tc->pool); - tc->pool = NULL; - tc->mpool = 0; + return (h % t->nPrevBuckets); } /* @@ -882,11 +855,18 @@ { int m, n; - AG_ObjectLock(t); - /* Copy the existing cells to the column pools and free the table. */ + AG_ObjectLock(t); /* Lock across TableBegin/End */ + + /* Copy the existing cells to the backing store and free the table. */ for (m = 0; m < t->m; m++) { for (n = 0; n < t->n; n++) { - AG_TablePoolAdd(t, m, n); + AG_TableCell *c = &t->cells[m][n], *cPrev; + AG_TableBucket *tbPrev = &t->cPrev[HashPrevCell(t,c)]; + + cPrev = Malloc(sizeof(AG_TableCell)); + memcpy(cPrev, c, sizeof(AG_TableCell)); + TAILQ_INSERT_HEAD(&tbPrev->cells, cPrev, cells); + TAILQ_INSERT_HEAD(&t->cPrevList, cPrev, cells_list); } Free(t->cells[m]); } @@ -896,6 +876,7 @@ t->flags &= ~(AG_TABLE_WIDGETS); } +/* Compare two "%[Ft]" cells. */ static int AG_TableCompareFnTxtCells(const AG_TableCell *c1, const AG_TableCell *c2) { @@ -907,6 +888,7 @@ return (strcoll(b1, b2)); } +/* Compare two table cells. */ int AG_TableCompareCells(const AG_TableCell *c1, const AG_TableCell *c2) { @@ -916,6 +898,9 @@ } return (1); } + if (c1->id != 0 && c2->id != 0) { + return (c1->id - c2->id); + } switch (c1->type) { case AG_CELL_STRING: return (strcoll(c1->data.s, c2->data.s)); @@ -975,43 +960,148 @@ return (1); } +/* Restore selection state on a per-row basis. */ +static void +TableRestoreRowSelections(AG_Table *t) +{ + int m, n; + int nMatched, nCompared, sel; + + for (m = 0; m < t->m; m++) { + nMatched = 0; + nCompared = 0; + sel = 0; + for (n = 0; n < t->n; n++) { + AG_TableCell *c = &t->cells[m][n]; + AG_TableCell *cPrev; + AG_TableBucket *tb; + + if (c->type == AG_CELL_NULL) { + continue; + } + tb = &t->cPrev[HashPrevCell(t,c)]; + TAILQ_FOREACH(cPrev, &tb->cells, cells) { + if (AG_TableCompareCells(c, cPrev) != 0) { + continue; + } + c->surface = cPrev->surface; + cPrev->surface = -1; + if (!(cPrev->flags & AG_TABLE_CELL_NOCOMPARE)) { + if (cPrev->selected) + nMatched++; + } + } + if (!(c->flags & AG_TABLE_CELL_NOCOMPARE)) + nCompared++; + } + if (nMatched == nCompared) + AG_TableSelectRow(t, m); + } +} + /* - * Restore the selection state and recover the surfaces of matching - * items in the column pool. Unlock the table. + * Restore selection state on a per-cell basis. In most applications, the + * user will almost always need to specify per-cell unique IDs for selections + * to restore properly. */ -void -AG_TableEnd(AG_Table *t) +static void +TableRestoreCellSelections(AG_Table *t) { - int m, n, mPool; + int m, n; for (n = 0; n < t->n; n++) { - AG_TableCol *tc = &t->cols[n]; + for (m = 0; m < t->m; m++) { + AG_TableCell *c = &t->cells[m][n]; + AG_TableCell *cPrev; + AG_TableBucket *tb; + + if (c->type == AG_CELL_NULL) { + continue; + } + tb = &t->cPrev[HashPrevCell(t,c)]; + TAILQ_FOREACH(cPrev, &tb->cells, cells) { + if (AG_TableCompareCells(c, cPrev) != 0) { + continue; + } + c->selected = cPrev->selected; + c->surface = cPrev->surface; + cPrev->surface = -1; + break; + } + } + } +} - /* - * Compare the new cells against the backing store to - * recycle surfaces and recover selection state. - */ - for (mPool = 0; mPool < tc->mpool; mPool++) { - AG_TableCell *cPool = &tc->pool[mPool]; +/* + * Restore selection state on a per-column basis. + */ +static void +TableRestoreColSelections(AG_Table *t) +{ + int m, n; - for (m = 0; m < t->m; m++) { - AG_TableCell *c = &t->cells[m][n]; - - if (AG_TableCompareCells(c, cPool) == 0) { - c->surface = cPool->surface; - c->selected = cPool->selected; - cPool->surface = -1; + for (n = 0; n < t->n; n++) { + for (m = 0; m < t->m; m++) { + AG_TableCell *c = &t->cells[m][n]; + AG_TableCell *cPrev; + AG_TableBucket *tb; + + if (c->type == AG_CELL_NULL) { + continue; + } + tb = &t->cPrev[HashPrevCell(t,c)]; + + TAILQ_FOREACH(cPrev, &tb->cells, cells) { + if (AG_TableCompareCells(c, cPrev) != 0) { + continue; } + c->surface = cPrev->surface; + cPrev->surface = -1; } - AG_TableFreeCell(t, cPool); } - Free(tc->pool); - tc->pool = NULL; - tc->mpool = 0; } - AG_ObjectUnlock(t); } +/* + * Restore the selection state and recover the surfaces of matching + * items in the backing store. Unlock the table. + */ +void +AG_TableEnd(AG_Table *t) +{ + AG_TableCell *tc, *tcNext; + + if (t->n == 0) + goto out; + + /* Recover surfaces and selection state from the backing store. */ + switch (t->selMode) { + case AG_TABLE_SEL_ROWS: + TableRestoreRowSelections(t); + break; + case AG_TABLE_SEL_CELLS: + TableRestoreCellSelections(t); + break; + case AG_TABLE_SEL_COLS: + TableRestoreColSelections(t); + break; + } + + /* Clear the backing store. */ + for (tc = TAILQ_FIRST(&t->cPrevList); + tc != TAILQ_END(&t->cPrevList); + tc = tcNext) { + tcNext = TAILQ_NEXT(tc, cells_list); + Free(tc); + } + TAILQ_INIT(&t->cPrevList); + /* It is safe to use memset() in place of TAILQ_INIT(). */ + memset(t->cPrev, 0, t->nPrevBuckets*sizeof(AG_TableBucket)); + +out: + AG_ObjectUnlock(t); /* Lock across TableBegin/End */ +} + static int AG_TableSortCellsAsc(const void *p1, const void *p2) { @@ -1818,8 +1908,6 @@ tc->selected = 0; tc->w = 0; tc->wPct = -1; - tc->pool = NULL; - tc->mpool = 0; AG_PushTextState(); AG_TextColor(agColors[TEXT_COLOR]); @@ -1851,13 +1939,13 @@ /* Resize the row arrays. */ for (m = 0; m < t->m; m++) { - AG_TableCell *cellsNew; + AG_TableCell *cNew; - if ((cellsNew = TryRealloc(t->cells[m], + if ((cNew = TryRealloc(t->cells[m], (t->n+1)*sizeof(AG_TableCell))) == NULL) { goto fail; } - t->cells[m] = cellsNew; + t->cells[m] = cNew; AG_TableInitCell(t, &t->cells[m][t->n]); } n = t->n++; @@ -1881,6 +1969,8 @@ c->surface = -1; c->widget = NULL; c->tbl = t; + c->id = 0; + c->flags = 0; } int @@ -1889,21 +1979,20 @@ char fmt[64], *sp = &fmt[0]; va_list ap; int n, rv; - AG_TableCell **cellsNew; + AG_TableCell **cNew; Strlcpy(fmt, fmtp, sizeof(fmt)); AG_ObjectLock(t); - if ((cellsNew = TryRealloc(t->cells, (t->m+1)*sizeof(AG_TableCell))) - == NULL) { + if ((cNew = TryRealloc(t->cells, (t->m+1)*sizeof(AG_TableCell))) == NULL) { goto fail; } - if ((cellsNew[t->m] = TryMalloc(t->n*sizeof(AG_TableCell))) == NULL) { - Free(cellsNew); + if ((cNew[t->m] = TryMalloc(t->n*sizeof(AG_TableCell))) == NULL) { + Free(cNew); goto fail; } - t->cells = cellsNew; + t->cells = cNew; va_start(ap, fmtp); for (n = 0; n < t->n; n++) { @@ -2116,7 +2205,7 @@ if (t->cols[n].name[0] == '\0') { continue; } - PrintCell(&t->cells[m][n], txt, sizeof(txt)); + AG_TablePrintCell(&t->cells[m][n], txt, sizeof(txt)); fputs(txt, f); fputc(sep, f); } @@ -2163,6 +2252,7 @@ Init(void *obj) { AG_Table *t = obj; + Uint i; WIDGET(t)->flags |= AG_WIDGET_FOCUSABLE| AG_WIDGET_UNFOCUSED_MOTION| @@ -2219,6 +2309,14 @@ t->wheelTicks = 0; SLIST_INIT(&t->popups); + t->nPrevBuckets = 256; + t->cPrev = Malloc(t->nPrevBuckets*sizeof(AG_TableBucket)); + for (i = 0; i < t->nPrevBuckets; i++) { + AG_TableBucket *tb = &t->cPrev[i]; + TAILQ_INIT(&tb->cells); + } + TAILQ_INIT(&t->cPrevList); + AG_SetEvent(t, "mouse-button-down", MouseButtonDown, NULL); AG_SetEvent(t, "mouse-button-up", MouseButtonUp, NULL); AG_SetEvent(t, "mouse-motion", MouseMotion, NULL); @@ -2258,23 +2356,38 @@ Destroy(void *obj) { AG_Table *t = obj; - AG_TablePopup *tp, *tpn; + AG_TablePopup *pop, *nPop; + AG_TableCell *c, *cNext; int i; - - for (tp = SLIST_FIRST(&t->popups); - tp != SLIST_END(&t->popups); - tp = tpn) { - tpn = SLIST_NEXT(tp, popups); - AG_ObjectDestroy(tp->menu); - Free(tp); + + /* Free the attached popup menus. */ + for (pop = SLIST_FIRST(&t->popups); + pop != SLIST_END(&t->popups); + pop = nPop) { + nPop = SLIST_NEXT(pop, popups); + AG_ObjectDestroy(pop->menu); + Free(pop); } - for (i = 0; i < t->n; i++) { - Free(t->cols[i].pool); + + /* Free the active cells. */ + for (i = 0; i < t->m; i++) { + Free(t->cells[i]); } + Free(t->cells); + + /* Free the backing store. */ + for (c = TAILQ_FIRST(&t->cPrevList); + c != TAILQ_END(&t->cPrevList); + c = cNext) { + cNext = TAILQ_NEXT(c, cells_list); + Free(c); + } + Free(t->cPrev); + + /* Free the columns. */ Free(t->cols); } - AG_WidgetClass agTableClass = { { "Agar(Widget:Table)", Index: table.h =================================================================== --- table.h (revision 8866) +++ table.h (working copy) @@ -8,9 +8,10 @@ #include -#define AG_TABLE_TXT_MAX 128 -#define AG_TABLE_FMT_MAX 16 -#define AG_TABLE_COL_NAME_MAX 48 +#define AG_TABLE_TXT_MAX 128 /* Length of fixed text cells */ +#define AG_TABLE_FMT_MAX 16 /* Length of cell specifier string */ +#define AG_TABLE_COL_NAME_MAX 48 /* Column name string */ +#define AG_TABLE_HASHBUF_MAX 64 /* Buffer used in hash function */ struct ag_table; @@ -84,8 +85,18 @@ int selected; /* Cell is selected */ int surface; /* Named of mapped surface */ struct ag_table *tbl; /* Back pointer to Table */ + Uint id; /* Optional user-specified ID */ + Uint flags; +#define AG_TABLE_CELL_NOCOMPARE 0x01 /* Ignore when comparing cells + against backing store. */ + AG_TAILQ_ENTRY(ag_table_cell) cells; /* In AG_TableBucket */ + AG_TAILQ_ENTRY(ag_table_cell) cells_list; /* In AG_Table */ } AG_TableCell; +typedef struct ag_table_bucket { + AG_TAILQ_HEAD_(ag_table_cell) cells; +} AG_TableBucket; + typedef struct ag_table_col { char name[AG_TABLE_COL_NAME_MAX]; int (*sortFn)(const void *, const void *); @@ -101,8 +112,6 @@ int wPct; /* Width (percent or -1) */ int x; /* Current position */ int surface; /* Text surface mapping */ - AG_TableCell *pool; /* Backing store */ - int mpool; /* Number of rows in backing store */ } AG_TableCol; typedef struct ag_table { @@ -129,8 +138,13 @@ columns */ int xOffs; /* Column display offset */ int mOffs; /* Row offset (for poll function) */ - AG_TableCol *cols; /* Column data */ - AG_TableCell **cells; /* Row data */ + + AG_TableCol *cols; /* Column data */ + AG_TableCell **cells; /* Current cell data (sorted rows) */ + AG_TableBucket *cPrev; /* Saved cells (value hash) */ + Uint nPrevBuckets; + AG_TAILQ_HEAD_(ag_table_cell) cPrevList; + int n; /* Number of columns */ int m; /* Number of rows */ int mVis; /* Maximum number of visible rows */ @@ -189,19 +203,15 @@ void AG_TableSetSelectionColor(AG_Table *, Uint8, Uint8, Uint8, Uint8); void AG_TableSetColumnAction(AG_Table *, Uint); -void AG_TableFreeCell(AG_Table *, AG_TableCell *); -int AG_TablePoolAdd(AG_Table *, int, int); -void AG_TablePoolFree(AG_Table *, int); - void AG_TableClear(AG_Table *); void AG_TableBegin(AG_Table *); void AG_TableEnd(AG_Table *); void AG_TableSort(AG_Table *); void AG_TableInitCell(AG_Table *, AG_TableCell *); -#define AG_TableCellSelected(t,m,n) ((t)->cells[m][n].selected) -#define AG_TableSelectCell(t,m,n) ((t)->cells[m][n].selected = 1) -#define AG_TableDeselectCell(t,m,n) ((t)->cells[m][n].selected = 0) +void AG_TablePrintCell(const AG_TableCell *, char *, size_t); +void AG_TableFreeCell(AG_Table *, AG_TableCell *); +void AG_TableForgetCells(AG_Table *); int AG_TableAddRow(AG_Table *, const char *, ...); void AG_TableSelectRow(AG_Table *, int); @@ -214,15 +224,53 @@ int (*)(const void *, const void *)); void AG_TableSelectAllCols(AG_Table *); void AG_TableDeselectAllCols(AG_Table *); -#define AG_TableSelectCol(t,n) do { (t)->cols[n].selected = 1; } while (0) -#define AG_TableDeselectCol(t,n) do { (t)->cols[n].selected = 0; } while (0) -#define AG_TableColSelected(t,n) ((t)->cols[n].selected) void AG_TableRedrawCells(AG_Table *); int AG_TableCompareCells(const AG_TableCell *, const AG_TableCell *); AG_MenuItem *AG_TableSetPopup(AG_Table *, int, int); int AG_TableSaveASCII(AG_Table *, FILE *, char); + +/* Return cell at [m,n]. */ +static __inline__ AG_TableCell * +AG_TableGetCell(AG_Table *t, int m, int n) +{ + return (&t->cells[m][n]); +} + +/* Cell selection control */ +static __inline__ int +AG_TableCellSelected(AG_Table *t, int m, int n) +{ + return (t->cells[m][n].selected); +} +static __inline__ void +AG_TableSelectCell(AG_Table *t, int m, int n) +{ + t->cells[m][n].selected = 1; +} +static __inline__ void +AG_TableDeselectCell(AG_Table *t, int m, int n) +{ + t->cells[m][n].selected = 0; +} + +/* Column selection control */ +static __inline__ int +AG_TableColSelected(AG_Table *t, int n) +{ + return (t->cols[n].selected); +} +static __inline__ void +AG_TableSelectCol(AG_Table *t, int n) +{ + t->cols[n].selected = 1; +} +static __inline__ void +AG_TableDeselectCol(AG_Table *t, int n) +{ + t->cols[n].selected = 0; +} __END_DECLS #include