/*
* Copyright (c) 2005-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 "table.h"
#include "primitive.h"
#include "cursors.h"
#include
#include
#define COLUMN_RESIZE_RANGE 10 /* Range in pixels for resize ctrls */
#define LAST_VISIBLE(t) ((t)->m - (t)->mVis + 1)
AG_Table *
AG_TableNew(void *parent, Uint flags)
{
AG_Table *t;
t = Malloc(sizeof(AG_Table));
AG_ObjectInit(t, &agTableClass);
t->flags |= flags;
if (flags & AG_TABLE_HFILL) { AG_ExpandHoriz(t); }
if (flags & AG_TABLE_VFILL) { AG_ExpandVert(t); }
AG_ObjectAttach(parent, t);
return (t);
}
AG_Table *
AG_TableNewPolled(void *parent, Uint flags, void (*fn)(AG_Event *),
const char *fmt, ...)
{
AG_Table *t;
t = AG_TableNew(parent, flags);
AG_ObjectLock(t);
t->flags |= AG_TABLE_POLL;
t->poll_ev = AG_SetEvent(t, "table-poll", fn, NULL);
AG_EVENT_GET_ARGS(t->poll_ev, fmt);
AG_ObjectUnlock(t);
return (t);
}
void
AG_TableSizeHint(AG_Table *t, int w, int nrows)
{
AG_ObjectLock(t);
if (w != -1) { t->wHint = w; }
if (nrows != -1) { t->hHint = nrows*agTextFontHeight; }
AG_ObjectUnlock(t);
}
void
AG_TableSetSelectionMode(AG_Table *t, enum ag_table_selmode mode)
{
AG_ObjectLock(t);
t->selMode = mode;
AG_ObjectUnlock(t);
}
void
AG_TableSetSelectionColor(AG_Table *t, Uint8 r, Uint8 g, Uint8 b, Uint8 a)
{
AG_ObjectLock(t);
t->selColor[0] = r;
t->selColor[1] = g;
t->selColor[2] = b;
t->selColor[3] = a;
AG_ObjectUnlock(t);
}
/* Change the set of recognized field separators (defaults to ":") */
void
AG_TableSetSeparator(AG_Table *t, const char *sep)
{
AG_ObjectLock(t);
t->sep = sep;
AG_ObjectUnlock(t);
}
/* Set column height in pixels */
void
AG_TableSetColHeight(AG_Table *t, int h)
{
AG_ObjectLock(t);
t->hCol = h;
AG_WindowUpdate(AG_ParentWindow(t));
AG_ObjectUnlock(t);
}
/* Set row height in pixels */
void
AG_TableSetRowHeight(AG_Table *t, int h)
{
AG_ObjectLock(t);
t->hRow = h;
AG_WindowUpdate(AG_ParentWindow(t));
AG_ObjectUnlock(t);
}
/* Specify the minimum allowed column width in pixels. */
void
AG_TableSetColMin(AG_Table *t, int w)
{
int n;
AG_ObjectLock(t);
t->wColMin = w;
for (n = 0; n < t->n; n++) {
AG_TableCol *tc = &t->cols[n];
if (tc->w < t->wColMin)
tc->w = t->wColMin;
}
AG_ObjectUnlock(t);
}
/*
* Specify a default column width in pixels to use in size requisition,
* for "%" and FILL columns.
*/
void
AG_TableSetDefaultColWidth(AG_Table *t, int w)
{
AG_ObjectLock(t);
t->wColDefault = w;
AG_ObjectUnlock(t);
}
/*
* If some column sizes were specified using percents, or use the FILL
* option, expand them to their effective pixel sizes. This is only done
* once, during initial sizing.
*/
static void
SizeColumns(AG_Table *t)
{
AG_TableCol *tc, *tcFill = NULL;
Uint n;
t->wTot = 0;
for (n = 0; n < t->n; n++) {
tc = &t->cols[n];
if (tc->wPct != -1) {
tc->w = tc->wPct*t->r.w/100;
t->wTot += tc->w;
tc->wPct = -1;
continue;
}
if (tc->flags & AG_TABLE_COL_FILL) {
tcFill = tc;
tcFill->flags &= ~(AG_TABLE_COL_FILL);
} else {
t->wTot += tc->w;
}
}
if (tcFill != NULL) {
tcFill->w = t->r.w;
for (n = 0; n < t->n; n++) {
tc = &t->cols[n];
if (tc != tcFill)
tcFill->w -= tc->w;
}
if (tcFill->w < t->wColMin) {
tcFill->w = t->wColMin;
}
t->wTot += tcFill->w;
}
}
/*
* Compute scrollbar values from the current geometry and visible
* row count. Table must be locked.
*/
static void
UpdateScrollbars(AG_Table *t)
{
AG_Variable *bValue, *bMax;
int *value, *max;
t->mVis = t->r.h/t->hRow;
if (t->r.h % t->hRow) { t->mVis++; }
if (t->vbar != NULL) {
bMax = AG_GetVariable(t->vbar, "max", &max);
bValue = AG_GetVariable(t->vbar, "value", &value);
if ((*max = LAST_VISIBLE(t)) < 0) {
*max = 0;
}
if (*value > *max) {
*value = *max;
} else if (*value < 0) {
*value = 0;
}
if (t->m > 0 && t->mVis > 0 && (t->mVis-1) < t->m) {
AG_ScrollbarSetBarSize(t->vbar,
(t->mVis-1)*(HEIGHT(t->vbar) - t->vbar->wButton*2) /
t->m);
} else {
AG_ScrollbarSetBarSize(t->vbar, -1);
}
AG_UnlockVariable(bValue);
AG_UnlockVariable(bMax);
}
if (t->hbar != NULL) {
SizeColumns(t);
if (t->wTot == 0 || t->wTot <= t->r.w || t->r.w == 0) {
AG_ScrollbarSetBarSize(t->hbar, -1);
} else {
AG_ScrollbarSetBarSize(t->hbar,
t->r.w*(WIDTH(t->hbar) - t->hbar->wButton*2) /
t->wTot);
}
bValue = AG_GetVariable(t->hbar, "value", &value);
if ((t->wTot - t->r.w - *value) < 0) {
*value = MAX(0, t->wTot - t->r.w);
}
AG_UnlockVariable(bValue);
}
}
/* Set the effective position of all embedded widgets in the table. */
static void
UpdateEmbeddedWidgets(AG_Table *t)
{
AG_SizeAlloc wa;
AG_Widget *wt;
AG_Rect rd;
AG_TableCol *col;
AG_TableCell *c;
Uint m, n;
int update = 0;
rd.h = t->hRow;
for (n = 0, rd.x = -t->xOffs;
n < t->n;
n++) {
col = &t->cols[n];
rd.w = col->w;
rd.y = t->hCol - t->mOffs*t->hRow;
for (m = 0;
m < t->m;
m++) {
c = &t->cells[m][n];
if (c->type != AG_CELL_WIDGET) {
continue;
}
wt = c->data.p;
if (wt->x != rd.x || wt->y != rd.y ||
wt->w != rd.w || wt->h != rd.h) {
wa.x = rd.x;
wa.y = rd.y;
wa.w = rd.w;
wa.h = rd.h;
AG_WidgetSizeAlloc(wt, &wa);
update++;
}
/*
* Adjust sensitivity rectangle if widget is
* partially visible.
*/
wt->rSens.w = (rd.x+rd.w > t->r.w) ?
(t->r.w - rd.x - 4) : rd.w;
wt->rSens.x2 = wt->rSens.x1 + wt->rSens.w;
wt->rSens.h = (rd.y+rd.h > t->r.h+t->hCol) ?
(t->r.h + t->hCol - rd.y - 4) : rd.h;
wt->rSens.y2 = wt->rSens.y1 + wt->rSens.h;
rd.y += t->hRow;
}
rd.x += col->w;
}
/* Apply any changes to widget size allocations. */
if (update) {
AG_WidgetUpdateCoords(t,
WIDGET(t)->rView.x1,
WIDGET(t)->rView.y1);
}
}
static void
SizeRequest(void *obj, AG_SizeReq *r)
{
AG_Table *t = obj;
AG_SizeReq rBar;
int n;
r->h = t->hHint;
if (t->wHint != -1) {
r->w = t->wHint;
} else {
for (n = 0; n < t->n; n++) {
AG_TableCol *tc = &t->cols[n];
if (tc->flags & AG_TABLE_COL_FILL ||
tc->wPct != -1) {
r->w += t->wColDefault;
} else {
r->w += tc->w;
}
}
}
if (t->vbar != NULL) {
AG_WidgetSizeReq(t->vbar, &rBar);
r->w += rBar.w;
}
if (t->hbar != NULL) {
AG_WidgetSizeReq(t->hbar, &rBar);
r->h += rBar.h;
}
}
static int
SizeAllocate(void *obj, const AG_SizeAlloc *a)
{
AG_Table *t = obj;
AG_SizeReq rBar;
AG_SizeAlloc aBar;
t->r.w = a->w;
t->r.h = a->h - t->hCol;
t->r.y = t->hCol;
if (t->r.h <= 0)
return (-1);
if (t->vbar) {
AG_WidgetSizeReq(t->vbar, &rBar);
if (rBar.w > a->w/2) { rBar.w = a->w/2; }
aBar.x = a->w - rBar.w;
aBar.y = 0;
aBar.w = rBar.w;
aBar.h = a->h - rBar.w;
AG_WidgetSizeAlloc(t->vbar, &aBar);
t->r.w -= WIDTH(t->vbar);
}
if (t->hbar) {
AG_WidgetSizeReq(t->hbar, &rBar);
if (rBar.h > a->h/2) { rBar.h = a->h/2; }
aBar.x = 0;
aBar.y = a->h - rBar.h;
aBar.w = a->w - (t->vbar ? WIDTH(t->vbar) : 0);
aBar.h = rBar.h;
AG_WidgetSizeAlloc(t->hbar, &aBar);
t->r.h -= HEIGHT(t->hbar);
}
SizeColumns(t);
UpdateScrollbars(t);
return (0);
}
static __inline__ void
PrintCell(AG_Table *t, AG_TableCell *c, char *buf, size_t bufsz)
{
switch (c->type) {
case AG_CELL_INT:
case AG_CELL_UINT:
Snprintf(buf, bufsz, c->fmt, c->data.i);
break;
case AG_CELL_LONG:
case AG_CELL_ULONG:
Snprintf(buf, bufsz, c->fmt, c->data.l);
break;
case AG_CELL_PINT:
Snprintf(buf, bufsz, c->fmt, *(int *)c->data.p);
break;
case AG_CELL_PUINT:
Snprintf(buf, bufsz, c->fmt, *(Uint *)c->data.p);
break;
case AG_CELL_PLONG:
Snprintf(buf, bufsz, c->fmt, *(long *)c->data.p);
break;
case AG_CELL_PULONG:
Snprintf(buf, bufsz, c->fmt, *(Ulong *)c->data.p);
break;
case AG_CELL_FLOAT:
Snprintf(buf, bufsz, c->fmt, (float)c->data.f);
break;
case AG_CELL_PFLOAT:
Snprintf(buf, bufsz, c->fmt, *(float *)c->data.p);
break;
case AG_CELL_DOUBLE:
Snprintf(buf, bufsz, c->fmt, c->data.f);
break;
case AG_CELL_PDOUBLE:
Snprintf(buf, bufsz, c->fmt, *(double *)c->data.p);
break;
#ifdef HAVE_64BIT
case AG_CELL_INT64:
case AG_CELL_UINT64:
Snprintf(buf, bufsz, c->fmt, c->data.f);
break;
case AG_CELL_PUINT64:
Snprintf(buf, bufsz, c->fmt, *(Uint64 *)c->data.p);
break;
case AG_CELL_PINT64:
Snprintf(buf, bufsz, c->fmt, *(Sint64 *)c->data.p);
break;
#endif
case AG_CELL_PUINT32:
Snprintf(buf, bufsz, c->fmt, *(Uint32 *)c->data.p);
break;
case AG_CELL_PSINT32:
Snprintf(buf, bufsz, c->fmt, *(Sint32 *)c->data.p);
break;
case AG_CELL_PUINT16:
Snprintf(buf, bufsz, c->fmt, *(Uint16 *)c->data.p);
break;
case AG_CELL_PSINT16:
Snprintf(buf, bufsz, c->fmt, *(Sint16 *)c->data.p);
break;
case AG_CELL_PUINT8:
Snprintf(buf, bufsz, c->fmt, *(Uint8 *)c->data.p);
break;
case AG_CELL_PSINT8:
Snprintf(buf, bufsz, c->fmt, *(Sint8 *)c->data.p);
break;
case AG_CELL_STRING:
Strlcpy(buf, c->data.s, bufsz);
break;
case AG_CELL_PSTRING:
Strlcpy(buf, (char *)c->data.p, bufsz);
break;
case AG_CELL_FN_TXT:
c->fnTxt(c->data.p, buf, bufsz);
break;
case AG_CELL_FN_SU:
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);
}
break;
case AG_CELL_WIDGET:
Strlcpy(buf, "", bufsz);
break;
}
}
static __inline__ void
DrawCell(AG_Table *t, AG_TableCell *c, AG_Rect *rd)
{
char txt[AG_TABLE_TXT_MAX];
if (c->surface >= 0) {
if (t->flags & AG_TABLE_REDRAW_CELLS) {
AG_WidgetUnmapSurface(t, c->surface);
} else {
goto blit;
}
}
switch (c->type) {
case AG_CELL_STRING: /* Avoid copy */
AG_TextColor(TEXT_COLOR);
c->surface = AG_WidgetMapSurface(t, AG_TextRender(c->data.s));
goto blit;
case AG_CELL_PSTRING: /* Avoid copy */
AG_TextColor(TEXT_COLOR);
c->surface = AG_WidgetMapSurface(t, AG_TextRender((char *)
c->data.p));
goto blit;
case AG_CELL_FN_SU:
c->surface = AG_WidgetMapSurface(t,
c->fnSu(c->data.p, rd->x, rd->y));
goto blit;
case AG_CELL_WIDGET:
if (WIDGET_OPS(c->data.p)->draw != NULL) {
AG_WidgetDraw(c->data.p);
}
c->surface = -1;
return;
case AG_CELL_NULL:
if (c->fmt[0] != '\0') {
AG_TextColor(TEXT_COLOR);
c->surface = AG_WidgetMapSurface(t,
AG_TextRender(c->fmt));
goto blit;
} else {
return;
}
break;
default:
PrintCell(t, c, txt, sizeof(txt));
break;
}
AG_TextColor(TEXT_COLOR);
c->surface = AG_WidgetMapSurface(t, AG_TextRender(txt));
blit:
AG_WidgetBlitSurface(t, c->surface,
rd->x,
rd->y + (t->hRow>>1) - (WSURFACE(t,c->surface)->h>>1));
}
static void
ScrollToSelection(AG_Table *t)
{
Uint m;
int offs;
if (t->n < 1) {
return;
}
for (m = 0; m < t->m; m++) {
if (!t->cells[m][0].selected) {
continue;
}
if (t->mOffs > m) {
AG_SetInt(t->vbar, "value", m);
} else {
offs = m - t->mVis + 2;
if (offs < 0) { offs = 0; }
AG_SetInt(t->vbar, "value", offs);
}
return;
}
}
#if 0
static void
SelectionToScroll(AG_Table *t)
{
Uint m, n;
for (n = 0; n < t->n; n++) {
for (m = 0; m < t->m; m++) {
if (!t->cells[m][n].selected) {
continue;
}
AG_TableDeselectRow(t, m);
if (m < t->mOffs) {
AG_TableSelectRow(t, t->mOffs);
} else {
AG_TableSelectRow(t, t->mOffs + t->mVis - 2);
}
return;
}
}
}
#endif
static int
SelectionVisible(AG_Table *t)
{
int n, m, mOffs;
int x, y;
mOffs = AG_GetInt(t->vbar, "value");
if (t->poll_ev != NULL) {
t->poll_ev->handler(t->poll_ev);
}
for (n = 0, x = -t->xOffs;
n < t->n && x < t->r.w;
n++) {
AG_TableCol *col = &t->cols[n];
int cw;
if (col->w <= 0) {
continue;
}
cw = ((x + col->w) < t->r.w) ? col->w: t->r.w - x;
for (m = mOffs, y = t->hCol;
m < MIN(t->m, mOffs+t->mVis) && (y < t->hCol+t->r.h);
m++) {
if (t->cells[m][n].selected &&
m < (mOffs + t->mVis - 1)) {
return (1);
}
y += t->hRow;
}
x += col->w;
}
return (0);
}
static void
Draw(void *obj)
{
AG_Table *t = obj;
AG_Rect rCol, rCell;
Uint n, m;
STYLE(t)->TableBackground(t, t->r);
AG_WidgetDraw(t->vbar);
AG_WidgetDraw(t->hbar);
t->mOffs = AG_GetInt(t->vbar, "value");
if (t->poll_ev != NULL) {
t->poll_ev->handler(t->poll_ev);
}
if (t->flags & AG_TABLE_WIDGETS)
UpdateEmbeddedWidgets(t);
rCol.y = 0;
rCol.h = t->hCol + t->r.h - 2;
rCell.h = t->hRow;
for (n = 0, rCell.x = -t->xOffs;
n < t->n && rCell.x < t->r.w;
n++) {
AG_TableCol *col = &t->cols[n];
if (col->w <= 0) {
continue;
}
rCell.w = col->w;
rCol.w = ((rCell.x + col->w) < t->r.w) ?
col->w : (t->r.w - rCell.x);
rCol.x = rCell.x;
/* Column header and separator */
if (rCol.x > 0 && rCol.x < t->r.w) {
AG_DrawLineV(t,
rCol.x - 1,
t->hCol - 1,
rCol.h,
AG_COLOR(TABLE_LINE_COLOR));
}
AG_PushClipRect(t, rCol);
STYLE(t)->TableColumnHeaderBackground(t, n,
AG_RECT(rCol.x, 0, rCol.w, t->hCol),
col->selected);
/* Column header label */
if (col->surface != -1) {
AG_WidgetBlitSurface(t, col->surface,
rCell.x + col->w/2 - WSURFACE(t,col->surface)->w/2,
t->hCol/2 - WSURFACE(t,col->surface)->h/2);
}
/* Rows of this column */
for (m = t->mOffs, rCell.y = t->hCol;
m < t->m && (rCell.y < rCol.h);
m++) {
AG_TableCell *c = &t->cells[m][n];
AG_DrawLineH(t, 0, t->r.w, rCell.y,
AG_COLOR(TABLE_LINE_COLOR));
DrawCell(t, c, &rCell);
if (c->selected) {
AG_DrawRectBlended(t, rCell, t->selColor,
AG_ALPHA_SRC);
}
rCell.y += t->hRow;
}
AG_DrawLineH(t, 0, t->r.w, rCell.y,
AG_COLOR(TABLE_LINE_COLOR));
/* Indicate column selection. */
if ((t->flags & AG_TABLE_HIGHLIGHT_COLS) && col->selected) {
STYLE(t)->TableSelectedColumnBackground(t, n, rCol);
}
AG_PopClipRect();
rCell.x += col->w;
}
if (rCell.x > 0 &&
rCell.x < t->r.w) {
AG_DrawLineV(t,
rCell.x - 1,
t->hCol - 1,
rCol.h,
AG_COLOR(TABLE_LINE_COLOR));
}
t->flags &= ~(AG_TABLE_REDRAW_CELLS);
}
AG_MenuItem *
AG_TableSetPopup(AG_Table *t, int m, int n)
{
AG_TablePopup *tp;
AG_MenuItem *rv;
AG_ObjectLock(t);
SLIST_FOREACH(tp, &t->popups, popups) {
if (tp->m == m && tp->n == n) {
AG_MenuItemFree(tp->item);
AG_ObjectUnlock(t);
return (tp->item);
}
}
tp = Malloc(sizeof(AG_TablePopup));
tp->m = m;
tp->n = n;
tp->panel = NULL;
tp->menu = AG_MenuNew(NULL, 0);
tp->item = tp->menu->root; /* XXX redundant */
SLIST_INSERT_HEAD(&t->popups, tp, popups);
rv = tp->item;
AG_ObjectUnlock(t);
return (rv);
}
void
AG_TableSetRowDblClickFn(AG_Table *t, AG_EventFn ev, const char *fmt, ...)
{
AG_ObjectLock(t);
t->dblClickRowEv = AG_SetEvent(t, NULL, ev, NULL);
AG_EVENT_GET_ARGS(t->dblClickRowEv, fmt);
AG_ObjectUnlock(t);
}
void
AG_TableSetColDblClickFn(AG_Table *t, AG_EventFn ev, const char *fmt, ...)
{
AG_ObjectLock(t);
t->dblClickColEv = AG_SetEvent(t, NULL, ev, NULL);
AG_EVENT_GET_ARGS(t->dblClickColEv, fmt);
AG_ObjectUnlock(t);
}
void
AG_TableSetCellDblClickFn(AG_Table *t, AG_EventFn ev, const char *fmt, ...)
{
AG_ObjectLock(t);
t->dblClickCellEv = AG_SetEvent(t, NULL, ev, NULL);
AG_EVENT_GET_ARGS(t->dblClickCellEv, fmt);
AG_ObjectUnlock(t);
}
void
AG_TableRedrawCells(AG_Table *t)
{
AG_ObjectLock(t);
t->flags |= AG_TABLE_REDRAW_CELLS;
AG_ObjectUnlock(t);
}
/* Table must be locked. */
void
AG_TableFreeCell(AG_Table *t, AG_TableCell *c)
{
if (c->widget != NULL) {
AG_ObjectDetach(c->widget);
AG_ObjectDestroy(c->widget);
}
if (c->surface >= 0)
AG_WidgetUnmapSurface(t, c->surface);
}
/* Table must be locked. */
int
AG_TablePoolAdd(AG_Table *t, Uint m, Uint n)
{
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++);
}
/* Table must be locked. */
void
AG_TablePoolFree(AG_Table *t, Uint n)
{
AG_TableCol *tc = &t->cols[n];
Uint m;
for (m = 0; m < tc->mpool; m++) {
AG_TableFreeCell(t, &tc->pool[m]);
}
Free(tc->pool);
tc->pool = NULL;
tc->mpool = 0;
}
/*
* Clear the items on the table and save the selection state. The function
* returns with the table locked.
*/
void
AG_TableBegin(AG_Table *t)
{
Uint m, n;
AG_ObjectLock(t);
/* Copy the existing cells to the column pools and free the table. */
for (m = 0; m < t->m; m++) {
for (n = 0; n < t->n; n++) {
AG_TablePoolAdd(t, m, n);
}
Free(t->cells[m]);
}
Free(t->cells);
t->cells = NULL;
t->m = 0;
t->flags &= ~(AG_TABLE_WIDGETS);
AG_SetInt(t->vbar, "max", 0);
}
int
AG_TableCompareCells(const AG_TableCell *c1, const AG_TableCell *c2)
{
if (c1->type != c2->type || strcmp(c1->fmt, c2->fmt) != 0) {
return (1);
}
switch (c1->type) {
case AG_CELL_STRING:
return (strcmp(c1->data.s, c2->data.s));
case AG_CELL_PSTRING:
return (strcmp((char *)c1->data.p, (char *)c2->data.p));
case AG_CELL_INT:
case AG_CELL_UINT:
return (c1->data.i - c2->data.i);
case AG_CELL_LONG:
case AG_CELL_ULONG:
return (c1->data.l - c2->data.l);
case AG_CELL_FLOAT:
case AG_CELL_DOUBLE:
return (c1->data.f != c2->data.f);
#ifdef HAVE_64BIT
case AG_CELL_INT64:
case AG_CELL_UINT64:
return (c1->data.u64 - c2->data.u64);
case AG_CELL_PUINT64:
case AG_CELL_PINT64:
return (*(Uint64 *)c1->data.p - *(Uint64 *)c2->data.p);
#endif
case AG_CELL_PULONG:
case AG_CELL_PLONG:
return (*(long *)c1->data.p - *(long *)c2->data.p);
case AG_CELL_PUINT:
case AG_CELL_PINT:
return (*(int *)c1->data.p - *(int *)c2->data.p);
case AG_CELL_PUINT8:
case AG_CELL_PSINT8:
return (*(Uint8 *)c1->data.p - *(Uint8 *)c2->data.p);
case AG_CELL_PUINT16:
case AG_CELL_PSINT16:
return (*(Uint16 *)c1->data.p - *(Uint16 *)c2->data.p);
case AG_CELL_PUINT32:
case AG_CELL_PSINT32:
return (*(Uint32 *)c1->data.p - *(Uint32 *)c2->data.p);
case AG_CELL_PFLOAT:
return (*(float *)c1->data.p - *(float *)c2->data.p);
case AG_CELL_PDOUBLE:
return (*(double *)c1->data.p - *(double *)c2->data.p);
case AG_CELL_POINTER:
return (c1->data.p != c2->data.p);
case AG_CELL_FN_SU:
return ((c1->data.p != c2->data.p) || (c1->fnSu != c2->fnSu));
case AG_CELL_FN_TXT:
{
char b1[AG_TABLE_TXT_MAX];
char b2[AG_TABLE_TXT_MAX];
c1->fnTxt(c1->data.p, b1, sizeof(b1));
c2->fnTxt(c2->data.p, b2, sizeof(b2));
return (strcmp(b1, b2));
}
case AG_CELL_WIDGET:
/* XXX TODO hooks */
return (1);
case AG_CELL_NULL:
return (0);
}
return (1);
}
/*
* Restore the selection state and recover the surfaces of matching
* items in the column pool. Unlock the table.
*/
void
AG_TableEnd(AG_Table *t)
{
Uint n, m;
for (n = 0; n < t->n; n++) {
AG_TableCol *tc = &t->cols[n];
for (m = 0; m < tc->mpool; m++) {
AG_TableCell *cPool = &tc->pool[m];
if (m < t->m) {
AG_TableCell *cDst = &t->cells[m][n];
if (AG_TableCompareCells(cDst, cPool) == 0) {
cDst->surface = cPool->surface;
cDst->selected = cPool->selected;
cPool->surface = -1;
}
}
AG_TableFreeCell(t, cPool);
}
Free(tc->pool);
tc->pool = NULL;
tc->mpool = 0;
}
if (t->r.h > 0 && t->r.w > 0) {
UpdateScrollbars(t);
}
AG_ObjectUnlock(t);
}
/* Return true if multiple item selection is enabled. */
static __inline__ int
SelectingMultiple(AG_Table *t)
{
return ((t->flags & AG_TABLE_MULTITOGGLE) ||
((t->flags & AG_TABLE_MULTI) && SDL_GetModState() & KMOD_CTRL));
}
/* Return true if a range of items are being selected. */
static __inline__ int
SelectingRange(AG_Table *t)
{
return ((t->flags & AG_TABLE_MULTI) &&
(SDL_GetModState() & KMOD_SHIFT));
}
/* Display the popup menu. */
static void
ShowPopup(AG_TablePopup *tp)
{
int x, y;
AG_MouseGetState(&x, &y);
if (tp->panel != NULL) {
AG_MenuCollapse(tp->menu, tp->item);
tp->panel = NULL;
}
tp->menu->itemSel = tp->item;
tp->panel = AG_MenuExpand(tp->menu, tp->item, x+4, y+4);
}
/* Right click on a column header; display the column's popup menu. */
static void
ColumnRightClick(AG_Table *t, int px)
{
Uint n;
int cx;
int x = px - (COLUMN_RESIZE_RANGE/2);
for (n = 0, cx = -t->xOffs;
n < t->n;
n++) {
AG_TableCol *tc = &t->cols[n];
int x2 = cx+tc->w;
AG_TablePopup *tp;
if (x > cx && x < x2) {
SLIST_FOREACH(tp, &t->popups, popups) {
if (tp->m == -1 && tp->n == n) {
ShowPopup(tp);
return;
}
}
}
cx += tc->w;
}
}
/* Left click on a column header; resize or set the sorting mode. */
static void
ColumnLeftClick(AG_Table *t, int px)
{
int x = px - (COLUMN_RESIZE_RANGE/2), x1;
int multi = SelectingMultiple(t);
Uint n;
for (n = 0, x1 = -t->xOffs;
n < t->n;
n++) {
AG_TableCol *tc = &t->cols[n];
int x2 = x1+tc->w;
if (x > x1 && x < x2) {
if ((x2 - x) < COLUMN_RESIZE_RANGE) {
if (t->nResizing == -1) {
t->nResizing = n;
}
} else {
if (multi) {
tc->selected = !tc->selected;
} else {
tc->selected = 1;
}
if (t->dblClickedCol != -1 &&
t->dblClickedCol == n) {
AG_CancelEvent(t,
"dblclick-col-expire");
if (t->dblClickColEv != NULL) {
AG_PostEvent(NULL, t,
t->dblClickColEv->name,
"%i", n);
}
AG_PostEvent(NULL, t,
"table-dblclick-col", "%i", n);
t->dblClickedCol = -1;
} else {
t->dblClickedCol = n;
AG_SchedEvent(NULL, t,
agMouseDblclickDelay,
"dblclick-col-expire", NULL);
}
goto cont;
}
}
if (!multi)
tc->selected = 0;
cont:
x1 += tc->w;
}
}
/* Process left click on a cell. */
static void
CellLeftClick(AG_Table *t, int mc, int x)
{
AG_TableCell *c;
AG_TableCol *tc;
Uint m, n, i, j, nc;
for (nc=0; nc < t->n; nc++) {
tc = &t->cols[nc];
if ((x + t->xOffs) > tc->x &&
(x + t->xOffs) < tc->x+tc->w)
break;
}
if (nc == t->n) { nc = t->n-1; }
switch (t->selMode) {
case AG_TABLE_SEL_ROWS:
if (SelectingRange(t)) {
for (m=0; m < t->m; m++) {
if (AG_TableRowSelected(t,m))
break;
}
if (m == t->m) {
break;
}
if (m < mc) {
for (i=m; i <= mc; i++)
AG_TableSelectRow(t, i);
} else if (m > mc) {
for (i=mc; i <= m; i++)
AG_TableSelectRow(t, i);
} else {
AG_TableSelectRow(t, mc);
}
} else if (SelectingMultiple(t)) {
for (n=0; n < t->n; n++) {
c = &t->cells[mc][n];
c->selected = !c->selected;
}
} else {
for (m=0; m < t->m; m++) {
for (n=0; n < t->n; n++) {
c = &t->cells[m][n];
c->selected = ((int)m == mc);
}
}
if (t->dblClickedRow != -1 &&
t->dblClickedRow == mc) {
AG_CancelEvent(t, "dblclick-row-expire");
if (t->dblClickRowEv != NULL) {
AG_PostEvent(NULL, t,
t->dblClickRowEv->name,
"%i", mc);
}
AG_PostEvent(NULL, t,
"table-dblclick-row", "%i", mc);
t->dblClickedRow = -1;
} else {
t->dblClickedRow = mc;
AG_SchedEvent(NULL, t, agMouseDblclickDelay,
"dblclick-row-expire", NULL);
}
}
break;
case AG_TABLE_SEL_CELLS:
if (SelectingRange(t)) {
for (m=0, n=0; m < t->m; m++) {
for (n=0; n < t->n; n++) {
if (AG_TableCellSelected(t,m,n))
break;
}
if (n == t->n)
break;
}
if (m == t->m) {
break;
}
if (m < mc && n < nc) {
for (i=n; i <= nc; i++)
for (j = m; j <= mc; j++)
AG_TableSelectCell(t, j,i);
} else if (m > mc && n > nc) {
for (i=nc; i <= n; i++)
for (j = mc; j <= m; j++)
AG_TableSelectCell(t, j,i);
} else {
AG_TableSelectCell(t, mc, nc);
}
} else if (SelectingMultiple(t)) {
c = &t->cells[mc][nc];
c->selected = !c->selected;
} else {
for (m=0; m < t->m; m++) {
for (n=0; n < t->n; n++) {
c = &t->cells[m][n];
c->selected = ((int)m == mc) &&
((int)n == nc);
}
}
if (t->dblClickedCell != -1 &&
t->dblClickedCell == mc) {
AG_CancelEvent(t, "dblclick-cell-expire");
if (t->dblClickCellEv != NULL) {
AG_PostEvent(NULL, t,
t->dblClickCellEv->name,
"%i", mc);
}
AG_PostEvent(NULL, t,
"table-dblclick-cell", "%i,%i", mc, nc);
t->dblClickedCell = -1;
} else {
t->dblClickedRow = mc;
AG_SchedEvent(NULL, t, agMouseDblclickDelay,
"dblclick-cell-expire", NULL);
}
}
break;
case AG_TABLE_SEL_COLS:
if (SelectingRange(t)) {
for (n=0; n < t->n; n++) {
if (AG_TableColSelected(t,n))
break;
}
if (n == t->n) {
break;
}
if (n < nc) {
for (i=n; i <= nc; i++)
AG_TableSelectCol(t, i);
} else if (n > nc) {
for (i=nc; i <= n; i++)
AG_TableSelectCol(t, i);
} else {
AG_TableSelectCol(t, nc);
}
} else if (SelectingMultiple(t)) {
for (n=0; n < t->n; n++) {
tc = &t->cols[n];
tc->selected = !tc->selected;
}
} else {
for (n=0; n < t->n; n++) {
tc = &t->cols[n];
tc->selected = ((int)n == nc);
}
if (t->dblClickedCol != -1 &&
t->dblClickedCol == nc) {
AG_CancelEvent(t, "dblclick-col-expire");
if (t->dblClickColEv != NULL) {
AG_PostEvent(NULL, t,
t->dblClickColEv->name,
"%i", nc);
}
AG_PostEvent(NULL, t,
"table-dblclick-col", "%i", nc);
t->dblClickedCol = -1;
} else {
t->dblClickedCol = nc;
AG_SchedEvent(NULL, t, agMouseDblclickDelay,
"dblclick-col-expire", NULL);
}
}
break;
}
}
/* Right click on cell; show the cell's popup menu. */
static void
CellRightClick(AG_Table *t, int m, int px)
{
int x = px - (COLUMN_RESIZE_RANGE/2), cx;
Uint n;
for (n = 0, cx = -t->xOffs;
n < t->n;
n++) {
AG_TableCol *tc = &t->cols[n];
AG_TablePopup *tp;
if (x < cx ||
x > cx+tc->w) {
continue;
}
SLIST_FOREACH(tp, &t->popups, popups) {
if ((tp->m == m || tp->m == -1) &&
(tp->n == n || tp->n == -1)) {
ShowPopup(tp);
return;
}
}
cx += tc->w;
}
}
/* Cursor is over column header? */
static __inline__ int
OverColumnHeader(AG_Table *t, int y)
{
return (y <= t->hCol);
}
/* Cursor is over column resize control? */
static __inline__ int
OverColumnResizeControl(AG_Table *t, int px)
{
int x = px - (COLUMN_RESIZE_RANGE/2);
Uint n;
int cx;
if (px < 0 || px > t->r.w) {
return (0);
}
for (n = 0, cx = -t->xOffs;
n < t->n;
n++) {
int x2 = cx + t->cols[n].w;
if (x > cx && x < x2) {
if ((x2 - x) < COLUMN_RESIZE_RANGE)
return (1);
}
cx += t->cols[n].w;
}
return (0);
}
/* Return the row at the given y-coordinate. */
static __inline__ int
RowAtY(AG_Table *t, int y)
{
int m;
m = AG_GetInt(t->vbar, "value");
if (y > t->hCol) {
m += (y - t->hCol)/t->hRow;
}
if (m < 0) { m = 0; }
if (m >= (int)t->m) { m = (t->m > 0) ? ((int)t->m - 1) : 0; }
return (m);
}
static void
DecrementSelection(AG_Table *t, int inc)
{
int m;
if (t->m < 1) {
return;
}
for (m = 0; m < t->m; m++) {
if (AG_TableRowSelected(t,m)) {
m -= inc;
if (m < 0) { m = 0; }
break;
}
}
if (m < t->m) {
AG_TableDeselectAllRows(t);
AG_TableSelectRow(t, m);
} else {
AG_TableSelectRow(t, 0);
}
if (!SelectionVisible(t))
ScrollToSelection(t);
}
static void
IncrementSelection(AG_Table *t, int inc)
{
int m;
if (t->m < 1) {
return;
}
for (m = t->m-1; m >= 0; m--) {
if (AG_TableRowSelected(t,m)) {
m += inc;
if (m >= t->m) { m = t->m-1; }
break;
}
}
if (m >= 0) {
AG_TableDeselectAllRows(t);
AG_TableSelectRow(t, m);
} else {
AG_TableSelectRow(t, 0);
}
if (!SelectionVisible(t))
ScrollToSelection(t);
}
static void
MouseButtonDown(AG_Event *event)
{
AG_Table *t = AG_SELF();
int button = AG_INT(1);
int x = AG_INT(2);
int y = AG_INT(3);
int m;
switch (button) {
case SDL_BUTTON_WHEELUP:
{
AG_Variable *offsb;
int *offs;
offsb = AG_GetVariable(t->vbar, "value", &offs);
(*offs) -= AG_WidgetScrollDelta(&t->wheelTicks);
if (*offs < 0) {
*offs = 0;
}
AG_UnlockVariable(offsb);
}
break;
case SDL_BUTTON_WHEELDOWN:
{
AG_Variable *offsb;
int *offs;
offsb = AG_GetVariable(t->vbar, "value", &offs);
(*offs) += AG_WidgetScrollDelta(&t->wheelTicks);
if (*offs > LAST_VISIBLE(t)) {
*offs = LAST_VISIBLE(t);
}
AG_UnlockVariable(offsb);
}
break;
case SDL_BUTTON_LEFT:
if (OverColumnHeader(t, y)) {
ColumnLeftClick(t, x);
break;
}
if (t->m == 0 || t->n == 0) {
goto out;
}
m = RowAtY(t, y);
CellLeftClick(t, m, x);
break;
case SDL_BUTTON_RIGHT:
if (OverColumnHeader(t, y)) {
ColumnRightClick(t, x);
break;
}
if (t->m == 0 || t->n == 0) {
goto out;
}
m = RowAtY(t, y);
CellRightClick(t, m, x);
break;
default:
break;
}
out:
AG_WidgetFocus(t);
}
static void
MouseButtonUp(AG_Event *event)
{
AG_Table *t = AG_SELF();
int button = AG_INT(1);
switch (button) {
case SDL_BUTTON_LEFT:
if (t->nResizing >= 0) {
t->nResizing = -1;
}
break;
}
}
static void
KeyDown(AG_Event *event)
{
AG_Table *t = AG_SELF();
int keysym = AG_INT(1);
switch (keysym) {
case SDLK_UP:
DecrementSelection(t, 1);
AG_DelTimeout(t, &t->incTo);
AG_ScheduleTimeout(t, &t->decTo, agKbdDelay);
break;
case SDLK_DOWN:
IncrementSelection(t, 1);
AG_DelTimeout(t, &t->decTo);
AG_ScheduleTimeout(t, &t->incTo, agKbdDelay);
break;
case SDLK_PAGEUP:
DecrementSelection(t, agPageIncrement);
AG_DelTimeout(t, &t->incTo);
AG_ScheduleTimeout(t, &t->decTo, agKbdDelay);
break;
case SDLK_PAGEDOWN:
IncrementSelection(t, agPageIncrement);
AG_DelTimeout(t, &t->decTo);
AG_ScheduleTimeout(t, &t->incTo, agKbdDelay);
break;
}
}
static void
MouseMotion(AG_Event *event)
{
AG_Table *t = AG_SELF();
int x = AG_INT(1);
int y = AG_INT(2);
int xrel = AG_INT(3);
if (x < 0 || y < 0 || x >= WIDTH(t) || y >= HEIGHT(t))
return;
if (t->nResizing >= 0 && (Uint)t->nResizing < t->n) {
AG_TableCol *tc = &t->cols[t->nResizing];
if ((tc->w += xrel) < t->wColMin) {
tc->w = t->wColMin;
}
SizeColumns(t);
if (t->r.h > 0 && t->r.w > 0) {
UpdateScrollbars(t);
}
AG_SetCursor(AG_HRESIZE_CURSOR);
} else {
if (OverColumnHeader(t, y) &&
OverColumnResizeControl(t, x))
AG_SetCursor(AG_HRESIZE_CURSOR);
}
}
static void
KeyUp(AG_Event *event)
{
AG_Table *t = AG_SELF();
int keysym = AG_INT(1);
switch (keysym) {
case SDLK_UP:
case SDLK_PAGEUP:
AG_DelTimeout(t, &t->decTo);
break;
case SDLK_DOWN:
case SDLK_PAGEDOWN:
AG_DelTimeout(t, &t->incTo);
break;
}
}
static void
LostFocus(AG_Event *event)
{
AG_Table *t = AG_SELF();
AG_DelTimeout(t, &t->incTo);
AG_DelTimeout(t, &t->decTo);
AG_CancelEvent(t, "dblclick-row-expire");
AG_CancelEvent(t, "dblclick-col-expire");
AG_CancelEvent(t, "dblclick-cell-expire");
if (t->nResizing >= 0)
t->nResizing = -1;
}
int
AG_TableRowSelected(AG_Table *t, Uint m)
{
Uint n;
AG_ObjectLock(t);
if (m >= t->m) {
goto out;
}
for (n = 0; n < t->n; n++) {
if (t->cells[m][n].selected) {
AG_ObjectUnlock(t);
return (1);
}
}
out:
AG_ObjectUnlock(t);
return (0);
}
void
AG_TableSelectRow(AG_Table *t, Uint m)
{
Uint n;
AG_ObjectLock(t);
if (m < t->m) {
for (n = 0; n < t->n; n++) {
t->cells[m][n].selected = 1;
}
}
AG_ObjectUnlock(t);
}
void
AG_TableDeselectRow(AG_Table *t, Uint m)
{
Uint n;
AG_ObjectLock(t);
if (m < t->m) {
for (n = 0; n < t->n; n++) {
t->cells[m][n].selected = 0;
}
}
AG_ObjectUnlock(t);
}
void
AG_TableSelectAllRows(AG_Table *t)
{
Uint m, n;
AG_ObjectLock(t);
for (n = 0; n < t->n; n++) {
for (m = 0; m < t->m; m++)
t->cells[m][n].selected = 1;
}
AG_ObjectUnlock(t);
}
void
AG_TableDeselectAllRows(AG_Table *t)
{
Uint m, n;
AG_ObjectLock(t);
for (n = 0; n < t->n; n++) {
for (m = 0; m < t->m; m++)
t->cells[m][n].selected = 0;
}
AG_ObjectUnlock(t);
}
void
AG_TableSelectAllCols(AG_Table *t)
{
Uint n;
AG_ObjectLock(t);
for (n = 0; n < t->n; n++) {
t->cols[n].selected = 1;
}
AG_ObjectUnlock(t);
}
void
AG_TableDeselectAllCols(AG_Table *t)
{
Uint n;
AG_ObjectLock(t);
for (n = 0; n < t->n; n++) {
t->cols[n].selected = 0;
}
AG_ObjectUnlock(t);
}
static void
ExpireRowDblClick(AG_Event *event)
{
AG_Table *t = AG_SELF();
t->dblClickedRow = -1;
}
static void
ExpireColDblClick(AG_Event *event)
{
AG_Table *t = AG_SELF();
t->dblClickedCol = -1;
}
static void
ExpireCellDblClick(AG_Event *event)
{
AG_Table *t = AG_SELF();
t->dblClickedCell = -1;
}
int
AG_TableAddCol(AG_Table *t, const char *name, const char *size_spec,
int (*sort_fn)(const void *, const void *))
{
AG_TableCol *tc, *lc;
Uint m, n;
AG_ObjectLock(t);
/* Initialize the column information structure. */
t->cols = Realloc(t->cols, (t->n+1)*sizeof(AG_TableCol));
tc = &t->cols[t->n];
if (name != NULL) {
Strlcpy(tc->name, name, sizeof(tc->name));
} else {
tc->name[0] = '\0';
}
tc->flags = AG_TABLE_SORT_ASCENDING;
tc->sort_fn = sort_fn;
tc->selected = 0;
tc->w = 0;
tc->wPct = -1;
tc->pool = NULL;
tc->mpool = 0;
/* XXX CONTEXT */
AG_TextColor(TEXT_COLOR);
tc->surface = (name == NULL) ? -1 :
AG_WidgetMapSurface(t, AG_TextRender(name));
if (t->n > 0) {
lc = &t->cols[t->n - 1];
tc->x = lc->x+lc->w;
} else {
tc->x = 0;
}
if (size_spec != NULL) {
switch (AG_WidgetParseSizeSpec(size_spec, &tc->w)) {
case AG_WIDGET_PERCENT:
tc->wPct = tc->w;
tc->w = 0;
break;
case AG_WIDGET_FILL:
tc->flags |= AG_TABLE_COL_FILL;
break;
default:
break;
}
} else {
tc->flags |= AG_TABLE_COL_FILL;
}
/* Resize the row arrays. */
for (m = 0; m < t->m; m++) {
t->cells[m] = Realloc(t->cells[m],
(t->n+1)*sizeof(AG_TableCell));
AG_TableInitCell(t, &t->cells[m][t->n]);
}
n = t->n++;
AG_ObjectUnlock(t);
return (n);
}
void
AG_TableInitCell(AG_Table *t, AG_TableCell *c)
{
c->type = AG_CELL_NULL;
c->fmt[0] = '\0';
c->fnSu = NULL;
c->fnTxt = NULL;
c->selected = 0;
c->surface = -1;
c->widget = NULL;
}
int
AG_TableAddRow(AG_Table *t, const char *fmtp, ...)
{
char fmt[64], *sp = &fmt[0];
va_list ap;
Uint n, rv;
Strlcpy(fmt, fmtp, sizeof(fmt));
AG_ObjectLock(t);
va_start(ap, fmtp);
t->cells = Realloc(t->cells, (t->m+1)*sizeof(AG_TableCell));
t->cells[t->m] = Malloc(t->n*sizeof(AG_TableCell));
for (n = 0; n < t->n; n++) {
AG_TableCell *c = &t->cells[t->m][n];
char *s = AG_Strsep(&sp, ":"), *sc;
int ptr = 0, lflag = 0, ptr_long = 0;
int infmt = 0;
AG_TableInitCell(t, c);
Strlcpy(c->fmt, s, sizeof(c->fmt));
for (sc = &s[0]; *sc != '\0'; sc++) {
if (*sc == '%') {
infmt = 1;
continue;
}
if (*sc == '*' && sc[1] != '\0') {
ptr++;
} else if (*sc == '[' && sc[1] != '\0') {
ptr_long++;
break;
} else if (*sc == 'l') {
lflag++;
} else if (infmt && strchr("sdiufgp]", *sc) != NULL) {
break;
} else if (strchr(".0123456789", *sc)) {
continue;
} else {
infmt = 0;
}
}
if (*sc == '\0' || !infmt) {
c->type = AG_CELL_NULL;
continue;
}
if (ptr) {
c->data.p = va_arg(ap, void *);
} else if (ptr_long) {
sc++;
c->data.p = va_arg(ap, void *);
if (sc[0] == 's') {
if (sc[1] == '3' && sc[2] == '2') {
c->type = AG_CELL_PSINT32;
} else if (s[1] == '1' && sc[2] == '6') {
c->type = AG_CELL_PSINT16;
} else if (s[1] == '8') {
c->type = AG_CELL_PSINT8;
}
} else if (sc[0] == 'u') {
if (sc[1] == '3' && sc[2] == '2') {
c->type = AG_CELL_PUINT32;
} else if (s[1] == '1' && sc[2] == '6') {
c->type = AG_CELL_PUINT16;
} else if (s[1] == '8') {
c->type = AG_CELL_PUINT8;
}
} else if (sc[0] == 'F' && sc[1] == 't') {
c->type = AG_CELL_FN_TXT;
c->fnTxt = c->data.p;
c->data.p = c;
} else if (sc[0] == 'F' && sc[1] == 's') {
c->type = AG_CELL_FN_SU;
c->fnSu = c->data.p;
c->data.p = c;
} else if (sc[0] == 'W') {
AG_SizeAlloc a;
a.x = 0;
a.y = 0;
a.w = 0;
a.h = 0;
c->type = AG_CELL_WIDGET;
c->widget = c->data.p;
AG_ObjectAttach(t, c->widget);
AG_WidgetSizeAlloc(c->widget, &a);
t->flags |= AG_TABLE_WIDGETS;
}
}
switch (sc[0]) {
case 's':
if (ptr) {
c->type = AG_CELL_PSTRING;
} else {
c->type = AG_CELL_STRING;
Strlcpy(c->data.s, va_arg(ap, char *),
sizeof(c->data.s));
}
break;
case 'd':
case 'i':
if (lflag == 0) {
if (ptr) {
c->type = AG_CELL_PINT;
} else {
c->type = AG_CELL_INT;
c->data.i = va_arg(ap, int);
}
#ifdef HAVE_64BIT
} else if (lflag == 2) {
if (ptr) {
c->type = AG_CELL_PINT64;
} else {
c->type = AG_CELL_INT64;
c->data.l = va_arg(ap, Sint64);
}
#endif
} else {
if (ptr) {
c->type = AG_CELL_PLONG;
} else {
c->type = AG_CELL_LONG;
c->data.l = va_arg(ap, long);
}
}
break;
case 'u':
if (lflag == 0) {
if (ptr) {
c->type = AG_CELL_PUINT;
} else {
c->type = AG_CELL_UINT;
c->data.i = va_arg(ap, int);
}
#ifdef HAVE_64BIT
} else if (lflag == 2) {
if (ptr) {
c->type = AG_CELL_PUINT64;
} else {
c->type = AG_CELL_UINT64;
c->data.l = va_arg(ap, Uint64);
}
#endif
} else {
if (ptr) {
c->type = AG_CELL_PULONG;
} else {
c->type = AG_CELL_ULONG;
c->data.l = va_arg(ap, Ulong);
}
}
break;
case 'f':
case 'g':
if (lflag) {
if (ptr) {
c->type = AG_CELL_PFLOAT;
} else {
c->type = AG_CELL_FLOAT;
c->data.f = va_arg(ap, double);
}
} else {
if (ptr) {
c->type = AG_CELL_PDOUBLE;
} else {
c->type = AG_CELL_DOUBLE;
c->data.f = va_arg(ap, double);
}
}
break;
case 'p':
c->type = AG_CELL_POINTER;
c->data.p = va_arg(ap, void *);
break;
default:
break;
}
}
va_end(ap);
rv = t->m++;
AG_ObjectUnlock(t);
return (rv);
}
int
AG_TableSaveASCII(AG_Table *t, FILE *f, char sep)
{
char txt[AG_TABLE_TXT_MAX];
Uint m, n;
AG_ObjectLock(t);
for (n = 0; n < t->n; n++) {
if (t->cols[n].name[0] == '\0') {
continue;
}
fputs(t->cols[n].name, f);
fputc(sep, f);
}
fputc('\n', f);
for (m = 0; m < t->m; m++) {
for (n = 0; n < t->n; n++) {
if (t->cols[n].name[0] == '\0') {
continue;
}
PrintCell(t, &t->cells[m][n], txt, sizeof(txt));
fputs(txt, f);
fputc(sep, f);
}
fputc('\n', f);
}
AG_ObjectUnlock(t);
return (0);
}
static Uint32
DecrementTimeout(void *obj, Uint32 ival, void *arg)
{
AG_Table *t = obj;
Uint8 *ks;
int numkeys;
ks = SDL_GetKeyState(&numkeys);
DecrementSelection(t, ks[SDLK_PAGEUP] ? agPageIncrement : 1);
return (agKbdRepeat);
}
static Uint32
IncrementTimeout(void *obj, Uint32 ival, void *arg)
{
AG_Table *t = obj;
Uint8 *ks;
int numkeys;
ks = SDL_GetKeyState(&numkeys);
IncrementSelection(t, ks[SDLK_PAGEDOWN] ? agPageIncrement : 1);
return (agKbdRepeat);
}
static void
Init(void *obj)
{
AG_Table *t = obj;
WIDGET(t)->flags |= AG_WIDGET_FOCUSABLE|
AG_WIDGET_UNFOCUSED_MOTION|
AG_WIDGET_UNFOCUSED_BUTTONUP;
t->sep = ":";
t->flags = 0;
t->hRow = agTextFontHeight+2;
t->hCol = agTextFontHeight+4;
t->wColMin = 16;
t->wColDefault = 80;
t->wHint = -1; /* Use column size specs */
t->hHint = t->hCol + t->hRow*2;
t->r = AG_RECT(0,0,0,0);
t->selMode = AG_TABLE_SEL_ROWS;
t->selColor[0] = 0;
t->selColor[1] = 0;
t->selColor[2] = 250;
t->selColor[3] = 32;
t->wTot = 0;
t->vbar = AG_ScrollbarNew(t, AG_SCROLLBAR_VERT, 0);
t->hbar = AG_ScrollbarNew(t, AG_SCROLLBAR_HORIZ, 0);
AG_SetInt(t->hbar, "min", 0);
AG_BindInt(t->hbar, "value", &t->xOffs);
AG_BindInt(t->hbar, "visible", &t->r.w);
AG_BindInt(t->hbar, "max", &t->wTot);
AG_WidgetSetFocusable(t->vbar, 0);
AG_WidgetSetFocusable(t->hbar, 0);
t->poll_ev = NULL;
t->nResizing = -1;
t->cols = NULL;
t->cells = NULL;
t->n = 0;
t->m = 0;
t->mVis = 0;
t->xOffs = 0;
t->dblClickRowEv = NULL;
t->dblClickColEv = NULL;
t->dblClickCellEv = NULL;
t->dblClickedRow = -1;
t->dblClickedCol = -1;
t->wheelTicks = 0;
SLIST_INIT(&t->popups);
AG_SetEvent(t, "window-mousebuttondown", MouseButtonDown, NULL);
AG_SetEvent(t, "window-mousebuttonup", MouseButtonUp, NULL);
AG_SetEvent(t, "window-mousemotion", MouseMotion, NULL);
AG_SetEvent(t, "window-keydown", KeyDown, NULL);
AG_SetEvent(t, "window-keyup", KeyUp, NULL);
AG_SetEvent(t, "widget-lostfocus", LostFocus, NULL);
AG_SetEvent(t, "widget-hidden", LostFocus, NULL);
AG_SetEvent(t, "detached", LostFocus, NULL);
AG_SetEvent(t, "dblclick-row-expire", ExpireRowDblClick, NULL);
AG_SetEvent(t, "dblclick-col-expire", ExpireColDblClick, NULL);
AG_SetEvent(t, "dblclick-cell-expire", ExpireCellDblClick, NULL);
AG_SetTimeout(&t->decTo, DecrementTimeout, NULL, 0);
AG_SetTimeout(&t->incTo, IncrementTimeout, NULL, 0);
}
static void
Destroy(void *obj)
{
AG_Table *t = obj;
AG_TablePopup *tp, *tpn;
Uint m, n;
for (tp = SLIST_FIRST(&t->popups);
tp != SLIST_END(&t->popups);
tp = tpn) {
tpn = SLIST_NEXT(tp, popups);
AG_ObjectDestroy(tp->menu);
Free(tp);
}
for (m = 0; m < t->m; m++) {
for (n = 0; n < t->n; n++)
AG_TableFreeCell(t, &t->cells[m][n]);
}
for (n = 0; n < t->n; n++) {
AG_TablePoolFree(t, n);
}
Free(t->cols);
}
AG_WidgetClass agTableClass = {
{
"Agar(Widget:Table)",
sizeof(AG_Table),
{ 0,0 },
Init,
NULL, /* free */
Destroy,
NULL, /* load */
NULL, /* save */
NULL /* edit */
},
Draw,
SizeRequest,
SizeAllocate
};