1889 lines
55 KiB
C
1889 lines
55 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 "../compiler/codegen.h"
|
|
#include "../compiler/opcodes.h"
|
|
#include "dvxDialog.h"
|
|
#include "dvxWm.h"
|
|
#include "widgetBox.h"
|
|
#include "thirdparty/stb_ds_wrap.h"
|
|
|
|
#include <ctype.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_LISTBOX_ITEMS 256
|
|
#define MAX_FRM_LINE_LEN 512
|
|
#define MAX_FRM_NESTING 16
|
|
#define MAX_AUX_DATA 128
|
|
|
|
// ============================================================
|
|
// Per-control listbox item storage
|
|
// ============================================================
|
|
|
|
typedef struct {
|
|
char *items[MAX_LISTBOX_ITEMS];
|
|
int32_t count;
|
|
} ListBoxItemsT;
|
|
|
|
// ============================================================
|
|
// Auxiliary data table for listbox item storage
|
|
// ============================================================
|
|
|
|
typedef struct {
|
|
BasControlT *ctrl;
|
|
ListBoxItemsT *items;
|
|
} AuxDataEntryT;
|
|
|
|
static AuxDataEntryT sAuxData[MAX_AUX_DATA];
|
|
static int32_t sAuxDataCount = 0;
|
|
|
|
// Module-level form runtime pointer for onFormClose callback
|
|
static BasFormRtT *sFormRt = NULL;
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
static BasStringT *basFormRtInputBox(void *ctx, const char *prompt, const char *title, const char *defaultText);
|
|
static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc);
|
|
static BasValueT callListBoxMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc);
|
|
static WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent);
|
|
static void freeListBoxItems(BasControlT *ctrl);
|
|
static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled);
|
|
static BasValueT getIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, bool *handled);
|
|
static ListBoxItemsT *getListBoxItems(BasControlT *ctrl);
|
|
static void onFormClose(WindowT *win);
|
|
static void onFormResize(WindowT *win, int32_t newW, int32_t newH);
|
|
static void onWidgetBlur(WidgetT *w);
|
|
static void onWidgetChange(WidgetT *w);
|
|
static void onWidgetClick(WidgetT *w);
|
|
static void onWidgetDblClick(WidgetT *w);
|
|
static void onWidgetFocus(WidgetT *w);
|
|
static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii);
|
|
static void onWidgetKeyDown(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 void rebuildListBoxItems(BasControlT *ctrl);
|
|
static const char *resolveTypeName(const char *typeName);
|
|
static bool setCommonProp(BasControlT *ctrl, const char *propName, BasValueT value);
|
|
static bool setIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, BasValueT value);
|
|
static BasValueT zeroValue(void);
|
|
|
|
// ============================================================
|
|
// basFormRtBindVm
|
|
// ============================================================
|
|
|
|
void basFormRtBindVm(BasFormRtT *rt) {
|
|
BasUiCallbacksT ui;
|
|
memset(&ui, 0, sizeof(ui));
|
|
|
|
ui.getProp = basFormRtGetProp;
|
|
ui.setProp = basFormRtSetProp;
|
|
ui.callMethod = basFormRtCallMethod;
|
|
ui.createCtrl = basFormRtCreateCtrl;
|
|
ui.findCtrl = basFormRtFindCtrl;
|
|
ui.loadForm = basFormRtLoadForm;
|
|
ui.unloadForm = basFormRtUnloadForm;
|
|
ui.showForm = basFormRtShowForm;
|
|
ui.hideForm = basFormRtHideForm;
|
|
ui.msgBox = basFormRtMsgBox;
|
|
ui.inputBox = basFormRtInputBox;
|
|
ui.ctx = rt;
|
|
|
|
basVmSetUiCallbacks(rt->vm, &ui);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtCallMethod
|
|
// ============================================================
|
|
|
|
BasValueT basFormRtCallMethod(void *ctx, void *ctrlRef, const char *methodName, BasValueT *args, int32_t argc) {
|
|
(void)ctx;
|
|
BasControlT *ctrl = (BasControlT *)ctrlRef;
|
|
|
|
if (!ctrl || !ctrl->widget) {
|
|
return zeroValue();
|
|
}
|
|
|
|
// ListBox-specific methods (AddItem/RemoveItem/Clear/List)
|
|
BasValueT result = callListBoxMethod(ctrl, methodName, args, argc);
|
|
|
|
if (result.type != 0 || strcasecmp(methodName, "AddItem") == 0 ||
|
|
strcasecmp(methodName, "RemoveItem") == 0 ||
|
|
strcasecmp(methodName, "Clear") == 0 ||
|
|
strcasecmp(methodName, "List") == 0) {
|
|
return result;
|
|
}
|
|
|
|
// Interface descriptor methods
|
|
const WgtIfaceT *iface = ctrl->iface;
|
|
|
|
if (iface) {
|
|
for (int32_t i = 0; i < iface->methodCount; i++) {
|
|
if (strcasecmp(iface->methods[i].name, methodName) != 0) {
|
|
continue;
|
|
}
|
|
|
|
const WgtMethodDescT *m = &iface->methods[i];
|
|
WidgetT *w = ctrl->widget;
|
|
|
|
switch (m->sig) {
|
|
case WGT_SIG_VOID:
|
|
((void (*)(WidgetT *))m->fn)(w);
|
|
return zeroValue();
|
|
|
|
case WGT_SIG_INT:
|
|
if (argc >= 1) {
|
|
((void (*)(WidgetT *, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]));
|
|
}
|
|
return zeroValue();
|
|
|
|
case WGT_SIG_BOOL:
|
|
if (argc >= 1) {
|
|
((void (*)(WidgetT *, bool))m->fn)(w, basValIsTruthy(args[0]));
|
|
}
|
|
return zeroValue();
|
|
|
|
case WGT_SIG_STR:
|
|
if (argc >= 1) {
|
|
BasStringT *s = basValFormatString(args[0]);
|
|
((void (*)(WidgetT *, const char *))m->fn)(w, s->data);
|
|
basStringUnref(s);
|
|
}
|
|
return zeroValue();
|
|
|
|
case WGT_SIG_INT_INT:
|
|
if (argc >= 2) {
|
|
((void (*)(WidgetT *, int32_t, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]), (int32_t)basValToNumber(args[1]));
|
|
}
|
|
return zeroValue();
|
|
|
|
case WGT_SIG_INT_BOOL:
|
|
if (argc >= 2) {
|
|
((void (*)(WidgetT *, int32_t, bool))m->fn)(w, (int32_t)basValToNumber(args[0]), basValIsTruthy(args[1]));
|
|
}
|
|
return zeroValue();
|
|
|
|
case WGT_SIG_RET_INT: {
|
|
int32_t v = ((int32_t (*)(const WidgetT *))m->fn)(w);
|
|
return basValLong(v);
|
|
}
|
|
|
|
case WGT_SIG_RET_BOOL: {
|
|
bool v = ((bool (*)(const WidgetT *))m->fn)(w);
|
|
return basValBool(v);
|
|
}
|
|
|
|
case WGT_SIG_RET_BOOL_INT: {
|
|
if (argc >= 1) {
|
|
bool v = ((bool (*)(const WidgetT *, int32_t))m->fn)(w, (int32_t)basValToNumber(args[0]));
|
|
return basValBool(v);
|
|
}
|
|
return basValBool(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Common methods (SetFocus, Refresh)
|
|
return callCommonMethod(ctrl, methodName, args, argc);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtCreate
|
|
// ============================================================
|
|
|
|
BasFormRtT *basFormRtCreate(AppContextT *ctx, BasVmT *vm, BasModuleT *module) {
|
|
BasFormRtT *rt = (BasFormRtT *)calloc(1, sizeof(BasFormRtT));
|
|
|
|
if (!rt) {
|
|
return NULL;
|
|
}
|
|
|
|
rt->ctx = ctx;
|
|
rt->vm = vm;
|
|
rt->module = module;
|
|
|
|
sFormRt = rt;
|
|
basFormRtBindVm(rt);
|
|
return rt;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtCreateCtrl
|
|
// ============================================================
|
|
|
|
void *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const char *ctrlName) {
|
|
(void)ctx;
|
|
BasFormT *form = (BasFormT *)formRef;
|
|
|
|
if (!form) {
|
|
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 entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
snprintf(entry.name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
|
arrput(form->controls, entry);
|
|
form->controlCount = (int32_t)arrlen(form->controls);
|
|
BasControlT *ctrl = &form->controls[form->controlCount - 1];
|
|
snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
|
ctrl->widget = widget;
|
|
ctrl->form = form;
|
|
ctrl->iface = wgtGetIface(wgtTypeName);
|
|
|
|
// Wire up event callbacks
|
|
widget->userData = ctrl;
|
|
widget->onClick = onWidgetClick;
|
|
widget->onDblClick = onWidgetDblClick;
|
|
widget->onChange = onWidgetChange;
|
|
widget->onFocus = onWidgetFocus;
|
|
widget->onBlur = onWidgetBlur;
|
|
|
|
return ctrl;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtDestroy
|
|
// ============================================================
|
|
|
|
void basFormRtDestroy(BasFormRtT *rt) {
|
|
if (!rt) {
|
|
return;
|
|
}
|
|
|
|
if (sFormRt == rt) {
|
|
sFormRt = NULL;
|
|
}
|
|
|
|
for (int32_t i = 0; i < rt->formCount; i++) {
|
|
BasFormT *form = &rt->forms[i];
|
|
|
|
for (int32_t j = 0; j < form->controlCount; j++) {
|
|
freeListBoxItems(&form->controls[j]);
|
|
}
|
|
|
|
arrfree(form->controls);
|
|
|
|
if (form->window) {
|
|
dvxDestroyWindow(rt->ctx, form->window);
|
|
form->window = NULL;
|
|
}
|
|
}
|
|
|
|
arrfree(rt->forms);
|
|
free(rt);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtFindCtrl
|
|
// ============================================================
|
|
|
|
void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName) {
|
|
(void)ctx;
|
|
BasFormT *form = (BasFormT *)formRef;
|
|
|
|
if (!form) {
|
|
return NULL;
|
|
}
|
|
|
|
for (int32_t i = 0; i < form->controlCount; i++) {
|
|
if (strcasecmp(form->controls[i].name, ctrlName) == 0) {
|
|
return &form->controls[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtFireEvent
|
|
// ============================================================
|
|
|
|
bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName) {
|
|
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;
|
|
rt->currentForm = form;
|
|
basVmSetCurrentForm(rt->vm, form);
|
|
|
|
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);
|
|
return ok;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtGetProp
|
|
// ============================================================
|
|
|
|
BasValueT basFormRtGetProp(void *ctx, void *ctrlRef, const char *propName) {
|
|
(void)ctx;
|
|
BasControlT *ctrl = (BasControlT *)ctrlRef;
|
|
|
|
if (!ctrl || !ctrl->widget) {
|
|
return zeroValue();
|
|
}
|
|
|
|
// Common properties (Name, Left, Top, Width, Height, Visible, Enabled)
|
|
bool handled;
|
|
BasValueT val = getCommonProp(ctrl, propName, &handled);
|
|
|
|
if (handled) {
|
|
return val;
|
|
}
|
|
|
|
// "Caption" and "Text" map to wgtGetText for all widgets
|
|
if (strcasecmp(propName, "Caption") == 0 || strcasecmp(propName, "Text") == 0) {
|
|
const char *text = wgtGetText(ctrl->widget);
|
|
return basValStringFromC(text ? text : "");
|
|
}
|
|
|
|
// "ListCount" for any widget with item storage
|
|
if (strcasecmp(propName, "ListCount") == 0) {
|
|
ListBoxItemsT *lb = getListBoxItems(ctrl);
|
|
return basValLong(lb ? lb->count : 0);
|
|
}
|
|
|
|
// Interface descriptor properties
|
|
if (ctrl->iface) {
|
|
val = getIfaceProp(ctrl->iface, ctrl->widget, propName, &handled);
|
|
|
|
if (handled) {
|
|
return val;
|
|
}
|
|
}
|
|
|
|
return zeroValue();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtHideForm
|
|
// ============================================================
|
|
|
|
void basFormRtHideForm(void *ctx, void *formRef) {
|
|
BasFormRtT *rt = (BasFormRtT *)ctx;
|
|
BasFormT *form = (BasFormT *)formRef;
|
|
|
|
if (!form || !form->window) {
|
|
return;
|
|
}
|
|
|
|
form->window->visible = false;
|
|
dvxInvalidateWindow(rt->ctx, form->window);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtInputBox
|
|
// ============================================================
|
|
|
|
static BasStringT *basFormRtInputBox(void *ctx, const char *prompt, const char *title, const char *defaultText) {
|
|
BasFormRtT *rt = (BasFormRtT *)ctx;
|
|
char buf[256];
|
|
|
|
buf[0] = '\0';
|
|
|
|
if (defaultText && defaultText[0]) {
|
|
strncpy(buf, defaultText, sizeof(buf) - 1);
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
}
|
|
|
|
if (!dvxInputBox(rt->ctx, title, prompt, defaultText, buf, sizeof(buf))) {
|
|
return NULL;
|
|
}
|
|
|
|
return basStringNew(buf, (int32_t)strlen(buf));
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtLoadForm
|
|
// ============================================================
|
|
|
|
void *basFormRtLoadForm(void *ctx, const char *formName) {
|
|
BasFormRtT *rt = (BasFormRtT *)ctx;
|
|
|
|
// Check if form already exists
|
|
for (int32_t i = 0; i < rt->formCount; i++) {
|
|
if (strcasecmp(rt->forms[i].name, formName) == 0) {
|
|
return &rt->forms[i];
|
|
}
|
|
}
|
|
|
|
// Forms start hidden; code must call Show to make them visible
|
|
WindowT *win = dvxCreateWindowCentered(rt->ctx, formName, DEFAULT_FORM_W, DEFAULT_FORM_H, true);
|
|
|
|
if (!win) {
|
|
return NULL;
|
|
}
|
|
|
|
win->visible = false;
|
|
|
|
WidgetT *root = wgtInitWindow(rt->ctx, win);
|
|
|
|
if (!root) {
|
|
dvxDestroyWindow(rt->ctx, win);
|
|
return NULL;
|
|
}
|
|
|
|
BasFormT entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
arrput(rt->forms, entry);
|
|
rt->formCount = (int32_t)arrlen(rt->forms);
|
|
BasFormT *form = &rt->forms[rt->formCount - 1];
|
|
snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
|
|
win->onClose = onFormClose;
|
|
win->onResize = onFormResize;
|
|
form->window = win;
|
|
form->root = root;
|
|
form->contentBox = NULL; // created lazily after Layout property is known
|
|
form->ctx = rt->ctx;
|
|
form->vm = rt->vm;
|
|
form->module = rt->module;
|
|
|
|
return form;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtLoadFrm
|
|
// ============================================================
|
|
|
|
BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen) {
|
|
if (!rt || !source || sourceLen <= 0) {
|
|
return NULL;
|
|
}
|
|
|
|
BasFormT *form = NULL;
|
|
BasControlT *current = NULL;
|
|
|
|
WidgetT *parentStack[MAX_FRM_NESTING];
|
|
int32_t nestDepth = 0;
|
|
|
|
// Track Begin/End blocks: true = container (Form/Frame), false = control
|
|
bool isContainer[MAX_FRM_NESTING];
|
|
int32_t blockDepth = 0;
|
|
|
|
const char *pos = source;
|
|
const char *end = source + sourceLen;
|
|
|
|
while (pos < end) {
|
|
const char *lineStart = pos;
|
|
|
|
while (pos < end && *pos != '\n' && *pos != '\r') {
|
|
pos++;
|
|
}
|
|
|
|
int32_t lineLen = (int32_t)(pos - lineStart);
|
|
|
|
if (pos < end && *pos == '\r') {
|
|
pos++;
|
|
}
|
|
|
|
if (pos < end && *pos == '\n') {
|
|
pos++;
|
|
}
|
|
|
|
char line[MAX_FRM_LINE_LEN];
|
|
|
|
if (lineLen >= MAX_FRM_LINE_LEN) {
|
|
lineLen = MAX_FRM_LINE_LEN - 1;
|
|
}
|
|
|
|
memcpy(line, lineStart, lineLen);
|
|
line[lineLen] = '\0';
|
|
|
|
char *trimmed = line;
|
|
|
|
while (*trimmed == ' ' || *trimmed == '\t') {
|
|
trimmed++;
|
|
}
|
|
|
|
if (*trimmed == '\0' || *trimmed == '\'') {
|
|
continue;
|
|
}
|
|
|
|
// "VERSION x.xx" -- skip version declaration
|
|
if (strncasecmp(trimmed, "VERSION ", 8) == 0) {
|
|
continue;
|
|
}
|
|
|
|
// "Begin TypeName CtrlName"
|
|
if (strncasecmp(trimmed, "Begin ", 6) == 0) {
|
|
char *rest = trimmed + 6;
|
|
char typeName[BAS_MAX_CTRL_NAME];
|
|
char ctrlName[BAS_MAX_CTRL_NAME];
|
|
|
|
int32_t ti = 0;
|
|
|
|
while (*rest && *rest != ' ' && *rest != '\t' && ti < BAS_MAX_CTRL_NAME - 1) {
|
|
typeName[ti++] = *rest++;
|
|
}
|
|
|
|
typeName[ti] = '\0';
|
|
|
|
while (*rest == ' ' || *rest == '\t') {
|
|
rest++;
|
|
}
|
|
|
|
int32_t ci = 0;
|
|
|
|
while (*rest && *rest != ' ' && *rest != '\t' && *rest != '\r' && *rest != '\n' && ci < BAS_MAX_CTRL_NAME - 1) {
|
|
ctrlName[ci++] = *rest++;
|
|
}
|
|
|
|
ctrlName[ci] = '\0';
|
|
|
|
if (strcasecmp(typeName, "Form") == 0) {
|
|
form = (BasFormT *)basFormRtLoadForm(rt, ctrlName);
|
|
|
|
if (!form) {
|
|
return NULL;
|
|
}
|
|
|
|
// contentBox is created lazily (see below) after
|
|
// form-level properties like Layout are parsed.
|
|
nestDepth = 1;
|
|
current = NULL;
|
|
|
|
if (blockDepth < MAX_FRM_NESTING) {
|
|
isContainer[blockDepth++] = true;
|
|
}
|
|
} else if (form && nestDepth > 0) {
|
|
// Create the content box on first control if not yet done
|
|
if (!form->contentBox && form->root) {
|
|
if (form->frmHBox) {
|
|
form->contentBox = wgtHBox(form->root);
|
|
} else {
|
|
form->contentBox = wgtVBox(form->root);
|
|
}
|
|
|
|
form->contentBox->weight = 100;
|
|
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;
|
|
memset(&ctrlEntry, 0, sizeof(ctrlEntry));
|
|
snprintf(ctrlEntry.name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
|
snprintf(ctrlEntry.typeName, BAS_MAX_CTRL_NAME, "%s", typeName);
|
|
ctrlEntry.widget = widget;
|
|
ctrlEntry.form = form;
|
|
ctrlEntry.iface = wgtGetIface(wgtTypeName);
|
|
arrput(form->controls, ctrlEntry);
|
|
form->controlCount = (int32_t)arrlen(form->controls);
|
|
|
|
// Re-derive pointer after arrput (may realloc)
|
|
current = &form->controls[form->controlCount - 1];
|
|
widget->userData = current;
|
|
widget->onClick = onWidgetClick;
|
|
widget->onDblClick = onWidgetDblClick;
|
|
widget->onChange = onWidgetChange;
|
|
widget->onFocus = onWidgetFocus;
|
|
widget->onBlur = onWidgetBlur;
|
|
widget->onKeyPress = onWidgetKeyPress;
|
|
widget->onKeyDown = onWidgetKeyDown;
|
|
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 < MAX_FRM_NESTING) {
|
|
parentStack[nestDepth++] = widget;
|
|
|
|
if (blockDepth < MAX_FRM_NESTING) {
|
|
isContainer[blockDepth++] = true;
|
|
}
|
|
} else {
|
|
if (blockDepth < MAX_FRM_NESTING) {
|
|
isContainer[blockDepth++] = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// "End"
|
|
if (strcasecmp(trimmed, "End") == 0) {
|
|
if (blockDepth > 0) {
|
|
blockDepth--;
|
|
|
|
// Only decrement parent nesting for containers (Form/Frame)
|
|
if (isContainer[blockDepth] && nestDepth > 0) {
|
|
nestDepth--;
|
|
}
|
|
}
|
|
|
|
current = NULL;
|
|
continue;
|
|
}
|
|
|
|
// Property assignment: Key = Value
|
|
char key[BAS_MAX_CTRL_NAME];
|
|
char value[MAX_FRM_LINE_LEN];
|
|
parseFrmLine(trimmed, key, value);
|
|
|
|
if (key[0] == '\0' || !form) {
|
|
continue;
|
|
}
|
|
|
|
if (current) {
|
|
BasValueT val;
|
|
|
|
if (value[0] == '"') {
|
|
int32_t vlen = (int32_t)strlen(value);
|
|
|
|
if (vlen >= 2 && value[vlen - 1] == '"') {
|
|
value[vlen - 1] = '\0';
|
|
}
|
|
|
|
val = basValStringFromC(value + 1);
|
|
} else {
|
|
val = basValLong(atoi(value));
|
|
}
|
|
|
|
basFormRtSetProp(rt, current, key, val);
|
|
basValRelease(&val);
|
|
} else if (nestDepth > 0) {
|
|
// Form-level property -- 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) {
|
|
if (strcasecmp(text, "HBox") == 0) {
|
|
form->frmHBox = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply accumulated form-level properties
|
|
if (form) {
|
|
// Ensure content box exists even if form has no controls
|
|
if (!form->contentBox && form->root) {
|
|
if (form->frmHBox) {
|
|
form->contentBox = wgtHBox(form->root);
|
|
} else {
|
|
form->contentBox = wgtVBox(form->root);
|
|
}
|
|
|
|
form->contentBox->weight = 100;
|
|
}
|
|
|
|
// Set resizable flag
|
|
if (form->frmHasResizable) {
|
|
form->window->resizable = form->frmResizable;
|
|
}
|
|
|
|
// Size and position the window
|
|
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);
|
|
} else {
|
|
dvxFitWindow(rt->ctx, form->window);
|
|
}
|
|
|
|
// Position: centered or explicit left/top
|
|
if (form->frmCentered) {
|
|
int32_t scrW = rt->ctx->display.width;
|
|
int32_t scrH = rt->ctx->display.height;
|
|
form->window->x = (scrW - form->window->w) / 2;
|
|
form->window->y = (scrH - form->window->h) / 2;
|
|
} else if (form->frmLeft > 0 || form->frmTop > 0) {
|
|
form->window->x = form->frmLeft;
|
|
form->window->y = form->frmTop;
|
|
}
|
|
// Re-wire widget->userData pointers now that the controls array
|
|
// is finalized. arrput may have reallocated during loading, so
|
|
// any userData set during parsing could be stale.
|
|
for (int32_t i = 0; i < form->controlCount; i++) {
|
|
if (form->controls[i].widget) {
|
|
form->controls[i].widget->userData = &form->controls[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fire the Load event now that the form and controls are ready
|
|
if (form) {
|
|
basFormRtFireEvent(rt, form, form->name, "Load");
|
|
}
|
|
|
|
return form;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtMsgBox
|
|
// ============================================================
|
|
|
|
int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags) {
|
|
BasFormRtT *rt = (BasFormRtT *)ctx;
|
|
|
|
return dvxMessageBox(rt->ctx, "DVX BASIC", message, flags);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtSetProp
|
|
// ============================================================
|
|
|
|
void basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasValueT value) {
|
|
(void)ctx;
|
|
BasControlT *ctrl = (BasControlT *)ctrlRef;
|
|
|
|
if (!ctrl || !ctrl->widget) {
|
|
return;
|
|
}
|
|
|
|
// Common properties
|
|
if (setCommonProp(ctrl, propName, value)) {
|
|
return;
|
|
}
|
|
|
|
// "Caption" and "Text" map to wgtSetText for all widgets.
|
|
// Copy to persistent buffer since some widgets (Button, Label)
|
|
// store the text pointer without copying.
|
|
if (strcasecmp(propName, "Caption") == 0 || strcasecmp(propName, "Text") == 0) {
|
|
BasStringT *s = basValFormatString(value);
|
|
snprintf(ctrl->textBuf, BAS_MAX_TEXT_BUF, "%s", s->data);
|
|
basStringUnref(s);
|
|
wgtSetText(ctrl->widget, ctrl->textBuf);
|
|
return;
|
|
}
|
|
|
|
// Interface descriptor properties
|
|
if (ctrl->iface) {
|
|
if (setIfaceProp(ctrl->iface, ctrl->widget, propName, value)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// basFormRtShowForm
|
|
// ============================================================
|
|
|
|
void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
|
|
BasFormRtT *rt = (BasFormRtT *)ctx;
|
|
BasFormT *form = (BasFormT *)formRef;
|
|
|
|
if (!form || !form->window) {
|
|
return;
|
|
}
|
|
|
|
form->window->visible = true;
|
|
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;
|
|
}
|
|
|
|
basFormRtFireEvent(rt, form, form->name, "Unload");
|
|
|
|
for (int32_t i = 0; i < form->controlCount; i++) {
|
|
freeListBoxItems(&form->controls[i]);
|
|
}
|
|
|
|
if (form->window) {
|
|
if (rt->ctx->modalWindow == form->window) {
|
|
rt->ctx->modalWindow = NULL;
|
|
}
|
|
|
|
dvxDestroyWindow(rt->ctx, form->window);
|
|
form->window = NULL;
|
|
form->root = NULL;
|
|
form->contentBox = NULL;
|
|
}
|
|
|
|
int32_t idx = (int32_t)(form - rt->forms);
|
|
|
|
if (idx >= 0 && idx < rt->formCount) {
|
|
rt->formCount--;
|
|
|
|
if (idx < rt->formCount) {
|
|
rt->forms[idx] = rt->forms[rt->formCount];
|
|
}
|
|
|
|
memset(&rt->forms[rt->formCount], 0, sizeof(BasFormT));
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// callCommonMethod
|
|
// ============================================================
|
|
|
|
static BasValueT callCommonMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc) {
|
|
(void)args;
|
|
(void)argc;
|
|
|
|
if (strcasecmp(methodName, "SetFocus") == 0) {
|
|
wgtSetFocused(ctrl->widget);
|
|
return zeroValue();
|
|
}
|
|
|
|
if (strcasecmp(methodName, "Refresh") == 0) {
|
|
wgtInvalidatePaint(ctrl->widget);
|
|
return zeroValue();
|
|
}
|
|
|
|
return zeroValue();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// callListBoxMethod
|
|
// ============================================================
|
|
//
|
|
// Handles AddItem/RemoveItem/Clear/List for any widget that
|
|
// supports item lists (listbox, combobox, dropdown).
|
|
|
|
static BasValueT callListBoxMethod(BasControlT *ctrl, const char *methodName, BasValueT *args, int32_t argc) {
|
|
if (strcasecmp(methodName, "AddItem") == 0) {
|
|
if (argc >= 1 && args[0].type == BAS_TYPE_STRING && args[0].strVal) {
|
|
ListBoxItemsT *lb = getListBoxItems(ctrl);
|
|
|
|
if (lb && lb->count < MAX_LISTBOX_ITEMS) {
|
|
lb->items[lb->count] = strdup(args[0].strVal->data);
|
|
lb->count++;
|
|
rebuildListBoxItems(ctrl);
|
|
}
|
|
}
|
|
|
|
return zeroValue();
|
|
}
|
|
|
|
if (strcasecmp(methodName, "RemoveItem") == 0) {
|
|
if (argc >= 1) {
|
|
int32_t idx = (int32_t)basValToNumber(args[0]);
|
|
ListBoxItemsT *lb = getListBoxItems(ctrl);
|
|
|
|
if (lb && idx >= 0 && idx < lb->count) {
|
|
free(lb->items[idx]);
|
|
|
|
for (int32_t i = idx; i < lb->count - 1; i++) {
|
|
lb->items[i] = lb->items[i + 1];
|
|
}
|
|
|
|
lb->count--;
|
|
rebuildListBoxItems(ctrl);
|
|
}
|
|
}
|
|
|
|
return zeroValue();
|
|
}
|
|
|
|
if (strcasecmp(methodName, "Clear") == 0) {
|
|
ListBoxItemsT *lb = getListBoxItems(ctrl);
|
|
|
|
if (lb) {
|
|
for (int32_t i = 0; i < lb->count; i++) {
|
|
free(lb->items[i]);
|
|
}
|
|
|
|
lb->count = 0;
|
|
rebuildListBoxItems(ctrl);
|
|
}
|
|
|
|
return zeroValue();
|
|
}
|
|
|
|
if (strcasecmp(methodName, "List") == 0) {
|
|
if (argc >= 1) {
|
|
int32_t idx = (int32_t)basValToNumber(args[0]);
|
|
ListBoxItemsT *lb = getListBoxItems(ctrl);
|
|
|
|
if (lb && idx >= 0 && idx < lb->count) {
|
|
return basValStringFromC(lb->items[idx]);
|
|
}
|
|
}
|
|
|
|
return basValStringFromC("");
|
|
}
|
|
|
|
return zeroValue();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// createWidget
|
|
// ============================================================
|
|
//
|
|
// Create a DVX widget by type name using its registered API.
|
|
// The API's create function signature varies by widget type, so
|
|
// we call with sensible defaults for each creation pattern.
|
|
|
|
static WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent) {
|
|
const void *api = wgtGetApi(wgtTypeName);
|
|
|
|
if (!api) {
|
|
return NULL;
|
|
}
|
|
|
|
// 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:
|
|
return NULL;
|
|
default: {
|
|
CreateParentFnT fn = *(CreateParentFnT *)api;
|
|
return fn(parent);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// freeListBoxItems
|
|
// ============================================================
|
|
|
|
static void freeListBoxItems(BasControlT *ctrl) {
|
|
for (int32_t i = 0; i < sAuxDataCount; i++) {
|
|
if (sAuxData[i].ctrl == ctrl) {
|
|
ListBoxItemsT *lb = sAuxData[i].items;
|
|
|
|
if (lb) {
|
|
for (int32_t j = 0; j < lb->count; j++) {
|
|
free(lb->items[j]);
|
|
}
|
|
|
|
free(lb);
|
|
}
|
|
|
|
sAuxDataCount--;
|
|
|
|
if (i < sAuxDataCount) {
|
|
sAuxData[i] = sAuxData[sAuxDataCount];
|
|
}
|
|
|
|
memset(&sAuxData[sAuxDataCount], 0, sizeof(AuxDataEntryT));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// getCommonProp
|
|
// ============================================================
|
|
|
|
static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled) {
|
|
*handled = true;
|
|
|
|
if (strcasecmp(propName, "Name") == 0) {
|
|
return basValStringFromC(ctrl->name);
|
|
}
|
|
|
|
if (strcasecmp(propName, "Left") == 0) {
|
|
return basValLong(ctrl->widget->x);
|
|
}
|
|
|
|
if (strcasecmp(propName, "Top") == 0) {
|
|
return basValLong(ctrl->widget->y);
|
|
}
|
|
|
|
if (strcasecmp(propName, "Width") == 0 || 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();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// getListBoxItems
|
|
// ============================================================
|
|
|
|
static ListBoxItemsT *getListBoxItems(BasControlT *ctrl) {
|
|
for (int32_t i = 0; i < sAuxDataCount; i++) {
|
|
if (sAuxData[i].ctrl == ctrl) {
|
|
return sAuxData[i].items;
|
|
}
|
|
}
|
|
|
|
if (sAuxDataCount >= MAX_AUX_DATA) {
|
|
return NULL;
|
|
}
|
|
|
|
ListBoxItemsT *lb = (ListBoxItemsT *)calloc(1, sizeof(ListBoxItemsT));
|
|
|
|
if (!lb) {
|
|
return NULL;
|
|
}
|
|
|
|
sAuxData[sAuxDataCount].ctrl = ctrl;
|
|
sAuxData[sAuxDataCount].items = lb;
|
|
sAuxDataCount++;
|
|
|
|
return lb;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onFormClose
|
|
// ============================================================
|
|
//
|
|
// Called when the user closes a BASIC form's window. Fires the
|
|
// Form_Unload event and stops the VM so the program exits.
|
|
|
|
static void onFormClose(WindowT *win) {
|
|
// Find which form owns this window
|
|
// The window's userData stores nothing useful, so we search
|
|
// by window pointer. We get the form runtime from sFormRt.
|
|
if (!sFormRt) {
|
|
return;
|
|
}
|
|
|
|
for (int32_t i = 0; i < sFormRt->formCount; i++) {
|
|
BasFormT *form = &sFormRt->forms[i];
|
|
|
|
if (form->window == win) {
|
|
basFormRtFireEvent(sFormRt, form, form->name, "Unload");
|
|
|
|
// Free control resources
|
|
for (int32_t j = 0; j < form->controlCount; j++) {
|
|
freeListBoxItems(&form->controls[j]);
|
|
}
|
|
|
|
// Destroy the window
|
|
if (sFormRt->ctx->modalWindow == win) {
|
|
sFormRt->ctx->modalWindow = NULL;
|
|
}
|
|
|
|
dvxDestroyWindow(sFormRt->ctx, win);
|
|
form->window = NULL;
|
|
form->root = NULL;
|
|
form->contentBox = NULL;
|
|
|
|
// Remove from form list
|
|
sFormRt->formCount--;
|
|
|
|
if (i < sFormRt->formCount) {
|
|
sFormRt->forms[i] = sFormRt->forms[sFormRt->formCount];
|
|
}
|
|
|
|
memset(&sFormRt->forms[sFormRt->formCount], 0, sizeof(BasFormT));
|
|
|
|
// If no forms left, stop the VM
|
|
if (sFormRt->formCount == 0 && sFormRt->vm) {
|
|
sFormRt->vm->running = false;
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onFormResize
|
|
// ============================================================
|
|
|
|
static void onFormResize(WindowT *win, int32_t newW, int32_t newH) {
|
|
(void)newW;
|
|
(void)newH;
|
|
|
|
if (!sFormRt) {
|
|
return;
|
|
}
|
|
|
|
for (int32_t i = 0; i < sFormRt->formCount; i++) {
|
|
BasFormT *form = &sFormRt->forms[i];
|
|
|
|
if (form->window == win) {
|
|
basFormRtFireEvent(sFormRt, form, form->name, "Resize");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onWidgetBlur
|
|
// ============================================================
|
|
|
|
static void onWidgetBlur(WidgetT *w) {
|
|
BasControlT *ctrl = (BasControlT *)w->userData;
|
|
|
|
if (!ctrl || !ctrl->form) {
|
|
return;
|
|
}
|
|
|
|
BasFormRtT *rt = NULL;
|
|
|
|
if (ctrl->form->vm) {
|
|
rt = (BasFormRtT *)ctrl->form->vm->ui.ctx;
|
|
}
|
|
|
|
if (rt) {
|
|
basFormRtFireEvent(rt, ctrl->form, ctrl->name, "LostFocus");
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onWidgetChange
|
|
// ============================================================
|
|
|
|
static void onWidgetChange(WidgetT *w) {
|
|
BasControlT *ctrl = (BasControlT *)w->userData;
|
|
|
|
if (!ctrl || !ctrl->form) {
|
|
return;
|
|
}
|
|
|
|
BasFormRtT *rt = NULL;
|
|
|
|
if (ctrl->form->vm) {
|
|
rt = (BasFormRtT *)ctrl->form->vm->ui.ctx;
|
|
}
|
|
|
|
if (rt) {
|
|
basFormRtFireEvent(rt, ctrl->form, ctrl->name, "Change");
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onWidgetClick
|
|
// ============================================================
|
|
|
|
static void onWidgetClick(WidgetT *w) {
|
|
BasControlT *ctrl = (BasControlT *)w->userData;
|
|
|
|
if (!ctrl || !ctrl->form) {
|
|
return;
|
|
}
|
|
|
|
BasFormRtT *rt = NULL;
|
|
|
|
if (ctrl->form->vm) {
|
|
rt = (BasFormRtT *)ctrl->form->vm->ui.ctx;
|
|
}
|
|
|
|
if (rt) {
|
|
basFormRtFireEvent(rt, ctrl->form, ctrl->name, "Click");
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onWidgetDblClick
|
|
// ============================================================
|
|
|
|
static void onWidgetDblClick(WidgetT *w) {
|
|
BasControlT *ctrl = (BasControlT *)w->userData;
|
|
|
|
if (!ctrl || !ctrl->form) {
|
|
return;
|
|
}
|
|
|
|
BasFormRtT *rt = NULL;
|
|
|
|
if (ctrl->form->vm) {
|
|
rt = (BasFormRtT *)ctrl->form->vm->ui.ctx;
|
|
}
|
|
|
|
if (rt) {
|
|
basFormRtFireEvent(rt, ctrl->form, ctrl->name, "DblClick");
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onWidgetFocus
|
|
// ============================================================
|
|
|
|
static void onWidgetFocus(WidgetT *w) {
|
|
BasControlT *ctrl = (BasControlT *)w->userData;
|
|
|
|
if (!ctrl || !ctrl->form) {
|
|
return;
|
|
}
|
|
|
|
BasFormRtT *rt = NULL;
|
|
|
|
if (ctrl->form->vm) {
|
|
rt = (BasFormRtT *)ctrl->form->vm->ui.ctx;
|
|
}
|
|
|
|
if (rt) {
|
|
basFormRtFireEvent(rt, ctrl->form, ctrl->name, "GotFocus");
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onWidgetKeyPress / onWidgetKeyDown
|
|
// ============================================================
|
|
|
|
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);
|
|
basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "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);
|
|
basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "KeyDown", args, 2);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onWidgetMouseDown / onWidgetMouseUp / onWidgetMouseMove
|
|
// ============================================================
|
|
|
|
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);
|
|
basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "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);
|
|
basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "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);
|
|
basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "MouseMove", args, 3);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onWidgetScroll
|
|
// ============================================================
|
|
|
|
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);
|
|
basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "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';
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// rebuildListBoxItems
|
|
// ============================================================
|
|
|
|
static void rebuildListBoxItems(BasControlT *ctrl) {
|
|
ListBoxItemsT *lb = getListBoxItems(ctrl);
|
|
|
|
if (!lb) {
|
|
return;
|
|
}
|
|
|
|
// Use the widget's setItems API if available
|
|
const WgtIfaceT *iface = ctrl->iface;
|
|
|
|
if (!iface) {
|
|
return;
|
|
}
|
|
|
|
// Look for a setItems-like method by checking the widget API directly
|
|
const void *api = wgtGetApi("listbox");
|
|
|
|
if (api) {
|
|
// ListBoxApiT has setItems as the second function pointer
|
|
typedef struct {
|
|
void *create;
|
|
void (*setItems)(WidgetT *, const char **, int32_t);
|
|
} SetItemsPatternT;
|
|
const SetItemsPatternT *p = (const SetItemsPatternT *)api;
|
|
p->setItems(ctrl->widget, (const char **)lb->items, lb->count);
|
|
return;
|
|
}
|
|
|
|
// Fallback: try combobox or dropdown API
|
|
api = wgtGetApi("combobox");
|
|
|
|
if (api) {
|
|
typedef struct {
|
|
void *create;
|
|
void (*setItems)(WidgetT *, const char **, int32_t);
|
|
} SetItemsPatternT;
|
|
const SetItemsPatternT *p = (const SetItemsPatternT *)api;
|
|
p->setItems(ctrl->widget, (const char **)lb->items, lb->count);
|
|
return;
|
|
}
|
|
|
|
api = wgtGetApi("dropdown");
|
|
|
|
if (api) {
|
|
typedef struct {
|
|
void *create;
|
|
void (*setItems)(WidgetT *, const char **, int32_t);
|
|
} SetItemsPatternT;
|
|
const SetItemsPatternT *p = (const SetItemsPatternT *)api;
|
|
p->setItems(ctrl->widget, (const char **)lb->items, lb->count);
|
|
}
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// resolveTypeName
|
|
// ============================================================
|
|
//
|
|
// Resolve a type name (VB-style or DVX widget name) to a DVX
|
|
// widget type name. First tries wgtFindByBasName for VB names
|
|
// like "CommandButton", then falls back to direct widget name.
|
|
|
|
static const char *resolveTypeName(const char *typeName) {
|
|
// Try VB name first (e.g. "CommandButton" -> "button")
|
|
const char *wgtName = wgtFindByBasName(typeName);
|
|
|
|
if (wgtName) {
|
|
return wgtName;
|
|
}
|
|
|
|
// Try as direct widget type name (e.g. "button", "slider")
|
|
if (wgtGetApi(typeName)) {
|
|
return typeName;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// setCommonProp
|
|
// ============================================================
|
|
|
|
static bool setCommonProp(BasControlT *ctrl, const char *propName, BasValueT value) {
|
|
if (strcasecmp(propName, "Left") == 0) {
|
|
ctrl->widget->x = (int32_t)basValToNumber(value);
|
|
wgtInvalidate(ctrl->widget);
|
|
return true;
|
|
}
|
|
|
|
if (strcasecmp(propName, "Top") == 0) {
|
|
ctrl->widget->y = (int32_t)basValToNumber(value);
|
|
wgtInvalidate(ctrl->widget);
|
|
return true;
|
|
}
|
|
|
|
if (strcasecmp(propName, "Width") == 0 || 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);
|
|
wgtInvalidate(ctrl->widget);
|
|
return true;
|
|
}
|
|
|
|
if (strcasecmp(propName, "ForeColor") == 0) {
|
|
ctrl->widget->fgColor = (uint32_t)(int32_t)basValToNumber(value);
|
|
wgtInvalidate(ctrl->widget);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// setIfaceProp
|
|
// ============================================================
|
|
//
|
|
// Look up a property in the interface descriptor and call its
|
|
// setter, marshaling the BasValueT to the native type.
|
|
|
|
static bool setIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, BasValueT value) {
|
|
for (int32_t i = 0; i < iface->propCount; i++) {
|
|
if (strcasecmp(iface->props[i].name, propName) != 0) {
|
|
continue;
|
|
}
|
|
|
|
const WgtPropDescT *p = &iface->props[i];
|
|
|
|
if (!p->setFn) {
|
|
return true; // read-only, but recognized
|
|
}
|
|
|
|
switch (p->type) {
|
|
case WGT_IFACE_STRING: {
|
|
BasStringT *s = basValFormatString(value);
|
|
((void (*)(WidgetT *, const char *))p->setFn)(w, s->data);
|
|
basStringUnref(s);
|
|
break;
|
|
}
|
|
|
|
case WGT_IFACE_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;
|
|
}
|