4611 lines
136 KiB
C
4611 lines
136 KiB
C
// 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 <ctype.h>
|
|
#include <dlfcn.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
|
|
// ============================================================
|
|
// 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;
|
|
}
|
|
|
|
|