// formrt.c -- DVX BASIC form runtime implementation // // Bridges BASIC programs to the DVX widget system. All widget types // are discovered dynamically via WgtIfaceT interface descriptors // registered by .wgt DXE files. No hardcoded control types. #include "formrt.h" #include "../compiler/codegen.h" #include "../compiler/opcodes.h" #include "dvxDialog.h" #include "dvxWm.h" #include "widgetBox.h" #include #include #include #include #include // ============================================================ // Defines // ============================================================ #define DEFAULT_FORM_W 400 #define DEFAULT_FORM_H 300 #define DEFAULT_CREATE_ARG 256 // default maxLen/interval for widget creation #define MAX_EVENT_NAME_LEN 128 #define MAX_LISTBOX_ITEMS 256 #define MAX_FRM_LINE_LEN 512 #define MAX_FRM_NESTING 16 #define MAX_AUX_DATA 128 // ============================================================ // Per-control listbox item storage // ============================================================ typedef struct { char *items[MAX_LISTBOX_ITEMS]; int32_t count; } ListBoxItemsT; // ============================================================ // Auxiliary data table for listbox item storage // ============================================================ typedef struct { BasControlT *ctrl; ListBoxItemsT *items; } AuxDataEntryT; static AuxDataEntryT sAuxData[MAX_AUX_DATA]; static int32_t sAuxDataCount = 0; // Module-level form runtime pointer for onFormClose callback static BasFormRtT *sFormRt = NULL; // ============================================================ // Prototypes // ============================================================ static BasStringT *basFormRtInputBox(void *ctx, const char *prompt, const char *title, const char *defaultText); static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc); static BasValueT callListBoxMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc); static WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent); static void freeListBoxItems(BasControlT *ctrl); static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled); static BasValueT getIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, bool *handled); static ListBoxItemsT *getListBoxItems(BasControlT *ctrl); static void onFormClose(WindowT *win); static void onFormResize(WindowT *win, int32_t newW, int32_t newH); static void onWidgetBlur(WidgetT *w); static void onWidgetChange(WidgetT *w); static void onWidgetClick(WidgetT *w); static void onWidgetDblClick(WidgetT *w); static void onWidgetFocus(WidgetT *w); static void parseFrmLine(const char *line, char *key, char *value); static void rebuildListBoxItems(BasControlT *ctrl); static const char *resolveTypeName(const char *typeName); static bool setCommonProp(BasControlT *ctrl, const char *propName, BasValueT value); static bool setIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, BasValueT value); static BasValueT zeroValue(void); // ============================================================ // basFormRtBindVm // ============================================================ void basFormRtBindVm(BasFormRtT *rt) { BasUiCallbacksT ui; memset(&ui, 0, sizeof(ui)); ui.getProp = basFormRtGetProp; ui.setProp = basFormRtSetProp; ui.callMethod = basFormRtCallMethod; ui.createCtrl = basFormRtCreateCtrl; ui.findCtrl = basFormRtFindCtrl; ui.loadForm = basFormRtLoadForm; ui.unloadForm = basFormRtUnloadForm; ui.showForm = basFormRtShowForm; ui.hideForm = basFormRtHideForm; ui.msgBox = basFormRtMsgBox; ui.inputBox = basFormRtInputBox; ui.ctx = rt; basVmSetUiCallbacks(rt->vm, &ui); } // ============================================================ // basFormRtCallMethod // ============================================================ BasValueT basFormRtCallMethod(void *ctx, void *ctrlRef, const char *methodName, BasValueT *args, int32_t argc) { (void)ctx; BasControlT *ctrl = (BasControlT *)ctrlRef; if (!ctrl || !ctrl->widget) { return zeroValue(); } // ListBox-specific methods (AddItem/RemoveItem/Clear/List) BasValueT result = callListBoxMethod(ctrl, methodName, args, argc); if (result.type != 0 || strcasecmp(methodName, "AddItem") == 0 || strcasecmp(methodName, "RemoveItem") == 0 || strcasecmp(methodName, "Clear") == 0 || strcasecmp(methodName, "List") == 0) { return result; } // Interface descriptor methods const WgtIfaceT *iface = ctrl->iface; if (iface) { for (int32_t i = 0; i < iface->methodCount; i++) { if (strcasecmp(iface->methods[i].name, methodName) != 0) { continue; } const WgtMethodDescT *m = &iface->methods[i]; WidgetT *w = ctrl->widget; switch (m->sig) { case WGT_SIG_VOID: ((void (*)(WidgetT *))m->fn)(w); return zeroValue(); case WGT_SIG_INT: if (argc >= 1) { ((void (*)(WidgetT *, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0])); } return zeroValue(); case WGT_SIG_BOOL: if (argc >= 1) { ((void (*)(WidgetT *, bool))m->fn)(w, basValIsTruthy(args[0])); } return zeroValue(); case WGT_SIG_STR: if (argc >= 1) { BasStringT *s = basValFormatString(args[0]); ((void (*)(WidgetT *, const char *))m->fn)(w, s->data); basStringUnref(s); } return zeroValue(); case WGT_SIG_INT_INT: if (argc >= 2) { ((void (*)(WidgetT *, int32_t, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]), (int32_t)basValToNumber(args[1])); } return zeroValue(); case WGT_SIG_INT_BOOL: if (argc >= 2) { ((void (*)(WidgetT *, int32_t, bool))m->fn)(w, (int32_t)basValToNumber(args[0]), basValIsTruthy(args[1])); } return zeroValue(); case WGT_SIG_RET_INT: { int32_t v = ((int32_t (*)(const WidgetT *))m->fn)(w); return basValLong(v); } case WGT_SIG_RET_BOOL: { bool v = ((bool (*)(const WidgetT *))m->fn)(w); return basValBool(v); } case WGT_SIG_RET_BOOL_INT: { if (argc >= 1) { bool v = ((bool (*)(const WidgetT *, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0])); return basValBool(v); } return basValBool(false); } } } } // Common methods (SetFocus, Refresh) return callCommonMethod(ctrl, methodName, args, argc); } // ============================================================ // basFormRtCreate // ============================================================ BasFormRtT *basFormRtCreate(AppContextT *ctx, BasVmT *vm, BasModuleT *module) { BasFormRtT *rt = (BasFormRtT *)calloc(1, sizeof(BasFormRtT)); if (!rt) { return NULL; } rt->ctx = ctx; rt->vm = vm; rt->module = module; sFormRt = rt; basFormRtBindVm(rt); return rt; } // ============================================================ // basFormRtCreateCtrl // ============================================================ void *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const char *ctrlName) { (void)ctx; BasFormT *form = (BasFormT *)formRef; if (!form || form->controlCount >= BAS_MAX_CTRLS) { return NULL; } // Resolve VB name to DVX widget type name const char *wgtTypeName = resolveTypeName(typeName); if (!wgtTypeName) { return NULL; } // Create the widget WidgetT *parent = form->contentBox ? form->contentBox : form->root; if (!parent) { return NULL; } WidgetT *widget = createWidget(wgtTypeName, parent); if (!widget) { return NULL; } wgtSetName(widget, ctrlName); // Initialize control entry BasControlT *ctrl = &form->controls[form->controlCount++]; memset(ctrl, 0, sizeof(*ctrl)); snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName); ctrl->widget = widget; ctrl->form = form; ctrl->iface = wgtGetIface(wgtTypeName); // Wire up event callbacks widget->userData = ctrl; widget->onClick = onWidgetClick; widget->onDblClick = onWidgetDblClick; widget->onChange = onWidgetChange; widget->onFocus = onWidgetFocus; widget->onBlur = onWidgetBlur; return ctrl; } // ============================================================ // basFormRtDestroy // ============================================================ void basFormRtDestroy(BasFormRtT *rt) { if (!rt) { return; } if (sFormRt == rt) { sFormRt = NULL; } for (int32_t i = 0; i < rt->formCount; i++) { BasFormT *form = &rt->forms[i]; for (int32_t j = 0; j < form->controlCount; j++) { freeListBoxItems(&form->controls[j]); } if (form->window) { dvxDestroyWindow(rt->ctx, form->window); form->window = NULL; } } free(rt); } // ============================================================ // basFormRtFindCtrl // ============================================================ void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName) { (void)ctx; BasFormT *form = (BasFormT *)formRef; if (!form) { return NULL; } for (int32_t i = 0; i < form->controlCount; i++) { if (strcasecmp(form->controls[i].name, ctrlName) == 0) { return &form->controls[i]; } } return NULL; } // ============================================================ // basFormRtFireEvent // ============================================================ bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName) { if (!rt || !form || !rt->vm || !rt->module) { return false; } char handlerName[MAX_EVENT_NAME_LEN]; snprintf(handlerName, sizeof(handlerName), "%s_%s", ctrlName, eventName); const BasProcEntryT *proc = basModuleFindProc(rt->module, handlerName); if (!proc) { return false; } if (proc->isFunction || proc->paramCount > 0) { return false; } BasFormT *prevForm = rt->currentForm; rt->currentForm = form; basVmSetCurrentForm(rt->vm, form); bool ok = basVmCallSub(rt->vm, proc->codeAddr); rt->currentForm = prevForm; basVmSetCurrentForm(rt->vm, prevForm); return ok; } // ============================================================ // basFormRtGetProp // ============================================================ BasValueT basFormRtGetProp(void *ctx, void *ctrlRef, const char *propName) { (void)ctx; BasControlT *ctrl = (BasControlT *)ctrlRef; if (!ctrl || !ctrl->widget) { return zeroValue(); } // Common properties (Name, Left, Top, Width, Height, Visible, Enabled) bool handled; BasValueT val = getCommonProp(ctrl, propName, &handled); if (handled) { return val; } // "Caption" and "Text" map to wgtGetText for all widgets if (strcasecmp(propName, "Caption") == 0 || strcasecmp(propName, "Text") == 0) { const char *text = wgtGetText(ctrl->widget); return basValStringFromC(text ? text : ""); } // "ListCount" for any widget with item storage if (strcasecmp(propName, "ListCount") == 0) { ListBoxItemsT *lb = getListBoxItems(ctrl); return basValLong(lb ? lb->count : 0); } // Interface descriptor properties if (ctrl->iface) { val = getIfaceProp(ctrl->iface, ctrl->widget, propName, &handled); if (handled) { return val; } } return zeroValue(); } // ============================================================ // basFormRtHideForm // ============================================================ void basFormRtHideForm(void *ctx, void *formRef) { BasFormRtT *rt = (BasFormRtT *)ctx; BasFormT *form = (BasFormT *)formRef; if (!form || !form->window) { return; } form->window->visible = false; dvxInvalidateWindow(rt->ctx, form->window); } // ============================================================ // basFormRtInputBox // ============================================================ static BasStringT *basFormRtInputBox(void *ctx, const char *prompt, const char *title, const char *defaultText) { BasFormRtT *rt = (BasFormRtT *)ctx; char buf[256]; buf[0] = '\0'; if (defaultText && defaultText[0]) { strncpy(buf, defaultText, sizeof(buf) - 1); buf[sizeof(buf) - 1] = '\0'; } if (!dvxInputBox(rt->ctx, title, prompt, defaultText, buf, sizeof(buf))) { return NULL; } return basStringNew(buf, (int32_t)strlen(buf)); } // ============================================================ // basFormRtLoadForm // ============================================================ void *basFormRtLoadForm(void *ctx, const char *formName) { BasFormRtT *rt = (BasFormRtT *)ctx; if (rt->formCount >= BAS_MAX_FORMS) { return NULL; } // Check if form already exists for (int32_t i = 0; i < rt->formCount; i++) { if (strcasecmp(rt->forms[i].name, formName) == 0) { return &rt->forms[i]; } } WindowT *win = dvxCreateWindowCentered(rt->ctx, formName, DEFAULT_FORM_W, DEFAULT_FORM_H, true); if (!win) { return NULL; } WidgetT *root = wgtInitWindow(rt->ctx, win); if (!root) { dvxDestroyWindow(rt->ctx, win); return NULL; } WidgetT *contentBox = wgtVBox(root); if (!contentBox) { dvxDestroyWindow(rt->ctx, win); return NULL; } contentBox->weight = 100; BasFormT *form = &rt->forms[rt->formCount++]; memset(form, 0, sizeof(*form)); snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName); win->onClose = onFormClose; win->onResize = onFormResize; form->window = win; form->root = root; form->contentBox = contentBox; form->ctx = rt->ctx; form->vm = rt->vm; form->module = rt->module; return form; } // ============================================================ // basFormRtLoadFrm // ============================================================ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen) { if (!rt || !source || sourceLen <= 0) { return NULL; } BasFormT *form = NULL; BasControlT *current = NULL; WidgetT *parentStack[MAX_FRM_NESTING]; int32_t nestDepth = 0; // Track Begin/End blocks: true = container (Form/Frame), false = control bool isContainer[MAX_FRM_NESTING]; int32_t blockDepth = 0; const char *pos = source; const char *end = source + sourceLen; while (pos < end) { const char *lineStart = pos; while (pos < end && *pos != '\n' && *pos != '\r') { pos++; } int32_t lineLen = (int32_t)(pos - lineStart); if (pos < end && *pos == '\r') { pos++; } if (pos < end && *pos == '\n') { pos++; } char line[MAX_FRM_LINE_LEN]; if (lineLen >= MAX_FRM_LINE_LEN) { lineLen = MAX_FRM_LINE_LEN - 1; } memcpy(line, lineStart, lineLen); line[lineLen] = '\0'; char *trimmed = line; while (*trimmed == ' ' || *trimmed == '\t') { trimmed++; } if (*trimmed == '\0' || *trimmed == '\'') { continue; } // "VERSION x.xx" -- skip version declaration if (strncasecmp(trimmed, "VERSION ", 8) == 0) { continue; } // "Begin TypeName CtrlName" if (strncasecmp(trimmed, "Begin ", 6) == 0) { char *rest = trimmed + 6; char typeName[BAS_MAX_CTRL_NAME]; char ctrlName[BAS_MAX_CTRL_NAME]; int32_t ti = 0; while (*rest && *rest != ' ' && *rest != '\t' && ti < BAS_MAX_CTRL_NAME - 1) { typeName[ti++] = *rest++; } typeName[ti] = '\0'; while (*rest == ' ' || *rest == '\t') { rest++; } int32_t ci = 0; while (*rest && *rest != ' ' && *rest != '\t' && *rest != '\r' && *rest != '\n' && ci < BAS_MAX_CTRL_NAME - 1) { ctrlName[ci++] = *rest++; } ctrlName[ci] = '\0'; if (strcasecmp(typeName, "Form") == 0) { form = (BasFormT *)basFormRtLoadForm(rt, ctrlName); if (!form) { return NULL; } parentStack[0] = form->contentBox; nestDepth = 1; current = NULL; if (blockDepth < MAX_FRM_NESTING) { isContainer[blockDepth++] = true; } } else if (form && nestDepth > 0) { WidgetT *parent = parentStack[nestDepth - 1]; const char *wgtTypeName = resolveTypeName(typeName); if (!wgtTypeName) { continue; } WidgetT *widget = createWidget(wgtTypeName, parent); if (!widget) { continue; } wgtSetName(widget, ctrlName); if (form->controlCount < BAS_MAX_CTRLS) { current = &form->controls[form->controlCount++]; memset(current, 0, sizeof(*current)); snprintf(current->name, BAS_MAX_CTRL_NAME, "%s", ctrlName); current->widget = widget; current->form = form; current->iface = wgtGetIface(wgtTypeName); widget->userData = current; widget->onClick = onWidgetClick; widget->onDblClick = onWidgetDblClick; widget->onChange = onWidgetChange; widget->onFocus = onWidgetFocus; widget->onBlur = onWidgetBlur; } // Track block type for End handling if (strcasecmp(typeName, "Frame") == 0 && nestDepth < MAX_FRM_NESTING) { // Frame is a container -- children go inside it parentStack[nestDepth++] = widget; if (blockDepth < MAX_FRM_NESTING) { isContainer[blockDepth++] = true; } } else { if (blockDepth < MAX_FRM_NESTING) { isContainer[blockDepth++] = false; } } } continue; } // "End" if (strcasecmp(trimmed, "End") == 0) { if (blockDepth > 0) { blockDepth--; // Only decrement parent nesting for containers (Form/Frame) if (isContainer[blockDepth] && nestDepth > 0) { nestDepth--; } } current = NULL; continue; } // Property assignment: Key = Value char key[BAS_MAX_CTRL_NAME]; char value[MAX_FRM_LINE_LEN]; parseFrmLine(trimmed, key, value); if (key[0] == '\0' || !form) { continue; } if (current) { BasValueT val; if (value[0] == '"') { int32_t vlen = (int32_t)strlen(value); if (vlen >= 2 && value[vlen - 1] == '"') { value[vlen - 1] = '\0'; } val = basValStringFromC(value + 1); } else { val = basValLong(atoi(value)); } basFormRtSetProp(rt, current, key, val); basValRelease(&val); } else if (nestDepth > 0) { // Form-level property if (strcasecmp(key, "Caption") == 0) { char *text = value; if (text[0] == '"') { text++; int32_t len = (int32_t)strlen(text); if (len > 0 && text[len - 1] == '"') { text[len - 1] = '\0'; } } dvxSetTitle(rt->ctx, form->window, text); } } } // Force layout recalculation now that all controls and properties are set if (form) { dvxFitWindow(rt->ctx, form->window); } return form; } // ============================================================ // basFormRtMsgBox // ============================================================ int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags) { BasFormRtT *rt = (BasFormRtT *)ctx; return dvxMessageBox(rt->ctx, "DVX BASIC", message, flags); } // ============================================================ // basFormRtSetProp // ============================================================ void basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT value) { (void)ctx; BasControlT *ctrl = (BasControlT *)ctrlRef; if (!ctrl || !ctrl->widget) { return; } // Common properties if (setCommonProp(ctrl, propName, value)) { return; } // "Caption" and "Text" map to wgtSetText for all widgets. // Copy to persistent buffer since some widgets (Button, Label) // store the text pointer without copying. if (strcasecmp(propName, "Caption") == 0 || strcasecmp(propName, "Text") == 0) { BasStringT *s = basValFormatString(value); snprintf(ctrl->textBuf, BAS_MAX_TEXT_BUF, "%s", s->data); basStringUnref(s); wgtSetText(ctrl->widget, ctrl->textBuf); return; } // Interface descriptor properties if (ctrl->iface) { if (setIfaceProp(ctrl->iface, ctrl->widget, propName, value)) { return; } } } // ============================================================ // basFormRtShowForm // ============================================================ void basFormRtShowForm(void *ctx, void *formRef, bool modal) { BasFormRtT *rt = (BasFormRtT *)ctx; BasFormT *form = (BasFormT *)formRef; if (!form || !form->window) { return; } form->window->visible = true; dvxFitWindow(rt->ctx, form->window); dvxInvalidateWindow(rt->ctx, form->window); if (modal) { rt->ctx->modalWindow = form->window; } basFormRtFireEvent(rt, form, form->name, "Load"); } // ============================================================ // basFormRtUnloadForm // ============================================================ void basFormRtUnloadForm(void *ctx, void *formRef) { BasFormRtT *rt = (BasFormRtT *)ctx; BasFormT *form = (BasFormT *)formRef; if (!form) { return; } basFormRtFireEvent(rt, form, form->name, "Unload"); for (int32_t i = 0; i < form->controlCount; i++) { freeListBoxItems(&form->controls[i]); } if (form->window) { if (rt->ctx->modalWindow == form->window) { rt->ctx->modalWindow = NULL; } dvxDestroyWindow(rt->ctx, form->window); form->window = NULL; form->root = NULL; form->contentBox = NULL; } int32_t idx = (int32_t)(form - rt->forms); if (idx >= 0 && idx < rt->formCount) { rt->formCount--; if (idx < rt->formCount) { rt->forms[idx] = rt->forms[rt->formCount]; } memset(&rt->forms[rt->formCount], 0, sizeof(BasFormT)); } } // ============================================================ // callCommonMethod // ============================================================ static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc) { (void)args; (void)argc; if (strcasecmp(methodName, "SetFocus") == 0) { wgtSetFocused(ctrl->widget); return zeroValue(); } if (strcasecmp(methodName, "Refresh") == 0) { wgtInvalidatePaint(ctrl->widget); return zeroValue(); } return zeroValue(); } // ============================================================ // callListBoxMethod // ============================================================ // // Handles AddItem/RemoveItem/Clear/List for any widget that // supports item lists (listbox, combobox, dropdown). static BasValueT callListBoxMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc) { if (strcasecmp(methodName, "AddItem") == 0) { if (argc >= 1 && args[0].type == BAS_TYPE_STRING && args[0].strVal) { ListBoxItemsT *lb = getListBoxItems(ctrl); if (lb && lb->count < MAX_LISTBOX_ITEMS) { lb->items[lb->count] = strdup(args[0].strVal->data); lb->count++; rebuildListBoxItems(ctrl); } } return zeroValue(); } if (strcasecmp(methodName, "RemoveItem") == 0) { if (argc >= 1) { int32_t idx = (int32_t)basValToNumber(args[0]); ListBoxItemsT *lb = getListBoxItems(ctrl); if (lb && idx >= 0 && idx < lb->count) { free(lb->items[idx]); for (int32_t i = idx; i < lb->count - 1; i++) { lb->items[i] = lb->items[i + 1]; } lb->count--; rebuildListBoxItems(ctrl); } } return zeroValue(); } if (strcasecmp(methodName, "Clear") == 0) { ListBoxItemsT *lb = getListBoxItems(ctrl); if (lb) { for (int32_t i = 0; i < lb->count; i++) { free(lb->items[i]); } lb->count = 0; rebuildListBoxItems(ctrl); } return zeroValue(); } if (strcasecmp(methodName, "List") == 0) { if (argc >= 1) { int32_t idx = (int32_t)basValToNumber(args[0]); ListBoxItemsT *lb = getListBoxItems(ctrl); if (lb && idx >= 0 && idx < lb->count) { return basValStringFromC(lb->items[idx]); } } return basValStringFromC(""); } return zeroValue(); } // ============================================================ // createWidget // ============================================================ // // Create a DVX widget by type name using its registered API. // The API's create function signature varies by widget type, so // we call with sensible defaults for each creation pattern. static WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent) { const void *api = wgtGetApi(wgtTypeName); if (!api) { return NULL; } // All widget APIs have create as the first function pointer. // The signature varies but the first arg is always parent. // We handle the common patterns. typedef WidgetT *(*CreateParentFnT)(WidgetT *); typedef WidgetT *(*CreateParentTextFnT)(WidgetT *, const char *); typedef WidgetT *(*CreateParentIntFnT)(WidgetT *, int32_t); typedef WidgetT *(*CreateParentIntIntFnT)(WidgetT *, int32_t, int32_t); typedef WidgetT *(*CreateParentIntBoolFnT)(WidgetT *, int32_t, bool); typedef WidgetT *(*CreateParentIntIntIntFnT)(WidgetT *, int32_t, int32_t, int32_t); // Determine creation pattern by widget type name if (strcasecmp(wgtTypeName, "button") == 0 || strcasecmp(wgtTypeName, "checkbox") == 0 || strcasecmp(wgtTypeName, "label") == 0) { // create(parent, text) CreateParentTextFnT fn = *(CreateParentTextFnT *)api; return fn(parent, ""); } if (strcasecmp(wgtTypeName, "textinput") == 0 || strcasecmp(wgtTypeName, "combobox") == 0) { // create(parent, maxLen) CreateParentIntFnT fn = *(CreateParentIntFnT *)api; return fn(parent, DEFAULT_CREATE_ARG); } if (strcasecmp(wgtTypeName, "slider") == 0) { // create(parent, minVal, maxVal) CreateParentIntIntFnT fn = *(CreateParentIntIntFnT *)api; return fn(parent, 0, 100); } if (strcasecmp(wgtTypeName, "spinner") == 0) { // create(parent, minVal, maxVal, step) CreateParentIntIntIntFnT fn = *(CreateParentIntIntIntFnT *)api; return fn(parent, 0, 100, 1); } if (strcasecmp(wgtTypeName, "timer") == 0) { // create(parent, intervalMs, repeat) CreateParentIntBoolFnT fn = *(CreateParentIntBoolFnT *)api; return fn(parent, 1000, true); } if (strcasecmp(wgtTypeName, "box") == 0) { // box API: first fn is vbox(parent), second is hbox, third is frame // For BASIC "Frame", use frame (third slot) typedef struct { WidgetT *(*vbox)(WidgetT *); WidgetT *(*hbox)(WidgetT *); WidgetT *(*frame)(WidgetT *, const char *); } BoxApiPatternT; const BoxApiPatternT *boxApi = (const BoxApiPatternT *)api; return boxApi->frame(parent, ""); } // Default: assume create(parent) with no extra args CreateParentFnT fn = *(CreateParentFnT *)api; return fn(parent); } // ============================================================ // freeListBoxItems // ============================================================ static void freeListBoxItems(BasControlT *ctrl) { for (int32_t i = 0; i < sAuxDataCount; i++) { if (sAuxData[i].ctrl == ctrl) { ListBoxItemsT *lb = sAuxData[i].items; if (lb) { for (int32_t j = 0; j < lb->count; j++) { free(lb->items[j]); } free(lb); } sAuxDataCount--; if (i < sAuxDataCount) { sAuxData[i] = sAuxData[sAuxDataCount]; } memset(&sAuxData[sAuxDataCount], 0, sizeof(AuxDataEntryT)); return; } } } // ============================================================ // getCommonProp // ============================================================ static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled) { *handled = true; if (strcasecmp(propName, "Name") == 0) { return basValStringFromC(ctrl->name); } if (strcasecmp(propName, "Left") == 0) { return basValLong(ctrl->widget->x); } if (strcasecmp(propName, "Top") == 0) { return basValLong(ctrl->widget->y); } if (strcasecmp(propName, "Width") == 0) { return basValLong(ctrl->widget->w); } if (strcasecmp(propName, "Height") == 0) { return basValLong(ctrl->widget->h); } if (strcasecmp(propName, "Visible") == 0) { return basValBool(ctrl->widget->visible); } if (strcasecmp(propName, "Enabled") == 0) { return basValBool(ctrl->widget->enabled); } if (strcasecmp(propName, "TabIndex") == 0) { return basValLong(0); } if (strcasecmp(propName, "BackColor") == 0) { return basValLong((int32_t)ctrl->widget->bgColor); } if (strcasecmp(propName, "ForeColor") == 0) { return basValLong((int32_t)ctrl->widget->fgColor); } *handled = false; return zeroValue(); } // ============================================================ // getIfaceProp // ============================================================ // // Look up a property in the interface descriptor and call its // getter, marshaling the return value to BasValueT. static BasValueT getIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, bool *handled) { *handled = false; for (int32_t i = 0; i < iface->propCount; i++) { if (strcasecmp(iface->props[i].name, propName) != 0) { continue; } *handled = true; const WgtPropDescT *p = &iface->props[i]; if (!p->getFn) { return zeroValue(); } switch (p->type) { case WGT_IFACE_STRING: { const char *s = ((const char *(*)(WidgetT *))p->getFn)(w); return basValStringFromC(s ? s : ""); } case WGT_IFACE_INT: { int32_t v = ((int32_t (*)(const WidgetT *))p->getFn)(w); return basValLong(v); } case WGT_IFACE_BOOL: { bool v = ((bool (*)(const WidgetT *))p->getFn)(w); return basValBool(v); } case WGT_IFACE_FLOAT: { float v = ((float (*)(const WidgetT *))p->getFn)(w); return basValSingle(v); } } return zeroValue(); } return zeroValue(); } // ============================================================ // getListBoxItems // ============================================================ static ListBoxItemsT *getListBoxItems(BasControlT *ctrl) { for (int32_t i = 0; i < sAuxDataCount; i++) { if (sAuxData[i].ctrl == ctrl) { return sAuxData[i].items; } } if (sAuxDataCount >= MAX_AUX_DATA) { return NULL; } ListBoxItemsT *lb = (ListBoxItemsT *)calloc(1, sizeof(ListBoxItemsT)); if (!lb) { return NULL; } sAuxData[sAuxDataCount].ctrl = ctrl; sAuxData[sAuxDataCount].items = lb; sAuxDataCount++; return lb; } // ============================================================ // onFormClose // ============================================================ // // Called when the user closes a BASIC form's window. Fires the // Form_Unload event and stops the VM so the program exits. static void onFormClose(WindowT *win) { // Find which form owns this window // The window's userData stores nothing useful, so we search // by window pointer. We get the form runtime from sFormRt. if (!sFormRt) { return; } for (int32_t i = 0; i < sFormRt->formCount; i++) { BasFormT *form = &sFormRt->forms[i]; if (form->window == win) { basFormRtFireEvent(sFormRt, form, form->name, "Unload"); // Free control resources for (int32_t j = 0; j < form->controlCount; j++) { freeListBoxItems(&form->controls[j]); } // Destroy the window if (sFormRt->ctx->modalWindow == win) { sFormRt->ctx->modalWindow = NULL; } dvxDestroyWindow(sFormRt->ctx, win); form->window = NULL; form->root = NULL; form->contentBox = NULL; // Remove from form list sFormRt->formCount--; if (i < sFormRt->formCount) { sFormRt->forms[i] = sFormRt->forms[sFormRt->formCount]; } memset(&sFormRt->forms[sFormRt->formCount], 0, sizeof(BasFormT)); // If no forms left, stop the VM if (sFormRt->formCount == 0 && sFormRt->vm) { sFormRt->vm->running = false; } return; } } } // ============================================================ // onFormResize // ============================================================ static void onFormResize(WindowT *win, int32_t newW, int32_t newH) { (void)newW; (void)newH; if (!sFormRt) { return; } for (int32_t i = 0; i < sFormRt->formCount; i++) { BasFormT *form = &sFormRt->forms[i]; if (form->window == win) { basFormRtFireEvent(sFormRt, form, form->name, "Resize"); return; } } } // ============================================================ // onWidgetBlur // ============================================================ static void onWidgetBlur(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form) { return; } BasFormRtT *rt = NULL; if (ctrl->form->vm) { rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; } if (rt) { basFormRtFireEvent(rt, ctrl->form, ctrl->name, "LostFocus"); } } // ============================================================ // onWidgetChange // ============================================================ static void onWidgetChange(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form) { return; } BasFormRtT *rt = NULL; if (ctrl->form->vm) { rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; } if (rt) { basFormRtFireEvent(rt, ctrl->form, ctrl->name, "Change"); } } // ============================================================ // onWidgetClick // ============================================================ static void onWidgetClick(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form) { return; } BasFormRtT *rt = NULL; if (ctrl->form->vm) { rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; } if (rt) { basFormRtFireEvent(rt, ctrl->form, ctrl->name, "Click"); } } // ============================================================ // onWidgetDblClick // ============================================================ static void onWidgetDblClick(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form) { return; } BasFormRtT *rt = NULL; if (ctrl->form->vm) { rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; } if (rt) { basFormRtFireEvent(rt, ctrl->form, ctrl->name, "DblClick"); } } // ============================================================ // onWidgetFocus // ============================================================ static void onWidgetFocus(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form) { return; } BasFormRtT *rt = NULL; if (ctrl->form->vm) { rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; } if (rt) { basFormRtFireEvent(rt, ctrl->form, ctrl->name, "GotFocus"); } } // ============================================================ // parseFrmLine // ============================================================ static void parseFrmLine(const char *line, char *key, char *value) { key[0] = '\0'; value[0] = '\0'; while (*line == ' ' || *line == '\t') { line++; } int32_t ki = 0; while (*line && *line != '=' && *line != ' ' && *line != '\t' && ki < BAS_MAX_CTRL_NAME - 1) { key[ki++] = *line++; } key[ki] = '\0'; while (*line == ' ' || *line == '\t') { line++; } if (*line == '=') { line++; } while (*line == ' ' || *line == '\t') { line++; } int32_t vi = 0; while (*line && *line != '\r' && *line != '\n' && vi < MAX_FRM_LINE_LEN - 1) { value[vi++] = *line++; } value[vi] = '\0'; while (vi > 0 && (value[vi - 1] == ' ' || value[vi - 1] == '\t')) { value[--vi] = '\0'; } } // ============================================================ // rebuildListBoxItems // ============================================================ static void rebuildListBoxItems(BasControlT *ctrl) { ListBoxItemsT *lb = getListBoxItems(ctrl); if (!lb) { return; } // Use the widget's setItems API if available const WgtIfaceT *iface = ctrl->iface; if (!iface) { return; } // Look for a setItems-like method by checking the widget API directly const void *api = wgtGetApi("listbox"); if (api) { // ListBoxApiT has setItems as the second function pointer typedef struct { void *create; void (*setItems)(WidgetT *, const char **, int32_t); } SetItemsPatternT; const SetItemsPatternT *p = (const SetItemsPatternT *)api; p->setItems(ctrl->widget, (const char **)lb->items, lb->count); return; } // Fallback: try combobox or dropdown API api = wgtGetApi("combobox"); if (api) { typedef struct { void *create; void (*setItems)(WidgetT *, const char **, int32_t); } SetItemsPatternT; const SetItemsPatternT *p = (const SetItemsPatternT *)api; p->setItems(ctrl->widget, (const char **)lb->items, lb->count); return; } api = wgtGetApi("dropdown"); if (api) { typedef struct { void *create; void (*setItems)(WidgetT *, const char **, int32_t); } SetItemsPatternT; const SetItemsPatternT *p = (const SetItemsPatternT *)api; p->setItems(ctrl->widget, (const char **)lb->items, lb->count); } } // ============================================================ // resolveTypeName // ============================================================ // // Resolve a type name (VB-style or DVX widget name) to a DVX // widget type name. First tries wgtFindByBasName for VB names // like "CommandButton", then falls back to direct widget name. static const char *resolveTypeName(const char *typeName) { // Try VB name first (e.g. "CommandButton" -> "button") const char *wgtName = wgtFindByBasName(typeName); if (wgtName) { return wgtName; } // Try as direct widget type name (e.g. "button", "slider") if (wgtGetApi(typeName)) { return typeName; } return NULL; } // ============================================================ // setCommonProp // ============================================================ static bool setCommonProp(BasControlT *ctrl, const char *propName, BasValueT value) { if (strcasecmp(propName, "Left") == 0) { ctrl->widget->x = (int32_t)basValToNumber(value); wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "Top") == 0) { ctrl->widget->y = (int32_t)basValToNumber(value); wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "Width") == 0) { int32_t w = (int32_t)basValToNumber(value); ctrl->widget->prefW = wgtPixels(w); wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "Height") == 0) { int32_t h = (int32_t)basValToNumber(value); ctrl->widget->prefH = wgtPixels(h); wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "Visible") == 0) { wgtSetVisible(ctrl->widget, basValIsTruthy(value)); return true; } if (strcasecmp(propName, "Enabled") == 0) { wgtSetEnabled(ctrl->widget, basValIsTruthy(value)); return true; } if (strcasecmp(propName, "TabIndex") == 0) { return true; } if (strcasecmp(propName, "BackColor") == 0) { ctrl->widget->bgColor = (uint32_t)(int32_t)basValToNumber(value); wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "ForeColor") == 0) { ctrl->widget->fgColor = (uint32_t)(int32_t)basValToNumber(value); wgtInvalidate(ctrl->widget); return true; } return false; } // ============================================================ // setIfaceProp // ============================================================ // // Look up a property in the interface descriptor and call its // setter, marshaling the BasValueT to the native type. static bool setIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, BasValueT value) { for (int32_t i = 0; i < iface->propCount; i++) { if (strcasecmp(iface->props[i].name, propName) != 0) { continue; } const WgtPropDescT *p = &iface->props[i]; if (!p->setFn) { return true; // read-only, but recognized } switch (p->type) { case WGT_IFACE_STRING: { BasStringT *s = basValFormatString(value); ((void (*)(WidgetT *, const char *))p->setFn)(w, s->data); basStringUnref(s); break; } case WGT_IFACE_INT: ((void (*)(WidgetT *, int32_t))p->setFn)(w, (int32_t)basValToNumber(value)); break; case WGT_IFACE_BOOL: ((void (*)(WidgetT *, bool))p->setFn)(w, basValIsTruthy(value)); break; case WGT_IFACE_FLOAT: ((void (*)(WidgetT *, float))p->setFn)(w, (float)basValToNumber(value)); break; } return true; } return false; } // ============================================================ // zeroValue // ============================================================ static BasValueT zeroValue(void) { BasValueT v; memset(&v, 0, sizeof(v)); return v; }