#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 "dvxWgtP.h" #include "../../sql/dvxSql.h" #include "thirdparty/stb_ds_wrap.h" #include #include #include #include // ============================================================ // 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); }