DVX_GUI/unused/formcfm.c

832 lines
23 KiB
C

// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
// formcfm.c -- Compiled form format implementation
//
// Serializes the layout portion of a .frm file (the Begin/End block
// structure) into a compact binary format. The BASIC code that follows
// the form layout is NOT included -- it's already compiled into the
// bytecode module.
//
// Binary format:
// magic 4 bytes "DCFM"
// version uint16 1
// formName len-prefixed string
// caption len-prefixed string
// layout len-prefixed string ("VBox", "HBox", etc.)
// width int16
// height int16
// left int16
// top int16
// flags uint8 (bit 0=resizable, 1=centered, 2=autoSize)
// controlCount uint16
// menuItemCount uint16
//
// Controls[controlCount]:
// typeName len-prefixed string
// ctrlName len-prefixed string
// parentIndex int16 (-1 = form root)
// arrayIndex int16 (-1 = not array)
// propertyCount uint16
// properties[]:
// key len-prefixed string
// valueType uint8 (0=string, 1=int, 2=bool)
// value type-dependent
//
// MenuItems[menuItemCount]:
// name len-prefixed string
// caption len-prefixed string
// level uint8
// flags uint8 (bit 0=checked, 1=radioCheck, 2=enabled)
#include "formcfm.h"
#include "../compiler/opcodes.h"
#include "thirdparty/stb_ds_wrap.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
// ============================================================
// Write helpers (same pattern as serialize.c)
// ============================================================
typedef struct {
uint8_t *buf;
int32_t len;
int32_t cap;
} CfmBufT;
static void cbufInit(CfmBufT *b) {
b->cap = 2048;
b->buf = (uint8_t *)malloc(b->cap);
b->len = 0;
}
static void cbufGrow(CfmBufT *b, int32_t need) {
while (b->len + need > b->cap) {
b->cap *= 2;
}
b->buf = (uint8_t *)realloc(b->buf, b->cap);
}
static void cbufWrite(CfmBufT *b, const void *data, int32_t len) {
cbufGrow(b, len);
memcpy(b->buf + b->len, data, len);
b->len += len;
}
static void cbufU8(CfmBufT *b, uint8_t v) {
cbufWrite(b, &v, 1);
}
static void cbufU16(CfmBufT *b, uint16_t v) {
cbufWrite(b, &v, 2);
}
static void cbufI16(CfmBufT *b, int16_t v) {
cbufWrite(b, &v, 2);
}
static void cbufStr(CfmBufT *b, const char *s) {
uint16_t len = s ? (uint16_t)strlen(s) : 0;
cbufU16(b, len);
if (len > 0) {
cbufWrite(b, s, len);
}
}
// ============================================================
// Read helpers
// ============================================================
typedef struct {
const uint8_t *data;
int32_t len;
int32_t pos;
} CfmReaderT;
static bool crOk(const CfmReaderT *r, int32_t need) {
return r->pos + need <= r->len;
}
static uint8_t crU8(CfmReaderT *r) {
return crOk(r, 1) ? r->data[r->pos++] : 0;
}
static uint16_t crU16(CfmReaderT *r) {
if (!crOk(r, 2)) {
return 0;
}
uint16_t v;
memcpy(&v, r->data + r->pos, 2);
r->pos += 2;
return v;
}
static int16_t crI16(CfmReaderT *r) {
if (!crOk(r, 2)) {
return 0;
}
int16_t v;
memcpy(&v, r->data + r->pos, 2);
r->pos += 2;
return v;
}
static int32_t crI32(CfmReaderT *r) {
if (!crOk(r, 4)) {
return 0;
}
int32_t v;
memcpy(&v, r->data + r->pos, 4);
r->pos += 4;
return v;
}
static char *crStr(CfmReaderT *r) {
uint16_t len = crU16(r);
if (len == 0 || !crOk(r, len)) {
char *s = (char *)malloc(1);
s[0] = '\0';
return s;
}
char *s = (char *)malloc(len + 1);
memcpy(s, r->data + r->pos, len);
s[len] = '\0';
r->pos += len;
return s;
}
// ============================================================
// .frm text parser for compilation
// ============================================================
#define MAX_LINE 512
typedef struct {
char typeName[BAS_MAX_CTRL_NAME];
char ctrlName[BAS_MAX_CTRL_NAME];
int16_t parentIndex;
int16_t arrayIndex;
// Properties
struct {
char key[64];
uint8_t valueType; // 0=string, 1=int, 2=bool
char strVal[256];
int32_t intVal;
} props[32];
int32_t propCount;
} CfmControlT;
typedef struct {
char name[BAS_MAX_CTRL_NAME];
char caption[256];
uint8_t level;
uint8_t flags; // bit 0=checked, 1=radioCheck, 2=enabled
} CfmMenuItemT;
static void parseFrmKeyVal(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 != '\t' && *line != '=' && ki < 63) {
key[ki++] = *line++;
}
key[ki] = '\0';
while (*line == ' ' || *line == '\t') {
line++;
}
if (*line == '=') {
line++;
}
while (*line == ' ' || *line == '\t') {
line++;
}
// Value: strip surrounding quotes if present
int32_t vi = 0;
bool inQ = false;
if (*line == '"') {
inQ = true;
line++;
}
while (*line && vi < 255) {
if (inQ && *line == '"') {
break;
}
value[vi++] = *line++;
}
value[vi] = '\0';
}
// ============================================================
// basFormCompile
// ============================================================
uint8_t *basFormCompile(const char *frmSource, int32_t frmLen, int32_t *outLen) {
if (!frmSource || frmLen <= 0 || !outLen) {
return NULL;
}
// Parse the .frm text to extract form layout
char formName[BAS_MAX_CTRL_NAME] = "";
char caption[256] = "";
char layout[BAS_MAX_CTRL_NAME] = "VBox";
int16_t width = 0;
int16_t height = 0;
int16_t left = 0;
int16_t top = 0;
bool resizable = true;
bool centered = true;
bool autoSize = true;
CfmControlT *controls = NULL;
int32_t controlCount = 0;
int32_t controlCap = 32;
controls = (CfmControlT *)calloc(controlCap, sizeof(CfmControlT));
CfmMenuItemT *menuItems = NULL;
int32_t menuCount = 0;
int32_t menuCap = 16;
menuItems = (CfmMenuItemT *)calloc(menuCap, sizeof(CfmMenuItemT));
// Parent index stack for nested containers
int16_t parentStack[BAS_MAX_FRM_NESTING];
int32_t nestDepth = 0;
int32_t menuNestDepth = 0;
bool inForm = false;
bool inMenu = false;
int32_t currentCtrl = -1;
const char *pos = frmSource;
const char *end = frmSource + frmLen;
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_LINE];
if (lineLen >= MAX_LINE) {
lineLen = MAX_LINE - 1;
}
memcpy(line, lineStart, lineLen);
line[lineLen] = '\0';
char *trimmed = line;
while (*trimmed == ' ' || *trimmed == '\t') {
trimmed++;
}
if (*trimmed == '\0' || *trimmed == '\'') {
continue;
}
// Stop at code section (first SUB/FUNCTION/DIM/executable statement after End Form)
if (!inForm && formName[0]) {
break;
}
// VERSION line -- skip
if (strncasecmp(trimmed, "VERSION ", 8) == 0) {
continue;
}
// Begin block
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) {
snprintf(formName, BAS_MAX_CTRL_NAME, "%s", ctrlName);
snprintf(caption, sizeof(caption), "%s", ctrlName);
inForm = true;
nestDepth = 0;
for (int32_t i = 0; i < BAS_MAX_FRM_NESTING; i++) {
parentStack[i] = -1;
}
} else if (strcasecmp(typeName, "Menu") == 0 && inForm) {
if (menuCount >= menuCap) {
menuCap *= 2;
menuItems = (CfmMenuItemT *)realloc(menuItems, menuCap * sizeof(CfmMenuItemT));
}
CfmMenuItemT *mi = &menuItems[menuCount++];
memset(mi, 0, sizeof(CfmMenuItemT));
snprintf(mi->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
mi->level = (uint8_t)menuNestDepth;
mi->flags = 0x04; // enabled by default
inMenu = true;
menuNestDepth++;
currentCtrl = -1;
} else if (inForm) {
if (controlCount >= controlCap) {
controlCap *= 2;
controls = (CfmControlT *)realloc(controls, controlCap * sizeof(CfmControlT));
}
CfmControlT *ctrl = &controls[controlCount];
memset(ctrl, 0, sizeof(CfmControlT));
snprintf(ctrl->typeName, BAS_MAX_CTRL_NAME, "%s", typeName);
snprintf(ctrl->ctrlName, BAS_MAX_CTRL_NAME, "%s", ctrlName);
ctrl->parentIndex = (nestDepth > 0) ? parentStack[nestDepth - 1] : -1;
ctrl->arrayIndex = -1;
currentCtrl = controlCount;
controlCount++;
// Check if this is a container -- push onto parent stack
const char *wgtName = wgtFindByBasName(typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface && iface->isContainer) {
if (nestDepth < BAS_MAX_FRM_NESTING) {
parentStack[nestDepth] = (int16_t)currentCtrl;
}
nestDepth++;
}
}
}
continue;
}
// End block
if (strncasecmp(trimmed, "End", 3) == 0 && (trimmed[3] == '\0' || trimmed[3] == ' ' || trimmed[3] == '\t' || trimmed[3] == '\r' || trimmed[3] == '\n')) {
if (inMenu) {
menuNestDepth--;
if (menuNestDepth <= 0) {
inMenu = false;
menuNestDepth = 0;
}
} else if (nestDepth > 0) {
nestDepth--;
} else {
// End Form
inForm = false;
}
currentCtrl = -1;
continue;
}
// Property assignment inside a block
if (inForm) {
char key[64];
char value[256];
parseFrmKeyVal(trimmed, key, value);
if (!key[0]) {
continue;
}
// Form-level properties
if (currentCtrl < 0 && !inMenu) {
if (strcasecmp(key, "Caption") == 0) {
snprintf(caption, sizeof(caption), "%s", value);
} else if (strcasecmp(key, "Layout") == 0) {
snprintf(layout, BAS_MAX_CTRL_NAME, "%s", value);
} else if (strcasecmp(key, "Width") == 0) {
width = (int16_t)atoi(value);
} else if (strcasecmp(key, "Height") == 0) {
height = (int16_t)atoi(value);
} else if (strcasecmp(key, "Left") == 0) {
left = (int16_t)atoi(value);
} else if (strcasecmp(key, "Top") == 0) {
top = (int16_t)atoi(value);
} else if (strcasecmp(key, "Resizable") == 0) {
resizable = (strcasecmp(value, "True") == 0);
} else if (strcasecmp(key, "Centered") == 0) {
centered = (strcasecmp(value, "True") == 0);
} else if (strcasecmp(key, "AutoSize") == 0) {
autoSize = (strcasecmp(value, "True") == 0);
}
} else if (inMenu && menuCount > 0) {
CfmMenuItemT *mi = &menuItems[menuCount - 1];
if (strcasecmp(key, "Caption") == 0) {
snprintf(mi->caption, sizeof(mi->caption), "%s", value);
} else if (strcasecmp(key, "Checked") == 0) {
if (strcasecmp(value, "True") == 0) {
mi->flags |= 0x01;
}
} else if (strcasecmp(key, "Enabled") == 0) {
if (strcasecmp(value, "False") == 0) {
mi->flags &= ~0x04;
}
}
} else if (currentCtrl >= 0 && currentCtrl < controlCount) {
CfmControlT *ctrl = &controls[currentCtrl];
// Index property for control arrays
if (strcasecmp(key, "Index") == 0) {
ctrl->arrayIndex = (int16_t)atoi(value);
continue;
}
if (ctrl->propCount < 32) {
int32_t pi = ctrl->propCount++;
snprintf(ctrl->props[pi].key, 64, "%s", key);
// Detect value type
if (strcasecmp(value, "True") == 0) {
ctrl->props[pi].valueType = 2;
ctrl->props[pi].intVal = 1;
} else if (strcasecmp(value, "False") == 0) {
ctrl->props[pi].valueType = 2;
ctrl->props[pi].intVal = 0;
} else if (value[0] == '-' || isdigit((unsigned char)value[0])) {
ctrl->props[pi].valueType = 1;
ctrl->props[pi].intVal = atoi(value);
} else {
ctrl->props[pi].valueType = 0;
snprintf(ctrl->props[pi].strVal, 256, "%s", value);
}
}
}
}
}
// Serialize to binary
CfmBufT b;
cbufInit(&b);
cbufWrite(&b, "DCFM", 4);
cbufU16(&b, 1); // version
cbufStr(&b, formName);
cbufStr(&b, caption);
cbufStr(&b, layout);
cbufI16(&b, width);
cbufI16(&b, height);
cbufI16(&b, left);
cbufI16(&b, top);
uint8_t flags = 0;
if (resizable) { flags |= 0x01; }
if (centered) { flags |= 0x02; }
if (autoSize) { flags |= 0x04; }
cbufU8(&b, flags);
cbufU16(&b, (uint16_t)controlCount);
cbufU16(&b, (uint16_t)menuCount);
for (int32_t i = 0; i < controlCount; i++) {
CfmControlT *ctrl = &controls[i];
cbufStr(&b, ctrl->typeName);
cbufStr(&b, ctrl->ctrlName);
cbufI16(&b, ctrl->parentIndex);
cbufI16(&b, ctrl->arrayIndex);
cbufU16(&b, (uint16_t)ctrl->propCount);
for (int32_t j = 0; j < ctrl->propCount; j++) {
cbufStr(&b, ctrl->props[j].key);
cbufU8(&b, ctrl->props[j].valueType);
switch (ctrl->props[j].valueType) {
case 0: // string
cbufStr(&b, ctrl->props[j].strVal);
break;
case 1: // int
cbufWrite(&b, &ctrl->props[j].intVal, 4);
break;
case 2: // bool
cbufU8(&b, ctrl->props[j].intVal ? 1 : 0);
break;
}
}
}
for (int32_t i = 0; i < menuCount; i++) {
CfmMenuItemT *mi = &menuItems[i];
cbufStr(&b, mi->name);
cbufStr(&b, mi->caption);
cbufU8(&b, mi->level);
cbufU8(&b, mi->flags);
}
free(controls);
free(menuItems);
*outLen = b.len;
return b.buf;
}
// ============================================================
// basFormLoadCompiled
// ============================================================
BasFormT *basFormLoadCompiled(BasFormRtT *rt, const uint8_t *data, int32_t dataLen) {
if (!rt || !data || dataLen < 10) {
return NULL;
}
CfmReaderT r = { data, dataLen, 0 };
// Check magic
if (data[0] != 'D' || data[1] != 'C' || data[2] != 'F' || data[3] != 'M') {
return NULL;
}
r.pos = 4;
uint16_t version = crU16(&r);
if (version != 1) {
return NULL;
}
char *formName = crStr(&r);
char *caption = crStr(&r);
char *layout = crStr(&r);
int16_t width = crI16(&r);
int16_t height = crI16(&r);
int16_t left = crI16(&r);
int16_t top = crI16(&r);
uint8_t flags = crU8(&r);
bool resizable = (flags & 0x01) != 0;
bool centered = (flags & 0x02) != 0;
bool autoSize = (flags & 0x04) != 0;
uint16_t controlCount = crU16(&r);
uint16_t menuCount = crU16(&r);
// Create the form
WidgetT *root;
WidgetT *contentBox;
WindowT *win = basFormRtCreateFormWindow(rt->ctx, caption, layout, resizable, centered, autoSize, width, height, left, top, &root, &contentBox);
if (!win) {
free(formName);
free(caption);
free(layout);
return NULL;
}
BasFormT *form = (BasFormT *)calloc(1, sizeof(BasFormT));
snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
snprintf(form->frmLayout, sizeof(form->frmLayout), "%s", layout);
form->frmWidth = width;
form->frmHeight = height;
form->frmLeft = left;
form->frmTop = top;
form->frmResizable = resizable;
form->frmCentered = centered;
form->frmAutoSize = autoSize;
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;
// Window callbacks
win->onClose = NULL; // stub sets these after
win->onResize = NULL;
win->onFocus = NULL;
win->onBlur = NULL;
// Create controls
for (uint16_t i = 0; i < controlCount; i++) {
char *typeName = crStr(&r);
char *ctrlName = crStr(&r);
int16_t parentIndex = crI16(&r);
int16_t arrayIndex = crI16(&r);
uint16_t propCount = crU16(&r);
// Determine parent widget
WidgetT *parent = contentBox;
if (parentIndex >= 0 && parentIndex < (int16_t)i) {
// Find the parent control's widget
BasControlT *parentCtrl = form->controls[parentIndex];
parent = parentCtrl->widget;
}
// Resolve widget type and create
const char *wgtTypeName = wgtFindByBasName(typeName);
WidgetT *widget = NULL;
if (wgtTypeName) {
widget = createWidget(wgtTypeName, parent);
}
if (!widget) {
// Skip properties
for (uint16_t j = 0; j < propCount; j++) {
free(crStr(&r)); // key
uint8_t vt = crU8(&r);
if (vt == 0) { free(crStr(&r)); }
else if (vt == 1) { crI32(&r); }
else { crU8(&r); }
}
free(typeName);
free(ctrlName);
continue;
}
wgtSetName(widget, ctrlName);
// Create control entry
BasControlT *ctrl = (BasControlT *)calloc(1, sizeof(BasControlT));
snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
snprintf(ctrl->typeName, BAS_MAX_CTRL_NAME, "%s", typeName);
ctrl->index = arrayIndex;
ctrl->widget = widget;
ctrl->form = form;
ctrl->iface = wgtGetIface(wgtTypeName);
arrput(form->controls, ctrl);
widget->userData = ctrl;
// Apply properties
for (uint16_t j = 0; j < propCount; j++) {
char *key = crStr(&r);
uint8_t vt = crU8(&r);
BasValueT val;
memset(&val, 0, sizeof(val));
switch (vt) {
case 0: { // string
char *sv = crStr(&r);
val = basValStringFromC(sv);
free(sv);
break;
}
case 1: // int
val = basValLong(crI32(&r));
break;
case 2: // bool
val = basValBool(crU8(&r) != 0);
break;
}
basFormRtSetProp(rt, ctrl, key, val);
basValRelease(&val);
free(key);
}
free(typeName);
free(ctrlName);
}
// TODO: menu items (skip for now, read and discard)
for (uint16_t i = 0; i < menuCount; i++) {
free(crStr(&r)); // name
free(crStr(&r)); // caption
crU8(&r); // level
crU8(&r); // flags
}
// Add form to runtime
arrput(rt->forms, form);
free(formName);
free(caption);
free(layout);
return form;
}
bool basFormGetCompiledName(const uint8_t *data, int32_t dataLen, char *nameBuf, int32_t bufSize) {
if (!data || dataLen < 10 || !nameBuf || bufSize < 2) {
return false;
}
if (data[0] != 'D' || data[1] != 'C' || data[2] != 'F' || data[3] != 'M') {
return false;
}
// Skip magic (4) + version (2) = offset 6
// Form name is the first length-prefixed string
if (dataLen < 8) {
return false;
}
uint16_t nameLen;
memcpy(&nameLen, data + 6, 2);
if (nameLen == 0 || 8 + nameLen > dataLen) {
return false;
}
int32_t copyLen = (nameLen < bufSize - 1) ? nameLen : bufSize - 1;
memcpy(nameBuf, data + 8, copyLen);
nameBuf[copyLen] = '\0';
return true;
}