// 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 "formcfm.h" #include "../compiler/opcodes.h" #include "dvxDlg.h" #include "dvxRes.h" #include "dvxWm.h" #include "box/box.h" #include "ansiTerm/ansiTerm.h" #include "dataCtrl/dataCtrl.h" #include "dbGrid/dbGrid.h" #include "thirdparty/stb_ds_wrap.h" #include #include #include #include #include #include // ============================================================ // Defines // ============================================================ #define DEFAULT_FORM_W 400 #define DEFAULT_FORM_H 300 #define MAX_EVENT_NAME_LEN 128 #define MAX_FRM_LINE_LEN 512 // Module-level form runtime pointer for onFormClose callback static BasFormRtT *sFormRt = NULL; // ============================================================ // basModuleFindProc -- find a SUB/FUNCTION by name in the module // ============================================================ static const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name) { if (!mod || !mod->procs || !name) { return NULL; } for (int32_t i = 0; i < mod->procCount; i++) { if (strcasecmp(mod->procs[i].name, name) == 0) { return &mod->procs[i]; } } return NULL; } // ============================================================ // basFormRtCreateContentBox -- create layout container for a form // ============================================================ WidgetT *basFormRtCreateContentBox(WidgetT *root, const char *layout) { if (!layout || !layout[0] || strcasecmp(layout, "VBox") == 0) { return root; } const char *wgtName = wgtFindByBasName(layout); if (wgtName) { const WgtIfaceT *iface = wgtGetIface(wgtName); if (iface && iface->isContainer && iface->createSig == WGT_CREATE_PARENT) { const void *api = wgtGetApi(wgtName); if (api) { WidgetT *(*createFn)(WidgetT *) = *(WidgetT *(*const *)(WidgetT *))api; WidgetT *box = createFn(root); box->weight = 100; return box; } } } return root; } // ============================================================ // basFormRtCreateFormWindow -- create a DVX window for a form // ============================================================ WindowT *basFormRtCreateFormWindow(AppContextT *ctx, const char *title, const char *layout, bool resizable, bool centered, bool autoSize, int32_t width, int32_t height, int32_t left, int32_t top, WidgetT **outRoot, WidgetT **outContentBox) { int32_t defW = (width > 0) ? width : DEFAULT_FORM_W; int32_t defH = (height > 0) ? height : DEFAULT_FORM_H; WindowT *win = dvxCreateWindowCentered(ctx, title, defW, defH, resizable); if (!win) { return NULL; } win->visible = false; WidgetT *root = wgtInitWindow(ctx, win); if (!root) { dvxDestroyWindow(ctx, win); return NULL; } WidgetT *contentBox = basFormRtCreateContentBox(root, layout); if (autoSize) { dvxFitWindow(ctx, win); } else if (width > 0 && height > 0) { dvxResizeWindow(ctx, win, width, height); } if (centered) { win->x = (ctx->display.width - win->w) / 2; win->y = (ctx->display.height - win->h) / 2; } else if (left > 0 || top > 0) { win->x = left; win->y = top; } *outRoot = root; *outContentBox = contentBox; return win; } // ============================================================ // Prototypes // ============================================================ static BasStringT *basFormRtInputBox(void *ctx, const char *prompt, const char *title, const char *defaultText); static void onFormMenu(WindowT *win, int32_t menuId); static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc); WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent); static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled); static BasValueT getIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, bool *handled); static void onFormActivate(WindowT *win); static void onFormClose(WindowT *win); static void onFormDeactivate(WindowT *win); static void onFormResize(WindowT *win, int32_t newW, int32_t newH); static void onWidgetBlur(WidgetT *w); static bool onWidgetValidate(WidgetT *w); static void onWidgetChange(WidgetT *w); static void onWidgetClick(WidgetT *w); static void onWidgetDblClick(WidgetT *w); static void onWidgetFocus(WidgetT *w); static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii); static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift); static void onWidgetKeyUp(WidgetT *w, int32_t keyCode, int32_t shift); static void onWidgetMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y); static void onWidgetMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y); static void onWidgetMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y); static void onWidgetScroll(WidgetT *w, int32_t delta); static void parseFrmLine(const char *line, char *key, char *value); 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 void refreshDetailControls(BasFormT *form, BasControlT *masterCtrl); static void updateBoundControls(BasFormT *form, BasControlT *dataCtrl); 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.findCtrlIdx = basFormRtFindCtrlIdx; ui.loadForm = basFormRtLoadForm; ui.unloadForm = basFormRtUnloadForm; ui.showForm = basFormRtShowForm; ui.hideForm = basFormRtHideForm; ui.msgBox = basFormRtMsgBox; ui.inputBox = basFormRtInputBox; ui.createForm = basFormRtCreateForm; ui.createCtrlEx = basFormRtCreateCtrlEx; ui.setEvent = basFormRtSetEvent; ui.removeCtrl = basFormRtRemoveCtrl; 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(); } // 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); } case WGT_SIG_INT3: if (argc >= 3) { ((void (*)(WidgetT *, int32_t, int32_t, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]), (int32_t)basValToNumber(args[1]), (int32_t)basValToNumber(args[2])); } return zeroValue(); case WGT_SIG_INT4: if (argc >= 4) { ((void (*)(WidgetT *, int32_t, int32_t, int32_t, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]), (int32_t)basValToNumber(args[1]), (int32_t)basValToNumber(args[2]), (int32_t)basValToNumber(args[3])); } return zeroValue(); case WGT_SIG_RET_INT_INT_INT: { if (argc >= 2) { int32_t v = ((int32_t (*)(const WidgetT *, int32_t, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]), (int32_t)basValToNumber(args[1])); return basValLong(v); } return zeroValue(); } case WGT_SIG_INT5: if (argc >= 5) { ((void (*)(WidgetT *, int32_t, int32_t, int32_t, int32_t, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]), (int32_t)basValToNumber(args[1]), (int32_t)basValToNumber(args[2]), (int32_t)basValToNumber(args[3]), (int32_t)basValToNumber(args[4])); } return zeroValue(); case WGT_SIG_RET_STR_INT: { if (argc >= 1) { const char *s = ((const char *(*)(const WidgetT *, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0])); return basValStringFromC(s ? s : ""); } return basValStringFromC(""); } case WGT_SIG_RET_STR_INT_INT: { if (argc >= 2) { const char *s = ((const char *(*)(const WidgetT *, int32_t, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]), (int32_t)basValToNumber(args[1])); return basValStringFromC(s ? s : ""); } return basValStringFromC(""); } case WGT_SIG_INT_INT_STR: if (argc >= 3) { BasStringT *s = basValFormatString(args[2]); ((void (*)(WidgetT *, int32_t, int32_t, const char *))m->fn)(w, (int32_t)basValToNumber(args[0]), (int32_t)basValToNumber(args[1]), s->data); basStringUnref(s); } return zeroValue(); case WGT_SIG_STR_BOOL_BOOL: { if (argc >= 3) { BasStringT *s = basValFormatString(args[0]); bool v = ((bool (*)(WidgetT *, const char *, bool, bool))m->fn)(w, s->data, basValIsTruthy(args[1]), basValIsTruthy(args[2])); basStringUnref(s); return basValBool(v); } return basValBool(false); } case WGT_SIG_STR_STR_BOOL: { if (argc >= 3) { BasStringT *s1 = basValFormatString(args[0]); BasStringT *s2 = basValFormatString(args[1]); int32_t v = ((int32_t (*)(WidgetT *, const char *, const char *, bool))m->fn)(w, s1->data, s2->data, basValIsTruthy(args[2])); basStringUnref(s1); basStringUnref(s2); return basValLong(v); } return zeroValue(); } case WGT_SIG_RET_STR: { const char *s = ((const char *(*)(const WidgetT *))m->fn)(w); return basValStringFromC(s ? s : ""); } case WGT_SIG_STR_INT: if (argc >= 2) { BasStringT *s = basValFormatString(args[0]); ((void (*)(WidgetT *, const char *, int32_t))m->fn)(w, s->data, (int32_t)basValToNumber(args[1])); basStringUnref(s); } return zeroValue(); case WGT_SIG_INT_STR: if (argc >= 2) { BasStringT *s = basValFormatString(args[1]); ((void (*)(WidgetT *, int32_t, const char *))m->fn)(w, (int32_t)basValToNumber(args[0]), s->data); basStringUnref(s); } return zeroValue(); } } } // 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; } // ============================================================ // basFormRtLoadAllForms -- load all cached forms and show startup // ============================================================ void basFormRtLoadAllForms(BasFormRtT *rt, const char *startupFormName) { if (!rt) { return; } // Load .frm text cache entries (skip if already loaded) for (int32_t i = 0; i < rt->frmCacheCount; i++) { basFormRtLoadForm(rt, rt->frmCache[i].formName); } // Load compiled form cache entries (skip if already loaded) for (int32_t i = 0; i < rt->cfmCacheCount; i++) { basFormRtLoadForm(rt, rt->cfmCache[i].formName); } // Show the startup form (named or first) int32_t formCount = (int32_t)arrlen(rt->forms); if (formCount > 0) { BasFormT *startup = rt->forms[0]; if (startupFormName && startupFormName[0]) { for (int32_t i = 0; i < formCount; i++) { if (strcasecmp(rt->forms[i]->name, startupFormName) == 0) { startup = rt->forms[i]; break; } } } basFormRtShowForm(rt, startup, false); } } // ============================================================ // basFormRtEventLoop -- VB-style event pump until all forms close // ============================================================ void basFormRtEventLoop(BasFormRtT *rt) { if (!rt || !rt->ctx || !rt->vm) { return; } while (rt->ctx->running && arrlen(rt->forms) > 0) { if (!dvxUpdate(rt->ctx)) { break; } } } // ============================================================ // basFormRtRunSimple -- non-debug execution for standalone apps // ============================================================ #define STUB_STEP_SLICE 10000 void basFormRtRunSimple(BasFormRtT *rt) { if (!rt || !rt->vm) { dvxLog("basFormRtRunSimple: no rt or vm"); return; } BasVmT *vm = rt->vm; basVmSetStepLimit(vm, STUB_STEP_SLICE); BasVmResultE result; do { result = basVmRun(vm); if (result == BAS_VM_STEP_LIMIT) { if (!dvxUpdate(rt->ctx)) { break; } } else if (result == BAS_VM_YIELDED) { // DoEvents returned, continue } else if (result == BAS_VM_ERROR) { const char *errMsg = basVmGetError(vm); char buf[512]; snprintf(buf, sizeof(buf), "Runtime error:\n%s", errMsg ? errMsg : "Unknown error"); dvxMessageBox(rt->ctx, "Error", buf, 0); break; } else { break; } } while (1); // VB-style event loop: keep alive while forms are open if (result == BAS_VM_HALTED) { basFormRtEventLoop(rt); } } // ============================================================ // basFormRtCreateCtrl // ============================================================ void *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const char *ctrlName) { (void)ctx; BasFormT *form = (BasFormT *)formRef; if (!form) { 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 = (BasControlT *)calloc(1, sizeof(BasControlT)); if (!ctrl) { return NULL; } ctrl->index = -1; snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName); ctrl->widget = widget; ctrl->form = form; ctrl->iface = wgtGetIface(wgtTypeName); arrput(form->controls, ctrl); // Wire up event callbacks widget->userData = ctrl; widget->onClick = onWidgetClick; widget->onDblClick = onWidgetDblClick; widget->onChange = onWidgetChange; widget->onFocus = onWidgetFocus; widget->onBlur = onWidgetBlur; widget->onValidate = onWidgetValidate; return ctrl; } // ============================================================ // basFormRtCreateCtrlEx -- create control with explicit parent // ============================================================ void *basFormRtCreateCtrlEx(void *ctx, void *formRef, const char *typeName, const char *ctrlName, void *parentRef) { (void)ctx; BasFormT *form = (BasFormT *)formRef; if (!form) { return NULL; } const char *wgtTypeName = resolveTypeName(typeName); if (!wgtTypeName) { return NULL; } // Determine parent widget: if parentRef is a control, use its widget; // otherwise fall back to the form's content box. WidgetT *parent = NULL; if (parentRef) { BasControlT *parentCtrl = (BasControlT *)parentRef; parent = parentCtrl->widget; } if (!parent) { parent = form->contentBox ? form->contentBox : form->root; } if (!parent) { return NULL; } WidgetT *widget = createWidget(wgtTypeName, parent); if (!widget) { return NULL; } wgtSetName(widget, ctrlName); BasControlT *ctrl = (BasControlT *)calloc(1, sizeof(BasControlT)); if (!ctrl) { return NULL; } ctrl->index = -1; snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName); ctrl->widget = widget; ctrl->form = form; ctrl->iface = wgtGetIface(wgtTypeName); arrput(form->controls, ctrl); widget->userData = ctrl; widget->onClick = onWidgetClick; widget->onDblClick = onWidgetDblClick; widget->onChange = onWidgetChange; widget->onFocus = onWidgetFocus; widget->onBlur = onWidgetBlur; widget->onValidate = onWidgetValidate; return ctrl; } // ============================================================ // basFormRtCreateForm -- create a form programmatically // ============================================================ void *basFormRtCreateForm(void *ctx, const char *formName, int32_t width, int32_t height) { BasFormRtT *rt = (BasFormRtT *)ctx; if (!rt || !formName) { return NULL; } // Check if form already exists for (int32_t i = 0; i < (int32_t)arrlen(rt->forms); i++) { if (strcasecmp(rt->forms[i]->name, formName) == 0) { return rt->forms[i]; } } if (width <= 0) { width = DEFAULT_FORM_W; } if (height <= 0) { height = DEFAULT_FORM_H; } WidgetT *root; WidgetT *contentBox; WindowT *win = basFormRtCreateFormWindow(rt->ctx, formName, "VBox", true, true, false, width, height, 0, 0, &root, &contentBox); if (!win) { return NULL; } BasFormT *form = (BasFormT *)calloc(1, sizeof(BasFormT)); arrput(rt->forms, form); snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName); snprintf(form->frmLayout, sizeof(form->frmLayout), "VBox"); form->frmWidth = width; form->frmHeight = height; form->frmResizable = true; form->frmCentered = true; win->onClose = onFormClose; win->onResize = onFormResize; win->onFocus = onFormActivate; win->onBlur = onFormDeactivate; form->window = win; form->root = root; form->contentBox = contentBox; form->ctx = rt->ctx; form->vm = rt->vm; form->module = rt->module; // Synthetic control for form-level property access memset(&form->formCtrl, 0, sizeof(form->formCtrl)); snprintf(form->formCtrl.name, BAS_MAX_CTRL_NAME, "%s", formName); form->formCtrl.widget = root; form->formCtrl.form = form; // Allocate per-form variable storage from module metadata if (rt->module && rt->module->formVarInfo) { for (int32_t j = 0; j < rt->module->formVarInfoCount; j++) { if (strcasecmp(rt->module->formVarInfo[j].formName, formName) == 0) { int32_t vc = rt->module->formVarInfo[j].varCount; if (vc > 0) { form->formVars = (BasValueT *)calloc(vc, sizeof(BasValueT)); form->formVarCount = vc; } int32_t initAddr = rt->module->formVarInfo[j].initCodeAddr; if (initAddr >= 0 && rt->vm) { basVmSetCurrentForm(rt->vm, form); basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount); basVmCallSub(rt->vm, initAddr); basVmSetCurrentForm(rt->vm, NULL); basVmSetCurrentFormVars(rt->vm, NULL, 0); } break; } } } return form; } // ============================================================ // basFormRtRemoveCtrl -- remove a control from a form // ============================================================ void basFormRtRemoveCtrl(void *ctx, void *formRef, const char *ctrlName) { (void)ctx; BasFormT *form = (BasFormT *)formRef; if (!form || !ctrlName) { return; } for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { if (strcasecmp(form->controls[i]->name, ctrlName) == 0) { BasControlT *ctrl = form->controls[i]; // Destroy the widget if (ctrl->widget) { wgtDestroy(ctrl->widget); } free(ctrl); arrdel(form->controls, i); return; } } } // ============================================================ // basFormRtSetEvent -- override event handler for a control // ============================================================ void basFormRtSetEvent(void *ctx, void *ctrlRef, const char *eventName, const char *handlerName) { (void)ctx; BasControlT *ctrl = (BasControlT *)ctrlRef; if (!ctrl || !eventName || !handlerName) { return; } // Check for existing override on this event and update it for (int32_t i = 0; i < ctrl->eventOverrideCount; i++) { if (strcasecmp(ctrl->eventOverrides[i].eventName, eventName) == 0) { snprintf(ctrl->eventOverrides[i].handlerName, BAS_MAX_CTRL_NAME, "%s", handlerName); return; } } // Add new override if (ctrl->eventOverrideCount < BAS_MAX_EVENT_OVERRIDES) { BasEventOverrideT *ov = &ctrl->eventOverrides[ctrl->eventOverrideCount++]; snprintf(ov->eventName, BAS_MAX_CTRL_NAME, "%s", eventName); snprintf(ov->handlerName, BAS_MAX_CTRL_NAME, "%s", handlerName); } } // ============================================================ // basFormRtDestroy // ============================================================ void basFormRtDestroy(BasFormRtT *rt) { if (!rt) { return; } if (sFormRt == rt) { sFormRt = NULL; } for (int32_t i = 0; i < (int32_t)arrlen(rt->forms); i++) { BasFormT *form = rt->forms[i]; if (form->formVars) { for (int32_t j = 0; j < form->formVarCount; j++) { basValRelease(&form->formVars[j]); } free(form->formVars); } for (int32_t j = 0; j < (int32_t)arrlen(form->controls); j++) { free(form->controls[j]); } arrfree(form->controls); for (int32_t j = 0; j < form->menuIdMapCount; j++) { free(form->menuIdMap[j].proxy); } arrfree(form->menuIdMap); if (form->window) { dvxDestroyWindow(rt->ctx, form->window); form->window = NULL; } free(form); } arrfree(rt->forms); // Free .frm source cache for (int32_t i = 0; i < rt->frmCacheCount; i++) { free(rt->frmCache[i].frmSource); } arrfree(rt->frmCache); free(rt); } // ============================================================ // basFormRtFindCtrl // ============================================================ void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName) { BasFormRtT *rt = (BasFormRtT *)ctx; BasFormT *form = (BasFormT *)formRef; if (!form) { return NULL; } // Check if the name refers to the current form itself if (strcasecmp(form->name, ctrlName) == 0) { return &form->formCtrl; } // Search controls on the current form for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { if (strcasecmp(form->controls[i]->name, ctrlName) == 0) { return form->controls[i]; } } // Search menu items on the current form for (int32_t i = 0; i < form->menuIdMapCount; i++) { if (strcasecmp(form->menuIdMap[i].name, ctrlName) == 0) { // Create proxy on first access if (!form->menuIdMap[i].proxy) { BasControlT *proxy = (BasControlT *)calloc(1, sizeof(BasControlT)); snprintf(proxy->name, BAS_MAX_CTRL_NAME, "%s", ctrlName); proxy->form = form; proxy->menuId = form->menuIdMap[i].id; form->menuIdMap[i].proxy = proxy; } return form->menuIdMap[i].proxy; } } // Search other loaded forms by name (for cross-form property access) if (rt) { for (int32_t i = 0; i < (int32_t)arrlen(rt->forms); i++) { if (strcasecmp(rt->forms[i]->name, ctrlName) == 0) { return &rt->forms[i]->formCtrl; } } } return NULL; } // ============================================================ // basFormRtFindCtrlIdx // ============================================================ void *basFormRtFindCtrlIdx(void *ctx, void *formRef, const char *ctrlName, int32_t index) { (void)ctx; BasFormT *form = (BasFormT *)formRef; if (!form) { return NULL; } for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { if (form->controls[i]->index == index && 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) { return basFormRtFireEventArgs(rt, form, ctrlName, eventName, NULL, 0); } bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName, const BasValueT *args, int32_t argCount) { 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) { return false; } // Strict parameter matching: the sub must declare exactly the // number of parameters the event provides, or zero (no params). if (proc->paramCount != 0 && proc->paramCount != argCount) { return false; } BasFormT *prevForm = rt->currentForm; BasValueT *prevVars = rt->vm->currentFormVars; int32_t prevVarCount = rt->vm->currentFormVarCount; rt->currentForm = form; basVmSetCurrentForm(rt->vm, form); basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount); bool ok; if (argCount > 0 && args) { ok = basVmCallSubWithArgs(rt->vm, proc->codeAddr, args, argCount); } else { ok = basVmCallSub(rt->vm, proc->codeAddr); } rt->currentForm = prevForm; basVmSetCurrentForm(rt->vm, prevForm); basVmSetCurrentFormVars(rt->vm, prevVars, prevVarCount); return ok; } // ============================================================ // basFormRtFireEventWithCancel -- fire an event that has a Cancel // parameter (first arg, Integer). Returns true if Cancel was set // to non-zero by the event handler. static bool basFormRtFireEventWithCancel(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 || proc->isFunction) { return false; } // Must accept 0 or 1 parameter if (proc->paramCount != 0 && proc->paramCount != 1) { return false; } BasFormT *prevForm = rt->currentForm; BasValueT *prevVars = rt->vm->currentFormVars; int32_t prevVarCount = rt->vm->currentFormVarCount; rt->currentForm = form; basVmSetCurrentForm(rt->vm, form); basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount); bool cancelled = false; if (proc->paramCount == 1) { BasValueT args[1]; args[0] = basValLong(0); // Cancel = 0 (don't cancel) BasValueT outArgs[1]; memset(outArgs, 0, sizeof(outArgs)); if (basVmCallSubWithArgsOut(rt->vm, proc->codeAddr, args, 1, outArgs, 1)) { cancelled = (basValToNumber(outArgs[0]) != 0); basValRelease(&outArgs[0]); } } else { basVmCallSub(rt->vm, proc->codeAddr); } rt->currentForm = prevForm; basVmSetCurrentForm(rt->vm, prevForm); basVmSetCurrentFormVars(rt->vm, prevVars, prevVarCount); return cancelled; } // ============================================================ // basFormRtGetProp // ============================================================ BasValueT basFormRtGetProp(void *ctx, void *ctrlRef, const char *propName) { (void)ctx; BasControlT *ctrl = (BasControlT *)ctrlRef; if (!ctrl) { return zeroValue(); } // Menu item properties if (ctrl->menuId > 0 && ctrl->form && ctrl->form->window && ctrl->form->window->menuBar) { MenuBarT *bar = ctrl->form->window->menuBar; if (strcasecmp(propName, "Checked") == 0) { return basValBool(wmMenuItemIsChecked(bar, ctrl->menuId)); } if (strcasecmp(propName, "Enabled") == 0) { return basValBool(true); // TODO: wmMenuItemIsEnabled not exposed yet } if (strcasecmp(propName, "Name") == 0) { return basValStringFromC(ctrl->name); } return zeroValue(); } if (!ctrl->widget) { return zeroValue(); } // Form-level properties use the window and BasFormT, not the root widget if (ctrl->form && ctrl == &ctrl->form->formCtrl) { WindowT *win = ctrl->form->window; BasFormT *frm = ctrl->form; if (strcasecmp(propName, "Name") == 0) { return basValStringFromC(frm->name); } if (strcasecmp(propName, "Caption") == 0) { return basValStringFromC(win ? win->title : ""); } if (strcasecmp(propName, "Width") == 0) { return basValLong(win ? win->w : 0); } if (strcasecmp(propName, "Height") == 0) { return basValLong(win ? win->h : 0); } if (strcasecmp(propName, "Left") == 0) { return basValLong(win ? win->x : 0); } if (strcasecmp(propName, "Top") == 0) { return basValLong(win ? win->y : 0); } if (strcasecmp(propName, "Visible") == 0) { return basValBool(win && win->visible); } if (strcasecmp(propName, "Resizable") == 0) { return basValBool(win && win->resizable); } if (strcasecmp(propName, "AutoSize") == 0) { return basValBool(frm->frmAutoSize); } if (strcasecmp(propName, "Centered") == 0) { return basValBool(frm->frmCentered); } if (strcasecmp(propName, "Layout") == 0) { return basValStringFromC(frm->frmLayout); } 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 : ""); } // Help topic if (strcasecmp(propName, "HelpTopic") == 0) { return basValStringFromC(ctrl->helpTopic); } // Data binding properties if (strcasecmp(propName, "DataSource") == 0) { return basValStringFromC(ctrl->dataSource); } if (strcasecmp(propName, "DataField") == 0) { return basValStringFromC(ctrl->dataField); } // "ListCount" for any widget with item storage if (strcasecmp(propName, "ListCount") == 0) { if (ctrl->iface) { for (int32_t m = 0; m < ctrl->iface->methodCount; m++) { if (strcasecmp(ctrl->iface->methods[m].name, "ListCount") == 0) { return basValLong(((int32_t (*)(const WidgetT *))ctrl->iface->methods[m].fn)(ctrl->widget)); } } } return basValLong(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; } dvxHideWindow(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)); } // ============================================================ // basFormRtRegisterFrm -- cache .frm source text for lazy loading // ============================================================ void basFormRtRegisterFrm(BasFormRtT *rt, const char *formName, const char *source, int32_t sourceLen) { if (!rt || !formName || !source || sourceLen <= 0) { return; } BasFrmCacheT entry; snprintf(entry.formName, BAS_MAX_CTRL_NAME, "%s", formName); entry.frmSource = (char *)malloc(sourceLen + 1); entry.frmSourceLen = sourceLen; memcpy(entry.frmSource, source, sourceLen); entry.frmSource[sourceLen] = '\0'; arrput(rt->frmCache, entry); rt->frmCacheCount = (int32_t)arrlen(rt->frmCache); } // ============================================================ // basFormRtRegisterCfm -- cache compiled form binary for lazy loading // ============================================================ void basFormRtRegisterCfm(BasFormRtT *rt, const char *formName, const uint8_t *data, int32_t dataLen) { if (!rt || !formName || !data || dataLen <= 0) { return; } BasCfmCacheT entry; snprintf(entry.formName, BAS_MAX_CTRL_NAME, "%s", formName); entry.data = (uint8_t *)malloc(dataLen); entry.dataLen = dataLen; memcpy(entry.data, data, dataLen); arrput(rt->cfmCache, entry); rt->cfmCacheCount = (int32_t)arrlen(rt->cfmCache); } // ============================================================ // basFormRtLoadCfm -- load form from compiled binary via formcfm // ============================================================ static void *basFormRtLoadCfm(BasFormRtT *rt, const uint8_t *data, int32_t dataLen) { dvxLog("basFormRtLoadCfm: loading compiled form (%ld bytes)", (long)dataLen); BasFormT *form = basFormLoadCompiled(rt, data, dataLen); if (!form || !form->window) { dvxLog("basFormRtLoadCfm: basFormLoadCompiled failed (form=%p)", (void *)form); return form; } dvxLog("basFormRtLoadCfm: form '%s' created, %ld controls", form->name, (long)arrlen(form->controls)); // Set window callbacks (same as basFormRtLoadForm) form->window->onClose = onFormClose; form->window->onResize = onFormResize; form->window->onFocus = onFormActivate; form->window->onBlur = onFormDeactivate; // Wire event callbacks on all controls for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { BasControlT *ctrl = form->controls[i]; if (ctrl->widget) { ctrl->widget->onClick = onWidgetClick; ctrl->widget->onDblClick = onWidgetDblClick; ctrl->widget->onChange = onWidgetChange; ctrl->widget->onFocus = onWidgetFocus; ctrl->widget->onBlur = onWidgetBlur; ctrl->widget->onValidate = onWidgetValidate; ctrl->widget->onKeyPress = onWidgetKeyPress; ctrl->widget->onKeyDown = onWidgetKeyDown; ctrl->widget->onKeyUp = onWidgetKeyUp; ctrl->widget->onMouseDown = onWidgetMouseDown; ctrl->widget->onMouseUp = onWidgetMouseUp; ctrl->widget->onMouseMove = onWidgetMouseMove; ctrl->widget->onScroll = onWidgetScroll; } } // Allocate per-form variable storage if (rt->module && rt->module->formVarInfo) { for (int32_t j = 0; j < rt->module->formVarInfoCount; j++) { if (strcasecmp(rt->module->formVarInfo[j].formName, form->name) == 0) { int32_t vc = rt->module->formVarInfo[j].varCount; if (vc > 0) { form->formVars = (BasValueT *)calloc(vc, sizeof(BasValueT)); form->formVarCount = vc; } int32_t initAddr = rt->module->formVarInfo[j].initCodeAddr; if (initAddr >= 0 && rt->vm) { basVmSetCurrentForm(rt->vm, form); basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount); basVmCallSub(rt->vm, initAddr); basVmSetCurrentForm(rt->vm, NULL); basVmSetCurrentFormVars(rt->vm, NULL, 0); } break; } } } // Fire Form_Load event basFormRtFireEvent(rt, form, form->name, "Load"); return form; } // ============================================================ // basFormRtLoadForm // ============================================================ void *basFormRtLoadForm(void *ctx, const char *formName) { BasFormRtT *rt = (BasFormRtT *)ctx; // Check if form already exists for (int32_t i = 0; i < (int32_t)arrlen(rt->forms); i++) { if (strcasecmp(rt->forms[i]->name, formName) == 0) { return rt->forms[i]; } } // Check the .frm cache for reload after unload. // Use a static guard to prevent recursion: basFormRtLoadFrm calls // basFormRtLoadForm for the "Begin Form" line, which would re-enter // this function. The guard lets the recursive call fall through to // bare form creation, which basFormRtLoadFrm then populates. static bool sLoadingFrm = false; if (!sLoadingFrm) { for (int32_t i = 0; i < rt->frmCacheCount; i++) { if (strcasecmp(rt->frmCache[i].formName, formName) == 0) { char *src = rt->frmCache[i].frmSource; int32_t srcLen = rt->frmCache[i].frmSourceLen; arrdel(rt->frmCache, i); rt->frmCacheCount = (int32_t)arrlen(rt->frmCache); sLoadingFrm = true; BasFormT *form = basFormRtLoadFrm(rt, src, srcLen); sLoadingFrm = false; free(src); return form; } } } // Check the compiled form cache (standalone apps) for (int32_t i = 0; i < rt->cfmCacheCount; i++) { if (strcasecmp(rt->cfmCache[i].formName, formName) == 0) { return basFormRtLoadCfm(rt, rt->cfmCache[i].data, rt->cfmCache[i].dataLen); } } // No cache entry — create a bare form (first-time load without .frm file) WidgetT *root; WidgetT *bareContentBox; WindowT *win = basFormRtCreateFormWindow(rt->ctx, formName, "VBox", false, true, false, DEFAULT_FORM_W, DEFAULT_FORM_H, 0, 0, &root, &bareContentBox); if (!win) { return NULL; } BasFormT *form = (BasFormT *)calloc(1, sizeof(BasFormT)); arrput(rt->forms, form); snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName); snprintf(form->frmLayout, sizeof(form->frmLayout), "VBox"); win->onClose = onFormClose; win->onResize = onFormResize; win->onFocus = onFormActivate; win->onBlur = onFormDeactivate; form->window = win; form->root = root; form->contentBox = bareContentBox; form->ctx = rt->ctx; form->vm = rt->vm; form->module = rt->module; // Initialize synthetic control for form-level property access memset(&form->formCtrl, 0, sizeof(form->formCtrl)); snprintf(form->formCtrl.name, BAS_MAX_CTRL_NAME, "%s", formName); form->formCtrl.widget = root; form->formCtrl.form = form; // Allocate per-form variable storage and run init code from module metadata if (rt->module && rt->module->formVarInfo) { for (int32_t j = 0; j < rt->module->formVarInfoCount; j++) { if (strcasecmp(rt->module->formVarInfo[j].formName, formName) == 0) { int32_t vc = rt->module->formVarInfo[j].varCount; if (vc > 0) { form->formVars = (BasValueT *)calloc(vc, sizeof(BasValueT)); form->formVarCount = vc; } // Execute per-form init block (DIM arrays, UDT init) int32_t initAddr = rt->module->formVarInfo[j].initCodeAddr; if (initAddr >= 0 && rt->vm) { basVmSetCurrentForm(rt->vm, form); basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount); basVmCallSub(rt->vm, initAddr); basVmSetCurrentForm(rt->vm, NULL); basVmSetCurrentFormVars(rt->vm, NULL, 0); } break; } } } 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[BAS_MAX_FRM_NESTING]; int32_t nestDepth = 0; // Track Begin/End blocks: true = container (Form/Frame), false = control bool isContainer[BAS_MAX_FRM_NESTING]; int32_t blockDepth = 0; // Temporary menu item accumulation typedef struct { char caption[256]; char name[BAS_MAX_CTRL_NAME]; int32_t level; bool checked; bool radioCheck; bool enabled; } TempMenuItemT; TempMenuItemT *menuItems = NULL; // stb_ds array TempMenuItemT *curMenuItem = NULL; int32_t menuNestDepth = 0; bool inMenu = false; 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 DVX x.xx" (native) or "VERSION x.xx" (VB import) if (strncasecmp(trimmed, "VERSION ", 8) == 0) { const char *ver = trimmed + 8; if (strncasecmp(ver, "DVX ", 4) != 0) { double vbVer = atof(ver); if (vbVer > 2.0) { return NULL; // VB4+ form, not compatible } } 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; } // contentBox may already be set from basFormRtLoadForm. // It gets replaced at the End block after Layout is known. nestDepth = 1; parentStack[0] = form->contentBox; current = NULL; if (blockDepth < BAS_MAX_FRM_NESTING) { isContainer[blockDepth++] = true; } } else if (strcasecmp(typeName, "Menu") == 0 && form) { TempMenuItemT mi; memset(&mi, 0, sizeof(mi)); snprintf(mi.name, BAS_MAX_CTRL_NAME, "%s", ctrlName); mi.level = menuNestDepth; mi.enabled = true; arrput(menuItems, mi); curMenuItem = &menuItems[arrlen(menuItems) - 1]; current = NULL; menuNestDepth++; inMenu = true; if (blockDepth < BAS_MAX_FRM_NESTING) { isContainer[blockDepth++] = false; } continue; } else if (form && nestDepth > 0) { // Create the content box on first control if not yet done if (!form->contentBox && form->root) { form->contentBox = basFormRtCreateContentBox(form->root, form->frmLayout); parentStack[0] = form->contentBox; } WidgetT *parent = parentStack[nestDepth - 1]; const char *wgtTypeName = resolveTypeName(typeName); if (!wgtTypeName) { continue; } WidgetT *widget = createWidget(wgtTypeName, parent); if (!widget) { continue; } wgtSetName(widget, ctrlName); { BasControlT *ctrlEntry = (BasControlT *)calloc(1, sizeof(BasControlT)); if (!ctrlEntry) { continue; } snprintf(ctrlEntry->name, BAS_MAX_CTRL_NAME, "%s", ctrlName); snprintf(ctrlEntry->typeName, BAS_MAX_CTRL_NAME, "%s", typeName); ctrlEntry->index = -1; ctrlEntry->widget = widget; ctrlEntry->form = form; ctrlEntry->iface = wgtGetIface(wgtTypeName); arrput(form->controls, ctrlEntry); current = ctrlEntry; widget->userData = current; widget->onClick = onWidgetClick; widget->onDblClick = onWidgetDblClick; widget->onChange = onWidgetChange; widget->onFocus = onWidgetFocus; widget->onBlur = onWidgetBlur; widget->onValidate = onWidgetValidate; widget->onKeyPress = onWidgetKeyPress; widget->onKeyDown = onWidgetKeyDown; widget->onKeyUp = onWidgetKeyUp; widget->onMouseDown = onWidgetMouseDown; widget->onMouseUp = onWidgetMouseUp; widget->onMouseMove = onWidgetMouseMove; widget->onScroll = onWidgetScroll; } // Track block type for End handling const WgtIfaceT *ctrlIface = wgtGetIface(wgtTypeName); bool isCtrlContainer = ctrlIface && ctrlIface->isContainer; if (isCtrlContainer && nestDepth < BAS_MAX_FRM_NESTING) { // Push the widget for now; the Layout property hasn't // been parsed yet. It will be applied when we see it // via containerLayout[] below. parentStack[nestDepth++] = widget; if (blockDepth < BAS_MAX_FRM_NESTING) { isContainer[blockDepth++] = true; } } else { if (blockDepth < BAS_MAX_FRM_NESTING) { isContainer[blockDepth++] = false; } } } continue; } // "End" if (strcasecmp(trimmed, "End") == 0) { if (inMenu) { menuNestDepth--; curMenuItem = NULL; if (menuNestDepth <= 0) { menuNestDepth = 0; inMenu = false; } if (blockDepth > 0) { blockDepth--; } continue; } 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 (curMenuItem) { // Menu item properties char *text = value; if (text[0] == '"') { text++; int32_t len = (int32_t)strlen(text); if (len > 0 && text[len - 1] == '"') { text[len - 1] = '\0'; } } if (strcasecmp(key, "Caption") == 0) { snprintf(curMenuItem->caption, sizeof(curMenuItem->caption), "%s", text); } else if (strcasecmp(key, "Checked") == 0) { curMenuItem->checked = (strcasecmp(text, "True") == 0 || strcasecmp(text, "-1") == 0); } else if (strcasecmp(key, "RadioCheck") == 0) { curMenuItem->radioCheck = (strcasecmp(text, "True") == 0 || strcasecmp(text, "-1") == 0); } else if (strcasecmp(key, "Enabled") == 0) { curMenuItem->enabled = (strcasecmp(text, "True") == 0 || strcasecmp(text, "-1") == 0 || strcasecmp(text, "False") != 0); } } else if (current) { // Control array index is stored on the struct, not as a widget property if (strcasecmp(key, "Index") == 0) { current->index = atoi(value); continue; } // HelpTopic is stored on BasControlT, not on the widget if (strcasecmp(key, "HelpTopic") == 0) { char *text = value; if (text[0] == '"') { text++; } int32_t tlen = (int32_t)strlen(text); if (tlen > 0 && text[tlen - 1] == '"') { text[tlen - 1] = '\0'; } snprintf(current->helpTopic, sizeof(current->helpTopic), "%s", text); continue; } // Layout property on a container: replace the parentStack entry // with a layout box inside the container widget. // NOTE: only do this for non-default layouts (HBox, WrapBox). // VBox is the default for Frame, so no wrapper needed. if (strcasecmp(key, "Layout") == 0 && current && current->widget && nestDepth > 0) { char *text = value; if (text[0] == '"') { text++; } int32_t tlen = (int32_t)strlen(text); if (tlen > 0 && text[tlen - 1] == '"') { text[tlen - 1] = '\0'; } if (strcasecmp(text, "VBox") != 0) { parentStack[nestDepth - 1] = basFormRtCreateContentBox(current->widget, text); } continue; } 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 if (strcasecmp(value, "True") == 0) { val = basValBool(true); } else if (strcasecmp(value, "False") == 0) { val = basValBool(false); } else { val = basValLong(atoi(value)); } basFormRtSetProp(rt, current, key, val); basValRelease(&val); } else if (nestDepth > 0) { // Form-level property -- strip quotes from string values char *text = value; if (text[0] == '"') { text++; int32_t len = (int32_t)strlen(text); if (len > 0 && text[len - 1] == '"') { text[len - 1] = '\0'; } } if (strcasecmp(key, "Caption") == 0) { dvxSetTitle(rt->ctx, form->window, text); } else if (strcasecmp(key, "Width") == 0) { form->frmWidth = atoi(value); } else if (strcasecmp(key, "Height") == 0) { form->frmHeight = atoi(value); } else if (strcasecmp(key, "Left") == 0) { form->frmLeft = atoi(value); } else if (strcasecmp(key, "Top") == 0) { form->frmTop = atoi(value); } else if (strcasecmp(key, "Resizable") == 0) { form->frmResizable = (strcasecmp(text, "True") == 0); form->frmHasResizable = true; } else if (strcasecmp(key, "Centered") == 0) { form->frmCentered = (strcasecmp(text, "True") == 0); } else if (strcasecmp(key, "AutoSize") == 0) { form->frmAutoSize = (strcasecmp(text, "True") == 0); } else if (strcasecmp(key, "Layout") == 0) { snprintf(form->frmLayout, sizeof(form->frmLayout), "%s", text); } else if (strcasecmp(key, "HelpTopic") == 0) { snprintf(form->helpTopic, sizeof(form->helpTopic), "%s", text); } } } // Apply accumulated form-level properties if (form) { // Ensure content box exists even if form has no controls if (!form->contentBox && form->root) { form->contentBox = basFormRtCreateContentBox(form->root, form->frmLayout); } // Build menu bar from accumulated menu items int32_t menuCount = (int32_t)arrlen(menuItems); if (menuCount > 0 && form->window) { MenuBarT *bar = wmAddMenuBar(form->window); if (bar) { #define MENU_ID_BASE 10000 MenuT *menuStack[16]; memset(menuStack, 0, sizeof(menuStack)); for (int32_t i = 0; i < menuCount; i++) { TempMenuItemT *mi = &menuItems[i]; bool isSep = (mi->caption[0] == '-' && (mi->caption[1] == '\0' || mi->caption[1] == '-')); bool isSubParent = (i + 1 < menuCount && menuItems[i + 1].level > mi->level); if (mi->level == 0) { // Top-level menu header menuStack[0] = wmAddMenu(bar, mi->caption); } else if (isSep && mi->level > 0 && menuStack[mi->level - 1]) { // Separator wmAddMenuSeparator(menuStack[mi->level - 1]); } else if (isSubParent && mi->level > 0 && menuStack[mi->level - 1]) { // Submenu parent menuStack[mi->level] = wmAddSubMenu(menuStack[mi->level - 1], mi->caption); } else if (mi->level > 0 && menuStack[mi->level - 1]) { // Regular menu item int32_t id = MENU_ID_BASE + i; if (mi->radioCheck) { wmAddMenuRadioItem(menuStack[mi->level - 1], mi->caption, id, mi->checked); } else if (mi->checked) { wmAddMenuCheckItem(menuStack[mi->level - 1], mi->caption, id, true); } else { wmAddMenuItem(menuStack[mi->level - 1], mi->caption, id); } if (!mi->enabled) { wmMenuItemSetEnabled(bar, id, false); } // Store ID-to-name mapping for event dispatch BasMenuIdMapT map; memset(&map, 0, sizeof(map)); map.id = id; snprintf(map.name, BAS_MAX_CTRL_NAME, "%s", mi->name); arrput(form->menuIdMap, map); form->menuIdMapCount = (int32_t)arrlen(form->menuIdMap); } } form->window->onMenu = onFormMenu; } } arrfree(menuItems); menuItems = NULL; // Call Resize on widgets that need it (e.g. PictureBox) so the // internal bitmap matches the layout size. Width/Height aren't // known until properties are parsed, so this must happen after // loading but before dvxFitWindow. for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { if (!form->controls[i]->widget) { continue; } const WgtIfaceT *ifc = form->controls[i]->iface; if (ifc) { WidgetT *wgt = form->controls[i]->widget; for (int32_t m = 0; m < ifc->methodCount; m++) { if (strcasecmp(ifc->methods[m].name, "Resize") == 0 && ifc->methods[m].sig == WGT_SIG_INT_INT && wgt->minW > 0 && wgt->minH > 0) { ((void (*)(WidgetT *, int32_t, int32_t))ifc->methods[m].fn)(wgt, wgt->minW, wgt->minH); break; } } } } // Apply form properties after Resize calls so that // PictureBox bitmaps are resized before dvxFitWindow. if (form->frmHasResizable) { form->window->resizable = form->frmResizable; } if (form->frmAutoSize) { dvxFitWindow(rt->ctx, form->window); } else if (form->frmWidth > 0 && form->frmHeight > 0) { dvxResizeWindow(rt->ctx, form->window, form->frmWidth, form->frmHeight); } if (form->frmCentered) { form->window->x = (rt->ctx->display.width - form->window->w) / 2; form->window->y = (rt->ctx->display.height - form->window->h) / 2; } else if (form->frmLeft > 0 || form->frmTop > 0) { form->window->x = form->frmLeft; form->window->y = form->frmTop; } } // Cache the .frm source for reload after unload if (form) { bool cached = false; for (int32_t i = 0; i < rt->frmCacheCount; i++) { if (strcasecmp(rt->frmCache[i].formName, form->name) == 0) { cached = true; break; } } if (!cached) { BasFrmCacheT entry; memset(&entry, 0, sizeof(entry)); snprintf(entry.formName, BAS_MAX_CTRL_NAME, "%s", form->name); entry.frmSource = (char *)malloc(sourceLen + 1); entry.frmSourceLen = sourceLen; if (entry.frmSource) { memcpy(entry.frmSource, source, sourceLen); entry.frmSource[sourceLen] = '\0'; } arrput(rt->frmCache, entry); rt->frmCacheCount = (int32_t)arrlen(rt->frmCache); } } // Allocate per-form variable storage and run init code. // This must happen AFTER controls are created (so init code can // reference controls by name) but BEFORE Form_Load fires. if (form && rt->module && rt->module->formVarInfo) { for (int32_t j = 0; j < rt->module->formVarInfoCount; j++) { if (strcasecmp(rt->module->formVarInfo[j].formName, form->name) != 0) { continue; } if (!form->formVars) { int32_t vc = rt->module->formVarInfo[j].varCount; if (vc > 0) { form->formVars = (BasValueT *)calloc(vc, sizeof(BasValueT)); form->formVarCount = vc; } } int32_t initAddr = rt->module->formVarInfo[j].initCodeAddr; if (initAddr >= 0 && rt->vm) { basVmSetCurrentForm(rt->vm, form); basVmSetCurrentFormVars(rt->vm, form->formVars, form->formVarCount); basVmCallSub(rt->vm, initAddr); basVmSetCurrentForm(rt->vm, NULL); basVmSetCurrentFormVars(rt->vm, NULL, 0); } break; } } // Fire the Load event now that the form and controls are ready if (form) { basFormRtFireEvent(rt, form, form->name, "Load"); // Auto-refresh Data controls that have no MasterSource (masters/standalone). // Detail controls are refreshed automatically by the master-detail cascade // when the master's Reposition event fires. for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { BasControlT *dc = form->controls[i]; if (strcasecmp(dc->typeName, "Data") != 0 || !dc->widget) { continue; } // Skip details — they'll be refreshed by the cascade const char *ms = NULL; if (dc->iface) { for (int32_t p = 0; p < dc->iface->propCount; p++) { if (strcasecmp(dc->iface->props[p].name, "MasterSource") == 0 && dc->iface->props[p].getFn) { ms = ((const char *(*)(const WidgetT *))dc->iface->props[p].getFn)(dc->widget); break; } } } if (ms && ms[0]) { continue; } wgtDataCtrlRefresh(dc->widget); updateBoundControls(form, dc); refreshDetailControls(form, dc); } } return form; } // ============================================================ // basFormRtMsgBox // ============================================================ int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags, const char *title) { BasFormRtT *rt = (BasFormRtT *)ctx; return dvxMessageBox(rt->ctx, (title && title[0]) ? title : "DVX BASIC", message, flags); } // ============================================================ // basFormRtSetProp // ============================================================ void basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT value) { BasFormRtT *rt = (BasFormRtT *)ctx; BasControlT *ctrl = (BasControlT *)ctrlRef; if (!ctrl) { return; } // Menu item properties if (ctrl->menuId > 0 && ctrl->form && ctrl->form->window && ctrl->form->window->menuBar) { MenuBarT *bar = ctrl->form->window->menuBar; if (strcasecmp(propName, "Checked") == 0) { wmMenuItemSetChecked(bar, ctrl->menuId, basValIsTruthy(value)); return; } if (strcasecmp(propName, "Enabled") == 0) { wmMenuItemSetEnabled(bar, ctrl->menuId, basValIsTruthy(value)); return; } return; } if (!ctrl->widget) { return; } // Form-level property assignment uses the window and BasFormT if (ctrl->form && ctrl == &ctrl->form->formCtrl) { WindowT *win = ctrl->form->window; BasFormT *frm = ctrl->form; if (strcasecmp(propName, "Caption") == 0) { BasStringT *s = basValFormatString(value); if (win) { dvxSetTitle(rt->ctx, win, s->data); } basStringUnref(s); return; } if (strcasecmp(propName, "Visible") == 0) { if (win) { if (basValIsTruthy(value)) { dvxShowWindow(rt->ctx, win); } else { dvxHideWindow(rt->ctx, win); } } return; } if (strcasecmp(propName, "Width") == 0 && win) { dvxResizeWindow(rt->ctx, win, (int32_t)basValToNumber(value), win->h); return; } if (strcasecmp(propName, "Height") == 0 && win) { dvxResizeWindow(rt->ctx, win, win->w, (int32_t)basValToNumber(value)); return; } if (strcasecmp(propName, "Left") == 0 && win) { dirtyListAdd(&rt->ctx->dirty, win->x, win->y, win->w, win->h); win->x = (int32_t)basValToNumber(value); dirtyListAdd(&rt->ctx->dirty, win->x, win->y, win->w, win->h); return; } if (strcasecmp(propName, "Top") == 0 && win) { dirtyListAdd(&rt->ctx->dirty, win->x, win->y, win->w, win->h); win->y = (int32_t)basValToNumber(value); dirtyListAdd(&rt->ctx->dirty, win->x, win->y, win->w, win->h); return; } if (strcasecmp(propName, "Resizable") == 0 && win) { dirtyListAdd(&rt->ctx->dirty, win->x, win->y, win->w, win->h); win->resizable = basValIsTruthy(value); dirtyListAdd(&rt->ctx->dirty, win->x, win->y, win->w, win->h); return; } if (strcasecmp(propName, "AutoSize") == 0) { frm->frmAutoSize = basValIsTruthy(value); if (frm->frmAutoSize && win) { dvxFitWindow(rt->ctx, win); } return; } if (strcasecmp(propName, "Centered") == 0 && win) { frm->frmCentered = basValIsTruthy(value); if (frm->frmCentered) { dirtyListAdd(&rt->ctx->dirty, win->x, win->y, win->w, win->h); win->x = (rt->ctx->display.width - win->w) / 2; win->y = (rt->ctx->display.height - win->h) / 2; dirtyListAdd(&rt->ctx->dirty, win->x, win->y, win->w, win->h); } return; } return; } // Common properties if (setCommonProp(ctrl, propName, value)) { return; } // "Caption" and "Text": pass directly to the widget (all widgets // strdup their text internally). if (strcasecmp(propName, "Caption") == 0 || strcasecmp(propName, "Text") == 0) { BasStringT *s = basValFormatString(value); wgtSetText(ctrl->widget, s->data); basStringUnref(s); return; } // Help topic if (strcasecmp(propName, "HelpTopic") == 0) { BasStringT *s = basValFormatString(value); snprintf(ctrl->helpTopic, BAS_MAX_CTRL_NAME, "%s", s->data); basStringUnref(s); return; } // Data binding properties (stored on BasControlT, not on the widget) if (strcasecmp(propName, "DataSource") == 0) { BasStringT *s = basValFormatString(value); snprintf(ctrl->dataSource, BAS_MAX_CTRL_NAME, "%s", s->data); basStringUnref(s); return; } if (strcasecmp(propName, "DataField") == 0) { BasStringT *s = basValFormatString(value); snprintf(ctrl->dataField, BAS_MAX_CTRL_NAME, "%s", s->data); basStringUnref(s); 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; } dvxShowWindow(rt->ctx, form->window); dvxRaiseWindow(rt->ctx, form->window); if (form->frmAutoSize) { dvxFitWindow(rt->ctx, form->window); } if (modal) { rt->ctx->modalWindow = form->window; } } // ============================================================ // basFormRtUnloadForm // ============================================================ void basFormRtUnloadForm(void *ctx, void *formRef) { BasFormRtT *rt = (BasFormRtT *)ctx; BasFormT *form = (BasFormT *)formRef; if (!form) { return; } // QueryUnload: give the form a chance to cancel if (basFormRtFireEventWithCancel(rt, form, form->name, "QueryUnload")) { return; } basFormRtFireEvent(rt, form, form->name, "Unload"); // Release per-form variables if (form->formVars) { for (int32_t i = 0; i < form->formVarCount; i++) { basValRelease(&form->formVars[i]); } free(form->formVars); form->formVars = NULL; form->formVarCount = 0; } for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { free(form->controls[i]); } arrfree(form->controls); form->controls = NULL; 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 = -1; for (int32_t i = 0; i < (int32_t)arrlen(rt->forms); i++) { if (rt->forms[i] == form) { idx = i; break; } } if (idx >= 0 && idx < (int32_t)arrlen(rt->forms)) { free(form); arrdel(rt->forms, idx); } } // ============================================================ // 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(); } // ============================================================ // 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. // For INT_INT types (e.g. PictureBox), hintW/hintH override the // default createArgs if non-zero (so the bitmap matches the .frm size). WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent) { const void *api = wgtGetApi(wgtTypeName); if (!api) { return NULL; } // Determine creation signature from the widget interface descriptor. const WgtIfaceT *iface = wgtGetIface(wgtTypeName); uint8_t sig = iface ? iface->createSig : WGT_CREATE_PARENT; 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 *(*CreateParentIntIntIntFnT)(WidgetT *, int32_t, int32_t, int32_t); typedef WidgetT *(*CreateParentIntBoolFnT)(WidgetT *, int32_t, bool); typedef WidgetT *(*CreateParentBoolFnT)(WidgetT *, bool); switch (sig) { case WGT_CREATE_PARENT_TEXT: { CreateParentTextFnT fn = *(CreateParentTextFnT *)api; return fn(parent, ""); } case WGT_CREATE_PARENT_INT: { CreateParentIntFnT fn = *(CreateParentIntFnT *)api; return fn(parent, iface->createArgs[0]); } case WGT_CREATE_PARENT_INT_INT: { CreateParentIntIntFnT fn = *(CreateParentIntIntFnT *)api; return fn(parent, iface->createArgs[0], iface->createArgs[1]); } case WGT_CREATE_PARENT_INT_INT_INT: { CreateParentIntIntIntFnT fn = *(CreateParentIntIntIntFnT *)api; return fn(parent, iface->createArgs[0], iface->createArgs[1], iface->createArgs[2]); } case WGT_CREATE_PARENT_INT_BOOL: { CreateParentIntBoolFnT fn = *(CreateParentIntBoolFnT *)api; return fn(parent, iface->createArgs[0], (bool)iface->createArgs[1]); } case WGT_CREATE_PARENT_BOOL: { CreateParentBoolFnT fn = *(CreateParentBoolFnT *)api; return fn(parent, (bool)iface->createArgs[0]); } case WGT_CREATE_PARENT_DATA: { // create(parent, NULL, 0, 0, 0) -- empty widget, load content later via properties typedef WidgetT *(*CreateDataFnT)(WidgetT *, uint8_t *, int32_t, int32_t, int32_t); CreateDataFnT fn = *(CreateDataFnT *)api; return fn(parent, NULL, 0, 0, 0); } default: { CreateParentFnT fn = *(CreateParentFnT *)api; return fn(parent); } } } // ============================================================ // 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 || strcasecmp(propName, "MinWidth") == 0) { return basValLong(ctrl->widget->w); } if (strcasecmp(propName, "Height") == 0 || strcasecmp(propName, "MinHeight") == 0) { return basValLong(ctrl->widget->h); } if (strcasecmp(propName, "MaxWidth") == 0) { return basValLong(ctrl->widget->maxW ? (ctrl->widget->maxW & WGT_SIZE_VAL_MASK) : 0); } if (strcasecmp(propName, "MaxHeight") == 0) { return basValLong(ctrl->widget->maxH ? (ctrl->widget->maxH & WGT_SIZE_VAL_MASK) : 0); } if (strcasecmp(propName, "Weight") == 0) { return basValLong(ctrl->widget->weight); } 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(); } // ============================================================ // onFormClose // ============================================================ // onFormMenu -- dispatch menu clicks as ControlName_Click events // ============================================================ static void onFormMenu(WindowT *win, int32_t menuId) { if (!sFormRt) { return; } for (int32_t i = 0; i < (int32_t)arrlen(sFormRt->forms); i++) { BasFormT *form = sFormRt->forms[i]; if (form->window == win) { for (int32_t j = 0; j < form->menuIdMapCount; j++) { if (form->menuIdMap[j].id == menuId) { basFormRtFireEvent(sFormRt, form, form->menuIdMap[j].name, "Click"); return; } } return; } } } // ============================================================ // 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) { if (!sFormRt) { return; } for (int32_t i = 0; i < (int32_t)arrlen(sFormRt->forms); i++) { BasFormT *form = sFormRt->forms[i]; if (form->window == win) { // QueryUnload: if Cancel is set, abort the close if (basFormRtFireEventWithCancel(sFormRt, form, form->name, "QueryUnload")) { return; } basFormRtFireEvent(sFormRt, form, form->name, "Unload"); // Release per-form variables if (form->formVars) { for (int32_t j = 0; j < form->formVarCount; j++) { basValRelease(&form->formVars[j]); } free(form->formVars); form->formVars = NULL; form->formVarCount = 0; } // Free control resources for (int32_t j = 0; j < (int32_t)arrlen(form->controls); j++) { free(form->controls[j]); } arrfree(form->controls); form->controls = NULL; // Destroy the window if (sFormRt->ctx->modalWindow == win) { sFormRt->ctx->modalWindow = NULL; } dvxDestroyWindow(sFormRt->ctx, win); // Remove from form list and free the form free(form); arrdel(sFormRt->forms, i); // If no forms left, stop the VM if (arrlen(sFormRt->forms) == 0 && sFormRt->vm) { sFormRt->vm->running = false; } return; } } } // ============================================================ // onFormResize // ============================================================ static void onFormResize(WindowT *win, int32_t newW, int32_t newH) { // Let the widget system re-evaluate scrollbars for the new size widgetOnResize(win, newW, newH); if (!sFormRt) { return; } for (int32_t i = 0; i < (int32_t)arrlen(sFormRt->forms); i++) { BasFormT *form = sFormRt->forms[i]; if (form->window == win) { basFormRtFireEvent(sFormRt, form, form->name, "Resize"); return; } } } // ============================================================ // onFormActivate / onFormDeactivate // ============================================================ static void onFormActivate(WindowT *win) { if (!sFormRt) { return; } for (int32_t i = 0; i < (int32_t)arrlen(sFormRt->forms); i++) { BasFormT *form = sFormRt->forms[i]; if (form->window == win) { basFormRtFireEvent(sFormRt, form, form->name, "Activate"); return; } } } static void onFormDeactivate(WindowT *win) { if (!sFormRt) { return; } for (int32_t i = 0; i < (int32_t)arrlen(sFormRt->forms); i++) { BasFormT *form = sFormRt->forms[i]; if (form->window == win) { basFormRtFireEvent(sFormRt, form, form->name, "Deactivate"); return; } } } // ============================================================ // fireCtrlEvent -- fire event, prepending Index for array members // ============================================================ #define MAX_FIRE_ARGS 8 static void fireCtrlEvent(BasFormRtT *rt, BasControlT *ctrl, const char *eventName, const BasValueT *args, int32_t argCount) { // Build final argument list (prepend Index for control arrays) BasValueT allArgs[MAX_FIRE_ARGS]; const BasValueT *finalArgs = args; int32_t finalArgCount = argCount; if (ctrl->index >= 0) { allArgs[0] = basValLong(ctrl->index); for (int32_t i = 0; i < argCount && i < MAX_FIRE_ARGS - 1; i++) { allArgs[i + 1] = args[i]; } finalArgs = allArgs; finalArgCount = argCount + 1; } // Check for event override (SetEvent) for (int32_t i = 0; i < ctrl->eventOverrideCount; i++) { if (strcasecmp(ctrl->eventOverrides[i].eventName, eventName) != 0) { continue; } // Override found: call the named SUB directly if (!rt->vm || !rt->module) { return; } const BasProcEntryT *proc = basModuleFindProc(rt->module, ctrl->eventOverrides[i].handlerName); if (!proc || proc->isFunction) { return; } BasFormT *prevForm = rt->currentForm; BasValueT *prevVars = rt->vm->currentFormVars; int32_t prevVarCount = rt->vm->currentFormVarCount; rt->currentForm = ctrl->form; basVmSetCurrentForm(rt->vm, ctrl->form); basVmSetCurrentFormVars(rt->vm, ctrl->form->formVars, ctrl->form->formVarCount); if (finalArgCount > 0 && finalArgs) { basVmCallSubWithArgs(rt->vm, proc->codeAddr, finalArgs, finalArgCount); } else { basVmCallSub(rt->vm, proc->codeAddr); } rt->currentForm = prevForm; basVmSetCurrentForm(rt->vm, prevForm); basVmSetCurrentFormVars(rt->vm, prevVars, prevVarCount); return; } // No override -- fall back to naming convention (CtrlName_EventName) if (finalArgCount > 0 && finalArgs) { basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, eventName, finalArgs, finalArgCount); } else { basFormRtFireEvent(rt, ctrl->form, ctrl->name, eventName); } } // ============================================================ // Widget event callbacks // ============================================================ static bool onWidgetValidate(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return true; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (!rt || !rt->module) { return true; } // Look for Data1_Validate(Cancel As Integer) handler char handlerName[MAX_EVENT_NAME_LEN]; snprintf(handlerName, sizeof(handlerName), "%s_Validate", ctrl->name); const BasProcEntryT *proc = basModuleFindProc(rt->module, handlerName); if (!proc || proc->isFunction) { return true; } // Pass Cancel = 0 (False). If the handler sets it to non-zero, cancel. BasValueT cancelArg = basValLong(0); BasFormT *prevForm = rt->currentForm; BasValueT *prevVars = rt->vm->currentFormVars; int32_t prevVarCount = rt->vm->currentFormVarCount; rt->currentForm = ctrl->form; basVmSetCurrentForm(rt->vm, ctrl->form); basVmSetCurrentFormVars(rt->vm, ctrl->form->formVars, ctrl->form->formVarCount); BasValueT outCancel = basValLong(0); if (proc->paramCount == 1) { basVmCallSubWithArgsOut(rt->vm, proc->codeAddr, &cancelArg, 1, &outCancel, 1); } else { basVmCallSub(rt->vm, proc->codeAddr); } rt->currentForm = prevForm; basVmSetCurrentForm(rt->vm, prevForm); basVmSetCurrentFormVars(rt->vm, prevVars, prevVarCount); // Non-zero Cancel means abort the write return !basValIsTruthy(outCancel); } static void onWidgetBlur(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { fireCtrlEvent(rt, ctrl, "LostFocus", NULL, 0); } // Write-back: if this control is data-bound, update the Data control's // cache and persist to the database if (ctrl->dataSource[0] && ctrl->dataField[0] && ctrl->widget && ctrl->form) { for (int32_t i = 0; i < (int32_t)arrlen(ctrl->form->controls); i++) { BasControlT *dc = ctrl->form->controls[i]; if (strcasecmp(dc->typeName, "Data") == 0 && strcasecmp(dc->name, ctrl->dataSource) == 0 && dc->widget) { const char *text = wgtGetText(ctrl->widget); if (text) { wgtDataCtrlSetField(dc->widget, ctrl->dataField, text); wgtDataCtrlUpdate(dc->widget); } break; } } } } // refreshDetailControls -- cascade master-detail: refresh detail Data controls static void refreshDetailControls(BasFormT *form, BasControlT *masterCtrl) { if (!masterCtrl->widget) { return; } for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { BasControlT *ctrl = form->controls[i]; if (strcasecmp(ctrl->typeName, "Data") != 0 || !ctrl->widget || !ctrl->iface) { continue; } // Read MasterSource, MasterField, DetailField from the widget's interface const char *ms = NULL; const char *mf = NULL; for (int32_t p = 0; p < ctrl->iface->propCount; p++) { const WgtPropDescT *pd = &ctrl->iface->props[p]; if (!pd->getFn) { continue; } if (strcasecmp(pd->name, "MasterSource") == 0) { ms = ((const char *(*)(const WidgetT *))pd->getFn)(ctrl->widget); } else if (strcasecmp(pd->name, "MasterField") == 0) { mf = ((const char *(*)(const WidgetT *))pd->getFn)(ctrl->widget); } } if (!ms || !ms[0] || strcasecmp(ms, masterCtrl->name) != 0) { continue; } // Get the master's current value for MasterField const char *val = ""; if (mf && mf[0]) { val = wgtDataCtrlGetField(masterCtrl->widget, mf); } // Set the filter value and refresh the detail wgtDataCtrlSetMasterValue(ctrl->widget, val); wgtDataCtrlRefresh(ctrl->widget); // Update bound controls for this detail updateBoundControls(form, ctrl); } } // updateBoundControls -- sync bound controls from Data control's current record static void updateBoundControls(BasFormT *form, BasControlT *dataCtrl) { if (!dataCtrl->widget) { return; } for (int32_t i = 0; i < (int32_t)arrlen(form->controls); i++) { BasControlT *ctrl = form->controls[i]; if (!ctrl->dataSource[0] || strcasecmp(ctrl->dataSource, dataCtrl->name) != 0) { continue; } if (!ctrl->widget) { continue; } // DBGrid: bind to the Data control widget and refresh if (strcasecmp(ctrl->typeName, "DBGrid") == 0) { wgtDbGridSetDataWidget(ctrl->widget, dataCtrl->widget); wgtDbGridRefresh(ctrl->widget); continue; } // Text-based controls: set text from current record field if (ctrl->dataField[0]) { const char *val = wgtDataCtrlGetField(dataCtrl->widget, ctrl->dataField); if (val) { wgtSetText(ctrl->widget, val); } } } } static void onWidgetChange(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { // Data controls fire "Reposition", update bound controls, and cascade to details if (strcasecmp(ctrl->typeName, "Data") == 0) { updateBoundControls(ctrl->form, ctrl); fireCtrlEvent(rt, ctrl, "Reposition", NULL, 0); refreshDetailControls(ctrl->form, ctrl); return; } // Timer widgets fire "Timer" event, everything else fires "Change" const char *evtName = (strcasecmp(ctrl->typeName, "Timer") == 0) ? "Timer" : "Change"; fireCtrlEvent(rt, ctrl, evtName, NULL, 0); } } static void onWidgetClick(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { fireCtrlEvent(rt, ctrl, "Click", NULL, 0); } } static void onWidgetDblClick(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { fireCtrlEvent(rt, ctrl, "DblClick", NULL, 0); } } static void onWidgetFocus(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { fireCtrlEvent(rt, ctrl, "GotFocus", NULL, 0); } } static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { BasValueT args[1]; args[0] = basValLong(keyAscii); fireCtrlEvent(rt, ctrl, "KeyPress", args, 1); } } static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { BasValueT args[2]; args[0] = basValLong(keyCode); args[1] = basValLong(shift); fireCtrlEvent(rt, ctrl, "KeyDown", args, 2); } } static void onWidgetKeyUp(WidgetT *w, int32_t keyCode, int32_t shift) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { BasValueT args[2]; args[0] = basValLong(keyCode); args[1] = basValLong(shift); fireCtrlEvent(rt, ctrl, "KeyUp", args, 2); } } static void onWidgetMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { BasValueT args[3]; args[0] = basValLong(button); args[1] = basValLong(x); args[2] = basValLong(y); fireCtrlEvent(rt, ctrl, "MouseDown", args, 3); } } static void onWidgetMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { BasValueT args[3]; args[0] = basValLong(button); args[1] = basValLong(x); args[2] = basValLong(y); fireCtrlEvent(rt, ctrl, "MouseUp", args, 3); } } static void onWidgetMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { BasValueT args[3]; args[0] = basValLong(button); args[1] = basValLong(x); args[2] = basValLong(y); fireCtrlEvent(rt, ctrl, "MouseMove", args, 3); } } static void onWidgetScroll(WidgetT *w, int32_t delta) { BasControlT *ctrl = (BasControlT *)w->userData; if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { BasValueT args[1]; args[0] = basValLong(delta); fireCtrlEvent(rt, ctrl, "Scroll", args, 1); } } // ============================================================ // 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'; } } // ============================================================ // 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 || strcasecmp(propName, "MinWidth") == 0) { int32_t w = (int32_t)basValToNumber(value); ctrl->widget->minW = wgtPixels(w); wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "Height") == 0 || strcasecmp(propName, "MinHeight") == 0) { int32_t h = (int32_t)basValToNumber(value); ctrl->widget->minH = wgtPixels(h); wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "MaxWidth") == 0) { int32_t mw = (int32_t)basValToNumber(value); ctrl->widget->maxW = mw > 0 ? wgtPixels(mw) : 0; wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "MaxHeight") == 0) { int32_t mh = (int32_t)basValToNumber(value); ctrl->widget->maxH = mh > 0 ? wgtPixels(mh) : 0; wgtInvalidate(ctrl->widget); return true; } if (strcasecmp(propName, "Weight") == 0) { ctrl->widget->weight = (int32_t)basValToNumber(value); 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); wgtInvalidatePaint(ctrl->widget); return true; } if (strcasecmp(propName, "ForeColor") == 0) { ctrl->widget->fgColor = (uint32_t)(int32_t)basValToNumber(value); wgtInvalidatePaint(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_ENUM: if (p->enumNames && value.type == BAS_TYPE_STRING && value.strVal) { // Map name to index int32_t enumVal = 0; for (int32_t en = 0; p->enumNames[en]; en++) { if (strcasecmp(p->enumNames[en], value.strVal->data) == 0) { enumVal = en; break; } } ((void (*)(WidgetT *, int32_t))p->setFn)(w, enumVal); } else { ((void (*)(WidgetT *, int32_t))p->setFn)(w, (int32_t)basValToNumber(value)); } 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; } // ============================================================ // Common dialog wrappers for DECLARE LIBRARY "commdlg" // ============================================================ // // Thin wrappers around dvx dialog functions. They retrieve the // AppContextT from the running form runtime internally, so BASIC // programs don't need to manage context pointers. Wrappers are // needed because: (1) dvx functions require AppContextT* as first // arg, and (2) functions with output buffers (file paths, text) // need C-side storage since BASIC strings can't be used as // writable char pointers. // Parse a filter string into FileFilterT entries. // // Format: "Label (pattern)|Label (pattern)|..." // The pattern is extracted from inside the parentheses. // Example: "Images (*.bmp;*.png;*.jpg)|Text (*.txt)|All Files (*.*)" // // If no parentheses, the entire entry is used as both label and pattern. // If no pipe, treat as a single pattern for backward compatibility. #define BAS_MAX_FILE_FILTERS 16 static int32_t parseFileFilters(const char *filter, FileFilterT *out, char *buf, int32_t bufSize) { if (!filter || !filter[0]) { out[0].label = "All Files (*.*)"; return 1; } // No pipe and no parentheses = old-style single pattern, wrap it if (!strchr(filter, '|') && !strchr(filter, '(')) { snprintf(buf, bufSize, "%s (%s)", filter, filter); out[0].label = buf; out[1].label = "All Files (*.*)"; return 2; } snprintf(buf, bufSize, "%s", filter); int32_t count = 0; char *p = buf; while (*p && count < BAS_MAX_FILE_FILTERS) { char *pipe = strchr(p, '|'); if (pipe) { *pipe = '\0'; } out[count].label = p; count++; p = pipe ? pipe + 1 : p + strlen(p); } return count > 0 ? count : 1; } const char *basFileOpen(const char *title, const char *filter) { if (!sFormRt) { return ""; } FileFilterT filters[BAS_MAX_FILE_FILTERS]; char filterBuf[1024]; int32_t count = parseFileFilters(filter, filters, filterBuf, sizeof(filterBuf)); static char path[DVX_MAX_PATH]; path[0] = '\0'; if (dvxFileDialog(sFormRt->ctx, title, FD_OPEN, NULL, filters, count, path, sizeof(path))) { return path; } return ""; } const char *basFileSave(const char *title, const char *filter) { if (!sFormRt) { return ""; } FileFilterT filters[BAS_MAX_FILE_FILTERS]; char filterBuf[1024]; int32_t count = parseFileFilters(filter, filters, filterBuf, sizeof(filterBuf)); static char path[DVX_MAX_PATH]; path[0] = '\0'; if (dvxFileDialog(sFormRt->ctx, title, FD_SAVE, NULL, filters, count, path, sizeof(path))) { return path; } return ""; } const char *basInputBox2(const char *title, const char *prompt, const char *defaultText) { if (!sFormRt) { return ""; } static char buf[512]; buf[0] = '\0'; if (dvxInputBox(sFormRt->ctx, title, prompt, defaultText, buf, sizeof(buf))) { return buf; } return ""; } int32_t basChoiceDialog(const char *title, const char *prompt, const char *items, int32_t defaultIdx) { if (!sFormRt || !items) { return -1; } // Split pipe-delimited items string into an array static char itemBuf[1024]; snprintf(itemBuf, sizeof(itemBuf), "%s", items); const char *ptrs[64]; int32_t count = 0; char *tok = itemBuf; while (*tok && count < 64) { ptrs[count++] = tok; char *sep = strchr(tok, '|'); if (sep) { *sep = '\0'; tok = sep + 1; } else { break; } } if (count == 0) { return -1; } int32_t chosen = 0; if (dvxChoiceDialog(sFormRt->ctx, title, prompt, ptrs, count, defaultIdx, &chosen)) { return chosen; } return -1; } int32_t basIntInput(const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal) { if (!sFormRt) { return defaultVal; } int32_t result = defaultVal; dvxIntInputBox(sFormRt->ctx, title, prompt, defaultVal, minVal, maxVal, 1, &result); return result; } int32_t basPromptSave(const char *title) { if (!sFormRt) { return 2; } return dvxPromptSave(sFormRt->ctx, title); } // ============================================================ // basExternResolve / basExternCall // // Shared extern call callbacks for DECLARE LIBRARY functions. // Used by both the IDE and the standalone stub. // ============================================================ void *basExternResolve(void *ctx, const char *libName, const char *funcName) { (void)ctx; (void)libName; char mangledName[256]; snprintf(mangledName, sizeof(mangledName), "_%s", funcName); return dlsym(NULL, mangledName); } BasValueT basExternCall(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType) { (void)ctx; (void)libName; (void)funcName; uint32_t nativeArgs[32]; char *tempStrings[16]; int32_t tempStringCount = 0; int32_t nativeCount = 0; for (int32_t i = 0; i < argc && i < 16; i++) { switch (args[i].type) { case BAS_TYPE_STRING: { BasStringT *s = basValFormatString(args[i]); char *cstr = strdup(s->data); basStringUnref(s); nativeArgs[nativeCount++] = (uint32_t)(uintptr_t)cstr; tempStrings[tempStringCount++] = cstr; break; } case BAS_TYPE_DOUBLE: case BAS_TYPE_SINGLE: { union { double d; uint32_t u[2]; } conv; conv.d = args[i].dblVal; nativeArgs[nativeCount++] = conv.u[0]; nativeArgs[nativeCount++] = conv.u[1]; break; } default: { nativeArgs[nativeCount++] = (uint32_t)(int32_t)basValToNumber(args[i]); break; } } } BasValueT result; memset(&result, 0, sizeof(result)); if (retType == BAS_TYPE_DOUBLE || retType == BAS_TYPE_SINGLE) { double dblResult = 0.0; __asm__ __volatile__( "movl %%esp, %%ebx\n\t" ::: "ebx" ); for (int32_t i = nativeCount - 1; i >= 0; i--) { uint32_t val = nativeArgs[i]; __asm__ __volatile__("pushl %0" :: "r"(val)); } __asm__ __volatile__( "call *%1\n\t" "fstpl %0\n\t" "movl %%ebx, %%esp\n\t" : "=m"(dblResult) : "r"(funcPtr) : "eax", "ecx", "edx", "memory" ); result = basValDouble(dblResult); } else if (retType == BAS_TYPE_STRING) { uint32_t rawResult = 0; __asm__ __volatile__( "movl %%esp, %%ebx\n\t" ::: "ebx" ); for (int32_t i = nativeCount - 1; i >= 0; i--) { uint32_t val = nativeArgs[i]; __asm__ __volatile__("pushl %0" :: "r"(val)); } __asm__ __volatile__( "call *%1\n\t" "movl %%ebx, %%esp\n\t" : "=a"(rawResult) : "r"(funcPtr) : "ecx", "edx", "memory" ); const char *str = (const char *)(uintptr_t)rawResult; result = basValStringFromC(str ? str : ""); } else { uint32_t rawResult = 0; __asm__ __volatile__( "movl %%esp, %%ebx\n\t" ::: "ebx" ); for (int32_t i = nativeCount - 1; i >= 0; i--) { uint32_t val = nativeArgs[i]; __asm__ __volatile__("pushl %0" :: "r"(val)); } __asm__ __volatile__( "call *%1\n\t" "movl %%ebx, %%esp\n\t" : "=a"(rawResult) : "r"(funcPtr) : "ecx", "edx", "memory" ); result = basValLong((int32_t)rawResult); } for (int32_t i = 0; i < tempStringCount; i++) { free(tempStrings[i]); } return result; } // ============================================================ // SQL extern wrappers (DECLARE LIBRARY "basrt") // // These bridge from the extern call convention to the // BasSqlCallbacksT callbacks on the VM. // ============================================================ int32_t SQLOpen(const char *path) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlOpen) { return 0; } return sFormRt->vm->sql.sqlOpen(path); } void SQLClose(int32_t db) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlClose) { return; } sFormRt->vm->sql.sqlClose(db); } int32_t SQLExec(int32_t db, const char *sql) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlExec) { return 0; } return sFormRt->vm->sql.sqlExec(db, sql) ? -1 : 0; } const char *SQLError(int32_t db) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlError) { return ""; } const char *err = sFormRt->vm->sql.sqlError(db); return err ? err : ""; } int32_t SQLQuery(int32_t db, const char *sql) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlQuery) { return 0; } return sFormRt->vm->sql.sqlQuery(db, sql); } int32_t SQLNext(int32_t rs) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlNext) { return 0; } return sFormRt->vm->sql.sqlNext(rs) ? -1 : 0; } int32_t SQLEof(int32_t rs) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlEof) { return -1; } return sFormRt->vm->sql.sqlEof(rs) ? -1 : 0; } int32_t SQLFieldCount(int32_t rs) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlFieldCount) { return 0; } return sFormRt->vm->sql.sqlFieldCount(rs); } const char *SQLFieldName(int32_t rs, int32_t col) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlFieldName) { return ""; } const char *name = sFormRt->vm->sql.sqlFieldName(rs, col); return name ? name : ""; } const char *SQLFieldText(int32_t rs, int32_t col) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlFieldText) { return ""; } const char *text = sFormRt->vm->sql.sqlFieldText(rs, col); return text ? text : ""; } const char *SQLField(int32_t rs, const char *colName) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlFieldByName) { return ""; } const char *text = sFormRt->vm->sql.sqlFieldByName(rs, colName); return text ? text : ""; } int32_t SQLFieldInt(int32_t rs, int32_t col) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlFieldInt) { return 0; } return sFormRt->vm->sql.sqlFieldInt(rs, col); } double SQLFieldDbl(int32_t rs, int32_t col) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlFieldDbl) { return 0.0; } return sFormRt->vm->sql.sqlFieldDbl(rs, col); } void SQLFreeResult(int32_t rs) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlFreeResult) { return; } sFormRt->vm->sql.sqlFreeResult(rs); } int32_t SQLAffected(int32_t db) { if (!sFormRt || !sFormRt->vm || !sFormRt->vm->sql.sqlAffectedRows) { return 0; } return sFormRt->vm->sql.sqlAffectedRows(db); } // ============================================================ // Serial communication extern wrappers (DECLARE LIBRARY "basrt") // // Two tiers: // SerXxx -- raw UART I/O via rs232 library // CommXxx -- packetized + optional encryption via secLink // // All functions resolved lazily via dlsym so the serial library // is only required when comm functions are actually called. // ============================================================ // ============================================================ // Raw serial API (rs232 function pointers) // ============================================================ typedef struct { int (*open)(int, int32_t, int, char, int, int); int (*close)(int); int (*read)(int, char *, int); int (*write)(int, const char *, int); int (*writeBuf)(int, const char *, int); int (*getRxBuffered)(int); int (*clearRxBuffer)(int); int (*getUartType)(int); int (*getBase)(int); int (*getIrq)(int); int (*setBase)(int, int); int (*setIrq)(int, int); } SerApiT; static SerApiT sSerApi; static bool sSerApiResolved = false; // Per-COM raw serial state for terminal attach #define SER_MAX_PORTS 4 #define SER_RECV_BUF 4096 typedef struct { bool attached; WidgetT *term; int32_t com; // 0-based COM port index } SerAttachT; static SerAttachT sSerAttach[SER_MAX_PORTS]; static bool serResolveApi(void) { if (sSerApiResolved) { return sSerApi.open != NULL; } sSerApiResolved = true; sSerApi.open = dlsym(NULL, "_rs232Open"); sSerApi.close = dlsym(NULL, "_rs232Close"); sSerApi.read = dlsym(NULL, "_rs232Read"); sSerApi.write = dlsym(NULL, "_rs232Write"); sSerApi.writeBuf = dlsym(NULL, "_rs232WriteBuf"); sSerApi.getRxBuffered = dlsym(NULL, "_rs232GetRxBuffered"); sSerApi.clearRxBuffer = dlsym(NULL, "_rs232ClearRxBuffer"); sSerApi.getUartType = dlsym(NULL, "_rs232GetUartType"); sSerApi.getBase = dlsym(NULL, "_rs232GetBase"); sSerApi.getIrq = dlsym(NULL, "_rs232GetIrq"); sSerApi.setBase = dlsym(NULL, "_rs232SetBase"); sSerApi.setIrq = dlsym(NULL, "_rs232SetIrq"); return sSerApi.open != NULL; } int32_t SerGetUart(int32_t com) { if (!serResolveApi() || !sSerApi.getUartType) { return 0; } return sSerApi.getUartType(com - 1); } void SerSetBase(int32_t com, int32_t base) { if (!serResolveApi() || !sSerApi.setBase) { return; } sSerApi.setBase(com - 1, base); } void SerSetIrq(int32_t com, int32_t irq) { if (!serResolveApi() || !sSerApi.setIrq) { return; } sSerApi.setIrq(com - 1, irq); } int32_t SerGetBase(int32_t com) { if (!serResolveApi() || !sSerApi.getBase) { return 0; } return sSerApi.getBase(com - 1); } int32_t SerGetIrq(int32_t com) { if (!serResolveApi() || !sSerApi.getIrq) { return 0; } return sSerApi.getIrq(com - 1); } int32_t SerOpen(int32_t com, int32_t baud, int32_t dataBits, const char *parity, int32_t stopBits, int32_t handshake) { if (!serResolveApi()) { return 0; } char parityChar = (parity && parity[0]) ? parity[0] : 'N'; int rc = sSerApi.open(com - 1, baud, dataBits, parityChar, stopBits, handshake); return (rc == 0) ? -1 : 0; } void SerClose(int32_t com) { if (!serResolveApi()) { return; } // Detach terminal if attached int32_t idx = com - 1; if (idx >= 0 && idx < SER_MAX_PORTS && sSerAttach[idx].attached) { wgtAnsiTermSetComm(sSerAttach[idx].term, NULL, NULL, NULL); sSerAttach[idx].attached = false; sSerAttach[idx].term = NULL; } sSerApi.close(com - 1); } int32_t SerWrite(int32_t com, const char *data) { if (!serResolveApi() || !data) { return 0; } int32_t len = (int32_t)strlen(data); if (len == 0) { return -1; } int rc = sSerApi.writeBuf(com - 1, data, len); return (rc >= 0) ? -1 : 0; } const char *SerRead(int32_t com) { if (!serResolveApi()) { return ""; } static char buf[SER_RECV_BUF]; int32_t n = sSerApi.read(com - 1, buf, (int)(sizeof(buf) - 1)); if (n <= 0) { return ""; } buf[n] = '\0'; return buf; } int32_t SerAvailable(int32_t com) { if (!serResolveApi() || !sSerApi.getRxBuffered) { return 0; } return sSerApi.getRxBuffered(com - 1); } void SerFlush(int32_t com) { if (!serResolveApi() || !sSerApi.clearRxBuffer) { return; } sSerApi.clearRxBuffer(com - 1); } // Raw serial terminal callbacks static int32_t serTermRead(void *ctx, uint8_t *buf, int32_t maxLen) { SerAttachT *sa = (SerAttachT *)ctx; if (!sSerApi.read) { return 0; } return sSerApi.read(sa->com, (char *)buf, maxLen); } static int32_t serTermWrite(void *ctx, const uint8_t *data, int32_t len) { SerAttachT *sa = (SerAttachT *)ctx; if (!sSerApi.writeBuf) { return 0; } int rc = sSerApi.writeBuf(sa->com, (const char *)data, len); return (rc >= 0) ? len : 0; } // Idle callback for raw serial: polls all attached ports static void serIdlePoll(void *ctx) { (void)ctx; for (int32_t i = 0; i < SER_MAX_PORTS; i++) { if (sSerAttach[i].attached && sSerAttach[i].term) { wgtAnsiTermPoll(sSerAttach[i].term); } } } void SerAttach(int32_t com, const char *termCtrlName) { if (!serResolveApi() || !sFormRt || !termCtrlName) { return; } int32_t idx = com - 1; if (idx < 0 || idx >= SER_MAX_PORTS) { return; } // Find the terminal widget by control name WidgetT *termWidget = NULL; for (int32_t i = 0; i < (int32_t)arrlen(sFormRt->forms); i++) { BasFormT *form = sFormRt->forms[i]; for (int32_t j = 0; j < (int32_t)arrlen(form->controls); j++) { if (strcasecmp(form->controls[j]->name, termCtrlName) == 0) { termWidget = form->controls[j]->widget; break; } } if (termWidget) { break; } } if (!termWidget) { return; } sSerAttach[idx].com = idx; sSerAttach[idx].term = termWidget; sSerAttach[idx].attached = true; wgtAnsiTermSetComm(termWidget, &sSerAttach[idx], serTermRead, serTermWrite); if (sFormRt->ctx) { sFormRt->ctx->idleCallback = serIdlePoll; sFormRt->ctx->idleCtx = NULL; } } void SerDetach(int32_t com) { int32_t idx = com - 1; if (idx < 0 || idx >= SER_MAX_PORTS || !sSerAttach[idx].attached) { return; } wgtAnsiTermSetComm(sSerAttach[idx].term, NULL, NULL, NULL); sSerAttach[idx].attached = false; sSerAttach[idx].term = NULL; } // ============================================================ // Packet/encrypted serial API (secLink function pointers) // ============================================================ #define COMM_MAX_LINKS 4 #define COMM_NUM_CHANNELS 128 #define COMM_CHAN_BUF_SIZE 4096 // SecLink function pointers (resolved lazily) typedef struct { void *(*open)(int, int32_t, int, char, int, int, void (*)(void *, const uint8_t *, int, uint8_t), void *); void (*close)(void *); int (*handshake)(void *); bool (*isReady)(void *); int (*poll)(void *); int (*send)(void *, const uint8_t *, int, uint8_t, bool, bool); int (*sendBuf)(void *, const uint8_t *, int, uint8_t, bool); int (*getPending)(void *); } CommSecLinkApiT; static CommSecLinkApiT sCommApi; static bool sCommApiResolved = false; // Per-channel receive ring buffer typedef struct { uint8_t data[COMM_CHAN_BUF_SIZE]; int32_t head; int32_t tail; } CommChanBufT; typedef struct { bool active; void *link; // SecLinkT* CommChanBufT channels[COMM_NUM_CHANNELS]; // per-channel receive buffers WidgetT *attachedTerm; // AnsiTerm widget if attached int32_t termChannel; // channel for terminal I/O bool termEncrypt; // encrypt terminal traffic } CommSlotT; static CommSlotT sCommSlots[COMM_MAX_LINKS]; static bool commResolveApi(void) { if (sCommApiResolved) { return sCommApi.open != NULL; } sCommApiResolved = true; sCommApi.open = dlsym(NULL, "_secLinkOpen"); sCommApi.close = dlsym(NULL, "_secLinkClose"); sCommApi.handshake = dlsym(NULL, "_secLinkHandshake"); sCommApi.isReady = dlsym(NULL, "_secLinkIsReady"); sCommApi.poll = dlsym(NULL, "_secLinkPoll"); sCommApi.send = dlsym(NULL, "_secLinkSend"); sCommApi.sendBuf = dlsym(NULL, "_secLinkSendBuf"); sCommApi.getPending = dlsym(NULL, "_secLinkGetPending"); return sCommApi.open != NULL; } static CommSlotT *commGetSlot(int32_t handle) { if (handle < 1 || handle > COMM_MAX_LINKS) { return NULL; } CommSlotT *slot = &sCommSlots[handle - 1]; return slot->active ? slot : NULL; } // SecLink receive callback: routes data into per-channel ring buffers static void commOnRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) { CommSlotT *slot = (CommSlotT *)ctx; CommChanBufT *cb = &slot->channels[channel & 0x7F]; for (int i = 0; i < len; i++) { int32_t next = (cb->head + 1) % COMM_CHAN_BUF_SIZE; if (next == cb->tail) { break; } cb->data[cb->head] = data[i]; cb->head = next; } } // AnsiTerm read callback: drains the terminal channel's ring buffer static int32_t commTermRead(void *ctx, uint8_t *buf, int32_t maxLen) { CommSlotT *slot = (CommSlotT *)ctx; CommChanBufT *cb = &slot->channels[slot->termChannel]; int32_t count = 0; while (count < maxLen && cb->tail != cb->head) { buf[count++] = cb->data[cb->tail]; cb->tail = (cb->tail + 1) % COMM_CHAN_BUF_SIZE; } return count; } // AnsiTerm write callback: sends keystrokes on the terminal channel static int32_t commTermWrite(void *ctx, const uint8_t *data, int32_t len) { CommSlotT *slot = (CommSlotT *)ctx; if (!slot->link || !sCommApi.send) { return 0; } int rc = sCommApi.send(slot->link, data, len, (uint8_t)slot->termChannel, slot->termEncrypt, false); return (rc == 0) ? len : 0; } // Idle callback: polls all active secLink connections static void commIdlePoll(void *ctx) { (void)ctx; for (int32_t i = 0; i < COMM_MAX_LINKS; i++) { if (sCommSlots[i].active && sCommSlots[i].link && sCommApi.poll) { sCommApi.poll(sCommSlots[i].link); } } } int32_t CommOpen(int32_t com, int32_t baud, int32_t dataBits, const char *parity, int32_t stopBits, int32_t handshake) { if (!commResolveApi()) { return 0; } // Find free slot int32_t handle = 0; for (int32_t i = 0; i < COMM_MAX_LINKS; i++) { if (!sCommSlots[i].active) { handle = i + 1; break; } } if (handle == 0) { return 0; } char parityChar = 'N'; if (parity && parity[0]) { parityChar = parity[0]; } CommSlotT *slot = &sCommSlots[handle - 1]; memset(slot, 0, sizeof(CommSlotT)); slot->link = sCommApi.open(com, baud, dataBits, parityChar, stopBits, handshake, commOnRecv, slot); if (!slot->link) { return 0; } slot->active = true; return handle; } void CommClose(int32_t handle) { CommSlotT *slot = commGetSlot(handle); if (!slot) { return; } if (slot->attachedTerm) { wgtAnsiTermSetComm(slot->attachedTerm, NULL, NULL, NULL); slot->attachedTerm = NULL; } if (sCommApi.close && slot->link) { sCommApi.close(slot->link); } slot->link = NULL; slot->active = false; } int32_t CommHandshake(int32_t handle) { CommSlotT *slot = commGetSlot(handle); if (!slot || !sCommApi.handshake) { return 0; } return (sCommApi.handshake(slot->link) == 0) ? -1 : 0; } int32_t CommIsReady(int32_t handle) { CommSlotT *slot = commGetSlot(handle); if (!slot || !sCommApi.isReady) { return 0; } return sCommApi.isReady(slot->link) ? -1 : 0; } int32_t CommSend(int32_t handle, const char *data, int32_t channel, int32_t encrypt) { CommSlotT *slot = commGetSlot(handle); if (!slot || !data || !sCommApi.sendBuf) { return 0; } int32_t len = (int32_t)strlen(data); if (len == 0) { return -1; } int rc = sCommApi.sendBuf(slot->link, (const uint8_t *)data, len, (uint8_t)channel, encrypt != 0); return (rc == 0) ? -1 : 0; } const char *CommRecv(int32_t handle, int32_t channel) { CommSlotT *slot = commGetSlot(handle); if (!slot || channel < 0 || channel >= COMM_NUM_CHANNELS) { return ""; } CommChanBufT *cb = &slot->channels[channel]; static char buf[COMM_CHAN_BUF_SIZE]; int32_t count = 0; while (count < (int32_t)sizeof(buf) - 1 && cb->tail != cb->head) { buf[count++] = cb->data[cb->tail]; cb->tail = (cb->tail + 1) % COMM_CHAN_BUF_SIZE; } buf[count] = '\0'; return buf; } int32_t CommPoll(int32_t handle) { CommSlotT *slot = commGetSlot(handle); if (!slot || !sCommApi.poll) { return 0; } return sCommApi.poll(slot->link); } int32_t CommPending(int32_t handle) { CommSlotT *slot = commGetSlot(handle); if (!slot || !sCommApi.getPending) { return 0; } return sCommApi.getPending(slot->link); } void CommAttach(int32_t handle, const char *termCtrlName, int32_t channel, int32_t encrypt) { CommSlotT *slot = commGetSlot(handle); if (!slot || !sFormRt || !termCtrlName) { return; } // Find the terminal widget by control name on any loaded form WidgetT *termWidget = NULL; for (int32_t i = 0; i < (int32_t)arrlen(sFormRt->forms); i++) { BasFormT *form = sFormRt->forms[i]; for (int32_t j = 0; j < (int32_t)arrlen(form->controls); j++) { if (strcasecmp(form->controls[j]->name, termCtrlName) == 0) { termWidget = form->controls[j]->widget; break; } } if (termWidget) { break; } } if (!termWidget) { return; } slot->termChannel = channel; slot->termEncrypt = (encrypt != 0); wgtAnsiTermSetComm(termWidget, slot, commTermRead, commTermWrite); slot->attachedTerm = termWidget; // Set idle callback for automatic polling if (sFormRt->ctx) { sFormRt->ctx->idleCallback = commIdlePoll; sFormRt->ctx->idleCtx = NULL; } } void CommDetach(int32_t handle) { CommSlotT *slot = commGetSlot(handle); if (!slot || !slot->attachedTerm) { return; } wgtAnsiTermSetComm(slot->attachedTerm, NULL, NULL, NULL); slot->attachedTerm = NULL; } // ============================================================ // Help system extern wrappers (DECLARE LIBRARY "basrt") // ============================================================ // hlpcCompile function pointer (resolved lazily) typedef int32_t (*HlpcCompileFnT)(const char **, int32_t, const char *, const char *, const char *, int32_t, void *, void *); static HlpcCompileFnT sHlpcCompile = NULL; static bool sHlpcResolved = false; // shellLoadAppWithArgs function pointer (resolved lazily) typedef int32_t (*ShellLoadAppFnT)(void *, const char *, const char *); static ShellLoadAppFnT sShellLoadApp = NULL; static bool sShellLoadResolved = false; int32_t HelpCompile(const char *inputFile, const char *outputFile) { if (!sHlpcResolved) { sHlpcResolved = true; sHlpcCompile = dlsym(NULL, "_hlpcCompile"); } if (!sHlpcCompile || !inputFile || !outputFile) { return 0; } const char *inputs[1]; inputs[0] = inputFile; int32_t rc = sHlpcCompile(inputs, 1, outputFile, NULL, NULL, 1, NULL, NULL); return (rc == 0) ? -1 : 0; } void HelpView(const char *hlpFile) { if (!sShellLoadResolved) { sShellLoadResolved = true; sShellLoadApp = dlsym(NULL, "_shellLoadAppWithArgs"); } if (!sShellLoadApp || !sFormRt || !sFormRt->ctx || !hlpFile) { return; } // If hlpFile has no directory component, resolve it against the calling // app's directory. This matches the convention where apps bundle their // help file alongside the .app. char resolved[DVX_MAX_PATH]; bool hasDir = strchr(hlpFile, '/') != NULL || strchr(hlpFile, '\\') != NULL || (hlpFile[0] && hlpFile[1] == ':'); if (!hasDir && sFormRt->vm && sFormRt->vm->appPath[0]) { snprintf(resolved, sizeof(resolved), "%s%c%s", sFormRt->vm->appPath, DVX_PATH_SEP, hlpFile); } else { snprintf(resolved, sizeof(resolved), "%s", hlpFile); } char viewerPath[DVX_MAX_PATH]; snprintf(viewerPath, sizeof(viewerPath), "APPS%cKPUNCH%cDVXHELP%cDVXHELP.APP", DVX_PATH_SEP, DVX_PATH_SEP, DVX_PATH_SEP); sShellLoadApp(sFormRt->ctx, viewerPath, resolved); } // ============================================================ // Resource file extern wrappers (DECLARE LIBRARY "basrt") // // Expose the DVX resource API to BASIC programs. Each function // is a thin wrapper around the C resource API. // ============================================================ // Maximum number of simultaneously open resource handles #define RES_MAX_HANDLES 8 static DvxResHandleT *sResHandles[RES_MAX_HANDLES]; int32_t ResOpen(const char *path) { if (!path) { return 0; } for (int32_t i = 0; i < RES_MAX_HANDLES; i++) { if (!sResHandles[i]) { sResHandles[i] = dvxResOpen(path); if (sResHandles[i]) { return i + 1; } return 0; } } return 0; } void ResClose(int32_t handle) { int32_t idx = handle - 1; if (idx < 0 || idx >= RES_MAX_HANDLES || !sResHandles[idx]) { return; } dvxResClose(sResHandles[idx]); sResHandles[idx] = NULL; } int32_t ResCount(int32_t handle) { int32_t idx = handle - 1; if (idx >= 0 && idx < RES_MAX_HANDLES && sResHandles[idx]) { return (int32_t)sResHandles[idx]->entryCount; } return 0; } const char *ResName(int32_t handle, int32_t index) { int32_t idx = handle - 1; if (idx >= 0 && idx < RES_MAX_HANDLES && sResHandles[idx] && index >= 0 && index < (int32_t)sResHandles[idx]->entryCount) { return sResHandles[idx]->entries[index].name; } return ""; } int32_t ResType(int32_t handle, int32_t index) { int32_t idx = handle - 1; if (idx >= 0 && idx < RES_MAX_HANDLES && sResHandles[idx] && index >= 0 && index < (int32_t)sResHandles[idx]->entryCount) { return (int32_t)sResHandles[idx]->entries[index].type; } return 0; } int32_t ResSize(int32_t handle, int32_t index) { int32_t idx = handle - 1; if (idx >= 0 && idx < RES_MAX_HANDLES && sResHandles[idx] && index >= 0 && index < (int32_t)sResHandles[idx]->entryCount) { return (int32_t)sResHandles[idx]->entries[index].size; } return 0; } const char *ResGetText(const char *path, const char *name) { static char sBuf[1024]; sBuf[0] = '\0'; if (!path || !name) { return sBuf; } DvxResHandleT *h = dvxResOpen(path); if (!h) { return sBuf; } uint32_t size = 0; void *data = dvxResRead(h, name, &size); dvxResClose(h); if (data) { uint32_t copyLen = size; if (copyLen >= sizeof(sBuf)) { copyLen = sizeof(sBuf) - 1; } memcpy(sBuf, data, copyLen); sBuf[copyLen] = '\0'; free(data); } return sBuf; } int32_t ResAddText(const char *path, const char *name, const char *text) { if (!path || !name) { return 0; } if (!text) { text = ""; } return dvxResAppend(path, name, DVX_RES_TEXT, text, (uint32_t)strlen(text) + 1) == 0 ? -1 : 0; } int32_t ResAddFile(const char *path, const char *name, int32_t type, const char *srcFile) { if (!path || !name || !srcFile) { return 0; } FILE *f = fopen(srcFile, "rb"); if (!f) { return 0; } fseek(f, 0, SEEK_END); long fileSize = ftell(f); fseek(f, 0, SEEK_SET); if (fileSize <= 0) { fclose(f); return 0; } uint8_t *buf = (uint8_t *)malloc((size_t)fileSize); if (!buf) { fclose(f); return 0; } if (fread(buf, 1, (size_t)fileSize, f) != (size_t)fileSize) { free(buf); fclose(f); return 0; } fclose(f); int32_t result = dvxResAppend(path, name, (uint32_t)type, buf, (uint32_t)fileSize) == 0 ? -1 : 0; free(buf); return result; } int32_t ResRemove(const char *path, const char *name) { if (!path || !name) { return 0; } return dvxResRemove(path, name) == 0 ? -1 : 0; } int32_t ResExtract(const char *path, const char *name, const char *outFile) { if (!path || !name || !outFile) { return 0; } DvxResHandleT *h = dvxResOpen(path); if (!h) { return 0; } uint32_t size = 0; void *data = dvxResRead(h, name, &size); dvxResClose(h); if (!data) { return 0; } FILE *f = fopen(outFile, "wb"); if (!f) { free(data); return 0; } int32_t result = (fwrite(data, 1, size, f) == size) ? -1 : 0; fclose(f); free(data); return result; }