We can make binaries from BASIC!
This commit is contained in:
parent
7157f97c28
commit
de60200f23
63 changed files with 6032 additions and 1311 deletions
|
|
@ -1,7 +1,7 @@
|
|||
// ctrlpanel.c -- DVX Control Panel
|
||||
//
|
||||
// System configuration app with four tabs:
|
||||
// Mouse -- scroll direction, double-click speed, acceleration
|
||||
// Mouse -- scroll direction, double-click speed, acceleration, cursor speed
|
||||
// Colors -- all 18 system colors, theme load/save
|
||||
// Desktop -- wallpaper image (stretch mode)
|
||||
// Video -- resolution and color depth
|
||||
|
|
@ -94,6 +94,7 @@ static uint8_t sSavedColorRgb[ColorCountE][3];
|
|||
static int32_t sSavedWheelDir;
|
||||
static int32_t sSavedDblClick;
|
||||
static int32_t sSavedAccel;
|
||||
static int32_t sSavedSpeed;
|
||||
static int32_t sSavedVideoW;
|
||||
static int32_t sSavedVideoH;
|
||||
static int32_t sSavedVideoBpp;
|
||||
|
|
@ -104,6 +105,8 @@ static WidgetT *sWheelDrop = NULL;
|
|||
static WidgetT *sDblClickSldr = NULL;
|
||||
static WidgetT *sDblClickLbl = NULL;
|
||||
static WidgetT *sAccelDrop = NULL;
|
||||
static WidgetT *sSpeedSldr = NULL;
|
||||
static WidgetT *sSpeedLbl = NULL;
|
||||
static WidgetT *sDblTestLbl = NULL;
|
||||
|
||||
// Colors tab widgets
|
||||
|
|
@ -147,6 +150,9 @@ static void buildVideoTab(WidgetT *page);
|
|||
static int32_t mapAccelName(const char *name);
|
||||
static const char *mapAccelValue(int32_t val);
|
||||
static void onAccelChange(WidgetT *w);
|
||||
static void onSpeedSlider(WidgetT *w);
|
||||
static void updateSpeedLabel(void);
|
||||
static void applyMouseConfig(void);
|
||||
static void onApplyTheme(WidgetT *w);
|
||||
static void onBrowseTheme(WidgetT *w);
|
||||
static void onResetColors(WidgetT *w);
|
||||
|
|
@ -373,6 +379,26 @@ static void buildMouseTab(WidgetT *page) {
|
|||
|
||||
wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);
|
||||
|
||||
// Speed (mickey-to-pixel ratio)
|
||||
wgtLabel(page, "Cursor Speed:");
|
||||
|
||||
WidgetT *speedRow = wgtHBox(page);
|
||||
speedRow->spacing = wgtPixels(CP_SPACING);
|
||||
|
||||
wgtLabel(speedRow, "Slow");
|
||||
sSpeedSldr = wgtSlider(speedRow, 2, 32);
|
||||
sSpeedSldr->weight = 100;
|
||||
sSpeedSldr->onChange = onSpeedSlider;
|
||||
|
||||
int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", 8);
|
||||
wgtSliderSetValue(sSpeedSldr, speed);
|
||||
wgtLabel(speedRow, "Fast ");
|
||||
sSpeedLbl = wgtLabel(speedRow, "");
|
||||
sSpeedLbl->prefW = wgtPixels(CP_DBLCLICK_LBL_W);
|
||||
updateSpeedLabel();
|
||||
|
||||
wgtSpacer(page)->prefH = wgtPixels(CP_SPACING_SMALL);
|
||||
|
||||
// Double-click test area
|
||||
WidgetT *testRow = wgtHBox(page);
|
||||
testRow->spacing = wgtPixels(CP_SPACING);
|
||||
|
|
@ -469,28 +495,15 @@ static const char *mapAccelValue(int32_t idx) {
|
|||
// ============================================================
|
||||
|
||||
static void onWheelChange(WidgetT *w) {
|
||||
int32_t sel = wgtDropdownGetSelected(w);
|
||||
int32_t dir = (sel == 1) ? -1 : 1;
|
||||
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
|
||||
|
||||
const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
|
||||
int32_t accelVal = mapAccelName(accelName);
|
||||
|
||||
dvxSetMouseConfig(sAc, dir, dbl, accelVal);
|
||||
(void)w;
|
||||
applyMouseConfig();
|
||||
}
|
||||
|
||||
|
||||
static void onDblClickSlider(WidgetT *w) {
|
||||
(void)w;
|
||||
updateDblClickLabel();
|
||||
|
||||
int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
|
||||
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
|
||||
|
||||
const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
|
||||
int32_t accelVal = mapAccelName(accelName);
|
||||
|
||||
dvxSetMouseConfig(sAc, dir, dbl, accelVal);
|
||||
applyMouseConfig();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -506,13 +519,15 @@ static void onDblClickTest(WidgetT *w) {
|
|||
|
||||
|
||||
static void onAccelChange(WidgetT *w) {
|
||||
int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
|
||||
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
|
||||
(void)w;
|
||||
applyMouseConfig();
|
||||
}
|
||||
|
||||
const char *accelName = mapAccelValue(wgtDropdownGetSelected(w));
|
||||
int32_t accelVal = mapAccelName(accelName);
|
||||
|
||||
dvxSetMouseConfig(sAc, dir, dbl, accelVal);
|
||||
static void onSpeedSlider(WidgetT *w) {
|
||||
(void)w;
|
||||
updateSpeedLabel();
|
||||
applyMouseConfig();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -808,6 +823,7 @@ static void onOk(WidgetT *w) {
|
|||
prefsSetString(sPrefs, "mouse", "wheel", wheelSel == 1 ? "reversed" : "normal");
|
||||
prefsSetInt(sPrefs, "mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
|
||||
prefsSetString(sPrefs, "mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop)));
|
||||
prefsSetInt(sPrefs, "mouse", "speed", wgtSliderGetValue(sSpeedSldr));
|
||||
|
||||
// Save colors to INI
|
||||
for (int32_t i = 0; i < ColorCountE; i++) {
|
||||
|
|
@ -881,6 +897,7 @@ static void saveSnapshot(void) {
|
|||
sSavedWheelDir = sAc->wheelDirection;
|
||||
sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC);
|
||||
sSavedAccel = wgtDropdownGetSelected(sAccelDrop);
|
||||
sSavedSpeed = wgtSliderGetValue(sSpeedSldr);
|
||||
sSavedVideoW = sAc->display.width;
|
||||
sSavedVideoH = sAc->display.height;
|
||||
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
|
||||
|
|
@ -900,7 +917,8 @@ static void restoreSnapshot(void) {
|
|||
// Restore mouse
|
||||
const char *accelName = mapAccelValue(sSavedAccel);
|
||||
int32_t accelVal = mapAccelName(accelName);
|
||||
dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal);
|
||||
int32_t speedVal = 34 - sSavedSpeed;
|
||||
dvxSetMouseConfig(sAc, sSavedWheelDir, sSavedDblClick, accelVal, speedVal);
|
||||
|
||||
// Restore video mode if changed
|
||||
if (sAc->display.width != sSavedVideoW ||
|
||||
|
|
@ -1067,6 +1085,40 @@ static void updateDblClickLabel(void) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// updateSpeedLabel
|
||||
// ============================================================
|
||||
|
||||
static void updateSpeedLabel(void) {
|
||||
static char buf[32];
|
||||
int32_t val = wgtSliderGetValue(sSpeedSldr);
|
||||
snprintf(buf, sizeof(buf), "%ld", (long)val);
|
||||
wgtSetText(sSpeedLbl, buf);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// applyMouseConfig -- gather current widget values and apply
|
||||
// ============================================================
|
||||
|
||||
static void applyMouseConfig(void) {
|
||||
int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
|
||||
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
|
||||
|
||||
const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
|
||||
int32_t accelVal = mapAccelName(accelName);
|
||||
|
||||
// Slider is inverted: low value = slow (high mickeys), high value = fast (low mickeys)
|
||||
// Slider range 2..32. Mickey ratio = 34 - sliderValue, so:
|
||||
// slider 2 -> 32 mickeys/8px (slowest)
|
||||
// slider 8 -> 26 (near default)
|
||||
// slider 32 -> 2 mickeys/8px (fastest)
|
||||
int32_t speedVal = 34 - wgtSliderGetValue(sSpeedSldr);
|
||||
|
||||
dvxSetMouseConfig(sAc, dir, dbl, accelVal, speedVal);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// updateSwatch
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -19,22 +19,23 @@ LIBSDIR = ../../bin/libs
|
|||
APPDIR = ../../bin/apps/kpunch/dvxbasic
|
||||
DVXRES = ../../bin/host/dvxres
|
||||
|
||||
# Runtime library objects (VM + values)
|
||||
RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o
|
||||
# Runtime library objects (VM + values + form runtime + serialization + compiled forms)
|
||||
RT_OBJS = $(OBJDIR)/vm.o $(OBJDIR)/values.o $(OBJDIR)/formrt.o $(OBJDIR)/serialize.o $(OBJDIR)/formcfm.o
|
||||
RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt
|
||||
RT_TARGET = $(RT_TARGETDIR)/basrt.lib
|
||||
|
||||
# Compiler objects (only needed by the IDE)
|
||||
COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o
|
||||
|
||||
# Form runtime objects (bridge between BASIC and DVX widgets)
|
||||
FORMRT_OBJS = $(OBJDIR)/formrt.o
|
||||
COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o $(OBJDIR)/strip.o
|
||||
|
||||
# IDE app objects
|
||||
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
|
||||
APP_OBJS = $(IDE_OBJS) $(FORMRT_OBJS)
|
||||
APP_OBJS = $(IDE_OBJS)
|
||||
APP_TARGET = $(APPDIR)/dvxbasic.app
|
||||
|
||||
# Standalone stub (embedded as IDE resource)
|
||||
STUB_OBJS = $(OBJDIR)/basstub.o
|
||||
STUB_TARGET = $(OBJDIR)/basstub.app
|
||||
|
||||
# Native test programs (host gcc, not cross-compiled)
|
||||
HOSTCC = gcc
|
||||
HOSTCFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -I. -I../../core
|
||||
|
|
@ -53,7 +54,7 @@ TEST_QUICK_SRCS = test_quick.c compiler/lexer.c compiler/parser.c compiler/co
|
|||
|
||||
.PHONY: all clean tests
|
||||
|
||||
all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(APP_TARGET)
|
||||
all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(STUB_TARGET) $(APP_TARGET)
|
||||
|
||||
tests: $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK)
|
||||
|
||||
|
|
@ -77,17 +78,34 @@ $(RT_TARGET): $(RT_OBJS) | $(RT_TARGETDIR)
|
|||
$(RT_TARGETDIR)/basrt.dep: ../../config/basrt.dep | $(RT_TARGETDIR)
|
||||
sed 's/$$/\r/' $< > $@
|
||||
|
||||
# IDE app DXE (compiler linked in, runtime from basrt.lib)
|
||||
$(APP_TARGET): $(COMP_OBJS) $(APP_OBJS) dvxbasic.res | $(APPDIR)
|
||||
# Standalone stub DXE (embedded as resource in IDE app)
|
||||
$(STUB_TARGET): $(STUB_OBJS) | $(APPDIR)
|
||||
$(DXE3GEN) -o $@ -U $(STUB_OBJS)
|
||||
|
||||
# IDE app DXE (compiler linked in, runtime from basrt.lib, stub embedded)
|
||||
$(APP_TARGET): $(COMP_OBJS) $(APP_OBJS) $(STUB_TARGET) dvxbasic.res | $(APPDIR)
|
||||
$(DXE3GEN) -o $@ -U $(COMP_OBJS) $(APP_OBJS)
|
||||
$(DVXRES) build $@ dvxbasic.res
|
||||
$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
|
||||
|
||||
|
||||
# Object files
|
||||
$(OBJDIR)/codegen.o: compiler/codegen.c compiler/codegen.h compiler/symtab.h compiler/opcodes.h runtime/values.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/formrt.o: formrt/formrt.c formrt/formrt.h compiler/codegen.h runtime/vm.h | $(OBJDIR)
|
||||
$(OBJDIR)/formrt.o: formrt/formrt.c formrt/formrt.h compiler/opcodes.h runtime/vm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/formcfm.o: formrt/formcfm.c formrt/formcfm.h formrt/formrt.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/serialize.o: runtime/serialize.c runtime/serialize.h runtime/vm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/strip.o: compiler/strip.c compiler/strip.h compiler/opcodes.h runtime/vm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/basstub.o: stub/basstub.c runtime/vm.h runtime/serialize.h formrt/formrt.h formrt/formcfm.h | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/ideDesigner.o: ide/ideDesigner.c ide/ideDesigner.h | $(OBJDIR)
|
||||
|
|
@ -140,5 +158,5 @@ $(BINDIR):
|
|||
mkdir -p $(BINDIR)
|
||||
|
||||
clean:
|
||||
rm -rf $(RT_OBJS) $(COMP_OBJS) $(FORMRT_OBJS) $(IDE_OBJS) $(RT_TARGET) $(APP_TARGET) $(RT_TARGETDIR)/basrt.dep $(RT_TARGETDIR) $(OBJDIR)/basrt_init.o
|
||||
rm -rf $(RT_OBJS) $(COMP_OBJS) $(IDE_OBJS) $(STUB_OBJS) $(RT_TARGET) $(APP_TARGET) $(STUB_TARGET) $(RT_TARGETDIR)/basrt.dep $(RT_TARGETDIR) $(OBJDIR)/basrt_init.o
|
||||
rm -f $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK)
|
||||
|
|
|
|||
|
|
@ -34,8 +34,10 @@ static const KeywordEntryT sKeywords[] = {
|
|||
{ "CASE", TOK_CASE },
|
||||
{ "CHDIR", TOK_CHDIR },
|
||||
{ "CHDRIVE", TOK_CHDRIVE },
|
||||
{ "CLOSE", TOK_CLOSE },
|
||||
{ "CURDIR", TOK_CURDIR },
|
||||
{ "CLOSE", TOK_CLOSE },
|
||||
{ "CREATECONTROL", TOK_CREATECONTROL },
|
||||
{ "CREATEFORM", TOK_CREATEFORM },
|
||||
{ "CURDIR", TOK_CURDIR },
|
||||
{ "CURDIR$", TOK_CURDIR },
|
||||
{ "CONST", TOK_CONST },
|
||||
{ "DATA", TOK_DATA },
|
||||
|
|
@ -95,8 +97,10 @@ static const KeywordEntryT sKeywords[] = {
|
|||
{ "NAME", TOK_NAME },
|
||||
{ "NEXT", TOK_NEXT },
|
||||
{ "NOT", TOK_NOT },
|
||||
{ "NOTHING", TOK_NOTHING },
|
||||
{ "ON", TOK_ON },
|
||||
{ "OPEN", TOK_OPEN },
|
||||
{ "OPTIONAL", TOK_OPTIONAL },
|
||||
{ "OPTION", TOK_OPTION },
|
||||
{ "OR", TOK_OR },
|
||||
{ "OUTPUT", TOK_OUTPUT },
|
||||
|
|
@ -106,8 +110,9 @@ static const KeywordEntryT sKeywords[] = {
|
|||
{ "RANDOM", TOK_RANDOM },
|
||||
{ "RANDOMIZE", TOK_RANDOMIZE },
|
||||
{ "READ", TOK_READ },
|
||||
{ "REDIM", TOK_REDIM },
|
||||
{ "REM", TOK_REM },
|
||||
{ "REDIM", TOK_REDIM },
|
||||
{ "REM", TOK_REM },
|
||||
{ "REMOVECONTROL", TOK_REMOVECONTROL },
|
||||
{ "RESTORE", TOK_RESTORE },
|
||||
{ "RESUME", TOK_RESUME },
|
||||
{ "RETURN", TOK_RETURN },
|
||||
|
|
@ -116,24 +121,12 @@ static const KeywordEntryT sKeywords[] = {
|
|||
{ "SELECT", TOK_SELECT },
|
||||
{ "SET", TOK_SET },
|
||||
{ "SETATTR", TOK_SETATTR },
|
||||
{ "SETEVENT", TOK_SETEVENT },
|
||||
{ "SHARED", TOK_SHARED },
|
||||
{ "SHELL", TOK_SHELL },
|
||||
{ "SHOW", TOK_SHOW },
|
||||
{ "SINGLE", TOK_SINGLE },
|
||||
{ "SLEEP", TOK_SLEEP },
|
||||
{ "SQLAFFECTED", TOK_SQLAFFECTED },
|
||||
{ "SQLCLOSE", TOK_SQLCLOSE },
|
||||
{ "SQLEOF", TOK_SQLEOF },
|
||||
{ "SQLERROR", TOK_SQLERROR },
|
||||
{ "SQLEXEC", TOK_SQLEXEC },
|
||||
{ "SQLFIELD", TOK_SQLFIELD },
|
||||
{ "SQLFIELDCOUNT", TOK_SQLFIELDCOUNT },
|
||||
{ "SQLFIELDDBL", TOK_SQLFIELDDBL },
|
||||
{ "SQLFIELDINT", TOK_SQLFIELDINT },
|
||||
{ "SQLFREERESULT", TOK_SQLFREERESULT },
|
||||
{ "SQLNEXT", TOK_SQLNEXT },
|
||||
{ "SQLOPEN", TOK_SQLOPEN },
|
||||
{ "SQLQUERY", TOK_SQLQUERY },
|
||||
{ "STATIC", TOK_STATIC },
|
||||
{ "STEP", TOK_STEP },
|
||||
{ "STRING", TOK_STRING_KW },
|
||||
|
|
@ -703,7 +696,7 @@ static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex) {
|
|||
|
||||
// If it's a keyword and has no suffix, return the keyword token.
|
||||
// String-returning builtins (SQLError$, SQLField$) also match with $.
|
||||
if (kwType != TOK_IDENT && (baseLen == idx || kwType == TOK_SQLERROR || kwType == TOK_SQLFIELD || kwType == TOK_INPUTBOX)) {
|
||||
if (kwType != TOK_IDENT && (baseLen == idx || kwType == TOK_INPUTBOX)) {
|
||||
return kwType;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ typedef enum {
|
|||
TOK_CASE,
|
||||
TOK_CLOSE,
|
||||
TOK_CONST,
|
||||
TOK_CREATECONTROL,
|
||||
TOK_CREATEFORM,
|
||||
TOK_DATA,
|
||||
TOK_DECLARE,
|
||||
TOK_DEF,
|
||||
|
|
@ -112,8 +114,10 @@ typedef enum {
|
|||
TOK_MSGBOX,
|
||||
TOK_NEXT,
|
||||
TOK_NOT,
|
||||
TOK_NOTHING,
|
||||
TOK_ON,
|
||||
TOK_OPEN,
|
||||
TOK_OPTIONAL,
|
||||
TOK_OPTION,
|
||||
TOK_OR,
|
||||
TOK_OUTPUT,
|
||||
|
|
@ -124,12 +128,14 @@ typedef enum {
|
|||
TOK_READ,
|
||||
TOK_REDIM,
|
||||
TOK_REM,
|
||||
TOK_REMOVECONTROL,
|
||||
TOK_RESTORE,
|
||||
TOK_RESUME,
|
||||
TOK_RETURN,
|
||||
TOK_SEEK,
|
||||
TOK_SELECT,
|
||||
TOK_SET,
|
||||
TOK_SETEVENT,
|
||||
TOK_SHARED,
|
||||
TOK_SHELL,
|
||||
TOK_SHOW,
|
||||
|
|
@ -137,19 +143,6 @@ typedef enum {
|
|||
TOK_SLEEP,
|
||||
TOK_INIREAD,
|
||||
TOK_INIWRITE,
|
||||
TOK_SQLCLOSE,
|
||||
TOK_SQLEOF,
|
||||
TOK_SQLERROR,
|
||||
TOK_SQLEXEC,
|
||||
TOK_SQLFIELD, // SQLField$(rs, col) or SQLField$(rs, "name")
|
||||
TOK_SQLFIELDCOUNT,
|
||||
TOK_SQLFIELDINT,
|
||||
TOK_SQLFIELDDBL,
|
||||
TOK_SQLFREERESULT,
|
||||
TOK_SQLNEXT,
|
||||
TOK_SQLOPEN,
|
||||
TOK_SQLQUERY,
|
||||
TOK_SQLAFFECTED,
|
||||
TOK_STATIC,
|
||||
TOK_STEP,
|
||||
TOK_STRING_KW,
|
||||
|
|
|
|||
|
|
@ -197,6 +197,10 @@
|
|||
#define OP_LOAD_FORM_VAR 0x8F // [uint16 idx] push currentFormVars[idx]
|
||||
#define OP_STORE_FORM_VAR 0x9B // [uint16 idx] pop, store to currentFormVars[idx]
|
||||
#define OP_PUSH_FORM_ADDR 0x9C // [uint16 idx] push ¤tFormVars[idx] (ByRef)
|
||||
#define OP_CREATE_CTRL_EX 0x9D // pop parentRef, pop name, pop type, pop formRef, push ctrlRef
|
||||
#define OP_CREATE_FORM 0xF0 // pop height, pop width, pop nameStr, push formRef
|
||||
#define OP_SET_EVENT 0xF1 // pop handlerNameStr, pop eventNameStr, pop ctrlRef
|
||||
#define OP_REMOVE_CTRL 0xF2 // pop ctrlNameStr, pop formRef
|
||||
|
||||
// ============================================================
|
||||
// Array / misc
|
||||
|
|
@ -313,26 +317,6 @@
|
|||
|
||||
#define OP_CALL_EXTERN 0xCD // [uint16 libNameIdx] [uint16 funcNameIdx] [uint8 argc] [uint8 retType]
|
||||
|
||||
// ============================================================
|
||||
// SQL database operations
|
||||
// ============================================================
|
||||
|
||||
#define OP_SQL_OPEN 0xCE // pop path, push db handle (int)
|
||||
#define OP_SQL_CLOSE 0xCF // pop db
|
||||
#define OP_SQL_EXEC 0xD0 // pop sql, pop db, push bool
|
||||
#define OP_SQL_ERROR 0xD1 // pop db, push error string
|
||||
#define OP_SQL_QUERY 0xD2 // pop sql, pop db, push cursor handle (int)
|
||||
#define OP_SQL_NEXT 0xD3 // pop rs, push bool
|
||||
#define OP_SQL_EOF 0xD4 // pop rs, push bool
|
||||
#define OP_SQL_FIELD_COUNT 0xD5 // pop rs, push int
|
||||
#define OP_SQL_FIELD_NAME 0xD6 // pop col, pop rs, push string
|
||||
#define OP_SQL_FIELD_TEXT 0xD7 // pop col, pop rs, push string
|
||||
#define OP_SQL_FIELD_BYNAME 0xD8 // pop name, pop rs, push string
|
||||
#define OP_SQL_FIELD_INT 0xD9 // pop col, pop rs, push int
|
||||
#define OP_SQL_FIELD_DBL 0xDA // pop col, pop rs, push double
|
||||
#define OP_SQL_FREE_RESULT 0xDB // pop rs
|
||||
#define OP_SQL_AFFECTED 0xDC // pop db, push int
|
||||
|
||||
// App object
|
||||
#define OP_APP_PATH 0xDD // push App.Path string
|
||||
#define OP_APP_CONFIG 0xDE // push App.Config string
|
||||
|
|
|
|||
|
|
@ -180,7 +180,9 @@ static void parseRedim(BasParserT *p);
|
|||
static void parseRestore(BasParserT *p);
|
||||
static void parseResume(BasParserT *p);
|
||||
static void parseRmDir(BasParserT *p);
|
||||
static void parseRemoveControl(BasParserT *p);
|
||||
static void parseSeek(BasParserT *p);
|
||||
static void parseSetEvent(BasParserT *p);
|
||||
static void parseSetAttr(BasParserT *p);
|
||||
static void parseSelectCase(BasParserT *p);
|
||||
static void parseShell(BasParserT *p);
|
||||
|
|
@ -717,13 +719,52 @@ static void emitFunctionCall(BasParserT *p, BasSymbolT *sym) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (argc != sym->paramCount) {
|
||||
// Determine minimum required arguments
|
||||
int32_t minArgs = sym->requiredParams;
|
||||
if (minArgs == 0 && sym->paramCount > 0) {
|
||||
// No optional params declared -- all are required
|
||||
bool hasOptional = false;
|
||||
for (int32_t i = 0; i < sym->paramCount && i < BAS_MAX_PARAMS; i++) {
|
||||
if (sym->paramOptional[i]) {
|
||||
hasOptional = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasOptional) {
|
||||
minArgs = sym->paramCount;
|
||||
}
|
||||
}
|
||||
|
||||
if (argc < minArgs || argc > sym->paramCount) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "Function '%s' expects %d arguments, got %d", sym->name, (int)sym->paramCount, (int)argc);
|
||||
if (minArgs == sym->paramCount) {
|
||||
snprintf(buf, sizeof(buf), "Function '%s' expects %d arguments, got %d", sym->name, (int)sym->paramCount, (int)argc);
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "Function '%s' expects %d to %d arguments, got %d", sym->name, (int)minArgs, (int)sym->paramCount, (int)argc);
|
||||
}
|
||||
error(p, buf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Push default zero-values for omitted optional parameters
|
||||
for (int32_t i = argc; i < sym->paramCount; i++) {
|
||||
uint8_t pdt = sym->paramTypes[i];
|
||||
if (pdt == BAS_TYPE_STRING) {
|
||||
uint16_t emptyIdx = basAddConstant(&p->cg, "", 0);
|
||||
basEmit8(&p->cg, OP_PUSH_STR);
|
||||
basEmitU16(&p->cg, emptyIdx);
|
||||
} else if (pdt == BAS_TYPE_OBJECT) {
|
||||
// Nothing -- push NULL object
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, 0);
|
||||
} else {
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, 0);
|
||||
}
|
||||
}
|
||||
|
||||
argc = sym->paramCount;
|
||||
|
||||
// External library function: emit OP_CALL_EXTERN
|
||||
if (sym->isExtern) {
|
||||
basEmit8(&p->cg, OP_CALL_EXTERN);
|
||||
|
|
@ -1280,6 +1321,49 @@ static void parsePrimary(BasParserT *p) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Nothing -- null object reference
|
||||
if (tt == TOK_NOTHING) {
|
||||
advance(p);
|
||||
basEmit8(&p->cg, OP_PUSH_INT16);
|
||||
basEmit16(&p->cg, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// CreateForm(name$, width%, height%) -- create a form in code
|
||||
if (tt == TOK_CREATEFORM) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p); // name
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // width
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // height
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_CREATE_FORM);
|
||||
return;
|
||||
}
|
||||
|
||||
// CreateControl(form, typeName$, ctrlName$ [, parent]) -- create a control
|
||||
if (tt == TOK_CREATECONTROL) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p); // form ref
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // type name
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // control name
|
||||
if (match(p, TOK_COMMA)) {
|
||||
// Optional parent parameter
|
||||
parseExpression(p); // parent ctrl ref
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_CREATE_CTRL_EX);
|
||||
} else {
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_CREATE_CTRL);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// EOF(#channel) -- file end-of-file test
|
||||
if (tt == TOK_EOF_KW) {
|
||||
advance(p);
|
||||
|
|
@ -1415,15 +1499,6 @@ static void parsePrimary(BasParserT *p) {
|
|||
}
|
||||
|
||||
// SQL expression functions -- all require parentheses
|
||||
if (tt == TOK_SQLOPEN) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p);
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_OPEN);
|
||||
return;
|
||||
}
|
||||
|
||||
// IniRead$(file, section, key, default)
|
||||
if (tt == TOK_INIREAD) {
|
||||
advance(p);
|
||||
|
|
@ -1440,115 +1515,6 @@ static void parsePrimary(BasParserT *p) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLERROR) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p);
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLQUERY) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p); // db
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // sql
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_QUERY);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLEOF) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p);
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_EOF);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLFIELDCOUNT) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p);
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_FIELD_COUNT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLFIELD) {
|
||||
// SQLField$(rs, col) or SQLField$(rs, "name")
|
||||
// If second arg is a string, use OP_SQL_FIELD_BYNAME; else OP_SQL_FIELD_TEXT
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p); // rs
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // col or name
|
||||
expect(p, TOK_RPAREN);
|
||||
// Runtime will determine type — use BYNAME if string, TEXT if int.
|
||||
// For simplicity, always use BYNAME (works with both int index and string name
|
||||
// since the C function handles both patterns). Actually we need separate opcodes.
|
||||
// Use a simple heuristic: if the arg is a string literal, use BYNAME.
|
||||
// For now, default to BYNAME which is more flexible.
|
||||
basEmit8(&p->cg, OP_SQL_FIELD_BYNAME);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLFIELDINT) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p); // rs
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // col
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_FIELD_INT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLFIELDDBL) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p); // rs
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // col
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_FIELD_DBL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLAFFECTED) {
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p);
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_AFFECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLEXEC) {
|
||||
// SQLExec as expression: SQLExec(db, sql) returns bool
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p); // db
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // sql
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_EXEC);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tt == TOK_SQLNEXT) {
|
||||
// SQLNext as expression: SQLNext(rs) returns bool
|
||||
advance(p);
|
||||
expect(p, TOK_LPAREN);
|
||||
parseExpression(p);
|
||||
expect(p, TOK_RPAREN);
|
||||
basEmit8(&p->cg, OP_SQL_NEXT);
|
||||
return;
|
||||
}
|
||||
|
||||
// SHELL("command") -- as function expression
|
||||
if (tt == TOK_SHELL) {
|
||||
advance(p);
|
||||
|
|
@ -3410,9 +3376,12 @@ static void parseFunction(BasParserT *p) {
|
|||
basSymTabAllocSlot(&p->sym);
|
||||
|
||||
// Parse parameter list
|
||||
int32_t paramCount = 0;
|
||||
int32_t paramCount = 0;
|
||||
int32_t requiredCount = 0;
|
||||
bool seenOptional = false;
|
||||
uint8_t paramTypes[BAS_MAX_PARAMS];
|
||||
bool paramByVal[BAS_MAX_PARAMS];
|
||||
bool paramOptional[BAS_MAX_PARAMS];
|
||||
|
||||
if (match(p, TOK_LPAREN)) {
|
||||
while (!check(p, TOK_RPAREN) && !check(p, TOK_EOF) && !p->hasError) {
|
||||
|
|
@ -3420,6 +3389,15 @@ static void parseFunction(BasParserT *p) {
|
|||
expect(p, TOK_COMMA);
|
||||
}
|
||||
|
||||
bool optional = false;
|
||||
if (match(p, TOK_OPTIONAL)) {
|
||||
optional = true;
|
||||
seenOptional = true;
|
||||
} else if (seenOptional) {
|
||||
error(p, "Required parameter cannot follow Optional parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
bool byVal = false;
|
||||
if (match(p, TOK_BYVAL)) {
|
||||
byVal = true;
|
||||
|
|
@ -3450,9 +3428,15 @@ static void parseFunction(BasParserT *p) {
|
|||
paramSym->isDefined = true;
|
||||
|
||||
if (paramCount < BAS_MAX_PARAMS) {
|
||||
paramTypes[paramCount] = pdt;
|
||||
paramByVal[paramCount] = byVal;
|
||||
paramTypes[paramCount] = pdt;
|
||||
paramByVal[paramCount] = byVal;
|
||||
paramOptional[paramCount] = optional;
|
||||
}
|
||||
|
||||
if (!optional) {
|
||||
requiredCount = paramCount + 1;
|
||||
}
|
||||
|
||||
paramCount++;
|
||||
}
|
||||
expect(p, TOK_RPAREN);
|
||||
|
|
@ -3485,13 +3469,15 @@ static void parseFunction(BasParserT *p) {
|
|||
return;
|
||||
}
|
||||
|
||||
funcSym->codeAddr = funcAddr;
|
||||
funcSym->isDefined = true;
|
||||
funcSym->paramCount = paramCount;
|
||||
funcSym->scope = SCOPE_GLOBAL;
|
||||
funcSym->codeAddr = funcAddr;
|
||||
funcSym->isDefined = true;
|
||||
funcSym->paramCount = paramCount;
|
||||
funcSym->requiredParams = requiredCount;
|
||||
funcSym->scope = SCOPE_GLOBAL;
|
||||
for (int32_t i = 0; i < paramCount && i < BAS_MAX_PARAMS; i++) {
|
||||
funcSym->paramTypes[i] = paramTypes[i];
|
||||
funcSym->paramByVal[i] = paramByVal[i];
|
||||
funcSym->paramTypes[i] = paramTypes[i];
|
||||
funcSym->paramByVal[i] = paramByVal[i];
|
||||
funcSym->paramOptional[i] = paramOptional[i];
|
||||
}
|
||||
|
||||
// Backpatch any forward-reference calls to this function
|
||||
|
|
@ -5102,6 +5088,48 @@ static void parseStatement(BasParserT *p) {
|
|||
basEmit8(&p->cg, OP_DO_EVENTS);
|
||||
break;
|
||||
|
||||
case TOK_CREATEFORM:
|
||||
// CreateForm used as statement (discard return value)
|
||||
parsePrimary(p);
|
||||
basEmit8(&p->cg, OP_POP);
|
||||
break;
|
||||
|
||||
case TOK_CREATECONTROL:
|
||||
// CreateControl used as statement (discard return value)
|
||||
parsePrimary(p);
|
||||
basEmit8(&p->cg, OP_POP);
|
||||
break;
|
||||
|
||||
case TOK_SETEVENT:
|
||||
parseSetEvent(p);
|
||||
break;
|
||||
|
||||
case TOK_REMOVECONTROL:
|
||||
parseRemoveControl(p);
|
||||
break;
|
||||
|
||||
case TOK_SET:
|
||||
// SET var = expr (object assignment)
|
||||
advance(p); // consume SET
|
||||
if (!check(p, TOK_IDENT)) {
|
||||
errorExpected(p, "variable name");
|
||||
break;
|
||||
}
|
||||
{
|
||||
char varName[BAS_MAX_TOKEN_LEN];
|
||||
strncpy(varName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1);
|
||||
varName[BAS_MAX_TOKEN_LEN - 1] = '\0';
|
||||
advance(p);
|
||||
expect(p, TOK_EQ);
|
||||
parseExpression(p);
|
||||
BasSymbolT *varSym = ensureVariable(p, varName);
|
||||
if (varSym) {
|
||||
varSym->dataType = BAS_TYPE_OBJECT;
|
||||
emitStore(p, varSym);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TOK_LOAD:
|
||||
// Load FormName (identifier, not string)
|
||||
advance(p);
|
||||
|
|
@ -5174,12 +5202,6 @@ static void parseStatement(BasParserT *p) {
|
|||
break;
|
||||
|
||||
// SQL statement forms (no return value)
|
||||
case TOK_SQLCLOSE:
|
||||
advance(p);
|
||||
parseExpression(p); // db
|
||||
basEmit8(&p->cg, OP_SQL_CLOSE);
|
||||
break;
|
||||
|
||||
case TOK_INIWRITE:
|
||||
// IniWrite file, section, key, value
|
||||
advance(p);
|
||||
|
|
@ -5193,30 +5215,6 @@ static void parseStatement(BasParserT *p) {
|
|||
basEmit8(&p->cg, OP_INI_WRITE);
|
||||
break;
|
||||
|
||||
case TOK_SQLEXEC:
|
||||
// SQLExec db, sql (statement form, discard result)
|
||||
advance(p);
|
||||
parseExpression(p); // db
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // sql
|
||||
basEmit8(&p->cg, OP_SQL_EXEC);
|
||||
basEmit8(&p->cg, OP_POP); // discard bool result
|
||||
break;
|
||||
|
||||
case TOK_SQLNEXT:
|
||||
// SQLNext rs (statement form, discard result)
|
||||
advance(p);
|
||||
parseExpression(p); // rs
|
||||
basEmit8(&p->cg, OP_SQL_NEXT);
|
||||
basEmit8(&p->cg, OP_POP); // discard bool result
|
||||
break;
|
||||
|
||||
case TOK_SQLFREERESULT:
|
||||
advance(p);
|
||||
parseExpression(p); // rs
|
||||
basEmit8(&p->cg, OP_SQL_FREE_RESULT);
|
||||
break;
|
||||
|
||||
case TOK_ME: {
|
||||
// Me.Show / Me.Hide / Me.CtrlName.Property = expr
|
||||
advance(p); // consume Me
|
||||
|
|
@ -5456,9 +5454,12 @@ static void parseSub(BasParserT *p) {
|
|||
exitListInit(&exitSubList);
|
||||
|
||||
// Parse parameter list
|
||||
int32_t paramCount = 0;
|
||||
int32_t paramCount = 0;
|
||||
int32_t requiredCount = 0;
|
||||
bool seenOptional = false;
|
||||
uint8_t paramTypes[BAS_MAX_PARAMS];
|
||||
bool paramByVal[BAS_MAX_PARAMS];
|
||||
bool paramOptional[BAS_MAX_PARAMS];
|
||||
|
||||
if (match(p, TOK_LPAREN)) {
|
||||
while (!check(p, TOK_RPAREN) && !check(p, TOK_EOF) && !p->hasError) {
|
||||
|
|
@ -5466,6 +5467,15 @@ static void parseSub(BasParserT *p) {
|
|||
expect(p, TOK_COMMA);
|
||||
}
|
||||
|
||||
bool optional = false;
|
||||
if (match(p, TOK_OPTIONAL)) {
|
||||
optional = true;
|
||||
seenOptional = true;
|
||||
} else if (seenOptional) {
|
||||
error(p, "Required parameter cannot follow Optional parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
bool byVal = false;
|
||||
if (match(p, TOK_BYVAL)) {
|
||||
byVal = true;
|
||||
|
|
@ -5496,9 +5506,15 @@ static void parseSub(BasParserT *p) {
|
|||
paramSym->isDefined = true;
|
||||
|
||||
if (paramCount < BAS_MAX_PARAMS) {
|
||||
paramTypes[paramCount] = pdt;
|
||||
paramByVal[paramCount] = byVal;
|
||||
paramTypes[paramCount] = pdt;
|
||||
paramByVal[paramCount] = byVal;
|
||||
paramOptional[paramCount] = optional;
|
||||
}
|
||||
|
||||
if (!optional) {
|
||||
requiredCount = paramCount + 1;
|
||||
}
|
||||
|
||||
paramCount++;
|
||||
}
|
||||
expect(p, TOK_RPAREN);
|
||||
|
|
@ -5522,13 +5538,15 @@ static void parseSub(BasParserT *p) {
|
|||
return;
|
||||
}
|
||||
|
||||
subSym->codeAddr = subAddr;
|
||||
subSym->isDefined = true;
|
||||
subSym->paramCount = paramCount;
|
||||
subSym->scope = SCOPE_GLOBAL;
|
||||
subSym->codeAddr = subAddr;
|
||||
subSym->isDefined = true;
|
||||
subSym->paramCount = paramCount;
|
||||
subSym->requiredParams = requiredCount;
|
||||
subSym->scope = SCOPE_GLOBAL;
|
||||
for (int32_t i = 0; i < paramCount && i < BAS_MAX_PARAMS; i++) {
|
||||
subSym->paramTypes[i] = paramTypes[i];
|
||||
subSym->paramByVal[i] = paramByVal[i];
|
||||
subSym->paramTypes[i] = paramTypes[i];
|
||||
subSym->paramByVal[i] = paramByVal[i];
|
||||
subSym->paramOptional[i] = paramOptional[i];
|
||||
}
|
||||
|
||||
// Backpatch any forward-reference calls to this sub
|
||||
|
|
@ -5761,6 +5779,28 @@ static void parsePut(BasParserT *p) {
|
|||
}
|
||||
|
||||
|
||||
static void parseRemoveControl(BasParserT *p) {
|
||||
// REMOVECONTROL formRef, ctrlName$
|
||||
advance(p); // consume REMOVECONTROL
|
||||
parseExpression(p); // form reference
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // control name string
|
||||
basEmit8(&p->cg, OP_REMOVE_CTRL);
|
||||
}
|
||||
|
||||
|
||||
static void parseSetEvent(BasParserT *p) {
|
||||
// SETEVENT ctrlRef, eventName$, handlerName$
|
||||
advance(p); // consume SETEVENT
|
||||
parseExpression(p); // control reference
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // event name string
|
||||
expect(p, TOK_COMMA);
|
||||
parseExpression(p); // handler name string
|
||||
basEmit8(&p->cg, OP_SET_EVENT);
|
||||
}
|
||||
|
||||
|
||||
static void parseSeek(BasParserT *p) {
|
||||
// SEEK #channel, position
|
||||
advance(p); // consume SEEK
|
||||
|
|
|
|||
39
apps/dvxbasic/compiler/strip.c
Normal file
39
apps/dvxbasic/compiler/strip.c
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// strip.c -- Release build stripping
|
||||
//
|
||||
// Removes debug information from a compiled module:
|
||||
// - Clears debug variable info (names, scopes, types)
|
||||
// - Clears debug UDT definitions
|
||||
//
|
||||
// Procedure names are preserved because the form runtime uses
|
||||
// them for event dispatch (ControlName_EventName convention).
|
||||
//
|
||||
// OP_LINE removal is deferred to a future version (requires
|
||||
// bytecode compaction and offset rewriting).
|
||||
|
||||
#include "strip.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
void basStripModule(BasModuleT *mod) {
|
||||
if (!mod) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear debug variable info
|
||||
free(mod->debugVars);
|
||||
mod->debugVars = NULL;
|
||||
mod->debugVarCount = 0;
|
||||
|
||||
// Clear debug UDT definitions
|
||||
if (mod->debugUdtDefs) {
|
||||
for (int32_t i = 0; i < mod->debugUdtDefCount; i++) {
|
||||
free(mod->debugUdtDefs[i].fields);
|
||||
}
|
||||
|
||||
free(mod->debugUdtDefs);
|
||||
mod->debugUdtDefs = NULL;
|
||||
mod->debugUdtDefCount = 0;
|
||||
}
|
||||
}
|
||||
18
apps/dvxbasic/compiler/strip.h
Normal file
18
apps/dvxbasic/compiler/strip.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// strip.h -- Release build stripping
|
||||
//
|
||||
// Removes debug information from a compiled module to prevent
|
||||
// decompilation. Clears procedure names, debug variable info,
|
||||
// and debug UDT definitions.
|
||||
|
||||
#ifndef DVXBASIC_STRIP_H
|
||||
#define DVXBASIC_STRIP_H
|
||||
|
||||
#include "../runtime/vm.h"
|
||||
|
||||
// Strip debug info from a module for release builds:
|
||||
// - Clear all procedure names
|
||||
// - Clear debug variable info
|
||||
// - Clear debug UDT definitions
|
||||
void basStripModule(BasModuleT *mod);
|
||||
|
||||
#endif // DVXBASIC_STRIP_H
|
||||
|
|
@ -72,8 +72,10 @@ typedef struct {
|
|||
|
||||
// For SUB/FUNCTION: parameter info
|
||||
int32_t paramCount;
|
||||
int32_t requiredParams; // count of non-optional params
|
||||
uint8_t paramTypes[BAS_MAX_PARAMS];
|
||||
bool paramByVal[BAS_MAX_PARAMS];
|
||||
bool paramOptional[BAS_MAX_PARAMS]; // true = OPTIONAL parameter
|
||||
|
||||
// Forward-reference backpatch list (code addresses to patch when defined)
|
||||
int32_t *patchAddrs; // stb_ds dynamic array
|
||||
|
|
|
|||
810
apps/dvxbasic/formrt/formcfm.c
Normal file
810
apps/dvxbasic/formrt/formcfm.c
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
// 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;
|
||||
}
|
||||
26
apps/dvxbasic/formrt/formcfm.h
Normal file
26
apps/dvxbasic/formrt/formcfm.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// formcfm.h -- Compiled form format
|
||||
//
|
||||
// Binary serialization of .frm layout data for standalone apps.
|
||||
// Widget types and layout stored as strings for stability across
|
||||
// widget set changes. No BASIC source code is included.
|
||||
|
||||
#ifndef DVXBASIC_FORMCFM_H
|
||||
#define DVXBASIC_FORMCFM_H
|
||||
|
||||
#include "formrt.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Serialize a .frm source string to compiled form binary.
|
||||
// Returns a malloc'd buffer (caller frees) and sets *outLen.
|
||||
uint8_t *basFormCompile(const char *frmSource, int32_t frmLen, int32_t *outLen);
|
||||
|
||||
// Load a compiled form binary into the form runtime.
|
||||
// Creates the form window and all controls.
|
||||
BasFormT *basFormLoadCompiled(BasFormRtT *rt, const uint8_t *data, int32_t dataLen);
|
||||
|
||||
// Extract the form name from a compiled form binary without loading it.
|
||||
// Writes to nameBuf (up to bufSize-1 chars). Returns true on success.
|
||||
bool basFormGetCompiledName(const uint8_t *data, int32_t dataLen, char *nameBuf, int32_t bufSize);
|
||||
|
||||
#endif // DVXBASIC_FORMCFM_H
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -26,7 +26,8 @@ typedef struct BasControlT BasControlT;
|
|||
// Limits
|
||||
// ============================================================
|
||||
|
||||
#define BAS_MAX_CTRL_NAME 32
|
||||
#define BAS_MAX_CTRL_NAME 32
|
||||
#define BAS_MAX_FRM_NESTING 16
|
||||
|
||||
// ============================================================
|
||||
// Menu ID to name mapping for event dispatch
|
||||
|
|
@ -42,7 +43,14 @@ typedef struct {
|
|||
// Control instance (a widget on a form)
|
||||
// ============================================================
|
||||
|
||||
#define BAS_MAX_TEXT_BUF 256
|
||||
#define BAS_MAX_TEXT_BUF 256
|
||||
#define BAS_MAX_EVENT_OVERRIDES 16
|
||||
|
||||
// Event handler override (SetEvent)
|
||||
typedef struct {
|
||||
char eventName[BAS_MAX_CTRL_NAME]; // e.g. "Click"
|
||||
char handlerName[BAS_MAX_CTRL_NAME]; // e.g. "HandleOkClick"
|
||||
} BasEventOverrideT;
|
||||
|
||||
typedef struct BasControlT {
|
||||
char name[BAS_MAX_CTRL_NAME]; // VB control name (e.g. "Command1")
|
||||
|
|
@ -56,6 +64,8 @@ typedef struct BasControlT {
|
|||
char dataField[BAS_MAX_CTRL_NAME]; // column name for binding
|
||||
char helpTopic[BAS_MAX_CTRL_NAME]; // help topic ID for F1
|
||||
int32_t menuId; // WM menu item ID (>0 for menu items, 0 for controls)
|
||||
BasEventOverrideT eventOverrides[BAS_MAX_EVENT_OVERRIDES];
|
||||
int32_t eventOverrideCount;
|
||||
} BasControlT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -69,7 +79,6 @@ typedef struct BasFormT {
|
|||
WidgetT *contentBox; // VBox/HBox for user controls
|
||||
AppContextT *ctx; // DVX app context
|
||||
BasControlT **controls; // stb_ds array of heap-allocated pointers
|
||||
int32_t controlCount;
|
||||
BasVmT *vm; // VM for event dispatch
|
||||
BasModuleT *module; // compiled module (for SUB lookup)
|
||||
// Form-level properties (accumulated during .frm parsing)
|
||||
|
|
@ -105,16 +114,24 @@ typedef struct {
|
|||
int32_t frmSourceLen;
|
||||
} BasFrmCacheT;
|
||||
|
||||
// Cached compiled form binary (for standalone apps)
|
||||
typedef struct {
|
||||
char formName[BAS_MAX_CTRL_NAME];
|
||||
uint8_t *data; // malloc'd binary data
|
||||
int32_t dataLen;
|
||||
} BasCfmCacheT;
|
||||
|
||||
typedef struct {
|
||||
AppContextT *ctx; // DVX app context
|
||||
BasVmT *vm; // shared VM instance
|
||||
BasModuleT *module; // compiled module
|
||||
BasFormT **forms; // stb_ds array of heap-allocated pointers
|
||||
int32_t formCount;
|
||||
BasFormT *currentForm; // form currently dispatching events
|
||||
char helpFile[256]; // project help file path (for F1)
|
||||
BasFrmCacheT *frmCache; // stb_ds array of cached .frm sources
|
||||
int32_t frmCacheCount;
|
||||
BasCfmCacheT *cfmCache; // stb_ds array of compiled form binaries
|
||||
int32_t cfmCacheCount;
|
||||
} BasFormRtT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -124,6 +141,18 @@ typedef struct {
|
|||
// Initialize the form runtime with a DVX context and a compiled module.
|
||||
BasFormRtT *basFormRtCreate(AppContextT *ctx, BasVmT *vm, BasModuleT *module);
|
||||
|
||||
// Load all cached forms (.frm text and compiled binaries) and show the
|
||||
// startup form. Called before bytecode execution begins.
|
||||
void basFormRtLoadAllForms(BasFormRtT *rt, const char *startupFormName);
|
||||
|
||||
// VB-style event loop: pump DVX events until all forms are closed.
|
||||
// Called after bytecode halts to keep the app alive for event handlers.
|
||||
void basFormRtEventLoop(BasFormRtT *rt);
|
||||
|
||||
// Simple non-debug execution: run bytecode in slices with dvxUpdate
|
||||
// between slices, then enter the event loop. Used by the standalone stub.
|
||||
void basFormRtRunSimple(BasFormRtT *rt);
|
||||
|
||||
// Destroy the form runtime and all forms/controls.
|
||||
void basFormRtDestroy(BasFormRtT *rt);
|
||||
|
||||
|
|
@ -144,6 +173,35 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal);
|
|||
void basFormRtHideForm(void *ctx, void *formRef);
|
||||
int32_t basFormRtMsgBox(void *ctx, const char *message, int32_t flags);
|
||||
|
||||
// ---- Extern call callbacks (shared by IDE and stub) ----
|
||||
|
||||
void *basExternResolve(void *ctx, const char *libName, const char *funcName);
|
||||
BasValueT basExternCall(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType);
|
||||
|
||||
// ---- Form caching ----
|
||||
|
||||
// Register .frm source text for lazy loading when bytecode calls Load.
|
||||
void basFormRtRegisterFrm(BasFormRtT *rt, const char *formName, const char *source, int32_t sourceLen);
|
||||
|
||||
// Register a compiled form binary for lazy loading when bytecode calls Load.
|
||||
void basFormRtRegisterCfm(BasFormRtT *rt, const char *formName, const uint8_t *data, int32_t dataLen);
|
||||
|
||||
// ---- Widget creation ----
|
||||
|
||||
WidgetT *createWidget(const char *wgtTypeName, WidgetT *parent);
|
||||
|
||||
// ---- Form window creation ----
|
||||
|
||||
WidgetT *basFormRtCreateContentBox(WidgetT *root, const char *layout);
|
||||
WindowT *basFormRtCreateFormWindow(AppContextT *ctx, const char *title, const char *layout, bool resizable, bool centered, bool autoSize, int32_t width, int32_t height, int32_t left, int32_t top, WidgetT **outRoot, WidgetT **outContentBox);
|
||||
|
||||
// ---- Dynamic form/control API ----
|
||||
|
||||
void *basFormRtCreateForm(void *ctx, const char *formName, int32_t width, int32_t height);
|
||||
void *basFormRtCreateCtrlEx(void *ctx, void *formRef, const char *typeName, const char *ctrlName, void *parentRef);
|
||||
void basFormRtSetEvent(void *ctx, void *ctrlRef, const char *eventName, const char *handlerName);
|
||||
void basFormRtRemoveCtrl(void *ctx, void *formRef, const char *ctrlName);
|
||||
|
||||
// ---- Event dispatch ----
|
||||
|
||||
bool basFormRtFireEvent(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
// handles and grid dots are drawn in the window's onPaint callback.
|
||||
|
||||
#include "ideDesigner.h"
|
||||
#include "../formrt/formrt.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxVideo.h"
|
||||
#include "dvxWm.h"
|
||||
|
|
@ -116,80 +117,17 @@ WidgetT *dsgnCreateDesignWidget(const char *vbTypeName, WidgetT *parent) {
|
|||
|
||||
|
||||
// ============================================================
|
||||
// dsgnCreateFormWindow
|
||||
// dsgnCreateFormWindow / dsgnCreateContentBox
|
||||
// Thin wrappers around formrt functions for backward compatibility.
|
||||
// ============================================================
|
||||
|
||||
WidgetT *dsgnCreateContentBox(WidgetT *root, const char *layout) {
|
||||
// wgtInitWindow creates a VBox root. If the requested layout is VBox
|
||||
// (or empty/missing), reuse root directly to avoid double-nesting.
|
||||
if (!layout || !layout[0] || strcasecmp(layout, "VBox") == 0) {
|
||||
return root;
|
||||
}
|
||||
|
||||
// Look up the layout widget by BASIC name and create it dynamically
|
||||
const char *wgtName = wgtFindByBasName(layout);
|
||||
|
||||
if (wgtName) {
|
||||
const WgtIfaceT *iface = wgtGetIface(wgtName);
|
||||
|
||||
if (iface && iface->isContainer && iface->createSig == WGT_CREATE_PARENT) {
|
||||
const void *api = wgtGetApi(wgtName);
|
||||
|
||||
if (api) {
|
||||
// All WGT_CREATE_PARENT APIs have create(parent) as the first function pointer
|
||||
WidgetT *(*createFn)(WidgetT *) = *(WidgetT *(*const *)(WidgetT *))api;
|
||||
WidgetT *box = createFn(root);
|
||||
box->weight = 100;
|
||||
return box;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown layout — fall back to root VBox
|
||||
return root;
|
||||
return basFormRtCreateContentBox(root, layout);
|
||||
}
|
||||
|
||||
|
||||
WindowT *dsgnCreateFormWindow(AppContextT *ctx, const char *title, const char *layout, bool resizable, bool centered, bool autoSize, int32_t width, int32_t height, int32_t left, int32_t top, WidgetT **outRoot, WidgetT **outContentBox) {
|
||||
int32_t defW = (width > 0) ? width : 400;
|
||||
int32_t defH = (height > 0) ? height : 300;
|
||||
|
||||
WindowT *win = dvxCreateWindowCentered(ctx, title, defW, defH, resizable);
|
||||
|
||||
if (!win) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
win->visible = false;
|
||||
|
||||
WidgetT *root = wgtInitWindow(ctx, win);
|
||||
|
||||
if (!root) {
|
||||
dvxDestroyWindow(ctx, win);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WidgetT *contentBox = dsgnCreateContentBox(root, layout);
|
||||
|
||||
// Apply sizing
|
||||
if (autoSize) {
|
||||
dvxFitWindow(ctx, win);
|
||||
} else if (width > 0 && height > 0) {
|
||||
dvxResizeWindow(ctx, win, width, height);
|
||||
}
|
||||
|
||||
// Apply positioning
|
||||
if (centered) {
|
||||
win->x = (ctx->display.width - win->w) / 2;
|
||||
win->y = (ctx->display.height - win->h) / 2;
|
||||
} else if (left > 0 || top > 0) {
|
||||
win->x = left;
|
||||
win->y = top;
|
||||
}
|
||||
|
||||
*outRoot = root;
|
||||
*outContentBox = contentBox;
|
||||
return win;
|
||||
return basFormRtCreateFormWindow(ctx, title, layout, resizable, centered, autoSize, width, height, left, top, outRoot, outContentBox);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -513,7 +451,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
bool inMenu = false;
|
||||
int32_t menuNestDepth = 0;
|
||||
int32_t blockDepth = 0; // Begin/End nesting depth (0 = form level)
|
||||
bool blockIsContainer[DSGN_MAX_FRM_NESTING]; // whether each block is a container
|
||||
bool blockIsContainer[BAS_MAX_FRM_NESTING]; // whether each block is a container
|
||||
|
||||
// Parent name stack for nesting (index 0 = form level)
|
||||
char parentStack[8][DSGN_MAX_NAME];
|
||||
|
|
@ -614,7 +552,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
curCtrl = NULL; // not a control
|
||||
menuNestDepth++;
|
||||
inMenu = true;
|
||||
if (blockDepth < DSGN_MAX_FRM_NESTING) { blockIsContainer[blockDepth] = false; }
|
||||
if (blockDepth < BAS_MAX_FRM_NESTING) { blockIsContainer[blockDepth] = false; }
|
||||
blockDepth++;
|
||||
} else if (inForm) {
|
||||
DsgnControlT *cp = (DsgnControlT *)calloc(1, sizeof(DsgnControlT));
|
||||
|
|
@ -632,7 +570,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
arrput(form->controls, cp);
|
||||
curCtrl = form->controls[arrlen(form->controls) - 1];
|
||||
bool isCtrl = dsgnIsContainer(typeName);
|
||||
if (blockDepth < DSGN_MAX_FRM_NESTING) { blockIsContainer[blockDepth] = isCtrl; }
|
||||
if (blockDepth < BAS_MAX_FRM_NESTING) { blockIsContainer[blockDepth] = isCtrl; }
|
||||
blockDepth++;
|
||||
|
||||
// If this is a container, push onto parent stack
|
||||
|
|
@ -659,7 +597,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
}
|
||||
} else {
|
||||
// If we're closing a container, pop the parent stack
|
||||
if (blockDepth < DSGN_MAX_FRM_NESTING && blockIsContainer[blockDepth] && nestDepth > 0) {
|
||||
if (blockDepth < BAS_MAX_FRM_NESTING && blockIsContainer[blockDepth] && nestDepth > 0) {
|
||||
nestDepth--;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include "dvxApp.h"
|
||||
#include "dvxWgt.h"
|
||||
#include "canvas/canvas.h"
|
||||
#include "../formrt/formrt.h"
|
||||
|
||||
#include "stb_ds_wrap.h"
|
||||
|
||||
|
|
@ -23,7 +24,6 @@
|
|||
#define DSGN_MAX_NAME 32
|
||||
#define DSGN_MAX_TEXT 256
|
||||
#define DSGN_MAX_PROPS 32
|
||||
#define DSGN_MAX_FRM_NESTING 16
|
||||
#define DSGN_GRID_SIZE 8
|
||||
#define DSGN_HANDLE_SIZE 6
|
||||
#define DSGN_MENU_ID_BASE 20000 // base ID for designer preview menu items
|
||||
|
|
|
|||
|
|
@ -41,7 +41,11 @@
|
|||
#include "ideProperties.h"
|
||||
|
||||
#include "../compiler/parser.h"
|
||||
#include "../compiler/strip.h"
|
||||
#include "../runtime/serialize.h"
|
||||
#include "../formrt/formrt.h"
|
||||
#include "../formrt/formcfm.h"
|
||||
#include "dvxRes.h"
|
||||
#include "../../sql/dvxSql.h"
|
||||
#include "../runtime/vm.h"
|
||||
#include "../runtime/values.h"
|
||||
|
|
@ -116,6 +120,7 @@
|
|||
#define CMD_DEBUG_LAYOUT 156
|
||||
#define CMD_OUTPUT_TO_LOG 159
|
||||
#define CMD_RECENT_BASE 160 // 160-167 reserved for recent files
|
||||
#define CMD_MAKE_EXE 170
|
||||
#define CMD_RECENT_MAX 8
|
||||
#define CMD_HELP_CONTENTS 157
|
||||
#define CMD_HELP_API 158
|
||||
|
|
@ -147,6 +152,7 @@ int32_t appMain(DxeAppContextT *ctx);
|
|||
static void buildWindow(void);
|
||||
static void clearOutput(void);
|
||||
static int cmpStrPtrs(const void *a, const void *b);
|
||||
static bool compileProject(void);
|
||||
static void compileAndRun(void);
|
||||
static void debugStartOrResume(int32_t cmd);
|
||||
static void toggleBreakpoint(void);
|
||||
|
|
@ -190,6 +196,7 @@ static bool findInProject(const char *needle, bool caseSensitive);
|
|||
static void openFindDialog(bool showReplace);
|
||||
static void handleEditCmd(int32_t cmd);
|
||||
static void handleFileCmd(int32_t cmd);
|
||||
static void makeExecutable(void);
|
||||
static void handleProjectCmd(int32_t cmd);
|
||||
static void handleRunCmd(int32_t cmd);
|
||||
static void handleViewCmd(int32_t cmd);
|
||||
|
|
@ -226,8 +233,6 @@ static void onObjDropdownChange(WidgetT *w);
|
|||
static void printCallback(void *ctx, const char *text, bool newline);
|
||||
static bool inputCallback(void *ctx, const char *prompt, char *buf, int32_t bufSize);
|
||||
static bool doEventsCallback(void *ctx);
|
||||
static void *resolveExternCallback(void *ctx, const char *libName, const char *funcName);
|
||||
static BasValueT callExternCallback(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType);
|
||||
static void runCached(void);
|
||||
static void runModule(BasModuleT *mod);
|
||||
static void onEditorChange(WidgetT *w);
|
||||
|
|
@ -909,6 +914,8 @@ static void buildWindow(void) {
|
|||
wmAddMenuItem(fileMenu, "&Save File\tCtrl+S", CMD_SAVE);
|
||||
wmAddMenuItem(fileMenu, "Save A&ll", CMD_SAVE_ALL);
|
||||
wmAddMenuSeparator(fileMenu);
|
||||
wmAddMenuItem(fileMenu, "&Make Executable...", CMD_MAKE_EXE);
|
||||
wmAddMenuSeparator(fileMenu);
|
||||
wmAddMenuItem(fileMenu, "&Remove File", CMD_PRJ_REMOVE);
|
||||
wmAddMenuSeparator(fileMenu);
|
||||
wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT);
|
||||
|
|
@ -1706,10 +1713,10 @@ static int cmpEvtPtrs(const void *a, const void *b) {
|
|||
|
||||
|
||||
// ============================================================
|
||||
// compileAndRun
|
||||
// compileProject -- compile without running, returns true on success
|
||||
// ============================================================
|
||||
|
||||
static void compileAndRun(void) {
|
||||
static bool compileProject(void) {
|
||||
// Save all dirty files before compiling if Save on Run is enabled
|
||||
if (sWin && sWin->menuBar && wmMenuItemIsChecked(sWin->menuBar, CMD_SAVE_ON_RUN)) {
|
||||
if (sProject.activeFileIdx >= 0) {
|
||||
|
|
@ -1759,7 +1766,7 @@ static void compileAndRun(void) {
|
|||
if (!concatBuf) {
|
||||
setStatus("Out of memory.");
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t pos = 0;
|
||||
|
|
@ -1920,9 +1927,9 @@ static void compileAndRun(void) {
|
|||
src = getFullSource();
|
||||
|
||||
if (!src || *src == '\0') {
|
||||
setStatus("No source code to run.");
|
||||
setStatus("No source code to compile.");
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
srcLen = (int32_t)strlen(src);
|
||||
|
|
@ -1935,7 +1942,7 @@ static void compileAndRun(void) {
|
|||
free(concatBuf);
|
||||
setStatus("Out of memory.");
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
basParserInit(parser, src, srcLen);
|
||||
|
|
@ -2007,7 +2014,7 @@ static void compileAndRun(void) {
|
|||
basParserFree(parser);
|
||||
free(parser);
|
||||
free(concatBuf);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
free(concatBuf);
|
||||
|
|
@ -2019,7 +2026,7 @@ static void compileAndRun(void) {
|
|||
if (!mod) {
|
||||
setStatus("Failed to build module.");
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
dvxSetBusy(sAc, false);
|
||||
|
|
@ -2034,7 +2041,18 @@ static void compileAndRun(void) {
|
|||
// Update Object/Event dropdowns
|
||||
updateDropdowns();
|
||||
|
||||
runModule(mod);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// compileAndRun
|
||||
// ============================================================
|
||||
|
||||
static void compileAndRun(void) {
|
||||
if (compileProject()) {
|
||||
runModule(sCachedModule);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2190,8 +2208,8 @@ static void runModule(BasModuleT *mod) {
|
|||
|
||||
// Set extern library callbacks (DECLARE LIBRARY support)
|
||||
BasExternCallbacksT extCb;
|
||||
extCb.resolveExtern = resolveExternCallback;
|
||||
extCb.callExtern = callExternCallback;
|
||||
extCb.resolveExtern = basExternResolve;
|
||||
extCb.callExtern = basExternCall;
|
||||
extCb.ctx = NULL;
|
||||
basVmSetExternCallbacks(vm, &extCb);
|
||||
|
||||
|
|
@ -2221,24 +2239,10 @@ static void runModule(BasModuleT *mod) {
|
|||
}
|
||||
}
|
||||
|
||||
// Load forms (AFTER breakpoints are set so init code breakpoints fire)
|
||||
// Load forms from project files (AFTER breakpoints are set so
|
||||
// init code breakpoints fire), then show the startup form.
|
||||
loadFrmFiles(formRt);
|
||||
|
||||
// Auto-show the startup form (or first form if none specified).
|
||||
if (formRt->formCount > 0) {
|
||||
BasFormT *startupForm = formRt->forms[0];
|
||||
|
||||
if (sProject.startupForm[0]) {
|
||||
for (int32_t i = 0; i < formRt->formCount; i++) {
|
||||
if (strcasecmp(formRt->forms[i]->name, sProject.startupForm) == 0) {
|
||||
startupForm = formRt->forms[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
basFormRtShowForm(formRt, startupForm, false);
|
||||
}
|
||||
basFormRtLoadAllForms(formRt, sProject.startupForm);
|
||||
|
||||
// Run in slices of 10000 steps, yielding to DVX between slices
|
||||
basVmSetStepLimit(vm, IDE_STEP_SLICE);
|
||||
|
|
@ -2308,11 +2312,11 @@ static void runModule(BasModuleT *mod) {
|
|||
// VB-style event loop: after module-level code finishes,
|
||||
// keep processing events as long as any form is loaded.
|
||||
// The program ends when all forms are unloaded (closed).
|
||||
if (result == BAS_VM_HALTED && formRt->formCount > 0) {
|
||||
if (result == BAS_VM_HALTED && (int32_t)arrlen(formRt->forms) > 0) {
|
||||
setStatus("Running (event loop)...");
|
||||
sStopRequested = false;
|
||||
|
||||
while (sWin && sAc->running && formRt->formCount > 0 && !sStopRequested && !vm->ended) {
|
||||
while (sWin && sAc->running && (int32_t)arrlen(formRt->forms) > 0 && !sStopRequested && !vm->ended) {
|
||||
|
||||
if (sDbgState == DBG_PAUSED) {
|
||||
// Paused inside an event handler
|
||||
|
|
@ -3943,6 +3947,192 @@ static void onClose(WindowT *win) {
|
|||
// onMenu
|
||||
// ============================================================
|
||||
|
||||
// ============================================================
|
||||
// makeExecutable -- compile project into standalone .app file
|
||||
// ============================================================
|
||||
|
||||
static void makeExecutable(void) {
|
||||
if (sProject.projectPath[0] == '\0') {
|
||||
setStatus("Save the project first.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Compile (always recompile to ensure latest code)
|
||||
if (!compileProject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask for output path
|
||||
FileFilterT filters[] = {
|
||||
{ "DVX Applications (*.app)", "*.app" },
|
||||
{ "All Files (*.*)", "*.*" }
|
||||
};
|
||||
char outPath[DVX_MAX_PATH];
|
||||
outPath[0] = '\0';
|
||||
|
||||
if (!dvxFileDialog(sAc, "Make Executable", FD_SAVE, NULL, filters, 2, outPath, sizeof(outPath))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ask debug or release
|
||||
const char *modeItems[] = { "Debug (include error info)", "Release (stripped)" };
|
||||
int32_t modeChoice = 0;
|
||||
|
||||
if (!dvxChoiceDialog(sAc, "Build Mode", "Select build mode:", modeItems, 2, 0, &modeChoice)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool release = (modeChoice == 1);
|
||||
|
||||
setStatus(release ? "Building release executable..." : "Building debug executable...");
|
||||
dvxSetBusy(sAc, true);
|
||||
dvxUpdate(sAc);
|
||||
|
||||
// Make a copy of the module for potential stripping
|
||||
int32_t modLen = 0;
|
||||
uint8_t *modData = basModuleSerialize(sCachedModule, &modLen);
|
||||
|
||||
if (!modData) {
|
||||
setStatus("Failed to serialize module.");
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// For release, deserialize, strip, re-serialize
|
||||
if (release) {
|
||||
BasModuleT *modCopy = basModuleDeserialize(modData, modLen);
|
||||
free(modData);
|
||||
|
||||
if (!modCopy) {
|
||||
setStatus("Failed to prepare release module.");
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
}
|
||||
|
||||
basStripModule(modCopy);
|
||||
modData = basModuleSerialize(modCopy, &modLen);
|
||||
basModuleFree(modCopy);
|
||||
|
||||
if (!modData) {
|
||||
setStatus("Failed to serialize stripped module.");
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize debug info from the original (unstripped) module
|
||||
int32_t dbgLen = 0;
|
||||
uint8_t *dbgData = NULL;
|
||||
|
||||
if (!release) {
|
||||
dbgData = basDebugSerialize(sCachedModule, &dbgLen);
|
||||
}
|
||||
|
||||
// Extract stub from our own resources
|
||||
DvxResHandleT *selfRes = dvxResOpen(sCtx->appPath);
|
||||
|
||||
if (!selfRes) {
|
||||
setStatus("Cannot read IDE resources.");
|
||||
free(modData);
|
||||
free(dbgData);
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t stubSize = 0;
|
||||
void *stubData = dvxResRead(selfRes, "STUB", &stubSize);
|
||||
dvxResClose(selfRes);
|
||||
|
||||
if (!stubData || stubSize == 0) {
|
||||
setStatus("Stub not found in IDE resources.");
|
||||
free(modData);
|
||||
free(dbgData);
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Write stub to output file
|
||||
FILE *outFile = fopen(outPath, "wb");
|
||||
|
||||
if (!outFile) {
|
||||
setStatus("Cannot create output file.");
|
||||
free(stubData);
|
||||
free(modData);
|
||||
free(dbgData);
|
||||
dvxSetBusy(sAc, false);
|
||||
return;
|
||||
}
|
||||
|
||||
fwrite(stubData, 1, stubSize, outFile);
|
||||
fclose(outFile);
|
||||
free(stubData);
|
||||
|
||||
// Attach app name from project properties
|
||||
const char *projName = sProject.name[0] ? sProject.name : "BASIC App";
|
||||
dvxResAppend(outPath, "APPNAME", DVX_RES_TEXT, projName, (uint32_t)strlen(projName) + 1);
|
||||
|
||||
// Attach MODULE resource
|
||||
dvxResAppend(outPath, "MODULE", DVX_RES_BINARY, modData, (uint32_t)modLen);
|
||||
free(modData);
|
||||
|
||||
// Attach DEBUG resource
|
||||
if (dbgData) {
|
||||
dvxResAppend(outPath, "DEBUG", DVX_RES_BINARY, dbgData, (uint32_t)dbgLen);
|
||||
free(dbgData);
|
||||
}
|
||||
|
||||
// Compile and attach form resources
|
||||
int32_t formIdx = 0;
|
||||
|
||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||
if (!sProject.files[i].isForm) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char *frmSrc = NULL;
|
||||
|
||||
if (sProject.files[i].buffer) {
|
||||
frmSrc = strdup(sProject.files[i].buffer);
|
||||
} else {
|
||||
char fp[DVX_MAX_PATH];
|
||||
prjFullPath(&sProject, i, fp, sizeof(fp));
|
||||
FILE *ff = fopen(fp, "r");
|
||||
|
||||
if (ff) {
|
||||
fseek(ff, 0, SEEK_END);
|
||||
long sz = ftell(ff);
|
||||
fseek(ff, 0, SEEK_SET);
|
||||
frmSrc = (char *)malloc(sz + 1);
|
||||
|
||||
if (frmSrc) {
|
||||
size_t rd = fread(frmSrc, 1, sz, ff);
|
||||
frmSrc[rd] = '\0';
|
||||
}
|
||||
|
||||
fclose(ff);
|
||||
}
|
||||
}
|
||||
|
||||
if (!frmSrc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t frmLen = (int32_t)strlen(frmSrc);
|
||||
char resName[16];
|
||||
snprintf(resName, sizeof(resName), "FORM%ld", (long)formIdx);
|
||||
dvxResAppend(outPath, resName, DVX_RES_BINARY, frmSrc, (uint32_t)frmLen);
|
||||
free(frmSrc);
|
||||
formIdx++;
|
||||
}
|
||||
|
||||
dvxSetBusy(sAc, false);
|
||||
|
||||
char msg[512];
|
||||
snprintf(msg, sizeof(msg), "Created %s (%s)", outPath, release ? "release" : "debug");
|
||||
setStatus(msg);
|
||||
}
|
||||
|
||||
|
||||
static void handleFileCmd(int32_t cmd) {
|
||||
switch (cmd) {
|
||||
case CMD_OPEN:
|
||||
|
|
@ -3984,6 +4174,10 @@ static void handleFileCmd(int32_t cmd) {
|
|||
updateDirtyIndicators();
|
||||
break;
|
||||
|
||||
case CMD_MAKE_EXE:
|
||||
makeExecutable();
|
||||
break;
|
||||
|
||||
case CMD_EXIT:
|
||||
if (sWin) {
|
||||
onClose(sWin);
|
||||
|
|
@ -5196,7 +5390,7 @@ static void helpQueryHandler(void *ctx) {
|
|||
|
||||
// Running BASIC program: check if focused window belongs to a form
|
||||
if (sDbgFormRt && sDbgFormRt->helpFile[0] && focusWin) {
|
||||
for (int32_t i = 0; i < sDbgFormRt->formCount; i++) {
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sDbgFormRt->forms); i++) {
|
||||
BasFormT *form = sDbgFormRt->forms[i];
|
||||
|
||||
if (form->window == focusWin) {
|
||||
|
|
@ -5926,163 +6120,6 @@ static void printCallback(void *ctx, const char *text, bool newline) {
|
|||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Extern library callbacks (DECLARE LIBRARY support)
|
||||
// ============================================================
|
||||
|
||||
// Resolve a native function by library and symbol name.
|
||||
// Library name is ignored (all DXE exports are globally visible via
|
||||
// RTLD_GLOBAL), but could be used to dlopen a specific library in
|
||||
// the future. The underscore prefix is added automatically to match
|
||||
// DJGPP's cdecl name mangling.
|
||||
static void *resolveExternCallback(void *ctx, const char *libName, const char *funcName) {
|
||||
(void)ctx;
|
||||
(void)libName;
|
||||
|
||||
// DJGPP adds underscore prefix to C symbols
|
||||
char mangledName[256];
|
||||
snprintf(mangledName, sizeof(mangledName), "_%s", funcName);
|
||||
|
||||
return dlsym(NULL, mangledName);
|
||||
}
|
||||
|
||||
|
||||
// Marshal a call from BASIC to a native C function.
|
||||
// Converts BasValueT arguments to C types, calls the function using
|
||||
// inline assembly (cdecl: push args right-to-left, caller cleans up),
|
||||
// and converts the return value back to BasValueT.
|
||||
//
|
||||
// Supported types: Integer (int16_t passed as int32_t), Long (int32_t),
|
||||
// Single/Double (double), String (const char *), Boolean (int32_t).
|
||||
static BasValueT callExternCallback(void *ctx, void *funcPtr, const char *libName, const char *funcName, BasValueT *args, int32_t argc, uint8_t retType) {
|
||||
(void)ctx;
|
||||
(void)libName;
|
||||
(void)funcName;
|
||||
|
||||
// Convert BASIC values to native C values and collect pointers
|
||||
// to temporary C strings so we can free them after the call.
|
||||
uint32_t nativeArgs[32];
|
||||
char *tempStrings[16];
|
||||
int32_t tempStringCount = 0;
|
||||
int32_t nativeCount = 0;
|
||||
|
||||
for (int32_t i = 0; i < argc && i < 16; i++) {
|
||||
switch (args[i].type) {
|
||||
case BAS_TYPE_STRING: {
|
||||
BasStringT *s = basValFormatString(args[i]);
|
||||
char *cstr = strdup(s->data);
|
||||
basStringUnref(s);
|
||||
nativeArgs[nativeCount++] = (uint32_t)(uintptr_t)cstr;
|
||||
tempStrings[tempStringCount++] = cstr;
|
||||
break;
|
||||
}
|
||||
|
||||
case BAS_TYPE_DOUBLE:
|
||||
case BAS_TYPE_SINGLE: {
|
||||
// Doubles are passed as 8 bytes (two 32-bit pushes on i386)
|
||||
union { double d; uint32_t u[2]; } conv;
|
||||
conv.d = args[i].dblVal;
|
||||
nativeArgs[nativeCount++] = conv.u[0];
|
||||
nativeArgs[nativeCount++] = conv.u[1];
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// Integer, Long, Boolean — all fit in 32 bits
|
||||
nativeArgs[nativeCount++] = (uint32_t)(int32_t)basValToNumber(args[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call the function using cdecl convention.
|
||||
// Push args right-to-left, call, read return value from eax (or FPU for double).
|
||||
BasValueT result;
|
||||
memset(&result, 0, sizeof(result));
|
||||
|
||||
if (retType == BAS_TYPE_DOUBLE || retType == BAS_TYPE_SINGLE) {
|
||||
// Return value in st(0)
|
||||
double dblResult = 0.0;
|
||||
|
||||
// Push args right-to-left and call
|
||||
__asm__ __volatile__(
|
||||
"movl %%esp, %%ebx\n\t" // save stack pointer
|
||||
::: "ebx"
|
||||
);
|
||||
|
||||
for (int32_t i = nativeCount - 1; i >= 0; i--) {
|
||||
uint32_t val = nativeArgs[i];
|
||||
__asm__ __volatile__("pushl %0" :: "r"(val));
|
||||
}
|
||||
|
||||
__asm__ __volatile__(
|
||||
"call *%1\n\t"
|
||||
"fstpl %0\n\t"
|
||||
"movl %%ebx, %%esp\n\t" // restore stack pointer
|
||||
: "=m"(dblResult)
|
||||
: "r"(funcPtr)
|
||||
: "eax", "ecx", "edx", "memory"
|
||||
);
|
||||
|
||||
result = basValDouble(dblResult);
|
||||
} else if (retType == BAS_TYPE_STRING) {
|
||||
// Return value is const char * in eax
|
||||
uint32_t rawResult = 0;
|
||||
|
||||
__asm__ __volatile__(
|
||||
"movl %%esp, %%ebx\n\t"
|
||||
::: "ebx"
|
||||
);
|
||||
|
||||
for (int32_t i = nativeCount - 1; i >= 0; i--) {
|
||||
uint32_t val = nativeArgs[i];
|
||||
__asm__ __volatile__("pushl %0" :: "r"(val));
|
||||
}
|
||||
|
||||
__asm__ __volatile__(
|
||||
"call *%1\n\t"
|
||||
"movl %%ebx, %%esp\n\t"
|
||||
: "=a"(rawResult)
|
||||
: "r"(funcPtr)
|
||||
: "ecx", "edx", "memory"
|
||||
);
|
||||
|
||||
const char *str = (const char *)(uintptr_t)rawResult;
|
||||
result = basValStringFromC(str ? str : "");
|
||||
} else {
|
||||
// Integer/Long/Boolean — return in eax
|
||||
uint32_t rawResult = 0;
|
||||
|
||||
__asm__ __volatile__(
|
||||
"movl %%esp, %%ebx\n\t"
|
||||
::: "ebx"
|
||||
);
|
||||
|
||||
for (int32_t i = nativeCount - 1; i >= 0; i--) {
|
||||
uint32_t val = nativeArgs[i];
|
||||
__asm__ __volatile__("pushl %0" :: "r"(val));
|
||||
}
|
||||
|
||||
__asm__ __volatile__(
|
||||
"call *%1\n\t"
|
||||
"movl %%ebx, %%esp\n\t"
|
||||
: "=a"(rawResult)
|
||||
: "r"(funcPtr)
|
||||
: "ecx", "edx", "memory"
|
||||
);
|
||||
|
||||
result = basValLong((int32_t)rawResult);
|
||||
}
|
||||
|
||||
// Free temporary strings
|
||||
for (int32_t i = 0; i < tempStringCount; i++) {
|
||||
free(tempStrings[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onFormWinMouse
|
||||
// ============================================================
|
||||
|
|
@ -7032,7 +7069,7 @@ static void debugSetBreakTitles(bool paused) {
|
|||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < sDbgFormRt->formCount; i++) {
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sDbgFormRt->forms); i++) {
|
||||
BasFormT *form = sDbgFormRt->forms[i];
|
||||
|
||||
if (!form->window) {
|
||||
|
|
|
|||
524
apps/dvxbasic/runtime/serialize.c
Normal file
524
apps/dvxbasic/runtime/serialize.c
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
// serialize.c -- DVX BASIC module serialization
|
||||
//
|
||||
// Binary format (all little-endian, no alignment padding):
|
||||
//
|
||||
// Header:
|
||||
// magic 4 bytes "DBXM"
|
||||
// version uint16 1
|
||||
// entryPoint int32
|
||||
// globalCount int32
|
||||
// codeLen int32
|
||||
// constCount int32
|
||||
// dataCount int32
|
||||
// procCount int32
|
||||
//
|
||||
// code[] codeLen bytes of raw bytecode
|
||||
//
|
||||
// constants[] constCount entries, each:
|
||||
// len uint16 + len bytes of string data
|
||||
//
|
||||
// dataPool[] dataCount entries, each:
|
||||
// type uint8 + value (type-dependent)
|
||||
//
|
||||
// procs[] procCount entries, each:
|
||||
// codeAddr int32
|
||||
// paramCount int32
|
||||
// localCount int32
|
||||
// returnType uint8
|
||||
// isFunction uint8
|
||||
// nameLen uint16 + nameLen bytes
|
||||
|
||||
#include "serialize.h"
|
||||
#include "../compiler/opcodes.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// ============================================================
|
||||
// Write helpers
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
uint8_t *buf;
|
||||
int32_t len;
|
||||
int32_t cap;
|
||||
} BufT;
|
||||
|
||||
|
||||
static void bufInit(BufT *b) {
|
||||
b->cap = 4096;
|
||||
b->buf = (uint8_t *)malloc(b->cap);
|
||||
b->len = 0;
|
||||
}
|
||||
|
||||
|
||||
static void bufGrow(BufT *b, int32_t need) {
|
||||
while (b->len + need > b->cap) {
|
||||
b->cap *= 2;
|
||||
}
|
||||
|
||||
b->buf = (uint8_t *)realloc(b->buf, b->cap);
|
||||
}
|
||||
|
||||
|
||||
static void bufWrite(BufT *b, const void *data, int32_t len) {
|
||||
bufGrow(b, len);
|
||||
memcpy(b->buf + b->len, data, len);
|
||||
b->len += len;
|
||||
}
|
||||
|
||||
|
||||
static void bufWriteU8(BufT *b, uint8_t v) {
|
||||
bufWrite(b, &v, 1);
|
||||
}
|
||||
|
||||
|
||||
static void bufWriteU16(BufT *b, uint16_t v) {
|
||||
bufWrite(b, &v, 2);
|
||||
}
|
||||
|
||||
|
||||
static void bufWriteI32(BufT *b, int32_t v) {
|
||||
bufWrite(b, &v, 4);
|
||||
}
|
||||
|
||||
|
||||
static void bufWriteStr(BufT *b, const char *s) {
|
||||
uint16_t len = s ? (uint16_t)strlen(s) : 0;
|
||||
bufWriteU16(b, len);
|
||||
|
||||
if (len > 0) {
|
||||
bufWrite(b, s, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Read helpers
|
||||
// ============================================================
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *data;
|
||||
int32_t len;
|
||||
int32_t pos;
|
||||
} ReaderT;
|
||||
|
||||
|
||||
static bool rOk(const ReaderT *r, int32_t need) {
|
||||
return r->pos + need <= r->len;
|
||||
}
|
||||
|
||||
|
||||
static uint8_t rU8(ReaderT *r) {
|
||||
if (!rOk(r, 1)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return r->data[r->pos++];
|
||||
}
|
||||
|
||||
|
||||
static uint16_t rU16(ReaderT *r) {
|
||||
if (!rOk(r, 2)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint16_t v;
|
||||
memcpy(&v, r->data + r->pos, 2);
|
||||
r->pos += 2;
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
static int32_t rI32(ReaderT *r) {
|
||||
if (!rOk(r, 4)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t v;
|
||||
memcpy(&v, r->data + r->pos, 4);
|
||||
r->pos += 4;
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
static double rF64(ReaderT *r) {
|
||||
if (!rOk(r, 8)) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double v;
|
||||
memcpy(&v, r->data + r->pos, 8);
|
||||
r->pos += 8;
|
||||
return v;
|
||||
}
|
||||
|
||||
|
||||
// Read a length-prefixed string into a malloc'd buffer.
|
||||
static char *rStr(ReaderT *r) {
|
||||
uint16_t len = rU16(r);
|
||||
|
||||
if (len == 0 || !rOk(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;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basModuleSerialize
|
||||
// ============================================================
|
||||
|
||||
uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen) {
|
||||
if (!mod || !outLen) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BufT b;
|
||||
bufInit(&b);
|
||||
|
||||
// Header
|
||||
bufWrite(&b, "DBXM", 4);
|
||||
bufWriteU16(&b, 1); // version
|
||||
bufWriteI32(&b, mod->entryPoint);
|
||||
bufWriteI32(&b, mod->globalCount);
|
||||
bufWriteI32(&b, mod->codeLen);
|
||||
bufWriteI32(&b, mod->constCount);
|
||||
bufWriteI32(&b, mod->dataCount);
|
||||
bufWriteI32(&b, mod->procCount);
|
||||
|
||||
// Code
|
||||
bufWrite(&b, mod->code, mod->codeLen);
|
||||
|
||||
// String constant pool
|
||||
for (int32_t i = 0; i < mod->constCount; i++) {
|
||||
const char *s = mod->constants[i] ? mod->constants[i]->data : "";
|
||||
bufWriteStr(&b, s);
|
||||
}
|
||||
|
||||
// Data pool
|
||||
for (int32_t i = 0; i < mod->dataCount; i++) {
|
||||
const BasValueT *v = &mod->dataPool[i];
|
||||
bufWriteU8(&b, v->type);
|
||||
|
||||
switch (v->type) {
|
||||
case BAS_TYPE_INTEGER:
|
||||
bufWriteI32(&b, v->intVal);
|
||||
break;
|
||||
|
||||
case BAS_TYPE_LONG:
|
||||
bufWriteI32(&b, v->longVal);
|
||||
break;
|
||||
|
||||
case BAS_TYPE_SINGLE: {
|
||||
float f = (float)v->dblVal;
|
||||
bufWrite(&b, &f, 4);
|
||||
break;
|
||||
}
|
||||
|
||||
case BAS_TYPE_DOUBLE:
|
||||
bufWrite(&b, &v->dblVal, 8);
|
||||
break;
|
||||
|
||||
case BAS_TYPE_STRING:
|
||||
bufWriteStr(&b, v->strVal ? v->strVal->data : "");
|
||||
break;
|
||||
|
||||
case BAS_TYPE_BOOLEAN:
|
||||
bufWriteU8(&b, v->intVal ? 1 : 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
bufWriteI32(&b, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Procedure table
|
||||
for (int32_t i = 0; i < mod->procCount; i++) {
|
||||
const BasProcEntryT *p = &mod->procs[i];
|
||||
bufWriteI32(&b, p->codeAddr);
|
||||
bufWriteI32(&b, p->paramCount);
|
||||
bufWriteI32(&b, p->localCount);
|
||||
bufWriteU8(&b, p->returnType);
|
||||
bufWriteU8(&b, p->isFunction ? 1 : 0);
|
||||
bufWriteStr(&b, p->name);
|
||||
}
|
||||
|
||||
// Form variable info (runtime-essential for per-form variable allocation)
|
||||
bufWriteI32(&b, mod->formVarInfoCount);
|
||||
|
||||
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
|
||||
const BasFormVarInfoT *fv = &mod->formVarInfo[i];
|
||||
bufWriteStr(&b, fv->formName);
|
||||
bufWriteI32(&b, fv->varCount);
|
||||
bufWriteI32(&b, fv->initCodeAddr);
|
||||
bufWriteI32(&b, fv->initCodeLen);
|
||||
}
|
||||
|
||||
*outLen = b.len;
|
||||
return b.buf;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basModuleDeserialize
|
||||
// ============================================================
|
||||
|
||||
BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen) {
|
||||
if (!data || dataLen < 30) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ReaderT r = { data, dataLen, 0 };
|
||||
|
||||
// Check magic
|
||||
if (data[0] != 'D' || data[1] != 'B' || data[2] != 'X' || data[3] != 'M') {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
r.pos = 4;
|
||||
|
||||
uint16_t version = rU16(&r);
|
||||
|
||||
if (version != 1) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BasModuleT *mod = (BasModuleT *)calloc(1, sizeof(BasModuleT));
|
||||
|
||||
mod->entryPoint = rI32(&r);
|
||||
mod->globalCount = rI32(&r);
|
||||
mod->codeLen = rI32(&r);
|
||||
mod->constCount = rI32(&r);
|
||||
mod->dataCount = rI32(&r);
|
||||
mod->procCount = rI32(&r);
|
||||
|
||||
// Code
|
||||
if (!rOk(&r, mod->codeLen)) {
|
||||
free(mod);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
mod->code = (uint8_t *)malloc(mod->codeLen);
|
||||
memcpy(mod->code, r.data + r.pos, mod->codeLen);
|
||||
r.pos += mod->codeLen;
|
||||
|
||||
// String constant pool
|
||||
mod->constants = (BasStringT **)calloc(mod->constCount, sizeof(BasStringT *));
|
||||
|
||||
for (int32_t i = 0; i < mod->constCount; i++) {
|
||||
char *s = rStr(&r);
|
||||
mod->constants[i] = basStringNew(s, (int32_t)strlen(s));
|
||||
free(s);
|
||||
}
|
||||
|
||||
// Data pool
|
||||
if (mod->dataCount > 0) {
|
||||
mod->dataPool = (BasValueT *)calloc(mod->dataCount, sizeof(BasValueT));
|
||||
|
||||
for (int32_t i = 0; i < mod->dataCount; i++) {
|
||||
uint8_t type = rU8(&r);
|
||||
mod->dataPool[i].type = type;
|
||||
|
||||
switch (type) {
|
||||
case BAS_TYPE_INTEGER:
|
||||
mod->dataPool[i].intVal = (int16_t)rI32(&r);
|
||||
break;
|
||||
|
||||
case BAS_TYPE_LONG:
|
||||
mod->dataPool[i].longVal = rI32(&r);
|
||||
break;
|
||||
|
||||
case BAS_TYPE_SINGLE: {
|
||||
float f;
|
||||
|
||||
if (rOk(&r, 4)) {
|
||||
memcpy(&f, r.data + r.pos, 4);
|
||||
r.pos += 4;
|
||||
} else {
|
||||
f = 0.0f;
|
||||
}
|
||||
|
||||
mod->dataPool[i].dblVal = (double)f;
|
||||
break;
|
||||
}
|
||||
|
||||
case BAS_TYPE_DOUBLE:
|
||||
mod->dataPool[i].dblVal = rF64(&r);
|
||||
break;
|
||||
|
||||
case BAS_TYPE_STRING: {
|
||||
char *s = rStr(&r);
|
||||
mod->dataPool[i].strVal = basStringNew(s, (int32_t)strlen(s));
|
||||
free(s);
|
||||
break;
|
||||
}
|
||||
|
||||
case BAS_TYPE_BOOLEAN:
|
||||
mod->dataPool[i].intVal = rU8(&r) ? -1 : 0;
|
||||
break;
|
||||
|
||||
default:
|
||||
rI32(&r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Procedure table
|
||||
if (mod->procCount > 0) {
|
||||
mod->procs = (BasProcEntryT *)calloc(mod->procCount, sizeof(BasProcEntryT));
|
||||
|
||||
for (int32_t i = 0; i < mod->procCount; i++) {
|
||||
BasProcEntryT *p = &mod->procs[i];
|
||||
p->codeAddr = rI32(&r);
|
||||
p->paramCount = rI32(&r);
|
||||
p->localCount = rI32(&r);
|
||||
p->returnType = rU8(&r);
|
||||
p->isFunction = rU8(&r) != 0;
|
||||
|
||||
char *name = rStr(&r);
|
||||
snprintf(p->name, BAS_MAX_PROC_NAME, "%s", name);
|
||||
free(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Form variable info
|
||||
mod->formVarInfoCount = rI32(&r);
|
||||
|
||||
if (mod->formVarInfoCount > 0) {
|
||||
mod->formVarInfo = (BasFormVarInfoT *)calloc(mod->formVarInfoCount, sizeof(BasFormVarInfoT));
|
||||
|
||||
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
|
||||
BasFormVarInfoT *fv = &mod->formVarInfo[i];
|
||||
char *fvName = rStr(&r);
|
||||
snprintf(fv->formName, BAS_MAX_PROC_NAME, "%s", fvName);
|
||||
free(fvName);
|
||||
fv->varCount = rI32(&r);
|
||||
fv->initCodeAddr = rI32(&r);
|
||||
fv->initCodeLen = rI32(&r);
|
||||
}
|
||||
}
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basModuleFree
|
||||
// ============================================================
|
||||
|
||||
void basModuleFree(BasModuleT *mod) {
|
||||
if (!mod) {
|
||||
return;
|
||||
}
|
||||
|
||||
free(mod->code);
|
||||
|
||||
if (mod->constants) {
|
||||
for (int32_t i = 0; i < mod->constCount; i++) {
|
||||
basStringUnref(mod->constants[i]);
|
||||
}
|
||||
|
||||
free(mod->constants);
|
||||
}
|
||||
|
||||
if (mod->dataPool) {
|
||||
for (int32_t i = 0; i < mod->dataCount; i++) {
|
||||
basValRelease(&mod->dataPool[i]);
|
||||
}
|
||||
|
||||
free(mod->dataPool);
|
||||
}
|
||||
|
||||
free(mod->procs);
|
||||
free(mod->debugVars);
|
||||
|
||||
if (mod->debugUdtDefs) {
|
||||
for (int32_t i = 0; i < mod->debugUdtDefCount; i++) {
|
||||
free(mod->debugUdtDefs[i].fields);
|
||||
}
|
||||
|
||||
free(mod->debugUdtDefs);
|
||||
}
|
||||
|
||||
if (mod->formVarInfo) {
|
||||
free(mod->formVarInfo);
|
||||
}
|
||||
|
||||
free(mod);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basDebugSerialize
|
||||
// ============================================================
|
||||
|
||||
uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen) {
|
||||
if (!mod || !outLen) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BufT b;
|
||||
bufInit(&b);
|
||||
|
||||
// Debug variables
|
||||
bufWriteI32(&b, mod->debugVarCount);
|
||||
|
||||
for (int32_t i = 0; i < mod->debugVarCount; i++) {
|
||||
const BasDebugVarT *v = &mod->debugVars[i];
|
||||
bufWriteStr(&b, v->name);
|
||||
bufWriteStr(&b, v->formName);
|
||||
bufWriteU8(&b, v->scope);
|
||||
bufWriteU8(&b, v->dataType);
|
||||
bufWriteI32(&b, v->index);
|
||||
bufWriteI32(&b, v->procIndex);
|
||||
}
|
||||
|
||||
*outLen = b.len;
|
||||
return b.buf;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// basDebugDeserialize
|
||||
// ============================================================
|
||||
|
||||
void basDebugDeserialize(BasModuleT *mod, const uint8_t *data, int32_t dataLen) {
|
||||
if (!mod || !data || dataLen < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReaderT r = { data, dataLen, 0 };
|
||||
|
||||
// Debug variables
|
||||
mod->debugVarCount = rI32(&r);
|
||||
|
||||
if (mod->debugVarCount > 0) {
|
||||
mod->debugVars = (BasDebugVarT *)calloc(mod->debugVarCount, sizeof(BasDebugVarT));
|
||||
|
||||
for (int32_t i = 0; i < mod->debugVarCount; i++) {
|
||||
BasDebugVarT *v = &mod->debugVars[i];
|
||||
char *name = rStr(&r);
|
||||
char *formName = rStr(&r);
|
||||
snprintf(v->name, BAS_MAX_PROC_NAME, "%s", name);
|
||||
snprintf(v->formName, BAS_MAX_PROC_NAME, "%s", formName);
|
||||
free(name);
|
||||
free(formName);
|
||||
v->scope = rU8(&r);
|
||||
v->dataType = rU8(&r);
|
||||
v->index = rI32(&r);
|
||||
v->procIndex = rI32(&r);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
41
apps/dvxbasic/runtime/serialize.h
Normal file
41
apps/dvxbasic/runtime/serialize.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
// serialize.h -- DVX BASIC module serialization
|
||||
//
|
||||
// Serialize/deserialize a compiled BasModuleT to/from a byte buffer.
|
||||
// Used by "Make Executable" (serialize) and the standalone stub
|
||||
// (deserialize). The format is a flat binary with no alignment padding.
|
||||
|
||||
#ifndef DVXBASIC_SERIALIZE_H
|
||||
#define DVXBASIC_SERIALIZE_H
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// ============================================================
|
||||
// Module serialization
|
||||
// ============================================================
|
||||
|
||||
// Serialize a compiled module to a malloc'd byte buffer.
|
||||
// Returns the buffer (caller frees) and sets *outLen to the size.
|
||||
// Returns NULL on failure.
|
||||
uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen);
|
||||
|
||||
// Deserialize a module from a byte buffer.
|
||||
// Returns a malloc'd BasModuleT (caller frees with basModuleFree).
|
||||
// Returns NULL on failure.
|
||||
BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen);
|
||||
|
||||
// Free a deserialized module and all its allocated members.
|
||||
void basModuleFree(BasModuleT *mod);
|
||||
|
||||
// ============================================================
|
||||
// Debug info serialization (separate resource)
|
||||
// ============================================================
|
||||
|
||||
// Serialize debug info (debugVars + formVarInfo) to a malloc'd buffer.
|
||||
uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen);
|
||||
|
||||
// Deserialize debug info and attach to an existing module.
|
||||
void basDebugDeserialize(BasModuleT *mod, const uint8_t *data, int32_t dataLen);
|
||||
|
||||
#endif // DVXBASIC_SERIALIZE_H
|
||||
|
|
@ -2977,6 +2977,36 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
break;
|
||||
}
|
||||
|
||||
case OP_CREATE_CTRL_EX: {
|
||||
// Stack: [formRef, typeStr, nameStr, parentRef] -- parent on top
|
||||
BasValueT parentVal;
|
||||
BasValueT nameVal;
|
||||
BasValueT typeVal;
|
||||
BasValueT formVal;
|
||||
|
||||
if (!pop(vm, &parentVal) || !pop(vm, &nameVal) || !pop(vm, &typeVal) || !pop(vm, &formVal)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
void *ctrlRef = NULL;
|
||||
|
||||
if (vm->ui.createCtrlEx && formVal.type == BAS_TYPE_OBJECT) {
|
||||
BasValueT sv1 = basValToString(typeVal);
|
||||
BasValueT sv2 = basValToString(nameVal);
|
||||
void *parentRef = (parentVal.type == BAS_TYPE_OBJECT) ? parentVal.objVal : NULL;
|
||||
ctrlRef = vm->ui.createCtrlEx(vm->ui.ctx, formVal.objVal, sv1.strVal->data, sv2.strVal->data, parentRef);
|
||||
basValRelease(&sv1);
|
||||
basValRelease(&sv2);
|
||||
}
|
||||
|
||||
basValRelease(&parentVal);
|
||||
basValRelease(&nameVal);
|
||||
basValRelease(&typeVal);
|
||||
basValRelease(&formVal);
|
||||
push(vm, basValObject(ctrlRef));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_FIND_CTRL: {
|
||||
BasValueT nameVal;
|
||||
BasValueT formVal;
|
||||
|
|
@ -3046,6 +3076,77 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
break;
|
||||
}
|
||||
|
||||
case OP_CREATE_FORM: {
|
||||
// Stack: [nameStr, width, height] -- height on top
|
||||
BasValueT heightVal;
|
||||
BasValueT widthVal;
|
||||
BasValueT nameVal;
|
||||
|
||||
if (!pop(vm, &heightVal) || !pop(vm, &widthVal) || !pop(vm, &nameVal)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
void *formRef = NULL;
|
||||
|
||||
if (vm->ui.createForm) {
|
||||
BasValueT sv = basValToString(nameVal);
|
||||
int32_t w = (int32_t)basValToNumber(widthVal);
|
||||
int32_t h = (int32_t)basValToNumber(heightVal);
|
||||
formRef = vm->ui.createForm(vm->ui.ctx, sv.strVal->data, w, h);
|
||||
basValRelease(&sv);
|
||||
}
|
||||
|
||||
basValRelease(&nameVal);
|
||||
basValRelease(&widthVal);
|
||||
basValRelease(&heightVal);
|
||||
push(vm, basValObject(formRef));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SET_EVENT: {
|
||||
// Stack: [ctrlRef, eventNameStr, handlerNameStr] -- handler on top
|
||||
BasValueT handlerVal;
|
||||
BasValueT eventVal;
|
||||
BasValueT ctrlVal;
|
||||
|
||||
if (!pop(vm, &handlerVal) || !pop(vm, &eventVal) || !pop(vm, &ctrlVal)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
if (vm->ui.setEvent && ctrlVal.type == BAS_TYPE_OBJECT) {
|
||||
BasValueT sv1 = basValToString(eventVal);
|
||||
BasValueT sv2 = basValToString(handlerVal);
|
||||
vm->ui.setEvent(vm->ui.ctx, ctrlVal.objVal, sv1.strVal->data, sv2.strVal->data);
|
||||
basValRelease(&sv1);
|
||||
basValRelease(&sv2);
|
||||
}
|
||||
|
||||
basValRelease(&handlerVal);
|
||||
basValRelease(&eventVal);
|
||||
basValRelease(&ctrlVal);
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_REMOVE_CTRL: {
|
||||
// Stack: [formRef, ctrlNameStr] -- name on top
|
||||
BasValueT nameVal;
|
||||
BasValueT formVal;
|
||||
|
||||
if (!pop(vm, &nameVal) || !pop(vm, &formVal)) {
|
||||
return BAS_VM_STACK_UNDERFLOW;
|
||||
}
|
||||
|
||||
if (vm->ui.removeCtrl && formVal.type == BAS_TYPE_OBJECT) {
|
||||
BasValueT sv = basValToString(nameVal);
|
||||
vm->ui.removeCtrl(vm->ui.ctx, formVal.objVal, sv.strVal->data);
|
||||
basValRelease(&sv);
|
||||
}
|
||||
|
||||
basValRelease(&nameVal);
|
||||
basValRelease(&formVal);
|
||||
break;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Form-level variables
|
||||
// ============================================================
|
||||
|
|
@ -3195,204 +3296,6 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
// SQL database operations
|
||||
// ============================================================
|
||||
|
||||
case OP_SQL_OPEN: {
|
||||
BasValueT pathVal;
|
||||
if (!pop(vm, &pathVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
int32_t handle = 0;
|
||||
if (vm->sql.sqlOpen) {
|
||||
BasValueT sv = basValToString(pathVal);
|
||||
handle = vm->sql.sqlOpen(sv.strVal->data);
|
||||
basValRelease(&sv);
|
||||
}
|
||||
basValRelease(&pathVal);
|
||||
push(vm, basValLong(handle));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_CLOSE: {
|
||||
BasValueT dbVal;
|
||||
if (!pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
if (vm->sql.sqlClose) {
|
||||
vm->sql.sqlClose((int32_t)basValToNumber(dbVal));
|
||||
}
|
||||
basValRelease(&dbVal);
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_EXEC: {
|
||||
BasValueT sqlVal;
|
||||
BasValueT dbVal;
|
||||
if (!pop(vm, &sqlVal) || !pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
bool ok = false;
|
||||
if (vm->sql.sqlExec) {
|
||||
BasValueT sv = basValToString(sqlVal);
|
||||
ok = vm->sql.sqlExec((int32_t)basValToNumber(dbVal), sv.strVal->data);
|
||||
basValRelease(&sv);
|
||||
}
|
||||
basValRelease(&sqlVal);
|
||||
basValRelease(&dbVal);
|
||||
push(vm, ok ? basValInteger(-1) : basValInteger(0));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_ERROR: {
|
||||
BasValueT dbVal;
|
||||
if (!pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
const char *err = "";
|
||||
if (vm->sql.sqlError) {
|
||||
err = vm->sql.sqlError((int32_t)basValToNumber(dbVal));
|
||||
}
|
||||
basValRelease(&dbVal);
|
||||
push(vm, basValStringFromC(err ? err : ""));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_QUERY: {
|
||||
BasValueT sqlVal;
|
||||
BasValueT dbVal;
|
||||
if (!pop(vm, &sqlVal) || !pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
int32_t handle = 0;
|
||||
if (vm->sql.sqlQuery) {
|
||||
BasValueT sv = basValToString(sqlVal);
|
||||
handle = vm->sql.sqlQuery((int32_t)basValToNumber(dbVal), sv.strVal->data);
|
||||
basValRelease(&sv);
|
||||
}
|
||||
basValRelease(&sqlVal);
|
||||
basValRelease(&dbVal);
|
||||
push(vm, basValLong(handle));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_NEXT: {
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
bool ok = false;
|
||||
if (vm->sql.sqlNext) {
|
||||
ok = vm->sql.sqlNext((int32_t)basValToNumber(rsVal));
|
||||
}
|
||||
basValRelease(&rsVal);
|
||||
push(vm, ok ? basValInteger(-1) : basValInteger(0));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_EOF: {
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
bool eof = true;
|
||||
if (vm->sql.sqlEof) {
|
||||
eof = vm->sql.sqlEof((int32_t)basValToNumber(rsVal));
|
||||
}
|
||||
basValRelease(&rsVal);
|
||||
push(vm, eof ? basValInteger(-1) : basValInteger(0));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_FIELD_COUNT: {
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
int32_t count = 0;
|
||||
if (vm->sql.sqlFieldCount) {
|
||||
count = vm->sql.sqlFieldCount((int32_t)basValToNumber(rsVal));
|
||||
}
|
||||
basValRelease(&rsVal);
|
||||
push(vm, basValLong(count));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_FIELD_NAME: {
|
||||
BasValueT colVal;
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &colVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
const char *name = "";
|
||||
if (vm->sql.sqlFieldName) {
|
||||
name = vm->sql.sqlFieldName((int32_t)basValToNumber(rsVal), (int32_t)basValToNumber(colVal));
|
||||
}
|
||||
basValRelease(&colVal);
|
||||
basValRelease(&rsVal);
|
||||
push(vm, basValStringFromC(name ? name : ""));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_FIELD_TEXT: {
|
||||
BasValueT colVal;
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &colVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
const char *text = "";
|
||||
if (vm->sql.sqlFieldText) {
|
||||
text = vm->sql.sqlFieldText((int32_t)basValToNumber(rsVal), (int32_t)basValToNumber(colVal));
|
||||
}
|
||||
basValRelease(&colVal);
|
||||
basValRelease(&rsVal);
|
||||
push(vm, basValStringFromC(text ? text : ""));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_FIELD_BYNAME: {
|
||||
BasValueT nameVal;
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &nameVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
const char *text = "";
|
||||
if (vm->sql.sqlFieldByName) {
|
||||
BasValueT sv = basValToString(nameVal);
|
||||
text = vm->sql.sqlFieldByName((int32_t)basValToNumber(rsVal), sv.strVal->data);
|
||||
basValRelease(&sv);
|
||||
}
|
||||
basValRelease(&nameVal);
|
||||
basValRelease(&rsVal);
|
||||
push(vm, basValStringFromC(text ? text : ""));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_FIELD_INT: {
|
||||
BasValueT colVal;
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &colVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
int32_t val = 0;
|
||||
if (vm->sql.sqlFieldInt) {
|
||||
val = vm->sql.sqlFieldInt((int32_t)basValToNumber(rsVal), (int32_t)basValToNumber(colVal));
|
||||
}
|
||||
basValRelease(&colVal);
|
||||
basValRelease(&rsVal);
|
||||
push(vm, basValLong(val));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_FIELD_DBL: {
|
||||
BasValueT colVal;
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &colVal) || !pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
double val = 0.0;
|
||||
if (vm->sql.sqlFieldDbl) {
|
||||
val = vm->sql.sqlFieldDbl((int32_t)basValToNumber(rsVal), (int32_t)basValToNumber(colVal));
|
||||
}
|
||||
basValRelease(&colVal);
|
||||
basValRelease(&rsVal);
|
||||
push(vm, basValDouble(val));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_FREE_RESULT: {
|
||||
BasValueT rsVal;
|
||||
if (!pop(vm, &rsVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
if (vm->sql.sqlFreeResult) {
|
||||
vm->sql.sqlFreeResult((int32_t)basValToNumber(rsVal));
|
||||
}
|
||||
basValRelease(&rsVal);
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_SQL_AFFECTED: {
|
||||
BasValueT dbVal;
|
||||
if (!pop(vm, &dbVal)) { return BAS_VM_STACK_UNDERFLOW; }
|
||||
int32_t rows = 0;
|
||||
if (vm->sql.sqlAffectedRows) {
|
||||
rows = vm->sql.sqlAffectedRows((int32_t)basValToNumber(dbVal));
|
||||
}
|
||||
basValRelease(&dbVal);
|
||||
push(vm, basValLong(rows));
|
||||
break;
|
||||
}
|
||||
|
||||
case OP_LINE: {
|
||||
uint16_t lineNum = readUint16(vm);
|
||||
vm->currentLine = lineNum;
|
||||
|
|
|
|||
|
|
@ -129,21 +129,38 @@ typedef int32_t (*BasUiMsgBoxFnT)(void *ctx, const char *message, int32_t flags)
|
|||
// Display an input box. Returns the entered string (empty on cancel).
|
||||
typedef BasStringT *(*BasUiInputBoxFnT)(void *ctx, const char *prompt, const char *title, const char *defaultText);
|
||||
|
||||
// Create a form programmatically (no .frm file). Returns form reference.
|
||||
typedef void *(*BasUiCreateFormFnT)(void *ctx, const char *formName, int32_t width, int32_t height);
|
||||
|
||||
// Create a control with an explicit parent container. parentRef may be NULL
|
||||
// to use the form's root content box.
|
||||
typedef void *(*BasUiCreateCtrlExFnT)(void *ctx, void *formRef, const char *typeName, const char *ctrlName, void *parentRef);
|
||||
|
||||
// Override the event handler for a specific control + event pair.
|
||||
typedef void (*BasUiSetEventFnT)(void *ctx, void *ctrlRef, const char *eventName, const char *handlerName);
|
||||
|
||||
// Remove a control from a form by name.
|
||||
typedef void (*BasUiRemoveCtrlFnT)(void *ctx, void *formRef, const char *ctrlName);
|
||||
|
||||
// Collected UI callbacks
|
||||
typedef struct {
|
||||
BasUiGetPropFnT getProp;
|
||||
BasUiSetPropFnT setProp;
|
||||
BasUiCallMethodFnT callMethod;
|
||||
BasUiCreateCtrlFnT createCtrl;
|
||||
BasUiFindCtrlFnT findCtrl;
|
||||
BasUiFindCtrlIdxFnT findCtrlIdx;
|
||||
BasUiLoadFormFnT loadForm;
|
||||
BasUiUnloadFormFnT unloadForm;
|
||||
BasUiShowFormFnT showForm;
|
||||
BasUiHideFormFnT hideForm;
|
||||
BasUiMsgBoxFnT msgBox;
|
||||
BasUiInputBoxFnT inputBox;
|
||||
void *ctx; // passed as first arg to all callbacks
|
||||
BasUiGetPropFnT getProp;
|
||||
BasUiSetPropFnT setProp;
|
||||
BasUiCallMethodFnT callMethod;
|
||||
BasUiCreateCtrlFnT createCtrl;
|
||||
BasUiFindCtrlFnT findCtrl;
|
||||
BasUiFindCtrlIdxFnT findCtrlIdx;
|
||||
BasUiLoadFormFnT loadForm;
|
||||
BasUiUnloadFormFnT unloadForm;
|
||||
BasUiShowFormFnT showForm;
|
||||
BasUiHideFormFnT hideForm;
|
||||
BasUiMsgBoxFnT msgBox;
|
||||
BasUiInputBoxFnT inputBox;
|
||||
BasUiCreateFormFnT createForm;
|
||||
BasUiCreateCtrlExFnT createCtrlEx;
|
||||
BasUiSetEventFnT setEvent;
|
||||
BasUiRemoveCtrlFnT removeCtrl;
|
||||
void *ctx; // passed as first arg to all callbacks
|
||||
} BasUiCallbacksT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
[Project]
|
||||
Name = Multi Form Test 1
|
||||
Author = Scott Duensing
|
||||
Company = Kangaroo Punch Studios
|
||||
Version = 1.00
|
||||
Copyright = Copyright 2026 Scott Duensing
|
||||
Description = Testing properties.
|
||||
Icon = icon32.bmp
|
||||
|
||||
[Modules]
|
||||
Count = 0
|
||||
|
||||
[Forms]
|
||||
File0 = multi1.frm
|
||||
Count = 1
|
||||
|
||||
[Settings]
|
||||
StartupForm =
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
VERSION 1.00
|
||||
Begin Form multi1
|
||||
Caption = "Welcome to DVX BASIC!"
|
||||
Layout = VBox
|
||||
AutoSize = False
|
||||
Resizable = True
|
||||
Centered = True
|
||||
Width = 400
|
||||
Height = 300
|
||||
End
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
' clickme.bas -- DVX BASIC click test
|
||||
'
|
||||
' Demonstrates form loading, control properties, and event handling.
|
||||
' Load this file in DVX BASIC, then click Run.
|
||||
|
||||
DIM clickCount AS INTEGER
|
||||
clickCount = 0
|
||||
|
||||
Load ClickMe
|
||||
ClickMe.Show
|
||||
|
||||
PRINT "Form loaded. Click the button!"
|
||||
|
||||
' Event loop -- keeps the program alive for events.
|
||||
' The IDE yields to DVX between VM slices, so widget
|
||||
' events fire during DoEvents pauses.
|
||||
DO
|
||||
DoEvents
|
||||
LOOP
|
||||
|
||||
SUB Command1_Click
|
||||
clickCount = clickCount + 1
|
||||
Text1.Text = "Clicked " + STR$(clickCount) + " times!"
|
||||
Label1.Caption = "Keep clicking!"
|
||||
END SUB
|
||||
|
||||
SUB ClickMe_Load
|
||||
PRINT "Form_Load fired!"
|
||||
END SUB
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
Begin Form ClickMe
|
||||
Caption = "DVX BASIC Demo"
|
||||
Begin CommandButton Command1
|
||||
Caption = "Click Me!"
|
||||
End
|
||||
Begin TextBox Text1
|
||||
Text = "Ready."
|
||||
End
|
||||
Begin Label Label1
|
||||
Caption = "Click the button above!"
|
||||
End
|
||||
End
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
' formtest.bas -- DVX BASIC form test
|
||||
'
|
||||
' Demonstrates form creation and MsgBox.
|
||||
|
||||
Load MyForm
|
||||
MyForm.Show
|
||||
MsgBox "Hello from DVX BASIC!"
|
||||
PRINT "Form test complete."
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
' hello.bas -- DVX BASIC Hello World
|
||||
'
|
||||
' A simple program to test the DVX BASIC runner.
|
||||
|
||||
PRINT "Hello from DVX BASIC!"
|
||||
PRINT
|
||||
PRINT "Testing arithmetic: 2 + 3 * 4 ="; 2 + 3 * 4
|
||||
PRINT "Testing strings: "; "Hello" & " " & "World"
|
||||
PRINT
|
||||
|
||||
DIM i AS INTEGER
|
||||
PRINT "Fibonacci sequence:"
|
||||
DIM a AS INTEGER
|
||||
DIM b AS INTEGER
|
||||
DIM temp AS INTEGER
|
||||
a = 0
|
||||
b = 1
|
||||
FOR i = 1 TO 15
|
||||
PRINT a;
|
||||
temp = a + b
|
||||
a = b
|
||||
b = temp
|
||||
NEXT i
|
||||
PRINT
|
||||
|
||||
PRINT
|
||||
PRINT "Done!"
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
' input.bas -- Test INPUT statement
|
||||
'
|
||||
' Demonstrates the INPUT dialog.
|
||||
|
||||
DIM name AS STRING
|
||||
DIM age AS INTEGER
|
||||
|
||||
INPUT "What is your name"; name
|
||||
PRINT "Hello, " + name + "!"
|
||||
|
||||
INPUT "How old are you"; age
|
||||
PRINT "You are" + STR$(age) + " years old."
|
||||
PRINT "Done!"
|
||||
217
apps/dvxbasic/stub/basstub.c
Normal file
217
apps/dvxbasic/stub/basstub.c
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
// basstub.c -- DVX BASIC standalone application stub
|
||||
//
|
||||
// Minimal entry point for compiled BASIC applications. Reads the
|
||||
// MODULE resource (serialized bytecode), FORMn resources (.frm text),
|
||||
// and optional DEBUG resource from its own DXE file. Creates a VM
|
||||
// and form runtime from basrt.lib and runs the program.
|
||||
//
|
||||
// This file is compiled into basstub.app, which is embedded as a
|
||||
// resource in dvxbasic.app. "Make Executable" extracts it, renames
|
||||
// it to the project name, and attaches the compiled resources.
|
||||
|
||||
#include "dvxApp.h"
|
||||
#include "dvxDlg.h"
|
||||
#include "dvxRes.h"
|
||||
#include "dvxWgt.h"
|
||||
#include "shellApp.h"
|
||||
#include "../runtime/vm.h"
|
||||
#include "../runtime/serialize.h"
|
||||
#include "../formrt/formrt.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
||||
// ============================================================
|
||||
// App descriptor
|
||||
// ============================================================
|
||||
|
||||
#define STUB_STACK_SIZE 65536
|
||||
|
||||
AppDescriptorT appDescriptor = {
|
||||
.name = "BASIC App",
|
||||
.hasMainLoop = true,
|
||||
.multiInstance = false,
|
||||
.stackSize = STUB_STACK_SIZE,
|
||||
.priority = TS_PRIORITY_NORMAL
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Callbacks
|
||||
// ============================================================
|
||||
|
||||
static AppContextT *sAc = NULL;
|
||||
|
||||
|
||||
static void stubPrint(void *ctx, const char *text, bool newline) {
|
||||
(void)ctx;
|
||||
(void)text;
|
||||
(void)newline;
|
||||
}
|
||||
|
||||
|
||||
static bool stubInput(void *ctx, const char *prompt, char *buf, int32_t bufSize) {
|
||||
(void)ctx;
|
||||
buf[0] = '\0';
|
||||
|
||||
if (!sAc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return dvxInputBox(sAc, "Input", prompt, "", buf, bufSize);
|
||||
}
|
||||
|
||||
|
||||
static bool stubDoEvents(void *ctx) {
|
||||
(void)ctx;
|
||||
|
||||
if (!sAc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return dvxUpdate(sAc);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// appMain
|
||||
// ============================================================
|
||||
|
||||
int32_t appMain(DxeAppContextT *ctx) {
|
||||
sAc = ctx->shellCtx;
|
||||
|
||||
// Open our own resources
|
||||
DvxResHandleT *res = dvxResOpen(ctx->appPath);
|
||||
|
||||
if (!res) {
|
||||
dvxMessageBox(sAc, "Error", "No compiled module found in this application.", 0);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read app name and update the shell's app record
|
||||
uint32_t nameSize = 0;
|
||||
char *appName = (char *)dvxResRead(res, "APPNAME", &nameSize);
|
||||
|
||||
if (appName) {
|
||||
ShellAppT *app = shellGetApp(ctx->appId);
|
||||
|
||||
if (app) {
|
||||
snprintf(app->name, SHELL_APP_NAME_MAX, "%s", appName);
|
||||
}
|
||||
|
||||
free(appName);
|
||||
}
|
||||
|
||||
// Load MODULE resource
|
||||
uint32_t modSize = 0;
|
||||
uint8_t *modData = (uint8_t *)dvxResRead(res, "MODULE", &modSize);
|
||||
|
||||
if (!modData) {
|
||||
dvxMessageBox(sAc, "Error", "MODULE resource not found.", 0);
|
||||
dvxResClose(res);
|
||||
return 1;
|
||||
}
|
||||
|
||||
BasModuleT *mod = basModuleDeserialize(modData, (int32_t)modSize);
|
||||
free(modData);
|
||||
|
||||
if (!mod) {
|
||||
dvxMessageBox(sAc, "Error", "Failed to deserialize module.", 0);
|
||||
dvxResClose(res);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Load optional DEBUG resource
|
||||
uint32_t dbgSize = 0;
|
||||
uint8_t *dbgData = (uint8_t *)dvxResRead(res, "DEBUG", &dbgSize);
|
||||
|
||||
if (dbgData) {
|
||||
basDebugDeserialize(mod, dbgData, (int32_t)dbgSize);
|
||||
free(dbgData);
|
||||
}
|
||||
|
||||
// Create VM
|
||||
BasVmT *vm = basVmCreate();
|
||||
basVmLoadModule(vm, mod);
|
||||
basVmSetPrintCallback(vm, stubPrint, NULL);
|
||||
basVmSetInputCallback(vm, stubInput, NULL);
|
||||
basVmSetDoEventsCallback(vm, stubDoEvents, NULL);
|
||||
|
||||
// Set app paths
|
||||
snprintf(vm->appPath, DVX_MAX_PATH, "%s", ctx->appDir);
|
||||
snprintf(vm->appConfig, DVX_MAX_PATH, "%s", ctx->appDir);
|
||||
snprintf(vm->appData, DVX_MAX_PATH, "%s", ctx->appDir);
|
||||
|
||||
// Set extern call callbacks (required for DECLARE LIBRARY functions)
|
||||
BasExternCallbacksT extCb;
|
||||
memset(&extCb, 0, sizeof(extCb));
|
||||
extCb.resolveExtern = basExternResolve;
|
||||
extCb.callExtern = basExternCall;
|
||||
basVmSetExternCallbacks(vm, &extCb);
|
||||
|
||||
// Create form runtime
|
||||
BasFormRtT *rt = basFormRtCreate(sAc, vm, mod);
|
||||
|
||||
// Register .frm source text for lazy loading
|
||||
for (int32_t i = 0; i < 256; i++) {
|
||||
char resName[16];
|
||||
snprintf(resName, sizeof(resName), "FORM%ld", (long)i);
|
||||
|
||||
uint32_t frmSize = 0;
|
||||
char *frmText = (char *)dvxResRead(res, resName, &frmSize);
|
||||
|
||||
if (!frmText) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Extract form name from "Begin Form <name>" line
|
||||
char frmName[BAS_MAX_CTRL_NAME] = "";
|
||||
const char *p = frmText;
|
||||
|
||||
while (*p) {
|
||||
while (*p == ' ' || *p == '\t') { p++; }
|
||||
|
||||
if (strncasecmp(p, "Begin Form ", 11) == 0) {
|
||||
p += 11;
|
||||
|
||||
while (*p == ' ' || *p == '\t') { p++; }
|
||||
|
||||
int32_t ni = 0;
|
||||
|
||||
while (*p && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && ni < BAS_MAX_CTRL_NAME - 1) {
|
||||
frmName[ni++] = *p++;
|
||||
}
|
||||
|
||||
frmName[ni] = '\0';
|
||||
break;
|
||||
}
|
||||
|
||||
while (*p && *p != '\n') { p++; }
|
||||
|
||||
if (*p == '\n') { p++; }
|
||||
}
|
||||
|
||||
if (frmName[0]) {
|
||||
basFormRtRegisterFrm(rt, frmName, frmText, (int32_t)frmSize);
|
||||
}
|
||||
|
||||
free(frmText);
|
||||
}
|
||||
|
||||
dvxResClose(res);
|
||||
|
||||
// Load all cached forms and show the startup form
|
||||
basFormRtLoadAllForms(rt, NULL);
|
||||
|
||||
// Run bytecode + VB-style event loop
|
||||
basFormRtRunSimple(rt);
|
||||
|
||||
// Cleanup
|
||||
basFormRtDestroy(rt);
|
||||
basVmDestroy(vm);
|
||||
basModuleFree(mod);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -13,11 +13,13 @@ bpp = 16
|
|||
; wheel: normal or reversed
|
||||
; doubleclick: double-click speed in milliseconds (200-900, default 500)
|
||||
; acceleration: off, low, medium, high (default medium)
|
||||
; speed: cursor speed (2-32, default 8; higher = faster)
|
||||
|
||||
[mouse]
|
||||
wheel = normal
|
||||
doubleclick = 500
|
||||
acceleration = medium
|
||||
speed = 8
|
||||
|
||||
; Shell settings.
|
||||
; desktop: path to the desktop app loaded at startup
|
||||
|
|
|
|||
|
|
@ -4833,7 +4833,7 @@ void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b)
|
|||
// dvxSetMouseConfig
|
||||
// ============================================================
|
||||
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold) {
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio) {
|
||||
ctx->wheelDirection = (wheelDir < 0) ? -1 : 1;
|
||||
ctx->dblClickTicks = (clock_t)dblClickMs * CLOCKS_PER_SEC / 1000;
|
||||
sDblClickTicks = ctx->dblClickTicks;
|
||||
|
|
@ -4841,6 +4841,10 @@ void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, i
|
|||
if (accelThreshold > 0) {
|
||||
platformMouseSetAccel(accelThreshold);
|
||||
}
|
||||
|
||||
if (mickeyRatio > 0) {
|
||||
platformMouseSetMickeys(mickeyRatio, mickeyRatio);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,8 @@ int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW, int32_t request
|
|||
// Configure mouse behaviour. wheelDir: 1 = normal, -1 = reversed.
|
||||
// dblClickMs: double-click speed in milliseconds (e.g. 500).
|
||||
// accelThreshold: double-speed threshold in mickeys/sec (0 = don't change).
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold);
|
||||
// mickeyRatio: mickeys per 8 pixels (0 = don't change, 8 = default speed).
|
||||
void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir, int32_t dblClickMs, int32_t accelThreshold, int32_t mickeyRatio);
|
||||
|
||||
// ============================================================
|
||||
// Color scheme
|
||||
|
|
|
|||
|
|
@ -72,4 +72,9 @@ const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name);
|
|||
// Close the handle and free all associated memory.
|
||||
void dvxResClose(DvxResHandleT *h);
|
||||
|
||||
// Append a resource to an existing DXE file.
|
||||
// Preserves existing resources and adds the new one.
|
||||
// Returns 0 on success, -1 on error.
|
||||
int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize);
|
||||
|
||||
#endif // DVX_RES_H
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
// block is located by reading the footer at the end of the file.
|
||||
|
||||
#include "dvxRes.h"
|
||||
#include "../tools/dvxResWrite.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
|
@ -138,3 +139,8 @@ void dvxResClose(DvxResHandleT *h) {
|
|||
free(h);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize) {
|
||||
return dvxResAppendEntry(path, name, type, data, dataSize);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -583,6 +583,11 @@ const void *wgtGetApi(const char *name);
|
|||
#define WGT_SIG_INT5 13 // void fn(WidgetT *, int32_t, int32_t, int32_t, int32_t, int32_t)
|
||||
#define WGT_SIG_RET_STR_INT 14 // const char *fn(const WidgetT *, int32_t)
|
||||
#define WGT_SIG_RET_STR_INT_INT 15 // const char *fn(const WidgetT *, int32_t, int32_t)
|
||||
#define WGT_SIG_STR_BOOL_BOOL 16 // bool fn(WidgetT *, const char *, bool, bool)
|
||||
#define WGT_SIG_STR_STR_BOOL 17 // int32_t fn(WidgetT *, const char *, const char *, bool)
|
||||
#define WGT_SIG_RET_STR 18 // const char *fn(const WidgetT *)
|
||||
#define WGT_SIG_STR_INT 19 // void fn(WidgetT *, const char *, int32_t)
|
||||
#define WGT_SIG_INT_STR 20 // void fn(WidgetT *, int32_t, const char *)
|
||||
|
||||
// Property descriptor
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -150,6 +150,11 @@ int32_t platformMouseWheelPoll(void);
|
|||
// A very high value (e.g. 10000) effectively disables acceleration.
|
||||
void platformMouseSetAccel(int32_t threshold);
|
||||
|
||||
// Set the mickey-to-pixel ratio. Controls base cursor speed.
|
||||
// horizMickeys/vertMickeys: mickeys per 8 pixels of cursor movement.
|
||||
// Higher values = slower cursor. Default is typically 8 horiz, 16 vert.
|
||||
void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys);
|
||||
|
||||
// Move the mouse cursor to an absolute screen position. Uses INT 33h
|
||||
// function 04h on DOS. Used to clamp the cursor to window edges during
|
||||
// resize operations.
|
||||
|
|
|
|||
|
|
@ -1604,6 +1604,28 @@ void platformMouseSetAccel(int32_t threshold) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformMouseSetMickeys
|
||||
// ============================================================
|
||||
//
|
||||
// Sets the mickey-to-pixel ratio via INT 33h function 0Fh.
|
||||
// horizMickeys: number of mickeys per 8 pixels horizontally
|
||||
// vertMickeys: number of mickeys per 8 pixels vertically
|
||||
//
|
||||
// Higher values = slower cursor (more mickeys needed per pixel).
|
||||
// Default is typically 8 horizontal, 16 vertical (1:1 and 2:1).
|
||||
|
||||
void platformMouseSetMickeys(int32_t horizMickeys, int32_t vertMickeys) {
|
||||
__dpmi_regs r;
|
||||
|
||||
memset(&r, 0, sizeof(r));
|
||||
r.x.ax = 0x000F;
|
||||
r.x.cx = horizMickeys;
|
||||
r.x.dx = vertMickeys;
|
||||
__dpmi_int(0x33, &r);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformMousePoll
|
||||
// ============================================================
|
||||
|
|
@ -2407,6 +2429,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
|||
DXE_EXPORT(platformMouseInit)
|
||||
DXE_EXPORT(platformMousePoll)
|
||||
DXE_EXPORT(platformMouseSetAccel)
|
||||
DXE_EXPORT(platformMouseSetMickeys)
|
||||
DXE_EXPORT(platformMouseWarp)
|
||||
DXE_EXPORT(platformMouseWheelInit)
|
||||
DXE_EXPORT(platformMouseWheelPoll)
|
||||
|
|
|
|||
|
|
@ -487,6 +487,8 @@ img { max-width: 100%; }
|
|||
<li><a href="#ctrl.terminal">Terminal</a></li>
|
||||
<li><a href="#ctrl.terminal">ANSI Terminal</a></li>
|
||||
<li><a href="#ctrl.terminal">VT100</a></li>
|
||||
<li><a href="#ctrl.terminal">CommAttach</a></li>
|
||||
<li><a href="#ctrl.terminal">CommOpen</a></li>
|
||||
<li><a href="#ctrl.frame">Frame</a></li>
|
||||
<li><a href="#ctrl.frame">Container</a></li>
|
||||
<li><a href="#ctrl.hbox">HBox</a></li>
|
||||
|
|
@ -499,6 +501,8 @@ img { max-width: 100%; }
|
|||
<li><a href="#ctrl.picturebox">PictureBox</a></li>
|
||||
<li><a href="#ctrl.picturebox">Canvas</a></li>
|
||||
<li><a href="#ctrl.picturebox">Drawing</a></li>
|
||||
<li><a href="#ctrl.picturebox">SetPenColor</a></li>
|
||||
<li><a href="#ctrl.picturebox">SetPenSize</a></li>
|
||||
<li><a href="#ctrl.checkbox">CheckBox</a></li>
|
||||
<li><a href="#ctrl.checkbox">Value</a></li>
|
||||
<li><a href="#ctrl.combobox">ComboBox</a></li>
|
||||
|
|
@ -529,6 +533,8 @@ img { max-width: 100%; }
|
|||
<li><a href="#ctrl.listbox">RemoveItem</a></li>
|
||||
<li><a href="#ctrl.listview">ListView</a></li>
|
||||
<li><a href="#ctrl.listview">Multi-Column List</a></li>
|
||||
<li><a href="#ctrl.listview">SetColumns</a></li>
|
||||
<li><a href="#ctrl.listview">SetSort</a></li>
|
||||
<li><a href="#ctrl.progressbar">ProgressBar</a></li>
|
||||
<li><a href="#ctrl.optionbutton">OptionButton</a></li>
|
||||
<li><a href="#ctrl.optionbutton">Radio Button</a></li>
|
||||
|
|
@ -557,12 +563,20 @@ img { max-width: 100%; }
|
|||
<li><a href="#ctrl.textbox">DataSource</a></li>
|
||||
<li><a href="#ctrl.textbox">DataField</a></li>
|
||||
<li><a href="#ctrl.textarea">TextArea</a></li>
|
||||
<li><a href="#ctrl.textarea">FindNext</a></li>
|
||||
<li><a href="#ctrl.textarea">ReplaceAll</a></li>
|
||||
<li><a href="#ctrl.textarea">GoToLine</a></li>
|
||||
<li><a href="#ctrl.textarea">CursorLine</a></li>
|
||||
<li><a href="#ctrl.textarea">LineNumbers</a></li>
|
||||
<li><a href="#ctrl.textarea">AutoIndent</a></li>
|
||||
<li><a href="#ctrl.timer">Timer</a></li>
|
||||
<li><a href="#ctrl.timer">Interval</a></li>
|
||||
<li><a href="#ctrl.timer">Start</a></li>
|
||||
<li><a href="#ctrl.timer">Stop</a></li>
|
||||
<li><a href="#ctrl.toolbar">Toolbar</a></li>
|
||||
<li><a href="#ctrl.treeview">TreeView</a></li>
|
||||
<li><a href="#ctrl.treeview">AddItem</a></li>
|
||||
<li><a href="#ctrl.treeview">AddChildItem</a></li>
|
||||
<li><a href="#ctrl.wrapbox">WrapBox</a></li>
|
||||
<li><a href="#ctrl.wrapbox">Flow Layout</a></li>
|
||||
</ul>
|
||||
|
|
@ -2210,11 +2224,19 @@ End Sub</code></pre>
|
|||
Rows Integer Number of character rows (read-only).
|
||||
Scrollback Integer Number of scrollback lines (write-only).</pre>
|
||||
<h2>Type-Specific Methods</h2>
|
||||
<pre> Method Parameters Description
|
||||
------ --------------- -------------------------------------------
|
||||
Clear (none) Clear the terminal screen.
|
||||
Write Text As String Write text (with ANSI escape processing) to the terminal.</pre>
|
||||
<pre> Method Description
|
||||
------ -----------
|
||||
Clear Clear the terminal screen.
|
||||
Poll Process pending comm data and update the display.
|
||||
Write text$ Write text (with ANSI escape processing) to the terminal.</pre>
|
||||
<p>No default event.</p>
|
||||
<h2>Serial Communication</h2>
|
||||
<p>Use the comm.bas library to connect a Terminal to a serial link:</p>
|
||||
<pre><code>Dim link As Integer
|
||||
link = CommOpen(1, 115200)
|
||||
CommHandshake link
|
||||
CommAttach link, "Terminal1"</code></pre>
|
||||
<p>See comm.bas for the full communications API.</p>
|
||||
<p><a href="#ctrl.common.props">Common Properties, Events, and Methods</a></p>
|
||||
</div>
|
||||
<div class="topic" id="ctrl.frame">
|
||||
|
|
@ -2286,12 +2308,32 @@ End Sub</code></pre>
|
|||
<h1>PictureBox</h1>
|
||||
<p>VB Equivalent: PictureBox -- DVX Widget: canvas | Name Prefix: Picture</p>
|
||||
<p>A drawing surface (canvas). Supports drawing lines, rectangles, circles, text, and individual pixels. Can save and load BMP images. The default canvas size is 64x64 pixels.</p>
|
||||
<p>Colors are specified as 0x00RRGGBB integers (e.g. &HFF0000 for red).</p>
|
||||
<h2>Type-Specific Methods</h2>
|
||||
<pre> Method Parameters Description
|
||||
------ ---------------- -------------------------------------------
|
||||
Clear Color As Integer Fill the entire canvas with the specified color.</pre>
|
||||
<p>Additional drawing methods (DrawLine, DrawRect, FillRect, FillCircle, SetPixel, GetPixel, DrawText, Save, Load) are available through the C API but not currently exposed through BASIC interface descriptors.</p>
|
||||
<pre> Method Description
|
||||
------ -----------
|
||||
Clear color% Fill the entire canvas with the specified color.
|
||||
DrawLine x0%, y0%, x1%, y1%, color% Draw a line between two points.
|
||||
DrawRect x%, y%, w%, h%, color% Draw a rectangle outline.
|
||||
DrawText x%, y%, text$ Draw text at the given position.
|
||||
FillCircle cx%, cy%, radius%, color% Draw a filled circle.
|
||||
FillRect x%, y%, w%, h%, color% Draw a filled rectangle.
|
||||
GetPixel(x%, y%) Returns the color at a pixel.
|
||||
Load path$ Load a BMP image onto the canvas.
|
||||
Resize w%, h% Resize the canvas to new dimensions.
|
||||
Save path$ Save the canvas as a BMP file.
|
||||
SetPenColor color% Set the drawing color for subsequent operations.
|
||||
SetPenSize size% Set the pen/brush size in pixels.
|
||||
SetPixel x%, y%, color% Set the color of a single pixel.</pre>
|
||||
<p>Default Event: Click</p>
|
||||
<h2>Example</h2>
|
||||
<pre><code>Picture1.Resize 200, 150
|
||||
Picture1.Clear &HFFFFFF
|
||||
Picture1.SetPenSize 2
|
||||
Picture1.DrawRect 10, 10, 180, 130, &H000000
|
||||
Picture1.FillCircle 100, 75, 40, &HFF0000
|
||||
Picture1.DrawText 60, 130, "Hello!"
|
||||
Picture1.Save "output.bmp"</code></pre>
|
||||
<p><a href="#ctrl.common.props">Common Properties, Events, and Methods</a></p>
|
||||
</div>
|
||||
<div class="topic" id="ctrl.checkbox">
|
||||
|
|
@ -2472,7 +2514,8 @@ End</code></pre>
|
|||
SetMultiSelect Multi As Boolean Enable or disable multi-select mode.
|
||||
SetReorderable Reorderable As Boolean Enable or disable drag-to-reorder.
|
||||
IsItemSelected Index As Integer Returns True if the item at Index is selected.
|
||||
SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific item.</pre>
|
||||
SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific item.
|
||||
SetItems Items As String Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue").</pre>
|
||||
<p>Default Event: Click</p>
|
||||
<h2>Example</h2>
|
||||
<pre><code>Sub Form_Load ()
|
||||
|
|
@ -2495,21 +2538,35 @@ End Sub</code></pre>
|
|||
<div class="topic" id="ctrl.listview">
|
||||
<h1>ListView</h1>
|
||||
<p>VB Equivalent: ListView -- DVX Widget: listview</p>
|
||||
<p>A multi-column list with column headers. Supports sorting, multi-select, and drag-to-reorder. Columns are configured via the C API (SetColumns, SetData).</p>
|
||||
<p>A multi-column list with column headers. Supports sorting, multi-select, and drag-to-reorder.</p>
|
||||
<h2>Type-Specific Properties</h2>
|
||||
<pre> Property Type Description
|
||||
--------- ------- -------------------------------------------
|
||||
ListIndex Integer Index of the currently selected row (-1 = none).</pre>
|
||||
<pre> Property Type R/W Description
|
||||
--------- ------- --- -------------------------------------------
|
||||
ListIndex Integer R/W Index of the currently selected row (-1 = none).</pre>
|
||||
<h2>Type-Specific Methods</h2>
|
||||
<pre> Method Parameters Description
|
||||
--------------- --------------------------------------- -------------------------------------------
|
||||
SelectAll (none) Select all rows.
|
||||
ClearSelection (none) Deselect all rows.
|
||||
SetMultiSelect Multi As Boolean Enable or disable multi-select.
|
||||
SetReorderable Reorderable As Boolean Enable or disable row reordering.
|
||||
IsItemSelected Index As Integer Returns True if the row at Index is selected.
|
||||
SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific row.</pre>
|
||||
<pre> Method Description
|
||||
------ -----------
|
||||
AddItem text$ Add a row (sets first column text).
|
||||
Clear Remove all rows.
|
||||
ClearSelection Deselect all rows.
|
||||
GetCell$(row%, col%) Returns the text of a cell.
|
||||
IsItemSelected(index%) Returns True if the row is selected.
|
||||
RemoveItem index% Remove a row by index.
|
||||
RowCount() Returns the number of rows.
|
||||
SelectAll Select all rows.
|
||||
SetCell row%, col%, text$ Set the text of a cell.
|
||||
SetColumns spec$ Define columns. Pipe-delimited "Name,Width|Name,Width" (width in characters).
|
||||
SetItemSelected index%, selected Select or deselect a row.
|
||||
SetMultiSelect multi Enable or disable multi-select.
|
||||
SetReorderable reorderable Enable or disable row reordering.
|
||||
SetSort col%, dir% Sort by column. dir: 0=none, 1=ascending, 2=descending.</pre>
|
||||
<p>Default Event: Click</p>
|
||||
<h2>Example</h2>
|
||||
<pre><code>ListView1.SetColumns "Name,20|Size,10|Type,15"
|
||||
ListView1.AddItem "readme.txt"
|
||||
ListView1.SetCell 0, 1, "1024"
|
||||
ListView1.SetCell 0, 2, "Text"
|
||||
ListView1.SetSort 0, 1</code></pre>
|
||||
<p><a href="#ctrl.common.props">Common Properties, Events, and Methods</a></p>
|
||||
</div>
|
||||
<div class="topic" id="ctrl.progressbar">
|
||||
|
|
@ -2630,9 +2687,11 @@ End Sub</code></pre>
|
|||
-------- ------- -------------------------------------------
|
||||
TabIndex Integer Index of the active tab (0-based). Note: this property name collides with the common VB-compatibility TabIndex property, which shadows it at runtime. Use the SetActive method instead to switch tabs.</pre>
|
||||
<h2>Type-Specific Methods</h2>
|
||||
<pre> Method Parameters Description
|
||||
--------- ----------------- -------------------------------------------
|
||||
SetActive Index As Integer Switch to the tab at the given index. This is the recommended way to change tabs at runtime (the TabIndex property is shadowed by the common property handler).</pre>
|
||||
<pre> Method Description
|
||||
------ -----------
|
||||
AddPage title$ Add a new tab page with the given title.
|
||||
GetActive() Returns the index of the active tab.
|
||||
SetActive index% Switch to the tab at the given index. This is the recommended way to change tabs at runtime (the TabIndex property is shadowed by the common property handler).</pre>
|
||||
<p>Container: Yes</p>
|
||||
<p>Default Event: Click</p>
|
||||
<blockquote><strong>Warning:</strong> The TabIndex property is shadowed by the common property handler at runtime. Use the SetActive method to change tabs programmatically.</blockquote>
|
||||
|
|
@ -2663,12 +2722,41 @@ End Sub</code></pre>
|
|||
<div class="topic" id="ctrl.textarea">
|
||||
<h1>TextArea</h1>
|
||||
<p>VB Equivalent: TextArea (DVX extension) -- DVX Widget: textarea (multi-line text input, max 4096 chars)</p>
|
||||
<p>A multi-line text editing area. This is a DVX extension with no direct VB3 equivalent (VB uses a TextBox with MultiLine=True). Supports syntax colorization, line numbers, auto-indent, and find/replace via the C API.</p>
|
||||
<p>A multi-line text editing area. This is a DVX extension with no direct VB3 equivalent (VB uses a TextBox with MultiLine=True). Supports line numbers, auto-indent, find/replace, and tab configuration.</p>
|
||||
<h2>Type-Specific Properties</h2>
|
||||
<pre> Property Type Description
|
||||
-------- ------ -------------------------------------------
|
||||
Text String The full text content.</pre>
|
||||
<pre> Property Type R/W Description
|
||||
---------- ------- --- -------------------------------------------
|
||||
Text String R/W The full text content.
|
||||
CursorLine Integer R Current cursor line number (0-based).</pre>
|
||||
<h2>Type-Specific Methods</h2>
|
||||
<pre> Method Description
|
||||
------ -----------
|
||||
FindNext needle$, caseSensitive, forward Search for text. Returns True if found.
|
||||
GetWordAtCursor() Returns the word under the cursor.
|
||||
GoToLine line% Scroll to and position cursor at the given line.
|
||||
ReplaceAll needle$, replacement$, caseSensitive Replace all occurrences. Returns count of replacements.
|
||||
SetAutoIndent enabled Enable or disable automatic indentation.
|
||||
SetCaptureTabs enabled When True, Tab key inserts a tab/spaces instead of moving focus.
|
||||
SetShowLineNumbers show Show or hide the line number gutter.
|
||||
SetSyntaxMode mode$ Activate built-in syntax highlighting. Modes: "dhs" (help source), "bas" (BASIC). Pass "" to disable.
|
||||
SetTabWidth width% Set the tab stop width in characters.
|
||||
SetUseTabChar useChar When True, Tab inserts a tab character; when False, inserts spaces.</pre>
|
||||
<p>Default Event: Change</p>
|
||||
<h2>Example</h2>
|
||||
<pre><code>TextArea1.SetShowLineNumbers True
|
||||
TextArea1.SetAutoIndent True
|
||||
TextArea1.SetTabWidth 4
|
||||
TextArea1.SetCaptureTabs True
|
||||
|
||||
' Search for text
|
||||
If TextArea1.FindNext("TODO", False, True) Then
|
||||
Print "Found at line"; TextArea1.CursorLine
|
||||
End If
|
||||
|
||||
' Replace all occurrences
|
||||
Dim count As Integer
|
||||
count = TextArea1.ReplaceAll("old", "new", False)
|
||||
Print "Replaced"; count; "occurrences"</code></pre>
|
||||
<p><a href="#ctrl.common.props">Common Properties, Events, and Methods</a></p>
|
||||
</div>
|
||||
<div class="topic" id="ctrl.timer">
|
||||
|
|
@ -2715,13 +2803,30 @@ End Sub</code></pre>
|
|||
<div class="topic" id="ctrl.treeview">
|
||||
<h1>TreeView</h1>
|
||||
<p>VB Equivalent: TreeView -- DVX Widget: treeview</p>
|
||||
<p>A hierarchical tree of expandable/collapsible nodes. Nodes are created via the C API (wgtTreeItem). Supports multi-select and drag-to-reorder.</p>
|
||||
<p>A hierarchical tree of expandable/collapsible nodes. Supports multi-select and drag-to-reorder. Items are accessed by depth-first index (0-based).</p>
|
||||
<h2>Type-Specific Methods</h2>
|
||||
<pre> Method Parameters Description
|
||||
-------------- ---------------------- -------------------------------------------
|
||||
SetMultiSelect Multi As Boolean Enable or disable multi-select mode.
|
||||
SetReorderable Reorderable As Boolean Enable or disable node reordering.</pre>
|
||||
<pre> Method Description
|
||||
------ -----------
|
||||
AddChildItem parentIndex%, text$ Add a child node under the node at the given index.
|
||||
AddItem text$ Add a root-level node.
|
||||
Clear Remove all nodes.
|
||||
GetItemText$(index%) Returns the text of the node at the given index.
|
||||
IsExpanded(index%) Returns True if the node at the given index is expanded.
|
||||
IsItemSelected(index%) Returns True if the node at the given index is selected.
|
||||
ItemCount() Returns the total number of nodes (all levels).
|
||||
SetExpanded index%, expanded Expand or collapse the node at the given index.
|
||||
SetItemSelected index%, selected Select or deselect the node at the given index.
|
||||
SetMultiSelect multi Enable or disable multi-select mode.
|
||||
SetReorderable reorderable Enable or disable node reordering.</pre>
|
||||
<p>Default Event: Click</p>
|
||||
<h2>Example</h2>
|
||||
<pre><code>TreeView1.AddItem "Animals"
|
||||
TreeView1.AddChildItem 0, "Cat"
|
||||
TreeView1.AddChildItem 0, "Dog"
|
||||
TreeView1.AddItem "Plants"
|
||||
TreeView1.AddChildItem 3, "Oak"
|
||||
TreeView1.SetExpanded 0, True
|
||||
Print "Items:"; TreeView1.ItemCount()</code></pre>
|
||||
<p><a href="#ctrl.common.props">Common Properties, Events, and Methods</a></p>
|
||||
</div>
|
||||
<div class="topic" id="ctrl.wrapbox">
|
||||
|
|
|
|||
128
sdk/include/basic/comm.bas
Normal file
128
sdk/include/basic/comm.bas
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
' comm.bas -- Serial Communications Library for DVX BASIC
|
||||
'
|
||||
' Three tiers of serial communication:
|
||||
'
|
||||
' 1. Raw serial (SerXxx) -- direct UART I/O with ISR-driven ring
|
||||
' buffers. No framing or error correction. Use this for talking
|
||||
' to existing BBSs, modems, and other plain serial devices.
|
||||
'
|
||||
' 2. Packet serial (CommXxx) -- adds HDLC framing, CRC-16 checksums,
|
||||
' and Go-Back-N ARQ for reliable delivery. Data is split into
|
||||
' packets and retransmitted on error. Requires both sides to
|
||||
' speak the same protocol.
|
||||
'
|
||||
' 3. Encrypted serial (CommHandshake + encrypt flag) -- adds DH key
|
||||
' exchange and XTEA-CTR encryption on top of packets. Use
|
||||
' CommSend with encrypt=True after CommHandshake.
|
||||
'
|
||||
' Usage: Add this file to your project.
|
||||
|
||||
' UART types (returned by SerGetUart)
|
||||
CONST SER_UART_NONE = 0
|
||||
CONST SER_UART_8250 = 1
|
||||
CONST SER_UART_16450 = 2
|
||||
CONST SER_UART_16550 = 3
|
||||
CONST SER_UART_16550A = 4
|
||||
|
||||
' Handshake modes (for SerOpen / CommOpen)
|
||||
CONST SER_HANDSHAKE_NONE = 0
|
||||
CONST SER_HANDSHAKE_XONXOFF = 1
|
||||
CONST SER_HANDSHAKE_RTSCTS = 2
|
||||
CONST SER_HANDSHAKE_DTRDSR = 3
|
||||
|
||||
DECLARE LIBRARY "basrt"
|
||||
|
||||
' =========================================================
|
||||
' UART Detection and Configuration
|
||||
' =========================================================
|
||||
|
||||
' Detect the UART type for a COM port (1-4) WITHOUT opening it.
|
||||
DECLARE FUNCTION SerGetUart(BYVAL com AS INTEGER) AS INTEGER
|
||||
|
||||
' Set the I/O base address for a COM port before opening.
|
||||
' Default bases: COM1=&H3F8, COM2=&H2F8, COM3=&H3E8, COM4=&H2E8
|
||||
DECLARE SUB SerSetBase(BYVAL com AS INTEGER, BYVAL base AS INTEGER)
|
||||
|
||||
' Set the IRQ for a COM port before opening.
|
||||
' Default IRQs: COM1=4, COM2=3, COM3=4, COM4=3
|
||||
DECLARE SUB SerSetIrq(BYVAL com AS INTEGER, BYVAL irq AS INTEGER)
|
||||
|
||||
' Get the I/O base address for a COM port.
|
||||
DECLARE FUNCTION SerGetBase(BYVAL com AS INTEGER) AS INTEGER
|
||||
|
||||
' Get the IRQ for a COM port.
|
||||
DECLARE FUNCTION SerGetIrq(BYVAL com AS INTEGER) AS INTEGER
|
||||
|
||||
' =========================================================
|
||||
' Raw Serial I/O (no framing, no error correction)
|
||||
' =========================================================
|
||||
|
||||
' Open a raw serial port. Returns True on success.
|
||||
DECLARE FUNCTION SerOpen(BYVAL com AS INTEGER, BYVAL baud AS LONG, BYVAL dataBits AS INTEGER, BYVAL parity AS STRING, BYVAL stopBits AS INTEGER, BYVAL handshake AS INTEGER) AS INTEGER
|
||||
|
||||
' Close a raw serial port.
|
||||
DECLARE SUB SerClose(BYVAL com AS INTEGER)
|
||||
|
||||
' Write a string to the serial port. Returns True on success.
|
||||
DECLARE FUNCTION SerWrite(BYVAL com AS INTEGER, BYVAL data AS STRING) AS INTEGER
|
||||
|
||||
' Read pending data from the serial port. Returns "" if none available.
|
||||
DECLARE FUNCTION SerRead$(BYVAL com AS INTEGER) AS STRING
|
||||
|
||||
' Return the number of bytes waiting in the receive buffer.
|
||||
DECLARE FUNCTION SerAvailable(BYVAL com AS INTEGER) AS INTEGER
|
||||
|
||||
' Clear the receive buffer.
|
||||
DECLARE SUB SerFlush(BYVAL com AS INTEGER)
|
||||
|
||||
' Attach a raw serial port to a Terminal widget.
|
||||
DECLARE SUB SerAttach(BYVAL com AS INTEGER, BYVAL termCtrlName AS STRING)
|
||||
|
||||
' Detach a raw serial port from a Terminal widget.
|
||||
DECLARE SUB SerDetach(BYVAL com AS INTEGER)
|
||||
|
||||
' =========================================================
|
||||
' Packet Serial (reliable delivery + channel multiplexing)
|
||||
' =========================================================
|
||||
|
||||
' Open a packet serial link. Returns a handle (>0), or 0 on failure.
|
||||
DECLARE FUNCTION CommOpen(BYVAL com AS INTEGER, BYVAL baud AS LONG, BYVAL dataBits AS INTEGER, BYVAL parity AS STRING, BYVAL stopBits AS INTEGER, BYVAL handshake AS INTEGER) AS INTEGER
|
||||
|
||||
' Close a packet serial link.
|
||||
DECLARE SUB CommClose(BYVAL handle AS INTEGER)
|
||||
|
||||
' Send data on a logical channel (0-127).
|
||||
' encrypt: True to encrypt (requires CommHandshake first).
|
||||
DECLARE FUNCTION CommSend(BYVAL handle AS INTEGER, BYVAL data AS STRING, BYVAL channel AS INTEGER, BYVAL encrypt AS INTEGER) AS INTEGER
|
||||
|
||||
' Receive pending data from a specific channel. Returns "" if none.
|
||||
DECLARE FUNCTION CommRecv$(BYVAL handle AS INTEGER, BYVAL channel AS INTEGER) AS STRING
|
||||
|
||||
' Poll for incoming packets. Call periodically if not using CommAttach.
|
||||
DECLARE FUNCTION CommPoll(BYVAL handle AS INTEGER) AS INTEGER
|
||||
|
||||
' Return the number of unacknowledged packets in the transmit window.
|
||||
DECLARE FUNCTION CommPending(BYVAL handle AS INTEGER) AS INTEGER
|
||||
|
||||
' =========================================================
|
||||
' Encryption (DH key exchange + XTEA-CTR)
|
||||
' =========================================================
|
||||
|
||||
' Perform Diffie-Hellman key exchange. Blocks until both sides
|
||||
' complete. After this, CommSend with encrypt=True will encrypt.
|
||||
DECLARE FUNCTION CommHandshake(BYVAL handle AS INTEGER) AS INTEGER
|
||||
|
||||
' Check if the key exchange is complete.
|
||||
DECLARE FUNCTION CommIsReady(BYVAL handle AS INTEGER) AS INTEGER
|
||||
|
||||
' =========================================================
|
||||
' Terminal Widget Integration
|
||||
' =========================================================
|
||||
|
||||
' Attach a packet link to a Terminal widget on a given channel.
|
||||
' encrypt: True to encrypt terminal traffic.
|
||||
DECLARE SUB CommAttach(BYVAL handle AS INTEGER, BYVAL termCtrlName AS STRING, BYVAL channel AS INTEGER, BYVAL encrypt AS INTEGER)
|
||||
|
||||
' Detach a packet link from a Terminal widget.
|
||||
DECLARE SUB CommDetach(BYVAL handle AS INTEGER)
|
||||
END DECLARE
|
||||
|
|
@ -4,14 +4,7 @@
|
|||
' Wrappers handle the AppContextT parameter internally so BASIC
|
||||
' programs just pass the visible arguments.
|
||||
'
|
||||
' Usage:
|
||||
' '$INCLUDE: 'commdlg.bas'
|
||||
'
|
||||
' DIM path AS STRING
|
||||
' path = basFileOpen("Open Image", "*.bmp")
|
||||
' IF path <> "" THEN
|
||||
' ' ... use the file ...
|
||||
' END IF
|
||||
' Usage: Add this file to your project.
|
||||
|
||||
DECLARE LIBRARY "basrt"
|
||||
' Show a file Open dialog. Returns selected path, or "" if cancelled.
|
||||
|
|
|
|||
15
sdk/include/basic/help.bas
Normal file
15
sdk/include/basic/help.bas
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
' help.bas -- Help System Library for DVX BASIC
|
||||
'
|
||||
' Provides access to the DVX help compiler and viewer.
|
||||
' Compile .dhs source files to .hlp and open them in the viewer.
|
||||
'
|
||||
' Usage: Add this file to your project.
|
||||
|
||||
DECLARE LIBRARY "basrt"
|
||||
' Compile a .dhs source file to a binary .hlp file.
|
||||
' Returns True on success.
|
||||
DECLARE FUNCTION HelpCompile(BYVAL inputFile AS STRING, BYVAL outputFile AS STRING) AS INTEGER
|
||||
|
||||
' Open a .hlp file in the DVX Help Viewer.
|
||||
DECLARE SUB HelpView(BYVAL hlpFile AS STRING)
|
||||
END DECLARE
|
||||
53
sdk/include/basic/sql.bas
Normal file
53
sdk/include/basic/sql.bas
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
' sql.bas -- SQL Database Library for DVX BASIC
|
||||
'
|
||||
' Provides SQLite database access through DECLARE LIBRARY wrappers.
|
||||
' Handles and cursors are integers; strings are passed by value.
|
||||
'
|
||||
' Usage: Add this file to your project.
|
||||
|
||||
DECLARE LIBRARY "basrt"
|
||||
' Open a database file. Returns a handle (>0), or 0 on failure.
|
||||
DECLARE FUNCTION SQLOpen(BYVAL path AS STRING) AS INTEGER
|
||||
|
||||
' Close a database.
|
||||
DECLARE SUB SQLClose(BYVAL db AS INTEGER)
|
||||
|
||||
' Execute a non-query SQL statement. Returns True on success.
|
||||
DECLARE FUNCTION SQLExec(BYVAL db AS INTEGER, BYVAL sql AS STRING) AS INTEGER
|
||||
|
||||
' Return the last error message for a database.
|
||||
DECLARE FUNCTION SQLError$(BYVAL db AS INTEGER) AS STRING
|
||||
|
||||
' Execute a query and return a result set cursor.
|
||||
DECLARE FUNCTION SQLQuery(BYVAL db AS INTEGER, BYVAL sql AS STRING) AS INTEGER
|
||||
|
||||
' Advance the cursor to the next row. Returns True if a row is available.
|
||||
DECLARE FUNCTION SQLNext(BYVAL rs AS INTEGER) AS INTEGER
|
||||
|
||||
' Test whether the cursor is past the last row.
|
||||
DECLARE FUNCTION SQLEof(BYVAL rs AS INTEGER) AS INTEGER
|
||||
|
||||
' Return the number of columns in a result set.
|
||||
DECLARE FUNCTION SQLFieldCount(BYVAL rs AS INTEGER) AS INTEGER
|
||||
|
||||
' Return a column name by index (0-based).
|
||||
DECLARE FUNCTION SQLFieldName$(BYVAL rs AS INTEGER, BYVAL col AS INTEGER) AS STRING
|
||||
|
||||
' Return a column value as text by index (0-based).
|
||||
DECLARE FUNCTION SQLFieldText$(BYVAL rs AS INTEGER, BYVAL col AS INTEGER) AS STRING
|
||||
|
||||
' Return a column value as text by column name.
|
||||
DECLARE FUNCTION SQLField$(BYVAL rs AS INTEGER, BYVAL colName AS STRING) AS STRING
|
||||
|
||||
' Return a column value as an integer by index (0-based).
|
||||
DECLARE FUNCTION SQLFieldInt(BYVAL rs AS INTEGER, BYVAL col AS INTEGER) AS INTEGER
|
||||
|
||||
' Return a column value as a double by index (0-based).
|
||||
DECLARE FUNCTION SQLFieldDbl(BYVAL rs AS INTEGER, BYVAL col AS INTEGER) AS DOUBLE
|
||||
|
||||
' Free a result set cursor.
|
||||
DECLARE SUB SQLFreeResult(BYVAL rs AS INTEGER)
|
||||
|
||||
' Return the number of rows affected by the last INSERT/UPDATE/DELETE.
|
||||
DECLARE FUNCTION SQLAffected(BYVAL db AS INTEGER) AS INTEGER
|
||||
END DECLARE
|
||||
156
sdk/samples/basic/commdlg.frm
Normal file
156
sdk/samples/basic/commdlg.frm
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
VERSION DVX 1.00
|
||||
' commdlg.frm -- Common Dialog Library Demo
|
||||
'
|
||||
' Demonstrates every function in the commdlg.bas include file:
|
||||
' basFileOpen - File Open dialog
|
||||
' basFileSave - File Save dialog
|
||||
' basInputBox2 - Text input dialog
|
||||
' basChoiceDialog - Choice list dialog
|
||||
' basIntInput - Integer spinner dialog
|
||||
' basPromptSave - Yes/No/Cancel save prompt
|
||||
'
|
||||
' Add commdlg.bas to your project, then click Run.
|
||||
|
||||
Begin Form CommDlgDemo
|
||||
Caption = "Common Dialogs Demo"
|
||||
Layout = VBox
|
||||
AutoSize = True
|
||||
Resizable = False
|
||||
Centered = True
|
||||
Begin Label LblTitle
|
||||
Caption = "Common Dialog Demonstrations"
|
||||
Weight = 0
|
||||
End
|
||||
Begin Line Line1
|
||||
Weight = 0
|
||||
End
|
||||
Begin CommandButton BtnFileOpen
|
||||
Caption = "File Open..."
|
||||
Weight = 0
|
||||
End
|
||||
Begin CommandButton BtnFileSave
|
||||
Caption = "File Save..."
|
||||
Weight = 0
|
||||
End
|
||||
Begin CommandButton BtnInputBox
|
||||
Caption = "Input Box..."
|
||||
Weight = 0
|
||||
End
|
||||
Begin CommandButton BtnChoice
|
||||
Caption = "Choice Dialog..."
|
||||
Weight = 0
|
||||
End
|
||||
Begin CommandButton BtnIntInput
|
||||
Caption = "Integer Input..."
|
||||
Weight = 0
|
||||
End
|
||||
Begin CommandButton BtnPromptSave
|
||||
Caption = "Prompt Save..."
|
||||
Weight = 0
|
||||
End
|
||||
Begin Line Line2
|
||||
Weight = 0
|
||||
End
|
||||
Begin Label LblResult
|
||||
Caption = "Result will appear here."
|
||||
Weight = 0
|
||||
End
|
||||
End
|
||||
|
||||
Load CommDlgDemo
|
||||
CommDlgDemo.Show
|
||||
|
||||
PRINT "Common Dialog Demo started."
|
||||
|
||||
DO
|
||||
DoEvents
|
||||
LOOP
|
||||
|
||||
|
||||
SUB BtnFileOpen_Click
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open a File", "*.bas")
|
||||
IF path <> "" THEN
|
||||
LblResult.Caption = "Opened: " + path
|
||||
PRINT "File Open: " + path
|
||||
ELSE
|
||||
LblResult.Caption = "File Open cancelled."
|
||||
PRINT "File Open cancelled."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnFileSave_Click
|
||||
DIM path AS STRING
|
||||
path = basFileSave("Save a File", "*.txt")
|
||||
IF path <> "" THEN
|
||||
LblResult.Caption = "Save to: " + path
|
||||
PRINT "File Save: " + path
|
||||
ELSE
|
||||
LblResult.Caption = "File Save cancelled."
|
||||
PRINT "File Save cancelled."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnInputBox_Click
|
||||
DIM text AS STRING
|
||||
text = basInputBox2("Text Input", "Enter your name:", "World")
|
||||
IF text <> "" THEN
|
||||
LblResult.Caption = "Hello, " + text + "!"
|
||||
PRINT "Input: " + text
|
||||
ELSE
|
||||
LblResult.Caption = "Input Box cancelled."
|
||||
PRINT "Input Box cancelled."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnChoice_Click
|
||||
DIM idx AS INTEGER
|
||||
idx = basChoiceDialog("Pick a Color", "Choose your favorite color:", "Red|Orange|Yellow|Green|Blue|Indigo|Violet", 0)
|
||||
IF idx >= 0 THEN
|
||||
DIM colors(6) AS STRING
|
||||
colors(0) = "Red"
|
||||
colors(1) = "Orange"
|
||||
colors(2) = "Yellow"
|
||||
colors(3) = "Green"
|
||||
colors(4) = "Blue"
|
||||
colors(5) = "Indigo"
|
||||
colors(6) = "Violet"
|
||||
LblResult.Caption = "You chose: " + colors(idx)
|
||||
PRINT "Choice: " + colors(idx) + " (index" + STR$(idx) + ")"
|
||||
ELSE
|
||||
LblResult.Caption = "Choice Dialog cancelled."
|
||||
PRINT "Choice Dialog cancelled."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnIntInput_Click
|
||||
DIM val AS INTEGER
|
||||
val = basIntInput("Pick a Number", "Enter a value (1-100):", 50, 1, 100)
|
||||
LblResult.Caption = "You entered:" + STR$(val)
|
||||
PRINT "Integer Input:" + STR$(val)
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnPromptSave_Click
|
||||
DIM result AS INTEGER
|
||||
result = basPromptSave("Unsaved Changes")
|
||||
IF result = DVX_SAVE_YES THEN
|
||||
LblResult.Caption = "You chose: Yes (save)"
|
||||
PRINT "Prompt Save: Yes"
|
||||
ELSEIF result = DVX_SAVE_NO THEN
|
||||
LblResult.Caption = "You chose: No (discard)"
|
||||
PRINT "Prompt Save: No"
|
||||
ELSE
|
||||
LblResult.Caption = "You chose: Cancel"
|
||||
PRINT "Prompt Save: Cancel"
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB CommDlgDemo_Load
|
||||
PRINT "Form loaded. Click any button to try a dialog."
|
||||
END SUB
|
||||
81
sdk/samples/basic/dynform.bas
Normal file
81
sdk/samples/basic/dynform.bas
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
' dynform.bas -- Dynamic Form Demo
|
||||
'
|
||||
' Demonstrates building a complete form entirely in code
|
||||
' with no .frm file needed:
|
||||
' CreateForm - create a form programmatically
|
||||
' CreateControl - add controls with optional parent
|
||||
' SetEvent - bind events to arbitrary handlers
|
||||
' RemoveControl - remove a control at runtime
|
||||
' Optional params - language extension for optional arguments
|
||||
'
|
||||
' Add commdlg.bas to your project, then click Run.
|
||||
|
||||
DIM frm AS LONG
|
||||
DIM lbl AS LONG
|
||||
DIM btnBar AS LONG
|
||||
|
||||
' Create a form entirely from code
|
||||
SET frm = CreateForm("DynDemo", 350, 200)
|
||||
frm.Caption = "Dynamic Form Demo"
|
||||
|
||||
' Add a label at the top
|
||||
SET lbl = CreateControl(frm, "Label", "StatusLabel")
|
||||
StatusLabel.Caption = "Built entirely in code!"
|
||||
|
||||
' Add a horizontal button bar container
|
||||
SET btnBar = CreateControl(frm, "HBox", "ButtonBar")
|
||||
|
||||
' Add buttons inside the HBox, using the optional parent parameter
|
||||
DIM btnHello AS LONG
|
||||
SET btnHello = CreateControl(frm, "CommandButton", "BtnHello", btnBar)
|
||||
BtnHello.Caption = "Hello"
|
||||
|
||||
DIM btnFile AS LONG
|
||||
SET btnFile = CreateControl(frm, "CommandButton", "BtnFile", btnBar)
|
||||
BtnFile.Caption = "Open File..."
|
||||
|
||||
DIM btnRemove AS LONG
|
||||
SET btnRemove = CreateControl(frm, "CommandButton", "BtnRemove", btnBar)
|
||||
BtnRemove.Caption = "Remove Me"
|
||||
|
||||
' Wire events to custom handler names using SetEvent
|
||||
SetEvent btnHello, "Click", "OnHelloClick"
|
||||
SetEvent btnFile, "Click", "OnFileClick"
|
||||
SetEvent btnRemove, "Click", "OnRemoveClick"
|
||||
|
||||
frm.Show
|
||||
|
||||
PRINT "Dynamic form created. Try the buttons!"
|
||||
|
||||
DO
|
||||
DoEvents
|
||||
LOOP
|
||||
|
||||
|
||||
SUB OnHelloClick
|
||||
DIM name AS STRING
|
||||
name = basInputBox2("Greeting", "What is your name?", "World")
|
||||
IF name <> "" THEN
|
||||
StatusLabel.Caption = "Hello, " + name + "!"
|
||||
PRINT "Greeted: " + name
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB OnFileClick
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open a File", "*.*")
|
||||
IF path <> "" THEN
|
||||
StatusLabel.Caption = "Selected: " + path
|
||||
PRINT "File: " + path
|
||||
ELSE
|
||||
StatusLabel.Caption = "No file selected."
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB OnRemoveClick
|
||||
StatusLabel.Caption = "Button removed!"
|
||||
RemoveControl frm, "BtnRemove"
|
||||
PRINT "BtnRemove removed from form."
|
||||
END SUB
|
||||
205
sdk/samples/basic/helpedit.frm
Normal file
205
sdk/samples/basic/helpedit.frm
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
VERSION DVX 1.00
|
||||
' helpedit.frm -- DVX Help Editor
|
||||
'
|
||||
' A .dhs help source editor with syntax highlighting and
|
||||
' live preview via the DVX Help Viewer.
|
||||
'
|
||||
' Add commdlg.bas and help.bas to your project, then click Run.
|
||||
|
||||
Begin Form HelpEdit
|
||||
Caption = "DVX Help Editor"
|
||||
Layout = VBox
|
||||
AutoSize = False
|
||||
Resizable = True
|
||||
Centered = True
|
||||
Width = 640
|
||||
Height = 440
|
||||
Begin HBox ToolBar
|
||||
Weight = 0
|
||||
Begin CommandButton BtnNew
|
||||
Caption = "New"
|
||||
MinWidth = 60
|
||||
End
|
||||
Begin CommandButton BtnOpen
|
||||
Caption = "Open"
|
||||
MinWidth = 60
|
||||
End
|
||||
Begin CommandButton BtnSave
|
||||
Caption = "Save"
|
||||
MinWidth = 60
|
||||
End
|
||||
Begin Spacer Spc1
|
||||
MinWidth = 16
|
||||
End
|
||||
Begin CommandButton BtnCompile
|
||||
Caption = "Compile"
|
||||
MinWidth = 80
|
||||
End
|
||||
Begin CommandButton BtnPreview
|
||||
Caption = "Preview"
|
||||
MinWidth = 80
|
||||
End
|
||||
Begin Spacer Spc2
|
||||
Weight = 1
|
||||
End
|
||||
Begin Label LblStatus
|
||||
Caption = "Ready."
|
||||
MinWidth = 150
|
||||
End
|
||||
End
|
||||
Begin TextArea Editor
|
||||
Weight = 1
|
||||
End
|
||||
End
|
||||
|
||||
DIM currentFile AS STRING
|
||||
DIM hlpFile AS STRING
|
||||
currentFile = ""
|
||||
hlpFile = ""
|
||||
|
||||
Load HelpEdit
|
||||
HelpEdit.Show
|
||||
|
||||
' Configure the editor
|
||||
Editor.SetSyntaxMode "dhs"
|
||||
Editor.SetShowLineNumbers True
|
||||
Editor.SetAutoIndent True
|
||||
Editor.SetCaptureTabs True
|
||||
Editor.SetTabWidth 2
|
||||
|
||||
' Start with a template
|
||||
Editor.Text = ".topic intro" + CHR$(10) + ".title My Help File" + CHR$(10) + ".toc 0 My Help File" + CHR$(10) + ".default" + CHR$(10) + CHR$(10) + ".h1 Welcome" + CHR$(10) + CHR$(10) + "This is your help file. Edit the .dhs source here," + CHR$(10) + "then click Compile and Preview to see the result." + CHR$(10)
|
||||
|
||||
LblStatus.Caption = "New file."
|
||||
|
||||
DO
|
||||
DoEvents
|
||||
LOOP
|
||||
|
||||
|
||||
SUB BtnNew_Click
|
||||
currentFile = ""
|
||||
hlpFile = ""
|
||||
Editor.Text = ".topic intro" + CHR$(10) + ".title My Help File" + CHR$(10) + ".toc 0 My Help File" + CHR$(10) + ".default" + CHR$(10) + CHR$(10) + ".h1 Welcome" + CHR$(10) + CHR$(10)
|
||||
LblStatus.Caption = "New file."
|
||||
HelpEdit.Caption = "DVX Help Editor"
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnOpen_Click
|
||||
DIM path AS STRING
|
||||
path = basFileOpen("Open Help Source", "*.dhs")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
' Read the entire file
|
||||
DIM text AS STRING
|
||||
DIM line AS STRING
|
||||
text = ""
|
||||
|
||||
OPEN path FOR INPUT AS #1
|
||||
DO WHILE NOT EOF(1)
|
||||
LINE INPUT #1, line
|
||||
IF text <> "" THEN
|
||||
text = text + CHR$(10)
|
||||
END IF
|
||||
text = text + line
|
||||
LOOP
|
||||
CLOSE #1
|
||||
|
||||
Editor.Text = text
|
||||
currentFile = path
|
||||
hlpFile = ""
|
||||
LblStatus.Caption = path
|
||||
HelpEdit.Caption = "DVX Help Editor - " + path
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnSave_Click
|
||||
DIM path AS STRING
|
||||
|
||||
IF currentFile <> "" THEN
|
||||
path = currentFile
|
||||
ELSE
|
||||
path = basFileSave("Save Help Source", "*.dhs")
|
||||
IF path = "" THEN
|
||||
EXIT SUB
|
||||
END IF
|
||||
END IF
|
||||
|
||||
' Write the editor contents to file
|
||||
OPEN path FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
|
||||
currentFile = path
|
||||
LblStatus.Caption = "Saved: " + path
|
||||
HelpEdit.Caption = "DVX Help Editor - " + path
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnCompile_Click
|
||||
' Save first if we have a file
|
||||
IF currentFile = "" THEN
|
||||
DIM path AS STRING
|
||||
path = basFileSave("Save Help Source", "*.dhs")
|
||||
IF path = "" THEN
|
||||
LblStatus.Caption = "Save cancelled."
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
OPEN path FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
currentFile = path
|
||||
ELSE
|
||||
OPEN currentFile FOR OUTPUT AS #1
|
||||
PRINT #1, Editor.Text
|
||||
CLOSE #1
|
||||
END IF
|
||||
|
||||
' Derive .hlp filename from .dhs filename
|
||||
DIM baseName AS STRING
|
||||
baseName = currentFile
|
||||
|
||||
' Strip .dhs extension if present
|
||||
IF LEN(baseName) > 4 THEN
|
||||
IF UCASE$(RIGHT$(baseName, 4)) = ".DHS" THEN
|
||||
baseName = LEFT$(baseName, LEN(baseName) - 4)
|
||||
END IF
|
||||
END IF
|
||||
|
||||
hlpFile = baseName + ".hlp"
|
||||
|
||||
LblStatus.Caption = "Compiling..."
|
||||
DoEvents
|
||||
|
||||
IF HelpCompile(currentFile, hlpFile) THEN
|
||||
LblStatus.Caption = "Compiled: " + hlpFile
|
||||
ELSE
|
||||
LblStatus.Caption = "Compile failed!"
|
||||
hlpFile = ""
|
||||
END IF
|
||||
END SUB
|
||||
|
||||
|
||||
SUB BtnPreview_Click
|
||||
IF hlpFile = "" THEN
|
||||
' Try compiling first
|
||||
BtnCompile_Click
|
||||
END IF
|
||||
|
||||
IF hlpFile = "" THEN
|
||||
LblStatus.Caption = "Nothing to preview. Compile first."
|
||||
EXIT SUB
|
||||
END IF
|
||||
|
||||
LblStatus.Caption = "Opening viewer..."
|
||||
HelpView hlpFile
|
||||
END SUB
|
||||
|
||||
|
||||
SUB HelpEdit_QueryUnload(Cancel AS INTEGER)
|
||||
' Could prompt to save here
|
||||
END SUB
|
||||
72
sdk/samples/basic/sqltest.bas
Normal file
72
sdk/samples/basic/sqltest.bas
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
' sqltest.bas -- SQL Database Demo
|
||||
'
|
||||
' Demonstrates the SQL include library:
|
||||
' SQLOpen / SQLClose - open and close a database
|
||||
' SQLExec - execute non-query statements
|
||||
' SQLQuery / SQLNext - run a query and iterate rows
|
||||
' SQLField$ / SQLFieldInt / SQLFieldDbl - read column values
|
||||
' SQLFieldCount / SQLFieldName$ - inspect result schema
|
||||
' SQLEof / SQLFreeResult / SQLAffected - cursor and status
|
||||
' SQLError$ - error reporting
|
||||
'
|
||||
' Add sql.bas to your project, then click Run.
|
||||
|
||||
DIM db AS LONG
|
||||
DIM rs AS LONG
|
||||
|
||||
PRINT "Opening database..."
|
||||
db = SQLOpen("sqltest.db")
|
||||
|
||||
IF db = 0 THEN
|
||||
PRINT "Failed to open database!"
|
||||
END
|
||||
END IF
|
||||
|
||||
PRINT "Database opened (handle"; db; ")"
|
||||
PRINT
|
||||
|
||||
' Create table and insert sample data
|
||||
SQLExec db, "CREATE TABLE IF NOT EXISTS parts (id INTEGER PRIMARY KEY, name TEXT, price REAL)"
|
||||
SQLExec db, "DELETE FROM parts"
|
||||
SQLExec db, "INSERT INTO parts VALUES (1, 'Resistor 10K', 0.05)"
|
||||
SQLExec db, "INSERT INTO parts VALUES (2, 'Capacitor 100uF', 0.25)"
|
||||
SQLExec db, "INSERT INTO parts VALUES (3, 'LED Red', 0.10)"
|
||||
SQLExec db, "INSERT INTO parts VALUES (4, '555 Timer', 0.75)"
|
||||
SQLExec db, "INSERT INTO parts VALUES (5, 'Arduino Nano', 12.50)"
|
||||
|
||||
PRINT "Inserted 5 rows. Affected:"; SQLAffected(db)
|
||||
PRINT
|
||||
|
||||
' Query and display results
|
||||
rs = SQLQuery(db, "SELECT * FROM parts ORDER BY price")
|
||||
|
||||
IF rs = 0 THEN
|
||||
PRINT "Query failed: "; SQLError$(db)
|
||||
SQLClose db
|
||||
END
|
||||
END IF
|
||||
|
||||
PRINT "Columns:"; SQLFieldCount(rs)
|
||||
|
||||
DIM i AS INTEGER
|
||||
FOR i = 0 TO SQLFieldCount(rs) - 1
|
||||
PRINT " ["; i; "] "; SQLFieldName$(rs, i)
|
||||
NEXT i
|
||||
|
||||
PRINT
|
||||
PRINT "Parts (sorted by price):"
|
||||
PRINT "-----------------------------------"
|
||||
|
||||
DO WHILE SQLNext(rs)
|
||||
PRINT "#"; SQLFieldInt(rs, 0);
|
||||
PRINT " "; SQLField$(rs, "name");
|
||||
PRINT " $"; SQLFieldDbl(rs, 2)
|
||||
LOOP
|
||||
|
||||
PRINT "-----------------------------------"
|
||||
|
||||
SQLFreeResult rs
|
||||
SQLClose db
|
||||
|
||||
PRINT
|
||||
PRINT "Done!"
|
||||
|
|
@ -283,8 +283,10 @@ int shellMain(int argc, char *argv[]) {
|
|||
else if (strcmp(accelStr, "medium") == 0) { accelVal = 64; }
|
||||
else if (strcmp(accelStr, "high") == 0) { accelVal = 32; }
|
||||
|
||||
dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal);
|
||||
dvxLog("Preferences: mouse wheel=%s doubleclick=%ldms accel=%s", wheelStr, (long)dblClick, accelStr);
|
||||
int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", 8);
|
||||
|
||||
dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal, speed);
|
||||
dvxLog("Preferences: mouse wheel=%s doubleclick=%ldms accel=%s speed=%ld", wheelStr, (long)dblClick, accelStr, (long)speed);
|
||||
|
||||
// Apply saved color scheme
|
||||
bool colorsLoaded = false;
|
||||
|
|
|
|||
280
tools/dvxResWrite.h
Normal file
280
tools/dvxResWrite.h
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
// dvxResWrite.h -- DVX resource writing library interface
|
||||
//
|
||||
// Shared implementation for appending resources to DXE files.
|
||||
// Used by both the dvxres command-line tool and the runtime
|
||||
// dvxResAppend() function.
|
||||
//
|
||||
// Include this header in exactly one .c file. The tool includes
|
||||
// it directly; the runtime includes it via dvxResource.c.
|
||||
|
||||
#ifndef DVX_RES_WRITE_H
|
||||
#define DVX_RES_WRITE_H
|
||||
|
||||
#include "../core/dvxRes.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// ============================================================
|
||||
// dvxResDxeContentSize
|
||||
// ============================================================
|
||||
//
|
||||
// Returns the size of the DXE3 content (before any appended
|
||||
// resources). If the file has a resource footer, returns the
|
||||
// start of the resource data. Otherwise returns the full file size.
|
||||
|
||||
static long dvxResDxeContentSize(const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fileSize = ftell(f);
|
||||
|
||||
if (fileSize < (long)sizeof(DvxResFooterT)) {
|
||||
fclose(f);
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END);
|
||||
DvxResFooterT footer;
|
||||
|
||||
if (fread(&footer, sizeof(footer), 1, f) == 1 && footer.magic == DVX_RES_MAGIC) {
|
||||
fseek(f, footer.dirOffset, SEEK_SET);
|
||||
DvxResDirEntryT entry;
|
||||
long earliest = footer.dirOffset;
|
||||
|
||||
for (uint32_t i = 0; i < footer.entryCount; i++) {
|
||||
if (fread(&entry, sizeof(entry), 1, f) == 1) {
|
||||
if ((long)entry.offset < earliest) {
|
||||
earliest = (long)entry.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return earliest;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dvxResReadExisting
|
||||
// ============================================================
|
||||
//
|
||||
// Reads any existing resource block from the file. Returns arrays
|
||||
// of directory entries and data blobs. Caller frees everything.
|
||||
|
||||
static int dvxResReadExisting(const char *path, long dxeSize, DvxResDirEntryT **outEntries, uint32_t *outCount, uint8_t ***outData) {
|
||||
*outEntries = NULL;
|
||||
*outCount = 0;
|
||||
*outData = NULL;
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fileSize = ftell(f);
|
||||
|
||||
if (fileSize <= dxeSize) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END);
|
||||
DvxResFooterT footer;
|
||||
|
||||
if (fread(&footer, sizeof(footer), 1, f) != 1 || footer.magic != DVX_RES_MAGIC) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
fseek(f, footer.dirOffset, SEEK_SET);
|
||||
DvxResDirEntryT *entries = (DvxResDirEntryT *)malloc(footer.entryCount * sizeof(DvxResDirEntryT));
|
||||
|
||||
if (!entries) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fread(entries, sizeof(DvxResDirEntryT), footer.entryCount, f) != footer.entryCount) {
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t **data = (uint8_t **)malloc(footer.entryCount * sizeof(uint8_t *));
|
||||
|
||||
if (!data) {
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < footer.entryCount; i++) {
|
||||
data[i] = (uint8_t *)malloc(entries[i].size);
|
||||
|
||||
if (!data[i]) {
|
||||
for (uint32_t j = 0; j < i; j++) {
|
||||
free(data[j]);
|
||||
}
|
||||
|
||||
free(data);
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(f, entries[i].offset, SEEK_SET);
|
||||
|
||||
if (fread(data[i], 1, entries[i].size, f) != entries[i].size) {
|
||||
for (uint32_t j = 0; j <= i; j++) {
|
||||
free(data[j]);
|
||||
}
|
||||
|
||||
free(data);
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
*outEntries = entries;
|
||||
*outCount = footer.entryCount;
|
||||
*outData = data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dvxResWriteBlock
|
||||
// ============================================================
|
||||
//
|
||||
// Truncates the file to dxeSize and writes the resource block.
|
||||
|
||||
static int dvxResWriteBlock(const char *path, long dxeSize, DvxResDirEntryT *entries, uint32_t count, uint8_t **data) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t *dxeBuf = (uint8_t *)malloc(dxeSize);
|
||||
|
||||
if (!dxeBuf) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fread(dxeBuf, 1, dxeSize, f) != (size_t)dxeSize) {
|
||||
free(dxeBuf);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
f = fopen(path, "wb");
|
||||
|
||||
if (!f) {
|
||||
free(dxeBuf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fwrite(dxeBuf, 1, dxeSize, f);
|
||||
free(dxeBuf);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
entries[i].offset = (uint32_t)ftell(f);
|
||||
fwrite(data[i], 1, entries[i].size, f);
|
||||
}
|
||||
|
||||
uint32_t dirOffset = (uint32_t)ftell(f);
|
||||
fwrite(entries, sizeof(DvxResDirEntryT), count, f);
|
||||
|
||||
DvxResFooterT footer;
|
||||
footer.magic = DVX_RES_MAGIC;
|
||||
footer.dirOffset = dirOffset;
|
||||
footer.entryCount = count;
|
||||
footer.reserved = 0;
|
||||
fwrite(&footer, sizeof(footer), 1, f);
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dvxResAppendEntry
|
||||
// ============================================================
|
||||
//
|
||||
// High-level: append a single resource to a DXE file.
|
||||
// Reads existing resources, adds the new one (replacing if
|
||||
// same name exists), and rewrites the resource block.
|
||||
// Returns 0 on success.
|
||||
|
||||
static int dvxResAppendEntry(const char *path, const char *name, uint32_t type, const void *resData, uint32_t resSize) {
|
||||
if (!path || !name || !resData || resSize == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
long dxeSize = dvxResDxeContentSize(path);
|
||||
|
||||
if (dxeSize < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
DvxResDirEntryT *entries = NULL;
|
||||
uint32_t count = 0;
|
||||
uint8_t **data = NULL;
|
||||
dvxResReadExisting(path, dxeSize, &entries, &count, &data);
|
||||
|
||||
// Remove existing entry with same name
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (strcmp(entries[i].name, name) == 0) {
|
||||
free(data[i]);
|
||||
|
||||
for (uint32_t j = i; j < count - 1; j++) {
|
||||
entries[j] = entries[j + 1];
|
||||
data[j] = data[j + 1];
|
||||
}
|
||||
|
||||
count--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Add new entry
|
||||
entries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT));
|
||||
data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
|
||||
|
||||
memset(&entries[count], 0, sizeof(DvxResDirEntryT));
|
||||
strncpy(entries[count].name, name, DVX_RES_NAME_MAX - 1);
|
||||
entries[count].type = type;
|
||||
entries[count].size = resSize;
|
||||
data[count] = (uint8_t *)malloc(resSize);
|
||||
memcpy(data[count], resData, resSize);
|
||||
count++;
|
||||
|
||||
int result = dvxResWriteBlock(path, dxeSize, entries, count, data);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
free(data[i]);
|
||||
}
|
||||
|
||||
free(data);
|
||||
free(entries);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif // DVX_RES_WRITE_H
|
||||
289
tools/dvxres.c
289
tools/dvxres.c
|
|
@ -16,13 +16,10 @@
|
|||
// [resource directory]
|
||||
// [footer with magic + directory offset + count]
|
||||
|
||||
#include "../core/dvxRes.h"
|
||||
#include "dvxResWrite.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
|
|
@ -33,62 +30,10 @@ static int cmdBuild(const char *dxePath, const char *manifestPath);
|
|||
static int cmdGet(const char *dxePath, const char *name, const char *outPath);
|
||||
static int cmdList(const char *dxePath);
|
||||
static int cmdStrip(const char *dxePath);
|
||||
static long dxeContentSize(const char *path);
|
||||
static int parseType(const char *typeStr);
|
||||
static int readExistingResources(const char *path, long dxeSize, DvxResDirEntryT **outEntries, uint32_t *outCount, uint8_t ***outData);
|
||||
static void usage(void);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// dxeContentSize
|
||||
// ============================================================
|
||||
//
|
||||
// Returns the size of the DXE3 content (before any appended resources).
|
||||
// If the file has a resource footer, returns the start of the resource
|
||||
// data. Otherwise returns the full file size.
|
||||
|
||||
static long dxeContentSize(const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fileSize = ftell(f);
|
||||
|
||||
if (fileSize < (long)sizeof(DvxResFooterT)) {
|
||||
fclose(f);
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
// Check for resource footer
|
||||
fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END);
|
||||
DvxResFooterT footer;
|
||||
|
||||
if (fread(&footer, sizeof(footer), 1, f) == 1 && footer.magic == DVX_RES_MAGIC) {
|
||||
// Find the earliest resource data offset
|
||||
fseek(f, footer.dirOffset, SEEK_SET);
|
||||
DvxResDirEntryT entry;
|
||||
long earliest = footer.dirOffset;
|
||||
|
||||
for (uint32_t i = 0; i < footer.entryCount; i++) {
|
||||
if (fread(&entry, sizeof(entry), 1, f) == 1) {
|
||||
if ((long)entry.offset < earliest) {
|
||||
earliest = (long)entry.offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return earliest;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return fileSize;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// parseType
|
||||
// ============================================================
|
||||
|
|
@ -110,164 +55,6 @@ static int parseType(const char *typeStr) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// readExistingResources
|
||||
// ============================================================
|
||||
//
|
||||
// Reads any existing resource block from the file. Returns arrays
|
||||
// of directory entries and data blobs. Caller frees everything.
|
||||
|
||||
static int readExistingResources(const char *path, long dxeSize, DvxResDirEntryT **outEntries, uint32_t *outCount, uint8_t ***outData) {
|
||||
*outEntries = NULL;
|
||||
*outCount = 0;
|
||||
*outData = NULL;
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long fileSize = ftell(f);
|
||||
|
||||
if (fileSize <= dxeSize) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for footer
|
||||
fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END);
|
||||
DvxResFooterT footer;
|
||||
|
||||
if (fread(&footer, sizeof(footer), 1, f) != 1 || footer.magic != DVX_RES_MAGIC) {
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read directory
|
||||
fseek(f, footer.dirOffset, SEEK_SET);
|
||||
DvxResDirEntryT *entries = (DvxResDirEntryT *)malloc(footer.entryCount * sizeof(DvxResDirEntryT));
|
||||
|
||||
if (!entries) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fread(entries, sizeof(DvxResDirEntryT), footer.entryCount, f) != footer.entryCount) {
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read each resource's data
|
||||
uint8_t **data = (uint8_t **)malloc(footer.entryCount * sizeof(uint8_t *));
|
||||
|
||||
if (!data) {
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < footer.entryCount; i++) {
|
||||
data[i] = (uint8_t *)malloc(entries[i].size);
|
||||
|
||||
if (!data[i]) {
|
||||
for (uint32_t j = 0; j < i; j++) {
|
||||
free(data[j]);
|
||||
}
|
||||
|
||||
free(data);
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(f, entries[i].offset, SEEK_SET);
|
||||
|
||||
if (fread(data[i], 1, entries[i].size, f) != entries[i].size) {
|
||||
fprintf(stderr, "Short read on entry %d\n", (int)i);
|
||||
for (uint32_t j = 0; j <= i; j++) {
|
||||
free(data[j]);
|
||||
}
|
||||
free(data);
|
||||
free(entries);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
*outEntries = entries;
|
||||
*outCount = footer.entryCount;
|
||||
*outData = data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// writeResources
|
||||
// ============================================================
|
||||
//
|
||||
// Truncates the file to dxeSize and writes the resource block.
|
||||
|
||||
static int writeResources(const char *path, long dxeSize, DvxResDirEntryT *entries, uint32_t count, uint8_t **data) {
|
||||
// Read the DXE content first
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
if (!f) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t *dxeBuf = (uint8_t *)malloc(dxeSize);
|
||||
|
||||
if (!dxeBuf) {
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (fread(dxeBuf, 1, dxeSize, f) != (size_t)dxeSize) {
|
||||
free(dxeBuf);
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
// Rewrite file: DXE content + resources
|
||||
f = fopen(path, "wb");
|
||||
|
||||
if (!f) {
|
||||
free(dxeBuf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fwrite(dxeBuf, 1, dxeSize, f);
|
||||
free(dxeBuf);
|
||||
|
||||
// Write resource data entries and update offsets
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
entries[i].offset = (uint32_t)ftell(f);
|
||||
fwrite(data[i], 1, entries[i].size, f);
|
||||
}
|
||||
|
||||
// Write directory
|
||||
uint32_t dirOffset = (uint32_t)ftell(f);
|
||||
fwrite(entries, sizeof(DvxResDirEntryT), count, f);
|
||||
|
||||
// Write footer
|
||||
DvxResFooterT footer;
|
||||
footer.magic = DVX_RES_MAGIC;
|
||||
footer.dirOffset = dirOffset;
|
||||
footer.entryCount = count;
|
||||
footer.reserved = 0;
|
||||
fwrite(&footer, sizeof(footer), 1, f);
|
||||
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// cmdAdd
|
||||
// ============================================================
|
||||
|
|
@ -285,40 +72,11 @@ static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, co
|
|||
return 1;
|
||||
}
|
||||
|
||||
long dxeSize = dxeContentSize(dxePath);
|
||||
|
||||
if (dxeSize < 0) {
|
||||
fprintf(stderr, "Cannot open: %s\n", dxePath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Read existing resources
|
||||
DvxResDirEntryT *entries = NULL;
|
||||
uint32_t count = 0;
|
||||
uint8_t **data = NULL;
|
||||
readExistingResources(dxePath, dxeSize, &entries, &count, &data);
|
||||
|
||||
// Remove existing entry with same name (replace)
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
if (strcmp(entries[i].name, name) == 0) {
|
||||
free(data[i]);
|
||||
|
||||
for (uint32_t j = i; j < count - 1; j++) {
|
||||
entries[j] = entries[j + 1];
|
||||
data[j] = data[j + 1];
|
||||
}
|
||||
|
||||
count--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare new resource data
|
||||
// Prepare resource data
|
||||
uint8_t *newData = NULL;
|
||||
uint32_t newSize = 0;
|
||||
|
||||
if (type == DVX_RES_TEXT) {
|
||||
// Text data from command line argument
|
||||
newSize = (uint32_t)strlen(dataArg) + 1;
|
||||
newData = (uint8_t *)malloc(newSize);
|
||||
|
||||
|
|
@ -329,11 +87,17 @@ static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, co
|
|||
|
||||
memcpy(newData, dataArg, newSize);
|
||||
} else {
|
||||
// Binary/icon data from file
|
||||
FILE *df = fopen(dataArg, "rb");
|
||||
// Binary/icon: dataArg is a file path (strip leading @ if present)
|
||||
const char *filePath = dataArg;
|
||||
|
||||
if (filePath[0] == '@') {
|
||||
filePath++;
|
||||
}
|
||||
|
||||
FILE *df = fopen(filePath, "rb");
|
||||
|
||||
if (!df) {
|
||||
fprintf(stderr, "Cannot open data file: %s\n", dataArg);
|
||||
fprintf(stderr, "Cannot open data file: %s\n", filePath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -352,34 +116,15 @@ static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, co
|
|||
if (fread(newData, 1, newSize, df) != newSize) {
|
||||
fclose(df);
|
||||
free(newData);
|
||||
fprintf(stderr, "Failed to read: %s\n", dataArg);
|
||||
fprintf(stderr, "Failed to read: %s\n", filePath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
fclose(df);
|
||||
}
|
||||
|
||||
// Append to arrays
|
||||
entries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT));
|
||||
data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
|
||||
|
||||
memset(&entries[count], 0, sizeof(DvxResDirEntryT));
|
||||
strncpy(entries[count].name, name, DVX_RES_NAME_MAX - 1);
|
||||
entries[count].type = (uint32_t)type;
|
||||
entries[count].size = newSize;
|
||||
data[count] = newData;
|
||||
count++;
|
||||
|
||||
// Write
|
||||
int result = writeResources(dxePath, dxeSize, entries, count, data);
|
||||
|
||||
// Cleanup
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
free(data[i]);
|
||||
}
|
||||
|
||||
free(data);
|
||||
free(entries);
|
||||
int result = dvxResAppendEntry(dxePath, name, (uint32_t)type, newData, newSize);
|
||||
free(newData);
|
||||
|
||||
if (result != 0) {
|
||||
fprintf(stderr, "Failed to write resources to: %s\n", dxePath);
|
||||
|
|
@ -403,7 +148,7 @@ static int cmdBuild(const char *dxePath, const char *manifestPath) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
long dxeSize = dxeContentSize(dxePath);
|
||||
long dxeSize = dvxResDxeContentSize(dxePath);
|
||||
|
||||
if (dxeSize < 0) {
|
||||
fclose(mf);
|
||||
|
|
@ -565,7 +310,7 @@ static int cmdBuild(const char *dxePath, const char *manifestPath) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
int result = writeResources(dxePath, dxeSize, entries, count, data);
|
||||
int result = dvxResWriteBlock(dxePath, dxeSize, entries, count, data);
|
||||
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
free(data[i]);
|
||||
|
|
@ -696,7 +441,7 @@ static int cmdGet(const char *dxePath, const char *name, const char *outPath) {
|
|||
// ============================================================
|
||||
|
||||
static int cmdStrip(const char *dxePath) {
|
||||
long dxeSize = dxeContentSize(dxePath);
|
||||
long dxeSize = dvxResDxeContentSize(dxePath);
|
||||
|
||||
if (dxeSize < 0) {
|
||||
fprintf(stderr, "Cannot open: %s\n", dxePath);
|
||||
|
|
|
|||
|
|
@ -22,15 +22,32 @@ A VT100/ANSI terminal emulator widget. Supports ANSI escape sequences, scrollbac
|
|||
Scrollback Integer Number of scrollback lines (write-only).
|
||||
.endtable
|
||||
|
||||
.index CommAttach
|
||||
.index CommOpen
|
||||
|
||||
.h2 Type-Specific Methods
|
||||
|
||||
.table
|
||||
Method Parameters Description
|
||||
------ --------------- -------------------------------------------
|
||||
Clear (none) Clear the terminal screen.
|
||||
Write Text As String Write text (with ANSI escape processing) to the terminal.
|
||||
Method Description
|
||||
------ -----------
|
||||
Clear Clear the terminal screen.
|
||||
Poll Process pending comm data and update the display.
|
||||
Write text$ Write text (with ANSI escape processing) to the terminal.
|
||||
.endtable
|
||||
|
||||
No default event.
|
||||
|
||||
.h2 Serial Communication
|
||||
|
||||
Use the comm.bas library to connect a Terminal to a serial link:
|
||||
|
||||
.code
|
||||
Dim link As Integer
|
||||
link = CommOpen(1, 115200)
|
||||
CommHandshake link
|
||||
CommAttach link, "Terminal1"
|
||||
.endcode
|
||||
|
||||
See comm.bas for the full communications API.
|
||||
|
||||
.link ctrl.common.props Common Properties, Events, and Methods
|
||||
|
|
|
|||
|
|
@ -1077,6 +1077,7 @@ static const WgtPropDescT sProps[] = {
|
|||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
{ "Clear", WGT_SIG_VOID, (void *)wgtAnsiTermClear },
|
||||
{ "Poll", WGT_SIG_VOID, (void *)wgtAnsiTermPoll },
|
||||
{ "Write", WGT_SIG_STR, (void *)wgtAnsiTermWriteString }
|
||||
};
|
||||
|
||||
|
|
@ -1085,7 +1086,7 @@ static const WgtIfaceT sIface = {
|
|||
.props = sProps,
|
||||
.propCount = 3,
|
||||
.methods = sMethods,
|
||||
.methodCount = 2,
|
||||
.methodCount = 3,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT_INT_INT,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
.index PictureBox
|
||||
.index Canvas
|
||||
.index Drawing
|
||||
.index SetPenColor
|
||||
.index SetPenSize
|
||||
|
||||
.h1 PictureBox
|
||||
|
||||
|
|
@ -11,16 +13,40 @@ VB Equivalent: PictureBox -- DVX Widget: canvas | Name Prefix: Picture
|
|||
|
||||
A drawing surface (canvas). Supports drawing lines, rectangles, circles, text, and individual pixels. Can save and load BMP images. The default canvas size is 64x64 pixels.
|
||||
|
||||
Colors are specified as 0x00RRGGBB integers (e.g. &HFF0000 for red).
|
||||
|
||||
.h2 Type-Specific Methods
|
||||
|
||||
.table
|
||||
Method Parameters Description
|
||||
------ ---------------- -------------------------------------------
|
||||
Clear Color As Integer Fill the entire canvas with the specified color.
|
||||
Method Description
|
||||
------ -----------
|
||||
Clear color% Fill the entire canvas with the specified color.
|
||||
DrawLine x0%, y0%, x1%, y1%, color% Draw a line between two points.
|
||||
DrawRect x%, y%, w%, h%, color% Draw a rectangle outline.
|
||||
DrawText x%, y%, text$ Draw text at the given position.
|
||||
FillCircle cx%, cy%, radius%, color% Draw a filled circle.
|
||||
FillRect x%, y%, w%, h%, color% Draw a filled rectangle.
|
||||
GetPixel(x%, y%) Returns the color at a pixel.
|
||||
Load path$ Load a BMP image onto the canvas.
|
||||
Resize w%, h% Resize the canvas to new dimensions.
|
||||
Save path$ Save the canvas as a BMP file.
|
||||
SetPenColor color% Set the drawing color for subsequent operations.
|
||||
SetPenSize size% Set the pen/brush size in pixels.
|
||||
SetPixel x%, y%, color% Set the color of a single pixel.
|
||||
.endtable
|
||||
|
||||
Additional drawing methods (DrawLine, DrawRect, FillRect, FillCircle, SetPixel, GetPixel, DrawText, Save, Load) are available through the C API but not currently exposed through BASIC interface descriptors.
|
||||
|
||||
Default Event: Click
|
||||
|
||||
.h2 Example
|
||||
|
||||
.code
|
||||
Picture1.Resize 200, 150
|
||||
Picture1.Clear &HFFFFFF
|
||||
Picture1.SetPenSize 2
|
||||
Picture1.DrawRect 10, 10, 180, 130, &H000000
|
||||
Picture1.FillCircle 100, 75, 40, &HFF0000
|
||||
Picture1.DrawText 60, 130, "Hello!"
|
||||
Picture1.Save "output.bmp"
|
||||
.endcode
|
||||
|
||||
.link ctrl.common.props Common Properties, Events, and Methods
|
||||
|
|
|
|||
|
|
@ -1044,18 +1044,24 @@ static const struct {
|
|||
.resize = wgtCanvasResize
|
||||
};
|
||||
|
||||
static void basSetPenColor(WidgetT *w, int32_t color) {
|
||||
wgtCanvasSetPenColor(w, rgbToPacked(w, (uint32_t)color));
|
||||
}
|
||||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
{ "Clear", WGT_SIG_INT, (void *)basClear },
|
||||
{ "DrawLine", WGT_SIG_INT5, (void *)basDrawLine },
|
||||
{ "DrawRect", WGT_SIG_INT5, (void *)basDrawRect },
|
||||
{ "DrawText", WGT_SIG_INT_INT_STR,(void *)wgtCanvasDrawText },
|
||||
{ "FillCircle", WGT_SIG_INT4, (void *)basFillCircle },
|
||||
{ "FillRect", WGT_SIG_INT5, (void *)basFillRect },
|
||||
{ "GetPixel", WGT_SIG_RET_INT_INT_INT, (void *)basGetPixel },
|
||||
{ "Load", WGT_SIG_STR, (void *)wgtCanvasLoad },
|
||||
{ "Save", WGT_SIG_STR, (void *)wgtCanvasSave },
|
||||
{ "Resize", WGT_SIG_INT_INT, (void *)wgtCanvasResize },
|
||||
{ "SetPixel", WGT_SIG_INT3, (void *)basSetPixel },
|
||||
{ "Clear", WGT_SIG_INT, (void *)basClear },
|
||||
{ "DrawLine", WGT_SIG_INT5, (void *)basDrawLine },
|
||||
{ "DrawRect", WGT_SIG_INT5, (void *)basDrawRect },
|
||||
{ "DrawText", WGT_SIG_INT_INT_STR, (void *)wgtCanvasDrawText },
|
||||
{ "FillCircle", WGT_SIG_INT4, (void *)basFillCircle },
|
||||
{ "FillRect", WGT_SIG_INT5, (void *)basFillRect },
|
||||
{ "GetPixel", WGT_SIG_RET_INT_INT_INT, (void *)basGetPixel },
|
||||
{ "Load", WGT_SIG_STR, (void *)wgtCanvasLoad },
|
||||
{ "Resize", WGT_SIG_INT_INT, (void *)wgtCanvasResize },
|
||||
{ "Save", WGT_SIG_STR, (void *)wgtCanvasSave },
|
||||
{ "SetPenColor", WGT_SIG_INT, (void *)basSetPenColor },
|
||||
{ "SetPenSize", WGT_SIG_INT, (void *)wgtCanvasSetPenSize },
|
||||
{ "SetPixel", WGT_SIG_INT3, (void *)basSetPixel },
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
|
|
@ -1063,7 +1069,7 @@ static const WgtIfaceT sIface = {
|
|||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = sMethods,
|
||||
.methodCount = 11,
|
||||
.methodCount = 13,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT_INT_INT,
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ A scrollable list of selectable items. Items are managed via methods (AddItem, R
|
|||
SetReorderable Reorderable As Boolean Enable or disable drag-to-reorder.
|
||||
IsItemSelected Index As Integer Returns True if the item at Index is selected.
|
||||
SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific item.
|
||||
SetItems Items As String Bulk-load items from a pipe-delimited string (e.g. "Red|Green|Blue").
|
||||
.endtable
|
||||
|
||||
Default Event: Click
|
||||
|
|
|
|||
|
|
@ -940,6 +940,40 @@ static const struct {
|
|||
.getItemCount = wgtListBoxGetItemCount
|
||||
};
|
||||
|
||||
// BASIC wrapper: SetItems "Item1|Item2|Item3" -- bulk load from pipe-delimited string
|
||||
#define BAS_MAX_LISTBOX_ITEMS 256
|
||||
|
||||
static void basSetItems(WidgetT *w, const char *spec) {
|
||||
static char buf[4096];
|
||||
static const char *ptrs[BAS_MAX_LISTBOX_ITEMS];
|
||||
|
||||
if (!spec || !*spec) {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s", spec);
|
||||
|
||||
int32_t count = 0;
|
||||
char *tok = buf;
|
||||
|
||||
while (*tok && count < BAS_MAX_LISTBOX_ITEMS) {
|
||||
ptrs[count++] = tok;
|
||||
char *sep = strchr(tok, '|');
|
||||
|
||||
if (sep) {
|
||||
*sep = '\0';
|
||||
tok = sep + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
wgtListBoxSetItems(w, ptrs, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "ListIndex", WGT_IFACE_INT, (void *)wgtListBoxGetSelected, (void *)wgtListBoxSetSelected, NULL }
|
||||
};
|
||||
|
|
@ -954,6 +988,7 @@ static const WgtMethodDescT sMethods[] = {
|
|||
{ "RemoveItem", WGT_SIG_INT, (void *)wgtListBoxRemoveItem },
|
||||
{ "SelectAll", WGT_SIG_VOID, (void *)wgtListBoxSelectAll },
|
||||
{ "SetItemSelected", WGT_SIG_INT_BOOL, (void *)wgtListBoxSetItemSelected },
|
||||
{ "SetItems", WGT_SIG_STR, (void *)basSetItems },
|
||||
{ "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtListBoxSetMultiSelect },
|
||||
{ "SetReorderable", WGT_SIG_BOOL, (void *)wgtListBoxSetReorderable },
|
||||
};
|
||||
|
|
@ -963,7 +998,7 @@ static const WgtIfaceT sIface = {
|
|||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = sMethods,
|
||||
.methodCount = 11,
|
||||
.methodCount = 12,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
|
|
|
|||
|
|
@ -4,34 +4,54 @@
|
|||
.toc 1 ListView
|
||||
.index ListView
|
||||
.index Multi-Column List
|
||||
.index SetColumns
|
||||
.index SetSort
|
||||
|
||||
.h1 ListView
|
||||
|
||||
VB Equivalent: ListView -- DVX Widget: listview
|
||||
|
||||
A multi-column list with column headers. Supports sorting, multi-select, and drag-to-reorder. Columns are configured via the C API (SetColumns, SetData).
|
||||
A multi-column list with column headers. Supports sorting, multi-select, and drag-to-reorder.
|
||||
|
||||
.h2 Type-Specific Properties
|
||||
|
||||
.table
|
||||
Property Type Description
|
||||
--------- ------- -------------------------------------------
|
||||
ListIndex Integer Index of the currently selected row (-1 = none).
|
||||
Property Type R/W Description
|
||||
--------- ------- --- -------------------------------------------
|
||||
ListIndex Integer R/W Index of the currently selected row (-1 = none).
|
||||
.endtable
|
||||
|
||||
.h2 Type-Specific Methods
|
||||
|
||||
.table
|
||||
Method Parameters Description
|
||||
--------------- --------------------------------------- -------------------------------------------
|
||||
SelectAll (none) Select all rows.
|
||||
ClearSelection (none) Deselect all rows.
|
||||
SetMultiSelect Multi As Boolean Enable or disable multi-select.
|
||||
SetReorderable Reorderable As Boolean Enable or disable row reordering.
|
||||
IsItemSelected Index As Integer Returns True if the row at Index is selected.
|
||||
SetItemSelected Index As Integer, Selected As Boolean Select or deselect a specific row.
|
||||
Method Description
|
||||
------ -----------
|
||||
AddItem text$ Add a row (sets first column text).
|
||||
Clear Remove all rows.
|
||||
ClearSelection Deselect all rows.
|
||||
GetCell$(row%, col%) Returns the text of a cell.
|
||||
IsItemSelected(index%) Returns True if the row is selected.
|
||||
RemoveItem index% Remove a row by index.
|
||||
RowCount() Returns the number of rows.
|
||||
SelectAll Select all rows.
|
||||
SetCell row%, col%, text$ Set the text of a cell.
|
||||
SetColumns spec$ Define columns. Pipe-delimited "Name,Width|Name,Width" (width in characters).
|
||||
SetItemSelected index%, selected Select or deselect a row.
|
||||
SetMultiSelect multi Enable or disable multi-select.
|
||||
SetReorderable reorderable Enable or disable row reordering.
|
||||
SetSort col%, dir% Sort by column. dir: 0=none, 1=ascending, 2=descending.
|
||||
.endtable
|
||||
|
||||
Default Event: Click
|
||||
|
||||
.h2 Example
|
||||
|
||||
.code
|
||||
ListView1.SetColumns "Name,20|Size,10|Type,15"
|
||||
ListView1.AddItem "readme.txt"
|
||||
ListView1.SetCell 0, 1, "1024"
|
||||
ListView1.SetCell 0, 2, "Text"
|
||||
ListView1.SetSort 0, 1
|
||||
.endcode
|
||||
|
||||
.link ctrl.common.props Common Properties, Events, and Methods
|
||||
|
|
|
|||
|
|
@ -1937,23 +1937,76 @@ static const struct {
|
|||
.getRowCount = wgtListViewGetRowCount
|
||||
};
|
||||
|
||||
// BASIC wrapper: SetColumns "Name,100|Description,200"
|
||||
#define BAS_MAX_LISTVIEW_COLS 32
|
||||
|
||||
static void basSetColumns(WidgetT *w, const char *spec) {
|
||||
if (!spec || !*spec) {
|
||||
return;
|
||||
}
|
||||
|
||||
static char buf[1024];
|
||||
snprintf(buf, sizeof(buf), "%s", spec);
|
||||
|
||||
ListViewColT cols[BAS_MAX_LISTVIEW_COLS];
|
||||
int32_t count = 0;
|
||||
char *tok = buf;
|
||||
|
||||
while (*tok && count < BAS_MAX_LISTVIEW_COLS) {
|
||||
// Parse "Title,Width" or just "Title"
|
||||
char *sep = strchr(tok, '|');
|
||||
|
||||
if (sep) {
|
||||
*sep = '\0';
|
||||
}
|
||||
|
||||
char *comma = strchr(tok, ',');
|
||||
|
||||
if (comma) {
|
||||
*comma = '\0';
|
||||
cols[count].title = tok;
|
||||
cols[count].width = wgtChars(atoi(comma + 1));
|
||||
cols[count].align = ListViewAlignLeftE;
|
||||
} else {
|
||||
cols[count].title = tok;
|
||||
cols[count].width = 0;
|
||||
cols[count].align = ListViewAlignLeftE;
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
if (sep) {
|
||||
tok = sep + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
wgtListViewSetColumns(w, cols, count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "ListIndex", WGT_IFACE_INT, (void *)wgtListViewGetSelected, (void *)wgtListViewSetSelected, NULL }
|
||||
};
|
||||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
{ "AddItem", WGT_SIG_STR, (void *)wgtListViewAddItem },
|
||||
{ "Clear", WGT_SIG_VOID, (void *)wgtListViewClear },
|
||||
{ "ClearSelection", WGT_SIG_VOID, (void *)wgtListViewClearSelection },
|
||||
{ "AddItem", WGT_SIG_STR, (void *)wgtListViewAddItem },
|
||||
{ "Clear", WGT_SIG_VOID, (void *)wgtListViewClear },
|
||||
{ "ClearSelection", WGT_SIG_VOID, (void *)wgtListViewClearSelection },
|
||||
{ "GetCell", WGT_SIG_RET_STR_INT_INT, (void *)wgtListViewGetCell },
|
||||
{ "IsItemSelected", WGT_SIG_RET_BOOL_INT, (void *)wgtListViewIsItemSelected },
|
||||
{ "RemoveItem", WGT_SIG_INT, (void *)wgtListViewRemoveRow },
|
||||
{ "RowCount", WGT_SIG_RET_INT, (void *)wgtListViewGetRowCount },
|
||||
{ "SelectAll", WGT_SIG_VOID, (void *)wgtListViewSelectAll },
|
||||
{ "SetCell", WGT_SIG_INT_INT_STR, (void *)wgtListViewSetCell },
|
||||
{ "SetItemSelected", WGT_SIG_INT_BOOL, (void *)wgtListViewSetItemSelected },
|
||||
{ "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtListViewSetMultiSelect },
|
||||
{ "SetReorderable", WGT_SIG_BOOL, (void *)wgtListViewSetReorderable },
|
||||
{ "IsItemSelected", WGT_SIG_RET_BOOL_INT, (void *)wgtListViewIsItemSelected },
|
||||
{ "RemoveItem", WGT_SIG_INT, (void *)wgtListViewRemoveRow },
|
||||
{ "RowCount", WGT_SIG_RET_INT, (void *)wgtListViewGetRowCount },
|
||||
{ "SelectAll", WGT_SIG_VOID, (void *)wgtListViewSelectAll },
|
||||
{ "SetCell", WGT_SIG_INT_INT_STR, (void *)wgtListViewSetCell },
|
||||
{ "SetColumns", WGT_SIG_STR, (void *)basSetColumns },
|
||||
{ "SetItemSelected", WGT_SIG_INT_BOOL, (void *)wgtListViewSetItemSelected },
|
||||
{ "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtListViewSetMultiSelect },
|
||||
{ "SetReorderable", WGT_SIG_BOOL, (void *)wgtListViewSetReorderable },
|
||||
{ "SetSort", WGT_SIG_INT_INT, (void *)wgtListViewSetSort },
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
|
|
@ -1961,7 +2014,7 @@ static const WgtIfaceT sIface = {
|
|||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = sMethods,
|
||||
.methodCount = 12,
|
||||
.methodCount = 14,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
|
|
|
|||
|
|
@ -23,9 +23,11 @@ A tabbed container. Each tab page is a separate container that holds child contr
|
|||
.h2 Type-Specific Methods
|
||||
|
||||
.table
|
||||
Method Parameters Description
|
||||
--------- ----------------- -------------------------------------------
|
||||
SetActive Index As Integer Switch to the tab at the given index. This is the recommended way to change tabs at runtime (the TabIndex property is shadowed by the common property handler).
|
||||
Method Description
|
||||
------ -----------
|
||||
AddPage title$ Add a new tab page with the given title.
|
||||
GetActive() Returns the index of the active tab.
|
||||
SetActive index% Switch to the tab at the given index. This is the recommended way to change tabs at runtime (the TabIndex property is shadowed by the common property handler).
|
||||
.endtable
|
||||
|
||||
Container: Yes
|
||||
|
|
|
|||
|
|
@ -699,8 +699,16 @@ static const WgtPropDescT sProps[] = {
|
|||
{ "TabIndex", WGT_IFACE_INT, (void *)wgtTabControlGetActive, (void *)wgtTabControlSetActive, NULL }
|
||||
};
|
||||
|
||||
// BASIC wrapper: AddPage creates a tab page and returns nothing.
|
||||
// The page is accessible by index via SetActive.
|
||||
static void basAddPage(WidgetT *w, const char *title) {
|
||||
wgtTabPage(w, title);
|
||||
}
|
||||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
{ "SetActive", WGT_SIG_INT, (void *)wgtTabControlSetActive }
|
||||
{ "AddPage", WGT_SIG_STR, (void *)basAddPage },
|
||||
{ "GetActive", WGT_SIG_RET_INT, (void *)wgtTabControlGetActive },
|
||||
{ "SetActive", WGT_SIG_INT, (void *)wgtTabControlSetActive },
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
|
|
@ -708,7 +716,7 @@ static const WgtIfaceT sIface = {
|
|||
.props = sProps,
|
||||
.propCount = 1,
|
||||
.methods = sMethods,
|
||||
.methodCount = 1,
|
||||
.methodCount = 3,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
|
|
|
|||
|
|
@ -42,21 +42,64 @@ End Sub
|
|||
.title TextArea
|
||||
.toc 1 TextArea
|
||||
.index TextArea
|
||||
.index FindNext
|
||||
.index ReplaceAll
|
||||
.index GoToLine
|
||||
.index CursorLine
|
||||
.index LineNumbers
|
||||
.index AutoIndent
|
||||
|
||||
.h1 TextArea
|
||||
|
||||
VB Equivalent: TextArea (DVX extension) -- DVX Widget: textarea (multi-line text input, max 4096 chars)
|
||||
|
||||
A multi-line text editing area. This is a DVX extension with no direct VB3 equivalent (VB uses a TextBox with MultiLine=True). Supports syntax colorization, line numbers, auto-indent, and find/replace via the C API.
|
||||
A multi-line text editing area. This is a DVX extension with no direct VB3 equivalent (VB uses a TextBox with MultiLine=True). Supports line numbers, auto-indent, find/replace, and tab configuration.
|
||||
|
||||
.h2 Type-Specific Properties
|
||||
|
||||
.table
|
||||
Property Type Description
|
||||
-------- ------ -------------------------------------------
|
||||
Text String The full text content.
|
||||
Property Type R/W Description
|
||||
---------- ------- --- -------------------------------------------
|
||||
Text String R/W The full text content.
|
||||
CursorLine Integer R Current cursor line number (0-based).
|
||||
.endtable
|
||||
|
||||
.h2 Type-Specific Methods
|
||||
|
||||
.table
|
||||
Method Description
|
||||
------ -----------
|
||||
FindNext needle$, caseSensitive, forward Search for text. Returns True if found.
|
||||
GetWordAtCursor() Returns the word under the cursor.
|
||||
GoToLine line% Scroll to and position cursor at the given line.
|
||||
ReplaceAll needle$, replacement$, caseSensitive Replace all occurrences. Returns count of replacements.
|
||||
SetAutoIndent enabled Enable or disable automatic indentation.
|
||||
SetCaptureTabs enabled When True, Tab key inserts a tab/spaces instead of moving focus.
|
||||
SetShowLineNumbers show Show or hide the line number gutter.
|
||||
SetSyntaxMode mode$ Activate built-in syntax highlighting. Modes: "dhs" (help source), "bas" (BASIC). Pass "" to disable.
|
||||
SetTabWidth width% Set the tab stop width in characters.
|
||||
SetUseTabChar useChar When True, Tab inserts a tab character; when False, inserts spaces.
|
||||
.endtable
|
||||
|
||||
Default Event: Change
|
||||
|
||||
.h2 Example
|
||||
|
||||
.code
|
||||
TextArea1.SetShowLineNumbers True
|
||||
TextArea1.SetAutoIndent True
|
||||
TextArea1.SetTabWidth 4
|
||||
TextArea1.SetCaptureTabs True
|
||||
|
||||
' Search for text
|
||||
If TextArea1.FindNext("TODO", False, True) Then
|
||||
Print "Found at line"; TextArea1.CursorLine
|
||||
End If
|
||||
|
||||
' Replace all occurrences
|
||||
Dim count As Integer
|
||||
count = TextArea1.ReplaceAll("old", "new", False)
|
||||
Print "Replaced"; count; "occurrences"
|
||||
.endcode
|
||||
|
||||
.link ctrl.common.props Common Properties, Events, and Methods
|
||||
|
|
|
|||
|
|
@ -3447,6 +3447,297 @@ static const struct {
|
|||
static const struct { WidgetT *(*create)(WidgetT *, int32_t); } sTextInputApi = { .create = wgtTextInput };
|
||||
static const struct { WidgetT *(*create)(WidgetT *, int32_t); } sTextAreaApi = { .create = wgtTextArea };
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Built-in syntax colorizers for SetSyntaxMode
|
||||
// ============================================================
|
||||
|
||||
// Color indices (match wgtTextAreaSetSyntaxColors slots):
|
||||
// 0=default, 1=keyword, 2=string, 3=comment, 4=number, 5=operator, 6=type, 7=reserved
|
||||
|
||||
// .dhs help source colorizer
|
||||
static void dhsColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx) {
|
||||
(void)ctx;
|
||||
|
||||
if (lineLen == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip leading whitespace
|
||||
int32_t i = 0;
|
||||
|
||||
while (i < lineLen && (line[i] == ' ' || line[i] == '\t')) {
|
||||
i++;
|
||||
}
|
||||
|
||||
// Lines starting with . are directives
|
||||
if (i < lineLen && line[i] == '.') {
|
||||
int32_t dirStart = i;
|
||||
i++;
|
||||
|
||||
// Directive name
|
||||
while (i < lineLen && line[i] != ' ' && line[i] != '\t') {
|
||||
i++;
|
||||
}
|
||||
|
||||
// Classify the directive
|
||||
int32_t dirLen = i - dirStart;
|
||||
const char *dir = line + dirStart;
|
||||
|
||||
// Block directives: .code, .endcode, .table, .endtable, .list, .endlist, .note, .endnote
|
||||
bool isBlock = false;
|
||||
|
||||
if (dirLen >= 4 && (strncasecmp(dir, ".code", 5) == 0 || strncasecmp(dir, ".table", 6) == 0 || strncasecmp(dir, ".list", 5) == 0 || strncasecmp(dir, ".note", 5) == 0 || strncasecmp(dir, ".endcode", 8) == 0 || strncasecmp(dir, ".endtable", 9) == 0 || strncasecmp(dir, ".endlist", 8) == 0 || strncasecmp(dir, ".endnote", 8) == 0 || strncasecmp(dir, ".item", 5) == 0 || strncasecmp(dir, ".hr", 3) == 0)) {
|
||||
isBlock = true;
|
||||
}
|
||||
|
||||
// Heading directives: .h1, .h2, .h3
|
||||
bool isHeading = (dirLen >= 3 && dir[0] == '.' && (dir[1] == 'h' || dir[1] == 'H') && dir[2] >= '1' && dir[2] <= '3');
|
||||
|
||||
// Link directive: .link
|
||||
bool isLink = (dirLen >= 5 && strncasecmp(dir, ".link", 5) == 0);
|
||||
|
||||
// Topic/index/toc/title directives
|
||||
bool isMeta = false;
|
||||
|
||||
if (dirLen >= 4 && (strncasecmp(dir, ".topic", 6) == 0 || strncasecmp(dir, ".title", 6) == 0 || strncasecmp(dir, ".toc", 4) == 0 || strncasecmp(dir, ".index", 6) == 0 || strncasecmp(dir, ".default", 8) == 0 || strncasecmp(dir, ".section", 8) == 0 || strncasecmp(dir, ".image", 6) == 0)) {
|
||||
isMeta = true;
|
||||
}
|
||||
|
||||
// Color the directive name
|
||||
uint8_t dirColor = 1; // keyword
|
||||
|
||||
if (isHeading) {
|
||||
dirColor = 6; // type (stands out more)
|
||||
} else if (isLink) {
|
||||
dirColor = 5; // operator
|
||||
} else if (isMeta) {
|
||||
dirColor = 7; // reserved
|
||||
} else if (isBlock) {
|
||||
dirColor = 1; // keyword
|
||||
}
|
||||
|
||||
for (int32_t j = dirStart; j < i; j++) {
|
||||
colors[j] = dirColor;
|
||||
}
|
||||
|
||||
// Color the rest of the line as the directive's argument
|
||||
if (isHeading) {
|
||||
// Heading text gets heading color
|
||||
for (int32_t j = i; j < lineLen; j++) {
|
||||
colors[j] = 6;
|
||||
}
|
||||
} else if (isLink) {
|
||||
// .link topicId displayText -- topicId is string-colored, rest is default
|
||||
while (i < lineLen && (line[i] == ' ' || line[i] == '\t')) {
|
||||
i++;
|
||||
}
|
||||
|
||||
int32_t idStart = i;
|
||||
|
||||
while (i < lineLen && line[i] != ' ' && line[i] != '\t') {
|
||||
i++;
|
||||
}
|
||||
|
||||
for (int32_t j = idStart; j < i; j++) {
|
||||
colors[j] = 2; // string
|
||||
}
|
||||
} else if (isMeta) {
|
||||
// Meta argument in string color
|
||||
for (int32_t j = i; j < lineLen; j++) {
|
||||
colors[j] = 2;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Non-directive lines are body text (default color), no special highlighting
|
||||
}
|
||||
|
||||
|
||||
// BASIC keyword colorizer (simplified version of IDE's basicColorize)
|
||||
static void basColorize(const char *line, int32_t lineLen, uint8_t *colors, void *ctx) {
|
||||
(void)ctx;
|
||||
|
||||
static const char *sBasKeywords[] = {
|
||||
"AND", "AS", "BYVAL", "CALL", "CASE", "CLOSE", "CONST",
|
||||
"CREATECONTROL", "CREATEFORM",
|
||||
"DATA", "DECLARE", "DIM", "DO", "DOEVENTS",
|
||||
"ELSE", "ELSEIF", "END", "ERASE", "EXIT",
|
||||
"FOR", "FUNCTION", "GET", "GOSUB", "GOTO",
|
||||
"HIDE", "IF", "INPUT", "IS",
|
||||
"LET", "LIBRARY", "LINE", "LOAD", "LOOP",
|
||||
"ME", "MOD", "MSGBOX",
|
||||
"NEXT", "NOTHING", "NOT",
|
||||
"ON", "OPEN", "OPTIONAL", "OPTION", "OR",
|
||||
"PRINT", "PUT",
|
||||
"RANDOMIZE", "READ", "REDIM", "REMOVECONTROL", "RESTORE", "RESUME", "RETURN",
|
||||
"SEEK", "SELECT", "SET", "SETEVENT", "SHARED", "SHELL", "SHOW", "SLEEP",
|
||||
"STATIC", "STEP", "SUB", "SWAP",
|
||||
"THEN", "TO", "TYPE",
|
||||
"UNLOAD", "UNTIL",
|
||||
"WEND", "WHILE", "WRITE",
|
||||
"XOR",
|
||||
NULL
|
||||
};
|
||||
|
||||
static const char *sBasTypes[] = {
|
||||
"BOOLEAN", "DOUBLE", "FALSE", "INTEGER", "LONG", "SINGLE", "STRING", "TRUE",
|
||||
NULL
|
||||
};
|
||||
|
||||
int32_t i = 0;
|
||||
|
||||
while (i < lineLen) {
|
||||
char ch = line[i];
|
||||
|
||||
// Comment
|
||||
if (ch == '\'') {
|
||||
while (i < lineLen) {
|
||||
colors[i++] = 3;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// String literal
|
||||
if (ch == '"') {
|
||||
colors[i++] = 2;
|
||||
|
||||
while (i < lineLen && line[i] != '"') {
|
||||
colors[i++] = 2;
|
||||
}
|
||||
|
||||
if (i < lineLen) {
|
||||
colors[i++] = 2;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Number
|
||||
if (isdigit((unsigned char)ch) || (ch == '&' && i + 1 < lineLen && (line[i + 1] == 'H' || line[i + 1] == 'h'))) {
|
||||
while (i < lineLen && (isxdigit((unsigned char)line[i]) || line[i] == '.' || line[i] == '&' || line[i] == 'H' || line[i] == 'h')) {
|
||||
colors[i++] = 4;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Identifier or keyword
|
||||
if (isalpha((unsigned char)ch) || ch == '_') {
|
||||
int32_t start = i;
|
||||
|
||||
while (i < lineLen && (isalnum((unsigned char)line[i]) || line[i] == '_' || line[i] == '$' || line[i] == '%' || line[i] == '&' || line[i] == '!' || line[i] == '#')) {
|
||||
i++;
|
||||
}
|
||||
|
||||
int32_t wordLen = i - start;
|
||||
|
||||
// Check for REM
|
||||
if (wordLen == 3 && (line[start] == 'R' || line[start] == 'r') && (line[start + 1] == 'E' || line[start + 1] == 'e') && (line[start + 2] == 'M' || line[start + 2] == 'm')) {
|
||||
for (int32_t j = start; j < lineLen; j++) {
|
||||
colors[j] = 3;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Uppercase for comparison
|
||||
char upper[48];
|
||||
int32_t uLen = wordLen < 47 ? wordLen : 47;
|
||||
|
||||
for (int32_t j = 0; j < uLen; j++) {
|
||||
upper[j] = (char)toupper((unsigned char)line[start + j]);
|
||||
}
|
||||
|
||||
// Strip type suffix for matching
|
||||
while (uLen > 0 && (upper[uLen - 1] == '$' || upper[uLen - 1] == '%' || upper[uLen - 1] == '&' || upper[uLen - 1] == '!' || upper[uLen - 1] == '#')) {
|
||||
uLen--;
|
||||
}
|
||||
|
||||
upper[uLen] = '\0';
|
||||
|
||||
// Check keywords
|
||||
uint8_t c = 0;
|
||||
|
||||
for (int32_t j = 0; sBasKeywords[j]; j++) {
|
||||
if (strcmp(upper, sBasKeywords[j]) == 0) {
|
||||
c = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (c == 0) {
|
||||
for (int32_t j = 0; sBasTypes[j]; j++) {
|
||||
if (strcmp(upper, sBasTypes[j]) == 0) {
|
||||
c = 6;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t j = start; j < i; j++) {
|
||||
colors[j] = c;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Operator
|
||||
if (ch == '=' || ch == '<' || ch == '>' || ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '\\') {
|
||||
colors[i++] = 5;
|
||||
continue;
|
||||
}
|
||||
|
||||
colors[i++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// SetSyntaxMode method: activate a built-in colorizer
|
||||
static void basSetSyntaxMode(WidgetT *w, const char *mode) {
|
||||
if (!mode || !*mode) {
|
||||
wgtTextAreaSetColorize(w, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcasecmp(mode, "dhs") == 0 || strcasecmp(mode, "help") == 0) {
|
||||
wgtTextAreaSetColorize(w, dhsColorize, NULL);
|
||||
} else if (strcasecmp(mode, "bas") == 0 || strcasecmp(mode, "basic") == 0) {
|
||||
wgtTextAreaSetColorize(w, basColorize, NULL);
|
||||
} else {
|
||||
wgtTextAreaSetColorize(w, NULL, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// BASIC wrapper: GetWordAtCursor returns the word as a string
|
||||
static const char *basGetWordAtCursor(const WidgetT *w) {
|
||||
static char buf[256];
|
||||
wgtTextAreaGetWordAtCursor(w, buf, sizeof(buf));
|
||||
return buf;
|
||||
}
|
||||
|
||||
// TextArea BASIC interface
|
||||
static const WgtPropDescT sTextAreaProps[] = {
|
||||
{ "CursorLine", WGT_IFACE_INT, (void *)wgtTextAreaGetCursorLine, NULL, NULL }
|
||||
};
|
||||
|
||||
static const WgtMethodDescT sTextAreaMethods[] = {
|
||||
{ "FindNext", WGT_SIG_STR_BOOL_BOOL, (void *)wgtTextAreaFindNext },
|
||||
{ "GetWordAtCursor", WGT_SIG_RET_STR, (void *)basGetWordAtCursor },
|
||||
{ "GoToLine", WGT_SIG_INT, (void *)wgtTextAreaGoToLine },
|
||||
{ "ReplaceAll", WGT_SIG_STR_STR_BOOL, (void *)wgtTextAreaReplaceAll },
|
||||
{ "SetAutoIndent", WGT_SIG_BOOL, (void *)wgtTextAreaSetAutoIndent },
|
||||
{ "SetCaptureTabs", WGT_SIG_BOOL, (void *)wgtTextAreaSetCaptureTabs },
|
||||
{ "SetShowLineNumbers", WGT_SIG_BOOL, (void *)wgtTextAreaSetShowLineNumbers },
|
||||
{ "SetSyntaxMode", WGT_SIG_STR, (void *)basSetSyntaxMode },
|
||||
{ "SetTabWidth", WGT_SIG_INT, (void *)wgtTextAreaSetTabWidth },
|
||||
{ "SetUseTabChar", WGT_SIG_BOOL, (void *)wgtTextAreaSetUseTabChar },
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIfaceTextInput = {
|
||||
.basName = "TextBox",
|
||||
.props = NULL,
|
||||
|
|
@ -3462,10 +3753,10 @@ static const WgtIfaceT sIfaceTextInput = {
|
|||
|
||||
static const WgtIfaceT sIfaceTextArea = {
|
||||
.basName = "TextArea",
|
||||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = NULL,
|
||||
.methodCount = 0,
|
||||
.props = sTextAreaProps,
|
||||
.propCount = 1,
|
||||
.methods = sTextAreaMethods,
|
||||
.methodCount = 10,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT_INT,
|
||||
|
|
|
|||
|
|
@ -2,22 +2,45 @@
|
|||
.title TreeView
|
||||
.toc 1 TreeView
|
||||
.index TreeView
|
||||
.index AddItem
|
||||
.index AddChildItem
|
||||
|
||||
.h1 TreeView
|
||||
|
||||
VB Equivalent: TreeView -- DVX Widget: treeview
|
||||
|
||||
A hierarchical tree of expandable/collapsible nodes. Nodes are created via the C API (wgtTreeItem). Supports multi-select and drag-to-reorder.
|
||||
A hierarchical tree of expandable/collapsible nodes. Supports multi-select and drag-to-reorder. Items are accessed by depth-first index (0-based).
|
||||
|
||||
.h2 Type-Specific Methods
|
||||
|
||||
.table
|
||||
Method Parameters Description
|
||||
-------------- ---------------------- -------------------------------------------
|
||||
SetMultiSelect Multi As Boolean Enable or disable multi-select mode.
|
||||
SetReorderable Reorderable As Boolean Enable or disable node reordering.
|
||||
Method Description
|
||||
------ -----------
|
||||
AddChildItem parentIndex%, text$ Add a child node under the node at the given index.
|
||||
AddItem text$ Add a root-level node.
|
||||
Clear Remove all nodes.
|
||||
GetItemText$(index%) Returns the text of the node at the given index.
|
||||
IsExpanded(index%) Returns True if the node at the given index is expanded.
|
||||
IsItemSelected(index%) Returns True if the node at the given index is selected.
|
||||
ItemCount() Returns the total number of nodes (all levels).
|
||||
SetExpanded index%, expanded Expand or collapse the node at the given index.
|
||||
SetItemSelected index%, selected Select or deselect the node at the given index.
|
||||
SetMultiSelect multi Enable or disable multi-select mode.
|
||||
SetReorderable reorderable Enable or disable node reordering.
|
||||
.endtable
|
||||
|
||||
Default Event: Click
|
||||
|
||||
.h2 Example
|
||||
|
||||
.code
|
||||
TreeView1.AddItem "Animals"
|
||||
TreeView1.AddChildItem 0, "Cat"
|
||||
TreeView1.AddChildItem 0, "Dog"
|
||||
TreeView1.AddItem "Plants"
|
||||
TreeView1.AddChildItem 3, "Oak"
|
||||
TreeView1.SetExpanded 0, True
|
||||
Print "Items:"; TreeView1.ItemCount()
|
||||
.endcode
|
||||
|
||||
.link ctrl.common.props Common Properties, Events, and Methods
|
||||
|
|
|
|||
|
|
@ -1862,9 +1862,153 @@ static const struct {
|
|||
.itemSetSelected = wgtTreeItemSetSelected
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// BASIC wrapper helpers: index-based tree item access
|
||||
// ============================================================
|
||||
|
||||
static WidgetT *basTreeFindByIndex(WidgetT *parent, int32_t target, int32_t *cur) {
|
||||
for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) {
|
||||
if (c->type != sTreeItemTypeId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*cur == target) {
|
||||
return c;
|
||||
}
|
||||
|
||||
(*cur)++;
|
||||
|
||||
WidgetT *found = basTreeFindByIndex(c, target, cur);
|
||||
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static WidgetT *basTreeItemAt(WidgetT *tree, int32_t index) {
|
||||
int32_t cur = 0;
|
||||
return basTreeFindByIndex(tree, index, &cur);
|
||||
}
|
||||
|
||||
|
||||
static int32_t basTreeCountItems(WidgetT *parent) {
|
||||
int32_t count = 0;
|
||||
|
||||
for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) {
|
||||
if (c->type != sTreeItemTypeId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
count++;
|
||||
count += basTreeCountItems(c);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
// AddItem text$ -- add root-level item
|
||||
static void basAddItem(WidgetT *w, const char *text) {
|
||||
wgtTreeItem(w, text);
|
||||
}
|
||||
|
||||
|
||||
// AddChildItem index%, text$ -- add child under item at depth-first index
|
||||
static void basAddChildItem(WidgetT *w, int32_t parentIdx, const char *text) {
|
||||
WidgetT *parent = basTreeItemAt(w, parentIdx);
|
||||
|
||||
if (parent) {
|
||||
wgtTreeItem(parent, text);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Clear -- remove all items
|
||||
static void basClear(WidgetT *w) {
|
||||
// Remove all tree item children
|
||||
WidgetT *c = w->firstChild;
|
||||
|
||||
while (c) {
|
||||
WidgetT *next = c->nextSibling;
|
||||
|
||||
if (c->type == sTreeItemTypeId) {
|
||||
wgtDestroy(c);
|
||||
}
|
||||
|
||||
c = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ItemCount -- return total number of items (depth-first)
|
||||
static int32_t basItemCount(const WidgetT *w) {
|
||||
return basTreeCountItems((WidgetT *)w);
|
||||
}
|
||||
|
||||
|
||||
// SetExpanded index%, expanded -- expand/collapse item at index
|
||||
static void basSetExpanded(WidgetT *w, int32_t idx, bool expanded) {
|
||||
WidgetT *item = basTreeItemAt(w, idx);
|
||||
|
||||
if (item) {
|
||||
wgtTreeItemSetExpanded(item, expanded);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// IsExpanded(index%) -- check if item at index is expanded
|
||||
static bool basIsExpanded(const WidgetT *w, int32_t idx) {
|
||||
WidgetT *item = basTreeItemAt((WidgetT *)w, idx);
|
||||
return item ? wgtTreeItemIsExpanded(item) : false;
|
||||
}
|
||||
|
||||
|
||||
// SetItemSelected index%, selected -- select/deselect item at index
|
||||
static void basSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
|
||||
WidgetT *item = basTreeItemAt(w, idx);
|
||||
|
||||
if (item) {
|
||||
wgtTreeItemSetSelected(item, selected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// IsItemSelected(index%) -- check if item at index is selected
|
||||
static bool basIsItemSelected(const WidgetT *w, int32_t idx) {
|
||||
WidgetT *item = basTreeItemAt((WidgetT *)w, idx);
|
||||
return item ? wgtTreeItemIsSelected(item) : false;
|
||||
}
|
||||
|
||||
|
||||
// GetItemText$(index%) -- get item text at index
|
||||
static const char *basGetItemText(const WidgetT *w, int32_t idx) {
|
||||
WidgetT *item = basTreeItemAt((WidgetT *)w, idx);
|
||||
|
||||
if (item && item->data) {
|
||||
TreeItemDataT *ti = (TreeItemDataT *)item->data;
|
||||
return ti->text ? ti->text : "";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
static const WgtMethodDescT sMethods[] = {
|
||||
{ "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtTreeViewSetMultiSelect },
|
||||
{ "SetReorderable", WGT_SIG_BOOL, (void *)wgtTreeViewSetReorderable }
|
||||
{ "AddChildItem", WGT_SIG_INT_STR, (void *)basAddChildItem },
|
||||
{ "AddItem", WGT_SIG_STR, (void *)basAddItem },
|
||||
{ "Clear", WGT_SIG_VOID, (void *)basClear },
|
||||
{ "GetItemText", WGT_SIG_RET_STR_INT, (void *)basGetItemText },
|
||||
{ "IsExpanded", WGT_SIG_RET_BOOL_INT, (void *)basIsExpanded },
|
||||
{ "IsItemSelected", WGT_SIG_RET_BOOL_INT, (void *)basIsItemSelected },
|
||||
{ "ItemCount", WGT_SIG_RET_INT, (void *)basItemCount },
|
||||
{ "SetExpanded", WGT_SIG_INT_BOOL, (void *)basSetExpanded },
|
||||
{ "SetItemSelected", WGT_SIG_INT_BOOL, (void *)basSetItemSelected },
|
||||
{ "SetMultiSelect", WGT_SIG_BOOL, (void *)wgtTreeViewSetMultiSelect },
|
||||
{ "SetReorderable", WGT_SIG_BOOL, (void *)wgtTreeViewSetReorderable },
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
|
|
@ -1872,7 +2016,7 @@ static const WgtIfaceT sIface = {
|
|||
.props = NULL,
|
||||
.propCount = 0,
|
||||
.methods = sMethods,
|
||||
.methodCount = 2,
|
||||
.methodCount = 11,
|
||||
.events = NULL,
|
||||
.eventCount = 0,
|
||||
.createSig = WGT_CREATE_PARENT,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue