DVX_GUI/widgets/dataCtrl/widgetDataCtrl.c

998 lines
28 KiB
C

#define DVX_WIDGET_IMPL
// widgetDataCtrl.c -- VB3-style Data control for database binding
//
// A visible navigation bar that connects to a SQLite database via
// dvxSql* functions. Reads all rows from the RecordSource query
// into an in-memory cache for bidirectional navigation. Fires
// Reposition events when the cursor moves so bound controls can
// update.
//
// Depends on the dvxsql library DXE (via datactrl.dep) so all
// dvxSql* functions are resolved at load time.
#include "dvxWidgetPlugin.h"
#include "../../sql/dvxSql.h"
#include "thirdparty/stb_ds_wrap.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
// ============================================================
// Constants
// ============================================================
#define DATA_HEIGHT 24
#define DATA_BTN_W 20
#define DATA_BORDER 2
#define DATA_MAX_COLS 64
#define DATA_MAX_FIELD 256
// ============================================================
// Per-row cache entry
// ============================================================
typedef struct {
char **fields; // array of strdup'd strings, one per column
int32_t fieldCount;
} DataRowT;
// ============================================================
// Per-instance data
// ============================================================
typedef struct {
char databaseName[DATA_MAX_FIELD];
char recordSource[DATA_MAX_FIELD];
char caption[DATA_MAX_FIELD];
char keyColumn[DATA_MAX_FIELD];
// Master-detail linking
char masterSource[DATA_MAX_FIELD]; // name of master Data control
char masterField[DATA_MAX_FIELD]; // column in master to read
char detailField[DATA_MAX_FIELD]; // column in this table to filter
char masterValue[DATA_MAX_FIELD]; // current filter value (set by runtime)
// Cached result set
DataRowT *rows; // stb_ds dynamic array
int32_t rowCount;
char **colNames; // stb_ds dynamic array of strdup'd names
int32_t colCount;
int32_t currentRow; // 0-based, -1 = no rows
bool bof;
bool eof;
bool dirty; // current row has unsaved changes
bool isNewRow; // current row was created by AddNew
} DataCtrlDataT;
static int32_t sTypeId = -1;
// Forward declarations for functions referenced before definition
void dataCtrlUpdate(WidgetT *w);
// ============================================================
// freeCache -- release the cached result set
// ============================================================
static void freeCache(DataCtrlDataT *d) {
for (int32_t i = 0; i < d->rowCount; i++) {
for (int32_t j = 0; j < d->rows[i].fieldCount; j++) {
free(d->rows[i].fields[j]);
}
free(d->rows[i].fields);
}
arrfree(d->rows);
d->rows = NULL;
d->rowCount = 0;
for (int32_t i = 0; i < d->colCount; i++) {
free(d->colNames[i]);
}
arrfree(d->colNames);
d->colNames = NULL;
d->colCount = 0;
d->currentRow = -1;
d->bof = true;
d->eof = true;
}
// ============================================================
// dataCtrlRefresh -- execute query and cache all rows
// ============================================================
void dataCtrlRefresh(WidgetT *w) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
freeCache(d);
if (!d->databaseName[0] || !d->recordSource[0]) {
return;
}
int32_t db = dvxSqlOpen(d->databaseName);
if (db <= 0) {
return;
}
// Build query from RecordSource, with optional master-detail WHERE clause
char query[1024];
if (strncasecmp(d->recordSource, "SELECT ", 7) == 0) {
snprintf(query, sizeof(query), "%s", d->recordSource);
} else {
snprintf(query, sizeof(query), "SELECT * FROM %s", d->recordSource);
}
// Append WHERE filter for master-detail linking
if (d->detailField[0] && d->masterValue[0]) {
char escaped[DATA_MAX_FIELD * 2];
dvxSqlEscape(d->masterValue, escaped, sizeof(escaped));
int32_t len = (int32_t)strlen(query);
snprintf(query + len, sizeof(query) - len, " WHERE %s='%s'", d->detailField, escaped);
}
int32_t rs = dvxSqlQuery(db, query);
if (rs <= 0) {
dvxSqlClose(db);
return;
}
// Cache column names
d->colCount = dvxSqlFieldCount(rs);
for (int32_t i = 0; i < d->colCount; i++) {
const char *name = dvxSqlFieldName(rs, i);
arrput(d->colNames, strdup(name ? name : ""));
}
// Cache all rows
while (dvxSqlNext(rs)) {
DataRowT row;
row.fieldCount = d->colCount;
row.fields = (char **)malloc(d->colCount * sizeof(char *));
for (int32_t i = 0; i < d->colCount; i++) {
const char *text = dvxSqlFieldText(rs, i);
row.fields[i] = strdup(text ? text : "");
}
arrput(d->rows, row);
}
d->rowCount = (int32_t)arrlen(d->rows);
dvxSqlFreeResult(rs);
dvxSqlClose(db);
if (d->rowCount > 0) {
d->currentRow = 0;
d->bof = false;
d->eof = false;
}
// Auto-caption
if (!d->caption[0]) {
snprintf(d->caption, sizeof(d->caption), "%s", d->recordSource);
}
wgtInvalidatePaint(w);
}
// ============================================================
// Navigation methods
// ============================================================
static void fireReposition(WidgetT *w) {
wgtInvalidatePaint(w);
if (w->onChange) {
w->onChange(w);
}
}
static void autoSave(WidgetT *w) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d->dirty) {
dataCtrlUpdate(w);
}
}
static void dataCtrlMoveFirst(WidgetT *w) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d->rowCount <= 0) {
return;
}
autoSave(w);
d->currentRow = 0;
d->bof = false;
d->eof = false;
fireReposition(w);
}
static void dataCtrlMovePrev(WidgetT *w) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d->rowCount <= 0 || d->currentRow <= 0) {
d->bof = true;
return;
}
autoSave(w);
d->currentRow--;
d->bof = (d->currentRow == 0);
d->eof = false;
fireReposition(w);
}
static void dataCtrlMoveNext(WidgetT *w) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d->rowCount <= 0 || d->currentRow >= d->rowCount - 1) {
d->eof = true;
return;
}
autoSave(w);
d->currentRow++;
d->bof = false;
d->eof = (d->currentRow >= d->rowCount - 1);
fireReposition(w);
}
static void dataCtrlMoveLast(WidgetT *w) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d->rowCount <= 0) {
return;
}
autoSave(w);
d->currentRow = d->rowCount - 1;
d->bof = false;
d->eof = false;
fireReposition(w);
}
// ============================================================
// Public accessor: get field value from current row by column name
// ============================================================
// Exported for form runtime data binding (resolved via dlsym)
const char *dataCtrlGetField(WidgetT *w, const char *colName) {
if (!w || !w->data || !colName) {
return "";
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d->currentRow < 0 || d->currentRow >= d->rowCount) {
return "";
}
for (int32_t i = 0; i < d->colCount; i++) {
if (strcasecmp(d->colNames[i], colName) == 0) {
return d->rows[d->currentRow].fields[i];
}
}
return "";
}
// ============================================================
// dataCtrlSetField -- update a field value in the current cached row
// ============================================================
void dataCtrlSetField(WidgetT *w, const char *colName, const char *value) {
if (!w || !w->data || !colName || !value) {
return;
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d->currentRow < 0 || d->currentRow >= d->rowCount) {
return;
}
for (int32_t i = 0; i < d->colCount; i++) {
if (strcasecmp(d->colNames[i], colName) == 0) {
free(d->rows[d->currentRow].fields[i]);
d->rows[d->currentRow].fields[i] = strdup(value);
d->dirty = true;
return;
}
}
}
// ============================================================
// findKeyCol -- locate the key column index
// ============================================================
static int32_t findKeyCol(const DataCtrlDataT *d) {
if (d->keyColumn[0]) {
for (int32_t i = 0; i < d->colCount; i++) {
if (strcasecmp(d->colNames[i], d->keyColumn) == 0) {
return i;
}
}
}
return 0;
}
// ============================================================
// canWriteBack -- check if write operations are possible
// ============================================================
static bool canWriteBack(const DataCtrlDataT *d) {
if (!d->databaseName[0] || !d->recordSource[0]) {
return false;
}
// Write-back only for simple table names, not SELECT queries
if (strncasecmp(d->recordSource, "SELECT ", 7) == 0) {
return false;
}
return true;
}
// ============================================================
// dataCtrlUpdate -- save the current row to the database
// ============================================================
//
// If the current row was created by AddNew, executes an INSERT.
// Otherwise executes an UPDATE using the KeyColumn to identify the row.
// Clears the dirty and isNewRow flags on success.
void dataCtrlUpdate(WidgetT *w) {
if (!w || !w->data) {
return;
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (!d->dirty || !canWriteBack(d)) {
return;
}
if (d->currentRow < 0 || d->currentRow >= d->rowCount || d->colCount == 0) {
return;
}
// Fire Validate event — return false cancels the write
if (w->onValidate && !w->onValidate(w)) {
return;
}
int32_t db = dvxSqlOpen(d->databaseName);
if (db <= 0) {
return;
}
char sql[2048];
int32_t pos = 0;
if (d->isNewRow) {
// INSERT INTO table (col1, col2, ...) VALUES ('val1', 'val2', ...)
pos = snprintf(sql, sizeof(sql), "INSERT INTO %s (", d->recordSource);
for (int32_t i = 0; i < d->colCount; i++) {
if (i > 0) {
pos += snprintf(sql + pos, sizeof(sql) - pos, ", ");
}
pos += snprintf(sql + pos, sizeof(sql) - pos, "%s", d->colNames[i]);
}
pos += snprintf(sql + pos, sizeof(sql) - pos, ") VALUES (");
for (int32_t i = 0; i < d->colCount; i++) {
if (i > 0) {
pos += snprintf(sql + pos, sizeof(sql) - pos, ", ");
}
char escaped[DATA_MAX_FIELD * 2];
dvxSqlEscape(d->rows[d->currentRow].fields[i], escaped, sizeof(escaped));
pos += snprintf(sql + pos, sizeof(sql) - pos, "'%s'", escaped);
}
pos += snprintf(sql + pos, sizeof(sql) - pos, ")");
} else {
// UPDATE table SET col1='val1', ... WHERE keyCol=keyVal
int32_t keyCol = findKeyCol(d);
const char *keyVal = d->rows[d->currentRow].fields[keyCol];
pos = snprintf(sql, sizeof(sql), "UPDATE %s SET ", d->recordSource);
bool first = true;
for (int32_t i = 0; i < d->colCount; i++) {
if (i == keyCol) {
continue;
}
if (!first) {
pos += snprintf(sql + pos, sizeof(sql) - pos, ", ");
}
char escaped[DATA_MAX_FIELD * 2];
dvxSqlEscape(d->rows[d->currentRow].fields[i], escaped, sizeof(escaped));
pos += snprintf(sql + pos, sizeof(sql) - pos, "%s='%s'", d->colNames[i], escaped);
first = false;
}
pos += snprintf(sql + pos, sizeof(sql) - pos, " WHERE %s=%s", d->colNames[keyCol], keyVal);
}
dvxSqlExec(db, sql);
dvxSqlClose(db);
d->dirty = false;
d->isNewRow = false;
}
// ============================================================
// dataCtrlUpdateRow -- legacy wrapper, calls dataCtrlUpdate
// ============================================================
void dataCtrlUpdateRow(WidgetT *w) {
dataCtrlUpdate(w);
}
// ============================================================
// dataCtrlAddNew -- append a blank row and move cursor to it
// ============================================================
void dataCtrlAddNew(WidgetT *w) {
if (!w || !w->data) {
return;
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
// Auto-save current row if dirty
if (d->dirty) {
dataCtrlUpdate(w);
}
if (d->colCount == 0) {
return;
}
// Append a blank row
DataRowT row;
row.fieldCount = d->colCount;
row.fields = (char **)malloc(d->colCount * sizeof(char *));
for (int32_t i = 0; i < d->colCount; i++) {
row.fields[i] = strdup("");
}
arrput(d->rows, row);
d->rowCount = (int32_t)arrlen(d->rows);
d->currentRow = d->rowCount - 1;
d->bof = false;
d->eof = false;
d->dirty = true;
d->isNewRow = true;
fireReposition(w);
}
// ============================================================
// dataCtrlDelete -- delete the current row from DB and cache
// ============================================================
void dataCtrlDelete(WidgetT *w) {
if (!w || !w->data) {
return;
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d->currentRow < 0 || d->currentRow >= d->rowCount || d->colCount == 0) {
return;
}
// Delete from database (unless it's an unsaved new row)
if (!d->isNewRow && canWriteBack(d)) {
int32_t db = dvxSqlOpen(d->databaseName);
if (db > 0) {
int32_t keyCol = findKeyCol(d);
const char *keyVal = d->rows[d->currentRow].fields[keyCol];
char sql[512];
snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE %s=%s",
d->recordSource, d->colNames[keyCol], keyVal);
dvxSqlExec(db, sql);
dvxSqlClose(db);
}
}
// Free the cached row
for (int32_t j = 0; j < d->rows[d->currentRow].fieldCount; j++) {
free(d->rows[d->currentRow].fields[j]);
}
free(d->rows[d->currentRow].fields);
arrdel(d->rows, d->currentRow);
d->rowCount = (int32_t)arrlen(d->rows);
// Adjust cursor
if (d->rowCount == 0) {
d->currentRow = -1;
d->bof = true;
d->eof = true;
} else {
if (d->currentRow >= d->rowCount) {
d->currentRow = d->rowCount - 1;
}
d->bof = (d->currentRow == 0);
d->eof = (d->currentRow >= d->rowCount - 1);
}
d->dirty = false;
d->isNewRow = false;
fireReposition(w);
}
// ============================================================
// Paint
// ============================================================
static void dataCtrlCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
w->calcMinW = DATA_BTN_W * 4 + 60; // 4 buttons + some caption space
w->calcMinH = DATA_HEIGHT;
}
static void dataCtrlPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
uint32_t face = colors->buttonFace;
uint32_t fg = colors->contentFg;
uint32_t hi = colors->windowHighlight;
uint32_t sh = colors->windowShadow;
// Background
rectFill(disp, ops, w->x, w->y, w->w, w->h, face);
// Outer bevel
BevelStyleT bevel;
bevel.highlight = hi;
bevel.shadow = sh;
bevel.face = face;
bevel.width = DATA_BORDER;
drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);
int32_t innerX = w->x + DATA_BORDER;
int32_t innerY = w->y + DATA_BORDER;
int32_t innerW = w->w - DATA_BORDER * 2;
int32_t innerH = w->h - DATA_BORDER * 2;
// Button zones
int32_t btnH = innerH;
int32_t btnY = innerY;
// |< button (MoveFirst)
BevelStyleT btnBevel = { hi, sh, face, 1 };
drawBevel(disp, ops, innerX, btnY, DATA_BTN_W, btnH, &btnBevel);
// Draw |< glyph
int32_t cx = innerX + DATA_BTN_W / 2;
int32_t cy = btnY + btnH / 2;
drawVLine(disp, ops, cx - 3, cy - 4, 9, fg);
for (int32_t i = 0; i < 4; i++) {
drawHLine(disp, ops, cx - 1 + i, cy - i, 1, fg);
drawHLine(disp, ops, cx - 1 + i, cy + i, 1, fg);
}
// < button (MovePrev)
drawBevel(disp, ops, innerX + DATA_BTN_W, btnY, DATA_BTN_W, btnH, &btnBevel);
cx = innerX + DATA_BTN_W + DATA_BTN_W / 2;
for (int32_t i = 0; i < 4; i++) {
drawHLine(disp, ops, cx + i, cy - i, 1, fg);
drawHLine(disp, ops, cx + i, cy + i, 1, fg);
}
// > button (MoveNext)
int32_t rightX = innerX + innerW - DATA_BTN_W * 2;
drawBevel(disp, ops, rightX, btnY, DATA_BTN_W, btnH, &btnBevel);
cx = rightX + DATA_BTN_W / 2;
for (int32_t i = 0; i < 4; i++) {
drawHLine(disp, ops, cx - i, cy - i, 1, fg);
drawHLine(disp, ops, cx - i, cy + i, 1, fg);
}
// >| button (MoveLast)
drawBevel(disp, ops, rightX + DATA_BTN_W, btnY, DATA_BTN_W, btnH, &btnBevel);
cx = rightX + DATA_BTN_W + DATA_BTN_W / 2;
drawVLine(disp, ops, cx + 3, cy - 4, 9, fg);
for (int32_t i = 0; i < 4; i++) {
drawHLine(disp, ops, cx - i, cy - i, 1, fg);
drawHLine(disp, ops, cx - i, cy + i, 1, fg);
}
// Caption in center
int32_t captionX = innerX + DATA_BTN_W * 2 + 4;
int32_t captionW = innerW - DATA_BTN_W * 4 - 8;
const char *text = d->caption[0] ? d->caption : d->recordSource;
if (text[0] && captionW > 0) {
int32_t tw = textWidth(font, text);
int32_t tx = captionX + (captionW - tw) / 2;
int32_t ty = innerY + (innerH - font->charHeight) / 2;
drawText(disp, ops, font, tx, ty, text, fg, face, false);
}
}
// ============================================================
// Mouse handler
// ============================================================
static void dataCtrlOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vy;
sFocusedWidget = w;
int32_t innerX = w->x + DATA_BORDER;
int32_t innerW = w->w - DATA_BORDER * 2;
int32_t relX = vx - innerX;
if (relX < DATA_BTN_W) {
dataCtrlMoveFirst(w);
} else if (relX < DATA_BTN_W * 2) {
dataCtrlMovePrev(w);
} else if (relX >= innerW - DATA_BTN_W) {
dataCtrlMoveLast(w);
} else if (relX >= innerW - DATA_BTN_W * 2) {
dataCtrlMoveNext(w);
}
}
// ============================================================
// Property getters/setters
// ============================================================
static const char *dataCtrlGetDatabaseName(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->databaseName;
}
static void dataCtrlSetDatabaseName(WidgetT *w, const char *val) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
snprintf(d->databaseName, sizeof(d->databaseName), "%s", val ? val : "");
}
static const char *dataCtrlGetRecordSource(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->recordSource;
}
static void dataCtrlSetRecordSource(WidgetT *w, const char *val) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
snprintf(d->recordSource, sizeof(d->recordSource), "%s", val ? val : "");
}
static const char *dataCtrlGetKeyColumn(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->keyColumn;
}
static void dataCtrlSetKeyColumn(WidgetT *w, const char *val) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
snprintf(d->keyColumn, sizeof(d->keyColumn), "%s", val ? val : "");
}
static const char *dataCtrlGetMasterSource(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->masterSource;
}
static void dataCtrlSetMasterSource(WidgetT *w, const char *val) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
snprintf(d->masterSource, sizeof(d->masterSource), "%s", val ? val : "");
}
static const char *dataCtrlGetMasterField(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->masterField;
}
static void dataCtrlSetMasterField(WidgetT *w, const char *val) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
snprintf(d->masterField, sizeof(d->masterField), "%s", val ? val : "");
}
// ============================================================
// Row-level accessors for DBGrid and other consumers
// ============================================================
int32_t dataCtrlGetRowCount(WidgetT *w) {
if (!w || !w->data) {
return 0;
}
return ((DataCtrlDataT *)w->data)->rowCount;
}
int32_t dataCtrlGetColCount(WidgetT *w) {
if (!w || !w->data) {
return 0;
}
return ((DataCtrlDataT *)w->data)->colCount;
}
const char *dataCtrlGetColName(WidgetT *w, int32_t col) {
if (!w || !w->data) {
return "";
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (col < 0 || col >= d->colCount) {
return "";
}
return d->colNames[col];
}
const char *dataCtrlGetCellText(WidgetT *w, int32_t row, int32_t col) {
if (!w || !w->data) {
return "";
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (row < 0 || row >= d->rowCount || col < 0 || col >= d->colCount) {
return "";
}
return d->rows[row].fields[col];
}
void dataCtrlSetCurrentRow(WidgetT *w, int32_t row) {
if (!w || !w->data) {
return;
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (row < 0 || row >= d->rowCount) {
return;
}
// Auto-save before moving
if (d->dirty) {
dataCtrlUpdate(w);
}
d->currentRow = row;
d->bof = (row == 0);
d->eof = (row >= d->rowCount - 1);
wgtInvalidatePaint(w);
if (w->onChange) {
w->onChange(w);
}
}
void dataCtrlSetMasterValue(WidgetT *w, const char *val) {
if (!w || !w->data) {
return;
}
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
snprintf(d->masterValue, sizeof(d->masterValue), "%s", val ? val : "");
}
static const char *dataCtrlGetDetailField(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->detailField;
}
static void dataCtrlSetDetailField(WidgetT *w, const char *val) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
snprintf(d->detailField, sizeof(d->detailField), "%s", val ? val : "");
}
static const char *dataCtrlGetCaption(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->caption;
}
static void dataCtrlSetCaption(WidgetT *w, const char *val) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
snprintf(d->caption, sizeof(d->caption), "%s", val ? val : "");
wgtInvalidatePaint(w);
}
static bool dataCtrlGetBof(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->bof;
}
static bool dataCtrlGetEof(const WidgetT *w) {
return ((DataCtrlDataT *)w->data)->eof;
}
// ============================================================
// Destroy
// ============================================================
static void dataCtrlDestroy(WidgetT *w) {
DataCtrlDataT *d = (DataCtrlDataT *)w->data;
if (d) {
freeCache(d);
free(d);
}
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClass = {
.version = WGT_CLASS_VERSION,
.flags = WCLASS_FOCUSABLE,
.handlers = {
[WGT_METHOD_PAINT] = (void *)dataCtrlPaint,
[WGT_METHOD_CALC_MIN_SIZE] = (void *)dataCtrlCalcMinSize,
[WGT_METHOD_ON_MOUSE] = (void *)dataCtrlOnMouse,
[WGT_METHOD_DESTROY] = (void *)dataCtrlDestroy,
}
};
static WidgetT *dataCtrlCreate(WidgetT *parent) {
if (!parent) {
return NULL;
}
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
DataCtrlDataT *d = (DataCtrlDataT *)calloc(1, sizeof(DataCtrlDataT));
if (d) {
d->currentRow = -1;
d->bof = true;
d->eof = true;
}
w->data = d;
w->minH = wgtPixels(DATA_HEIGHT);
}
return w;
}
static const struct {
WidgetT *(*create)(WidgetT *parent);
void (*refresh)(WidgetT *w);
void (*moveFirst)(WidgetT *w);
void (*movePrev)(WidgetT *w);
void (*moveNext)(WidgetT *w);
void (*moveLast)(WidgetT *w);
const char *(*getField)(WidgetT *w, const char *colName);
void (*setField)(WidgetT *w, const char *colName, const char *value);
void (*updateRow)(WidgetT *w);
void (*update)(WidgetT *w);
void (*addNew)(WidgetT *w);
void (*delete)(WidgetT *w);
void (*setMasterValue)(WidgetT *w, const char *val);
int32_t (*getRowCount)(WidgetT *w);
int32_t (*getColCount)(WidgetT *w);
const char *(*getColName)(WidgetT *w, int32_t col);
const char *(*getCellText)(WidgetT *w, int32_t row, int32_t col);
void (*setCurrentRow)(WidgetT *w, int32_t row);
} sApi = {
.create = dataCtrlCreate,
.refresh = dataCtrlRefresh,
.moveFirst = dataCtrlMoveFirst,
.movePrev = dataCtrlMovePrev,
.moveNext = dataCtrlMoveNext,
.moveLast = dataCtrlMoveLast,
.getField = dataCtrlGetField,
.setField = dataCtrlSetField,
.updateRow = dataCtrlUpdateRow,
.update = dataCtrlUpdate,
.addNew = dataCtrlAddNew,
.delete = dataCtrlDelete,
.setMasterValue = dataCtrlSetMasterValue,
.getRowCount = dataCtrlGetRowCount,
.getColCount = dataCtrlGetColCount,
.getColName = dataCtrlGetColName,
.getCellText = dataCtrlGetCellText,
.setCurrentRow = dataCtrlSetCurrentRow,
};
static const WgtPropDescT sProps[] = {
{ "DatabaseName", WGT_IFACE_STRING, (void *)dataCtrlGetDatabaseName, (void *)dataCtrlSetDatabaseName, NULL },
{ "RecordSource", WGT_IFACE_STRING, (void *)dataCtrlGetRecordSource, (void *)dataCtrlSetRecordSource, NULL },
{ "KeyColumn", WGT_IFACE_STRING, (void *)dataCtrlGetKeyColumn, (void *)dataCtrlSetKeyColumn, NULL },
{ "MasterSource", WGT_IFACE_STRING, (void *)dataCtrlGetMasterSource, (void *)dataCtrlSetMasterSource, NULL },
{ "MasterField", WGT_IFACE_STRING, (void *)dataCtrlGetMasterField, (void *)dataCtrlSetMasterField, NULL },
{ "DetailField", WGT_IFACE_STRING, (void *)dataCtrlGetDetailField, (void *)dataCtrlSetDetailField, NULL },
{ "Caption", WGT_IFACE_STRING, (void *)dataCtrlGetCaption, (void *)dataCtrlSetCaption, NULL },
{ "BOF", WGT_IFACE_BOOL, (void *)dataCtrlGetBof, NULL, NULL },
{ "EOF", WGT_IFACE_BOOL, (void *)dataCtrlGetEof, NULL, NULL },
};
static const WgtMethodDescT sMethods[] = {
{ "AddNew", WGT_SIG_VOID, (void *)dataCtrlAddNew },
{ "Delete", WGT_SIG_VOID, (void *)dataCtrlDelete },
{ "MoveFirst", WGT_SIG_VOID, (void *)dataCtrlMoveFirst },
{ "MoveLast", WGT_SIG_VOID, (void *)dataCtrlMoveLast },
{ "MoveNext", WGT_SIG_VOID, (void *)dataCtrlMoveNext },
{ "MovePrevious", WGT_SIG_VOID, (void *)dataCtrlMovePrev },
{ "Refresh", WGT_SIG_VOID, (void *)dataCtrlRefresh },
{ "Update", WGT_SIG_VOID, (void *)dataCtrlUpdate },
};
static const WgtEventDescT sEvents[] = {
{ "Reposition" },
{ "Validate" },
};
static const WgtIfaceT sIface = {
.basName = "Data",
.props = sProps,
.propCount = 9,
.methods = sMethods,
.methodCount = 8,
.events = sEvents,
.eventCount = 2,
.createSig = WGT_CREATE_PARENT,
.isContainer = false,
.defaultEvent = "Reposition",
.namePrefix = "Data",
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClass);
wgtRegisterApi("data", &sApi);
wgtRegisterIface("data", &sIface);
}