832 lines
23 KiB
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;
|
|
}
|