Adding project support and breaking a lot of stuff.
This commit is contained in:
parent
5dd632a862
commit
82939c3f27
33 changed files with 2699 additions and 391 deletions
|
|
@ -87,6 +87,7 @@ typedef struct {
|
||||||
static DxeAppContextT *sCtx = NULL;
|
static DxeAppContextT *sCtx = NULL;
|
||||||
static AppContextT *sAc = NULL;
|
static AppContextT *sAc = NULL;
|
||||||
static WindowT *sWin = NULL;
|
static WindowT *sWin = NULL;
|
||||||
|
static PrefsHandleT *sPrefs = NULL;
|
||||||
|
|
||||||
// Saved state for Cancel
|
// Saved state for Cancel
|
||||||
static uint8_t sSavedColorRgb[ColorCountE][3];
|
static uint8_t sSavedColorRgb[ColorCountE][3];
|
||||||
|
|
@ -338,7 +339,7 @@ static void buildMouseTab(WidgetT *page) {
|
||||||
sDblClickSldr->weight = 100;
|
sDblClickSldr->weight = 100;
|
||||||
sDblClickSldr->onChange = onDblClickSlider;
|
sDblClickSldr->onChange = onDblClickSlider;
|
||||||
|
|
||||||
int32_t dblMs = prefsGetInt("mouse", "doubleclick", 500);
|
int32_t dblMs = prefsGetInt(sPrefs, "mouse", "doubleclick", 500);
|
||||||
wgtSliderSetValue(sDblClickSldr, dblMs);
|
wgtSliderSetValue(sDblClickSldr, dblMs);
|
||||||
wgtLabel(dblRow, "Slow ");
|
wgtLabel(dblRow, "Slow ");
|
||||||
sDblClickLbl = wgtLabel(dblRow, "");
|
sDblClickLbl = wgtLabel(dblRow, "");
|
||||||
|
|
@ -358,7 +359,7 @@ static void buildMouseTab(WidgetT *page) {
|
||||||
sAccelDrop->onChange = onAccelChange;
|
sAccelDrop->onChange = onAccelChange;
|
||||||
wgtDropdownSetItems(sAccelDrop, accelItems, 4);
|
wgtDropdownSetItems(sAccelDrop, accelItems, 4);
|
||||||
|
|
||||||
const char *accelStr = prefsGetString("mouse", "acceleration", "medium");
|
const char *accelStr = prefsGetString(sPrefs, "mouse", "acceleration", "medium");
|
||||||
|
|
||||||
if (strcmp(accelStr, "off") == 0) {
|
if (strcmp(accelStr, "off") == 0) {
|
||||||
wgtDropdownSetSelected(sAccelDrop, 0);
|
wgtDropdownSetSelected(sAccelDrop, 0);
|
||||||
|
|
@ -804,9 +805,9 @@ static void onOk(WidgetT *w) {
|
||||||
|
|
||||||
// Save mouse settings
|
// Save mouse settings
|
||||||
int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop);
|
int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop);
|
||||||
prefsSetString("mouse", "wheel", wheelSel == 1 ? "reversed" : "normal");
|
prefsSetString(sPrefs, "mouse", "wheel", wheelSel == 1 ? "reversed" : "normal");
|
||||||
prefsSetInt("mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
|
prefsSetInt(sPrefs, "mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
|
||||||
prefsSetString("mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop)));
|
prefsSetString(sPrefs, "mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop)));
|
||||||
|
|
||||||
// Save colors to INI
|
// Save colors to INI
|
||||||
for (int32_t i = 0; i < ColorCountE; i++) {
|
for (int32_t i = 0; i < ColorCountE; i++) {
|
||||||
|
|
@ -817,14 +818,14 @@ static void onOk(WidgetT *w) {
|
||||||
|
|
||||||
char val[16];
|
char val[16];
|
||||||
snprintf(val, sizeof(val), "%d,%d,%d", r, g, b);
|
snprintf(val, sizeof(val), "%d,%d,%d", r, g, b);
|
||||||
prefsSetString("colors", dvxColorName((ColorIdE)i), val);
|
prefsSetString(sPrefs, "colors", dvxColorName((ColorIdE)i), val);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save desktop settings
|
// Save desktop settings
|
||||||
if (sWallpaperPath[0]) {
|
if (sWallpaperPath[0]) {
|
||||||
prefsSetString("desktop", "wallpaper", sWallpaperPath);
|
prefsSetString(sPrefs, "desktop", "wallpaper", sWallpaperPath);
|
||||||
} else {
|
} else {
|
||||||
prefsRemove("desktop", "wallpaper");
|
prefsRemove(sPrefs, "desktop", "wallpaper");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *modeStr = "stretch";
|
const char *modeStr = "stretch";
|
||||||
|
|
@ -835,14 +836,16 @@ static void onOk(WidgetT *w) {
|
||||||
modeStr = "center";
|
modeStr = "center";
|
||||||
}
|
}
|
||||||
|
|
||||||
prefsSetString("desktop", "mode", modeStr);
|
prefsSetString(sPrefs, "desktop", "mode", modeStr);
|
||||||
|
|
||||||
// Save video settings
|
// Save video settings
|
||||||
prefsSetInt("video", "width", sAc->display.width);
|
prefsSetInt(sPrefs, "video", "width", sAc->display.width);
|
||||||
prefsSetInt("video", "height", sAc->display.height);
|
prefsSetInt(sPrefs, "video", "height", sAc->display.height);
|
||||||
prefsSetInt("video", "bpp", sAc->display.format.bitsPerPixel);
|
prefsSetInt(sPrefs, "video", "bpp", sAc->display.format.bitsPerPixel);
|
||||||
|
|
||||||
prefsSave();
|
prefsSave(sPrefs);
|
||||||
|
prefsClose(sPrefs);
|
||||||
|
sPrefs = NULL;
|
||||||
dvxDestroyWindow(sAc, sWin);
|
dvxDestroyWindow(sAc, sWin);
|
||||||
sWin = NULL;
|
sWin = NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -851,6 +854,8 @@ static void onOk(WidgetT *w) {
|
||||||
static void onCancel(WidgetT *w) {
|
static void onCancel(WidgetT *w) {
|
||||||
(void)w;
|
(void)w;
|
||||||
restoreSnapshot();
|
restoreSnapshot();
|
||||||
|
prefsClose(sPrefs);
|
||||||
|
sPrefs = NULL;
|
||||||
dvxDestroyWindow(sAc, sWin);
|
dvxDestroyWindow(sAc, sWin);
|
||||||
sWin = NULL;
|
sWin = NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -858,6 +863,8 @@ static void onCancel(WidgetT *w) {
|
||||||
|
|
||||||
static void onClose(WindowT *win) {
|
static void onClose(WindowT *win) {
|
||||||
restoreSnapshot();
|
restoreSnapshot();
|
||||||
|
prefsClose(sPrefs);
|
||||||
|
sPrefs = NULL;
|
||||||
dvxDestroyWindow(sAc, win);
|
dvxDestroyWindow(sAc, win);
|
||||||
sWin = NULL;
|
sWin = NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -1085,6 +1092,7 @@ static void updateSwatch(void) {
|
||||||
int32_t appMain(DxeAppContextT *ctx) {
|
int32_t appMain(DxeAppContextT *ctx) {
|
||||||
sCtx = ctx;
|
sCtx = ctx;
|
||||||
sAc = ctx->shellCtx;
|
sAc = ctx->shellCtx;
|
||||||
|
sPrefs = prefsLoad("CONFIG/DVX.INI");
|
||||||
|
|
||||||
int32_t winX = (sAc->display.width - CP_WIN_W) / 2;
|
int32_t winX = (sAc->display.width - CP_WIN_W) / 2;
|
||||||
int32_t winY = (sAc->display.height - CP_WIN_H) / 2;
|
int32_t winY = (sAc->display.height - CP_WIN_H) / 2;
|
||||||
|
|
|
||||||
|
|
@ -31,13 +31,13 @@ COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/s
|
||||||
FORMRT_OBJS = $(OBJDIR)/formrt.o
|
FORMRT_OBJS = $(OBJDIR)/formrt.o
|
||||||
|
|
||||||
# IDE app objects
|
# IDE app objects
|
||||||
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
|
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
|
||||||
APP_OBJS = $(IDE_OBJS) $(FORMRT_OBJS)
|
APP_OBJS = $(IDE_OBJS) $(FORMRT_OBJS)
|
||||||
APP_TARGET = $(APPDIR)/dvxbasic.app
|
APP_TARGET = $(APPDIR)/dvxbasic.app
|
||||||
|
|
||||||
# Native test programs (host gcc, not cross-compiled)
|
# Native test programs (host gcc, not cross-compiled)
|
||||||
HOSTCC = gcc
|
HOSTCC = gcc
|
||||||
HOSTCFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -I.
|
HOSTCFLAGS = -O2 -Wall -Wextra -Wno-type-limits -Wno-sign-compare -I. -I../../core
|
||||||
BINDIR = ../bin
|
BINDIR = ../bin
|
||||||
|
|
||||||
TEST_COMPILER = $(BINDIR)/test_compiler
|
TEST_COMPILER = $(BINDIR)/test_compiler
|
||||||
|
|
@ -45,10 +45,11 @@ TEST_VM = $(BINDIR)/test_vm
|
||||||
TEST_LEX = $(BINDIR)/test_lex
|
TEST_LEX = $(BINDIR)/test_lex
|
||||||
TEST_QUICK = $(BINDIR)/test_quick
|
TEST_QUICK = $(BINDIR)/test_quick
|
||||||
|
|
||||||
TEST_COMPILER_SRCS = test_compiler.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c
|
STB_DS_IMPL = ../../core/thirdparty/stb_ds_impl.c
|
||||||
TEST_VM_SRCS = test_vm.c runtime/vm.c runtime/values.c
|
TEST_COMPILER_SRCS = test_compiler.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c $(STB_DS_IMPL)
|
||||||
|
TEST_VM_SRCS = test_vm.c runtime/vm.c runtime/values.c $(STB_DS_IMPL)
|
||||||
TEST_LEX_SRCS = test_lex.c compiler/lexer.c
|
TEST_LEX_SRCS = test_lex.c compiler/lexer.c
|
||||||
TEST_QUICK_SRCS = test_quick.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c
|
TEST_QUICK_SRCS = test_quick.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c $(STB_DS_IMPL)
|
||||||
|
|
||||||
.PHONY: all clean tests
|
.PHONY: all clean tests
|
||||||
|
|
||||||
|
|
@ -96,7 +97,10 @@ $(OBJDIR)/formrt.o: formrt/formrt.c formrt/formrt.h compiler/codegen.h runtime/v
|
||||||
$(OBJDIR)/ideDesigner.o: ide/ideDesigner.c ide/ideDesigner.h | $(OBJDIR)
|
$(OBJDIR)/ideDesigner.o: ide/ideDesigner.c ide/ideDesigner.h | $(OBJDIR)
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
$(OBJDIR)/ideMain.o: ide/ideMain.c ide/ideDesigner.h ide/ideToolbox.h ide/ideProperties.h compiler/parser.h runtime/vm.h | $(OBJDIR)
|
$(OBJDIR)/ideMain.o: ide/ideMain.c ide/ideDesigner.h ide/ideProject.h ide/ideToolbox.h ide/ideProperties.h compiler/parser.h runtime/vm.h | $(OBJDIR)
|
||||||
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
|
$(OBJDIR)/ideProject.o: ide/ideProject.c ide/ideProject.h | $(OBJDIR)
|
||||||
$(CC) $(CFLAGS) -c -o $@ $<
|
$(CC) $(CFLAGS) -c -o $@ $<
|
||||||
|
|
||||||
$(OBJDIR)/ideProperties.o: ide/ideProperties.c ide/ideProperties.h ide/ideDesigner.h | $(OBJDIR)
|
$(OBJDIR)/ideProperties.o: ide/ideProperties.c ide/ideProperties.h ide/ideDesigner.h | $(OBJDIR)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "codegen.h"
|
#include "codegen.h"
|
||||||
#include "symtab.h"
|
#include "symtab.h"
|
||||||
#include "opcodes.h"
|
#include "opcodes.h"
|
||||||
|
#include "thirdparty/stb_ds_wrap.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
@ -13,11 +14,9 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool basAddData(BasCodeGenT *cg, BasValueT val) {
|
bool basAddData(BasCodeGenT *cg, BasValueT val) {
|
||||||
if (cg->dataCount >= BAS_MAX_CONSTANTS) {
|
BasValueT copy = basValCopy(val);
|
||||||
return false;
|
arrput(cg->dataPool, copy);
|
||||||
}
|
cg->dataCount = (int32_t)arrlen(cg->dataPool);
|
||||||
|
|
||||||
cg->dataPool[cg->dataCount++] = basValCopy(val);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,12 +33,10 @@ uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cg->constCount >= BAS_MAX_CONSTANTS) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t idx = (uint16_t)cg->constCount;
|
uint16_t idx = (uint16_t)cg->constCount;
|
||||||
cg->constants[cg->constCount++] = basStringNew(text, len);
|
BasStringT *s = basStringNew(text, len);
|
||||||
|
arrput(cg->constants, s);
|
||||||
|
cg->constCount = (int32_t)arrlen(cg->constants);
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,6 +172,12 @@ void basCodeGenFree(BasCodeGenT *cg) {
|
||||||
basValRelease(&cg->dataPool[i]);
|
basValRelease(&cg->dataPool[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrfree(cg->code);
|
||||||
|
arrfree(cg->constants);
|
||||||
|
arrfree(cg->dataPool);
|
||||||
|
cg->code = NULL;
|
||||||
|
cg->constants = NULL;
|
||||||
|
cg->dataPool = NULL;
|
||||||
cg->constCount = 0;
|
cg->constCount = 0;
|
||||||
cg->dataCount = 0;
|
cg->dataCount = 0;
|
||||||
cg->codeLen = 0;
|
cg->codeLen = 0;
|
||||||
|
|
@ -204,9 +207,8 @@ int32_t basCodePos(const BasCodeGenT *cg) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void basEmit8(BasCodeGenT *cg, uint8_t b) {
|
void basEmit8(BasCodeGenT *cg, uint8_t b) {
|
||||||
if (cg->codeLen < BAS_MAX_CODE) {
|
arrput(cg->code, b);
|
||||||
cg->code[cg->codeLen++] = b;
|
cg->codeLen = (int32_t)arrlen(cg->code);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -215,10 +217,11 @@ void basEmit8(BasCodeGenT *cg, uint8_t b) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void basEmit16(BasCodeGenT *cg, int16_t v) {
|
void basEmit16(BasCodeGenT *cg, int16_t v) {
|
||||||
if (cg->codeLen + 2 <= BAS_MAX_CODE) {
|
uint8_t buf[2];
|
||||||
memcpy(&cg->code[cg->codeLen], &v, 2);
|
memcpy(buf, &v, 2);
|
||||||
cg->codeLen += 2;
|
arrput(cg->code, buf[0]);
|
||||||
}
|
arrput(cg->code, buf[1]);
|
||||||
|
cg->codeLen = (int32_t)arrlen(cg->code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -227,10 +230,14 @@ void basEmit16(BasCodeGenT *cg, int16_t v) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void basEmitDouble(BasCodeGenT *cg, double v) {
|
void basEmitDouble(BasCodeGenT *cg, double v) {
|
||||||
if (cg->codeLen + (int32_t)sizeof(double) <= BAS_MAX_CODE) {
|
uint8_t buf[sizeof(double)];
|
||||||
memcpy(&cg->code[cg->codeLen], &v, sizeof(double));
|
memcpy(buf, &v, sizeof(double));
|
||||||
cg->codeLen += (int32_t)sizeof(double);
|
|
||||||
|
for (int32_t i = 0; i < (int32_t)sizeof(double); i++) {
|
||||||
|
arrput(cg->code, buf[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cg->codeLen = (int32_t)arrlen(cg->code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -239,10 +246,14 @@ void basEmitDouble(BasCodeGenT *cg, double v) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void basEmitFloat(BasCodeGenT *cg, float v) {
|
void basEmitFloat(BasCodeGenT *cg, float v) {
|
||||||
if (cg->codeLen + (int32_t)sizeof(float) <= BAS_MAX_CODE) {
|
uint8_t buf[sizeof(float)];
|
||||||
memcpy(&cg->code[cg->codeLen], &v, sizeof(float));
|
memcpy(buf, &v, sizeof(float));
|
||||||
cg->codeLen += (int32_t)sizeof(float);
|
|
||||||
|
for (int32_t i = 0; i < (int32_t)sizeof(float); i++) {
|
||||||
|
arrput(cg->code, buf[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cg->codeLen = (int32_t)arrlen(cg->code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -251,10 +262,11 @@ void basEmitFloat(BasCodeGenT *cg, float v) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void basEmitU16(BasCodeGenT *cg, uint16_t v) {
|
void basEmitU16(BasCodeGenT *cg, uint16_t v) {
|
||||||
if (cg->codeLen + 2 <= BAS_MAX_CODE) {
|
uint8_t buf[2];
|
||||||
memcpy(&cg->code[cg->codeLen], &v, 2);
|
memcpy(buf, &v, 2);
|
||||||
cg->codeLen += 2;
|
arrput(cg->code, buf[0]);
|
||||||
}
|
arrput(cg->code, buf[1]);
|
||||||
|
cg->codeLen = (int32_t)arrlen(cg->code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,16 +19,13 @@
|
||||||
// Code generator state
|
// Code generator state
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
#define BAS_MAX_CODE 65536
|
|
||||||
#define BAS_MAX_CONSTANTS 1024
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t code[BAS_MAX_CODE];
|
uint8_t *code; // stb_ds dynamic array
|
||||||
int32_t codeLen;
|
int32_t codeLen;
|
||||||
BasStringT *constants[BAS_MAX_CONSTANTS];
|
BasStringT **constants; // stb_ds dynamic array
|
||||||
int32_t constCount;
|
int32_t constCount;
|
||||||
int32_t globalCount;
|
int32_t globalCount;
|
||||||
BasValueT dataPool[BAS_MAX_CONSTANTS];
|
BasValueT *dataPool; // stb_ds dynamic array
|
||||||
int32_t dataCount;
|
int32_t dataCount;
|
||||||
} BasCodeGenT;
|
} BasCodeGenT;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include "opcodes.h"
|
#include "opcodes.h"
|
||||||
|
#include "thirdparty/stb_ds_wrap.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -529,8 +530,8 @@ static void emitFunctionCall(BasParserT *p, BasSymbolT *sym) {
|
||||||
basEmit8(&p->cg, baseSlot);
|
basEmit8(&p->cg, baseSlot);
|
||||||
|
|
||||||
// If not yet defined, record the address for backpatching
|
// If not yet defined, record the address for backpatching
|
||||||
if (!sym->isDefined && sym->patchCount < BAS_MAX_CALL_PATCHES) {
|
if (!sym->isDefined && true) {
|
||||||
sym->patchAddrs[sym->patchCount++] = addrPos;
|
arrput(sym->patchAddrs, addrPos); sym->patchCount = (int32_t)arrlen(sym->patchAddrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -574,9 +575,8 @@ static void emitJumpToLabel(BasParserT *p, uint8_t opcode, const char *labelName
|
||||||
basEmit16(&p->cg, 0);
|
basEmit16(&p->cg, 0);
|
||||||
|
|
||||||
// Record patch address for backpatching when label is defined
|
// Record patch address for backpatching when label is defined
|
||||||
if (sym->patchCount < BAS_MAX_CALL_PATCHES) {
|
arrput(sym->patchAddrs, patchAddr);
|
||||||
sym->patchAddrs[sym->patchCount++] = patchAddr;
|
sym->patchCount = (int32_t)arrlen(sym->patchAddrs);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1616,8 +1616,8 @@ static void parseAssignOrCall(BasParserT *p) {
|
||||||
basEmit8(&p->cg, (uint8_t)argc);
|
basEmit8(&p->cg, (uint8_t)argc);
|
||||||
basEmit8(&p->cg, baseSlot);
|
basEmit8(&p->cg, baseSlot);
|
||||||
|
|
||||||
if (!sym->isDefined && sym->patchCount < BAS_MAX_CALL_PATCHES) {
|
if (!sym->isDefined && true) {
|
||||||
sym->patchAddrs[sym->patchCount++] = addrPos;
|
arrput(sym->patchAddrs, addrPos); sym->patchCount = (int32_t)arrlen(sym->patchAddrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2248,12 +2248,17 @@ static void parseDimBounds(BasParserT *p, int32_t *outDims) {
|
||||||
int32_t exprLen = basCodePos(&p->cg) - exprStart;
|
int32_t exprLen = basCodePos(&p->cg) - exprStart;
|
||||||
int32_t insertLen = 3; // OP_PUSH_INT16 + 2 bytes
|
int32_t insertLen = 3; // OP_PUSH_INT16 + 2 bytes
|
||||||
|
|
||||||
if (basCodePos(&p->cg) + insertLen <= BAS_MAX_CODE) {
|
{
|
||||||
|
// Grow the array to make room for the insertion
|
||||||
|
for (int32_t pad = 0; pad < insertLen; pad++) {
|
||||||
|
arrput(p->cg.code, 0);
|
||||||
|
}
|
||||||
|
|
||||||
memmove(&p->cg.code[exprStart + insertLen], &p->cg.code[exprStart], exprLen);
|
memmove(&p->cg.code[exprStart + insertLen], &p->cg.code[exprStart], exprLen);
|
||||||
p->cg.code[exprStart] = OP_PUSH_INT16;
|
p->cg.code[exprStart] = OP_PUSH_INT16;
|
||||||
int16_t lbound = (int16_t)p->optionBase;
|
int16_t lbound = (int16_t)p->optionBase;
|
||||||
memcpy(&p->cg.code[exprStart + 1], &lbound, 2);
|
memcpy(&p->cg.code[exprStart + 1], &lbound, 2);
|
||||||
p->cg.codeLen += insertLen;
|
p->cg.codeLen = (int32_t)arrlen(p->cg.code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3289,9 +3294,8 @@ static void parseOnError(BasParserT *p) {
|
||||||
int32_t patchAddr = basCodePos(&p->cg);
|
int32_t patchAddr = basCodePos(&p->cg);
|
||||||
basEmit16(&p->cg, 0);
|
basEmit16(&p->cg, 0);
|
||||||
|
|
||||||
if (sym->patchCount < BAS_MAX_CALL_PATCHES) {
|
arrput(sym->patchAddrs, patchAddr);
|
||||||
sym->patchAddrs[sym->patchCount++] = patchAddr;
|
sym->patchCount = (int32_t)arrlen(sym->patchAddrs);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4178,8 +4182,8 @@ static void parseStatement(BasParserT *p) {
|
||||||
basEmit8(&p->cg, 0);
|
basEmit8(&p->cg, 0);
|
||||||
basEmit8(&p->cg, baseSlot);
|
basEmit8(&p->cg, baseSlot);
|
||||||
|
|
||||||
if (!sym->isDefined && sym->patchCount < BAS_MAX_CALL_PATCHES) {
|
if (!sym->isDefined && true) {
|
||||||
sym->patchAddrs[sym->patchCount++] = addrPos;
|
arrput(sym->patchAddrs, addrPos); sym->patchCount = (int32_t)arrlen(sym->patchAddrs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4513,26 +4517,23 @@ static void parseType(BasParserT *p) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeSym->fieldCount >= BAS_MAX_UDT_FIELDS) {
|
BasFieldDefT field;
|
||||||
error(p, "Too many fields in TYPE");
|
memset(&field, 0, sizeof(field));
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasFieldDefT *field = &typeSym->fields[typeSym->fieldCount];
|
|
||||||
// Truncation is intentional -- field names are clamped to BAS_MAX_SYMBOL_NAME.
|
// Truncation is intentional -- field names are clamped to BAS_MAX_SYMBOL_NAME.
|
||||||
#pragma GCC diagnostic push
|
#pragma GCC diagnostic push
|
||||||
#pragma GCC diagnostic ignored "-Wformat-truncation"
|
#pragma GCC diagnostic ignored "-Wformat-truncation"
|
||||||
snprintf(field->name, BAS_MAX_SYMBOL_NAME, "%s", p->lex.token.text);
|
snprintf(field.name, BAS_MAX_SYMBOL_NAME, "%s", p->lex.token.text);
|
||||||
#pragma GCC diagnostic pop
|
#pragma GCC diagnostic pop
|
||||||
advance(p);
|
advance(p);
|
||||||
|
|
||||||
expect(p, TOK_AS);
|
expect(p, TOK_AS);
|
||||||
field->dataType = resolveTypeName(p);
|
field.dataType = resolveTypeName(p);
|
||||||
if (field->dataType == BAS_TYPE_UDT) {
|
if (field.dataType == BAS_TYPE_UDT) {
|
||||||
field->udtTypeId = p->lastUdtTypeId;
|
field.udtTypeId = p->lastUdtTypeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
typeSym->fieldCount++;
|
arrput(typeSym->fields, field);
|
||||||
|
typeSym->fieldCount = (int32_t)arrlen(typeSym->fields);
|
||||||
|
|
||||||
expectEndOfStatement(p);
|
expectEndOfStatement(p);
|
||||||
skipNewlines(p);
|
skipNewlines(p);
|
||||||
|
|
@ -4806,4 +4807,14 @@ BasModuleT *basParserBuildModule(BasParserT *p) {
|
||||||
|
|
||||||
void basParserFree(BasParserT *p) {
|
void basParserFree(BasParserT *p) {
|
||||||
basCodeGenFree(&p->cg);
|
basCodeGenFree(&p->cg);
|
||||||
|
|
||||||
|
// Free per-symbol dynamic arrays
|
||||||
|
for (int32_t i = 0; i < p->sym.count; i++) {
|
||||||
|
arrfree(p->sym.symbols[i].patchAddrs);
|
||||||
|
arrfree(p->sym.symbols[i].fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(p->sym.symbols);
|
||||||
|
p->sym.symbols = NULL;
|
||||||
|
p->sym.count = 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
// symtab.c -- DVX BASIC symbol table implementation
|
// symtab.c -- DVX BASIC symbol table implementation
|
||||||
|
|
||||||
#include "symtab.h"
|
#include "symtab.h"
|
||||||
|
#include "thirdparty/stb_ds_wrap.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -31,10 +33,6 @@ static bool namesEqual(const char *a, const char *b) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) {
|
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) {
|
||||||
if (tab->count >= BAS_MAX_SYMBOLS) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for duplicate in current scope
|
// Check for duplicate in current scope
|
||||||
BasScopeE scope = tab->inLocalScope ? SCOPE_LOCAL : SCOPE_GLOBAL;
|
BasScopeE scope = tab->inLocalScope ? SCOPE_LOCAL : SCOPE_GLOBAL;
|
||||||
|
|
||||||
|
|
@ -44,8 +42,11 @@ BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, ui
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BasSymbolT *sym = &tab->symbols[tab->count++];
|
BasSymbolT entry;
|
||||||
memset(sym, 0, sizeof(*sym));
|
memset(&entry, 0, sizeof(entry));
|
||||||
|
arrput(tab->symbols, entry);
|
||||||
|
tab->count = (int32_t)arrlen(tab->symbols);
|
||||||
|
BasSymbolT *sym = &tab->symbols[tab->count - 1];
|
||||||
strncpy(sym->name, name, BAS_MAX_SYMBOL_NAME - 1);
|
strncpy(sym->name, name, BAS_MAX_SYMBOL_NAME - 1);
|
||||||
sym->name[BAS_MAX_SYMBOL_NAME - 1] = '\0';
|
sym->name[BAS_MAX_SYMBOL_NAME - 1] = '\0';
|
||||||
sym->kind = kind;
|
sym->kind = kind;
|
||||||
|
|
@ -128,11 +129,14 @@ void basSymTabInit(BasSymTabT *tab) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void basSymTabLeaveLocal(BasSymTabT *tab) {
|
void basSymTabLeaveLocal(BasSymTabT *tab) {
|
||||||
// Remove all local symbols
|
// Remove all local symbols, freeing their dynamic arrays
|
||||||
int32_t newCount = 0;
|
int32_t newCount = 0;
|
||||||
|
|
||||||
for (int32_t i = 0; i < tab->count; i++) {
|
for (int32_t i = 0; i < tab->count; i++) {
|
||||||
if (tab->symbols[i].scope != SCOPE_LOCAL) {
|
if (tab->symbols[i].scope == SCOPE_LOCAL) {
|
||||||
|
arrfree(tab->symbols[i].patchAddrs);
|
||||||
|
arrfree(tab->symbols[i].fields);
|
||||||
|
} else {
|
||||||
if (i != newCount) {
|
if (i != newCount) {
|
||||||
tab->symbols[newCount] = tab->symbols[i];
|
tab->symbols[newCount] = tab->symbols[i];
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +145,7 @@ void basSymTabLeaveLocal(BasSymTabT *tab) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrsetlen(tab->symbols, newCount);
|
||||||
tab->count = newCount;
|
tab->count = newCount;
|
||||||
tab->inLocalScope = false;
|
tab->inLocalScope = false;
|
||||||
tab->nextLocalIdx = 0;
|
tab->nextLocalIdx = 0;
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,6 @@ typedef enum {
|
||||||
|
|
||||||
#define BAS_MAX_SYMBOL_NAME 64
|
#define BAS_MAX_SYMBOL_NAME 64
|
||||||
#define BAS_MAX_PARAMS 16
|
#define BAS_MAX_PARAMS 16
|
||||||
#define BAS_MAX_CALL_PATCHES 32
|
|
||||||
#define BAS_MAX_UDT_FIELDS 32
|
|
||||||
|
|
||||||
// UDT field definition
|
// UDT field definition
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -74,7 +72,7 @@ typedef struct {
|
||||||
bool paramByVal[BAS_MAX_PARAMS];
|
bool paramByVal[BAS_MAX_PARAMS];
|
||||||
|
|
||||||
// Forward-reference backpatch list (code addresses to patch when defined)
|
// Forward-reference backpatch list (code addresses to patch when defined)
|
||||||
int32_t patchAddrs[BAS_MAX_CALL_PATCHES];
|
int32_t *patchAddrs; // stb_ds dynamic array
|
||||||
int32_t patchCount;
|
int32_t patchCount;
|
||||||
|
|
||||||
// For CONST: the constant value
|
// For CONST: the constant value
|
||||||
|
|
@ -85,7 +83,7 @@ typedef struct {
|
||||||
char constStr[256];
|
char constStr[256];
|
||||||
|
|
||||||
// For TYPE_DEF: field definitions
|
// For TYPE_DEF: field definitions
|
||||||
BasFieldDefT fields[BAS_MAX_UDT_FIELDS];
|
BasFieldDefT *fields; // stb_ds dynamic array
|
||||||
int32_t fieldCount;
|
int32_t fieldCount;
|
||||||
} BasSymbolT;
|
} BasSymbolT;
|
||||||
|
|
||||||
|
|
@ -93,10 +91,8 @@ typedef struct {
|
||||||
// Symbol table
|
// Symbol table
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
#define BAS_MAX_SYMBOLS 512
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
BasSymbolT symbols[BAS_MAX_SYMBOLS];
|
BasSymbolT *symbols; // stb_ds dynamic array
|
||||||
int32_t count;
|
int32_t count;
|
||||||
int32_t nextGlobalIdx; // next global variable slot
|
int32_t nextGlobalIdx; // next global variable slot
|
||||||
int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION)
|
int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION)
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,5 @@ tb_run icon tb_run.bmp
|
||||||
tb_stop icon tb_stop.bmp
|
tb_stop icon tb_stop.bmp
|
||||||
tb_code icon tb_code.bmp
|
tb_code icon tb_code.bmp
|
||||||
tb_design icon tb_design.bmp
|
tb_design icon tb_design.bmp
|
||||||
|
# Placeholder icon (32x32)
|
||||||
|
noicon icon noicon.bmp
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include "dvxDialog.h"
|
#include "dvxDialog.h"
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "widgetBox.h"
|
#include "widgetBox.h"
|
||||||
|
#include "thirdparty/stb_ds_wrap.h"
|
||||||
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
@ -230,7 +231,7 @@ void *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const
|
||||||
(void)ctx;
|
(void)ctx;
|
||||||
BasFormT *form = (BasFormT *)formRef;
|
BasFormT *form = (BasFormT *)formRef;
|
||||||
|
|
||||||
if (!form || form->controlCount >= BAS_MAX_CTRLS) {
|
if (!form) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,8 +258,12 @@ void *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const
|
||||||
wgtSetName(widget, ctrlName);
|
wgtSetName(widget, ctrlName);
|
||||||
|
|
||||||
// Initialize control entry
|
// Initialize control entry
|
||||||
BasControlT *ctrl = &form->controls[form->controlCount++];
|
BasControlT entry;
|
||||||
memset(ctrl, 0, sizeof(*ctrl));
|
memset(&entry, 0, sizeof(entry));
|
||||||
|
snprintf(entry.name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
||||||
|
arrput(form->controls, entry);
|
||||||
|
form->controlCount = (int32_t)arrlen(form->controls);
|
||||||
|
BasControlT *ctrl = &form->controls[form->controlCount - 1];
|
||||||
snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
snprintf(ctrl->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
||||||
ctrl->widget = widget;
|
ctrl->widget = widget;
|
||||||
ctrl->form = form;
|
ctrl->form = form;
|
||||||
|
|
@ -296,12 +301,15 @@ void basFormRtDestroy(BasFormRtT *rt) {
|
||||||
freeListBoxItems(&form->controls[j]);
|
freeListBoxItems(&form->controls[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrfree(form->controls);
|
||||||
|
|
||||||
if (form->window) {
|
if (form->window) {
|
||||||
dvxDestroyWindow(rt->ctx, form->window);
|
dvxDestroyWindow(rt->ctx, form->window);
|
||||||
form->window = NULL;
|
form->window = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arrfree(rt->forms);
|
||||||
free(rt);
|
free(rt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -454,10 +462,6 @@ static BasStringT *basFormRtInputBox(void *ctx, const char *prompt, const char *
|
||||||
void *basFormRtLoadForm(void *ctx, const char *formName) {
|
void *basFormRtLoadForm(void *ctx, const char *formName) {
|
||||||
BasFormRtT *rt = (BasFormRtT *)ctx;
|
BasFormRtT *rt = (BasFormRtT *)ctx;
|
||||||
|
|
||||||
if (rt->formCount >= BAS_MAX_FORMS) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if form already exists
|
// Check if form already exists
|
||||||
for (int32_t i = 0; i < rt->formCount; i++) {
|
for (int32_t i = 0; i < rt->formCount; i++) {
|
||||||
if (strcasecmp(rt->forms[i].name, formName) == 0) {
|
if (strcasecmp(rt->forms[i].name, formName) == 0) {
|
||||||
|
|
@ -478,8 +482,11 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
BasFormT *form = &rt->forms[rt->formCount++];
|
BasFormT entry;
|
||||||
memset(form, 0, sizeof(*form));
|
memset(&entry, 0, sizeof(entry));
|
||||||
|
arrput(rt->forms, entry);
|
||||||
|
rt->formCount = (int32_t)arrlen(rt->forms);
|
||||||
|
BasFormT *form = &rt->forms[rt->formCount - 1];
|
||||||
snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
|
snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
|
||||||
win->onClose = onFormClose;
|
win->onClose = onFormClose;
|
||||||
win->onResize = onFormResize;
|
win->onResize = onFormResize;
|
||||||
|
|
@ -626,15 +633,19 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
||||||
|
|
||||||
wgtSetName(widget, ctrlName);
|
wgtSetName(widget, ctrlName);
|
||||||
|
|
||||||
if (form->controlCount < BAS_MAX_CTRLS) {
|
{
|
||||||
current = &form->controls[form->controlCount++];
|
BasControlT ctrlEntry;
|
||||||
memset(current, 0, sizeof(*current));
|
memset(&ctrlEntry, 0, sizeof(ctrlEntry));
|
||||||
snprintf(current->name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
snprintf(ctrlEntry.name, BAS_MAX_CTRL_NAME, "%s", ctrlName);
|
||||||
snprintf(current->typeName, BAS_MAX_CTRL_NAME, "%s", typeName);
|
snprintf(ctrlEntry.typeName, BAS_MAX_CTRL_NAME, "%s", typeName);
|
||||||
current->widget = widget;
|
ctrlEntry.widget = widget;
|
||||||
current->form = form;
|
ctrlEntry.form = form;
|
||||||
current->iface = wgtGetIface(wgtTypeName);
|
ctrlEntry.iface = wgtGetIface(wgtTypeName);
|
||||||
|
arrput(form->controls, ctrlEntry);
|
||||||
|
form->controlCount = (int32_t)arrlen(form->controls);
|
||||||
|
|
||||||
|
// Re-derive pointer after arrput (may realloc)
|
||||||
|
current = &form->controls[form->controlCount - 1];
|
||||||
widget->userData = current;
|
widget->userData = current;
|
||||||
widget->onClick = onWidgetClick;
|
widget->onClick = onWidgetClick;
|
||||||
widget->onDblClick = onWidgetDblClick;
|
widget->onDblClick = onWidgetDblClick;
|
||||||
|
|
@ -779,6 +790,14 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
||||||
form->window->x = form->frmLeft;
|
form->window->x = form->frmLeft;
|
||||||
form->window->y = form->frmTop;
|
form->window->y = form->frmTop;
|
||||||
}
|
}
|
||||||
|
// Re-wire widget->userData pointers now that the controls array
|
||||||
|
// is finalized. arrput may have reallocated during loading, so
|
||||||
|
// any userData set during parsing could be stale.
|
||||||
|
for (int32_t i = 0; i < form->controlCount; i++) {
|
||||||
|
if (form->controls[i].widget) {
|
||||||
|
form->controls[i].widget->userData = &form->controls[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return form;
|
return form;
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,6 @@ typedef struct BasControlT BasControlT;
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
#define BAS_MAX_CTRL_NAME 32
|
#define BAS_MAX_CTRL_NAME 32
|
||||||
#define BAS_MAX_CTRLS 64 // max controls per form
|
|
||||||
#define BAS_MAX_FORMS 8
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Control instance (a widget on a form)
|
// Control instance (a widget on a form)
|
||||||
|
|
@ -55,7 +53,7 @@ typedef struct BasFormT {
|
||||||
WidgetT *root; // widget root (from wgtInitWindow)
|
WidgetT *root; // widget root (from wgtInitWindow)
|
||||||
WidgetT *contentBox; // VBox/HBox for user controls
|
WidgetT *contentBox; // VBox/HBox for user controls
|
||||||
AppContextT *ctx; // DVX app context
|
AppContextT *ctx; // DVX app context
|
||||||
BasControlT controls[BAS_MAX_CTRLS]; // controls on this form
|
BasControlT *controls; // stb_ds dynamic array
|
||||||
int32_t controlCount;
|
int32_t controlCount;
|
||||||
BasVmT *vm; // VM for event dispatch
|
BasVmT *vm; // VM for event dispatch
|
||||||
BasModuleT *module; // compiled module (for SUB lookup)
|
BasModuleT *module; // compiled module (for SUB lookup)
|
||||||
|
|
@ -79,7 +77,7 @@ typedef struct {
|
||||||
AppContextT *ctx; // DVX app context
|
AppContextT *ctx; // DVX app context
|
||||||
BasVmT *vm; // shared VM instance
|
BasVmT *vm; // shared VM instance
|
||||||
BasModuleT *module; // compiled module
|
BasModuleT *module; // compiled module
|
||||||
BasFormT forms[BAS_MAX_FORMS];
|
BasFormT *forms; // stb_ds dynamic array
|
||||||
int32_t formCount;
|
int32_t formCount;
|
||||||
BasFormT *currentForm; // form currently dispatching events
|
BasFormT *currentForm; // form currently dispatching events
|
||||||
} BasFormRtT;
|
} BasFormRtT;
|
||||||
|
|
|
||||||
|
|
@ -524,7 +524,7 @@ void dsgnNewForm(DsgnStateT *ds, const char *name) {
|
||||||
form->top = 0;
|
form->top = 0;
|
||||||
snprintf(form->layout, DSGN_MAX_NAME, "VBox");
|
snprintf(form->layout, DSGN_MAX_NAME, "VBox");
|
||||||
form->centered = true;
|
form->centered = true;
|
||||||
form->autoSize = true;
|
form->autoSize = false;
|
||||||
form->resizable = true;
|
form->resizable = true;
|
||||||
snprintf(form->name, DSGN_MAX_NAME, "%s", name);
|
snprintf(form->name, DSGN_MAX_NAME, "%s", name);
|
||||||
snprintf(form->caption, DSGN_MAX_TEXT, "%s", name);
|
snprintf(form->caption, DSGN_MAX_TEXT, "%s", name);
|
||||||
|
|
|
||||||
|
|
@ -175,4 +175,14 @@ bool dsgnIsContainer(const char *typeName);
|
||||||
// Free designer resources.
|
// Free designer resources.
|
||||||
void dsgnFree(DsgnStateT *ds);
|
void dsgnFree(DsgnStateT *ds);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Code rename support (implemented in ideMain.c)
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Rename all references to a form or control in project .bas files.
|
||||||
|
// Replaces OldName. -> NewName. and OldName_ -> NewName_ (case-insensitive,
|
||||||
|
// word-boundary aware). Also handles FormName.ControlName. patterns.
|
||||||
|
|
||||||
|
void ideRenameInCode(const char *oldName, const char *newName);
|
||||||
|
|
||||||
#endif // IDE_DESIGNER_H
|
#endif // IDE_DESIGNER_H
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
853
apps/dvxbasic/ide/ideProject.c
Normal file
853
apps/dvxbasic/ide/ideProject.c
Normal file
|
|
@ -0,0 +1,853 @@
|
||||||
|
// ideProject.c -- DVX BASIC project file management and project window
|
||||||
|
//
|
||||||
|
// The .dbp (DVX BASIC Project) file is INI-format:
|
||||||
|
//
|
||||||
|
// [Project]
|
||||||
|
// Name = MyProject
|
||||||
|
//
|
||||||
|
// [Modules]
|
||||||
|
// Count = 2
|
||||||
|
// File0 = MAIN.BAS
|
||||||
|
// File1 = UTILS.BAS
|
||||||
|
//
|
||||||
|
// [Forms]
|
||||||
|
// Count = 1
|
||||||
|
// File0 = FORM1.FRM
|
||||||
|
//
|
||||||
|
// [Settings]
|
||||||
|
// StartupForm = Form1
|
||||||
|
//
|
||||||
|
// All file paths are relative to the directory containing the .dbp file.
|
||||||
|
// Uses the handle-based dvxPrefs API with a dedicated handle per load/save
|
||||||
|
// so project files don't interfere with the IDE's own preferences.
|
||||||
|
|
||||||
|
#include "ideProject.h"
|
||||||
|
#include "dvxApp.h"
|
||||||
|
#include "dvxDialog.h"
|
||||||
|
#include "dvxPrefs.h"
|
||||||
|
#include "dvxWm.h"
|
||||||
|
#include "widgetBox.h"
|
||||||
|
#include "widgetButton.h"
|
||||||
|
#include "widgetImage.h"
|
||||||
|
#include "widgetLabel.h"
|
||||||
|
#include "widgetTextInput.h"
|
||||||
|
#include "widgetTreeView.h"
|
||||||
|
|
||||||
|
#include "thirdparty/stb_ds_wrap.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <strings.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Constants
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define PRJ_WIN_W 180
|
||||||
|
#define PRJ_WIN_H 300
|
||||||
|
#define PRJ_MAX_FILES 256
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Module state
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static PrjStateT *sPrj = NULL;
|
||||||
|
static WindowT *sPrjWin = NULL;
|
||||||
|
static WidgetT *sTree = NULL;
|
||||||
|
static PrjFileClickFnT sOnClick = NULL;
|
||||||
|
static char **sLabels = NULL; // stb_ds array of strdup'd strings
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Prototypes
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void onPrjWinClose(WindowT *win);
|
||||||
|
static void onTreeItemClick(WidgetT *w);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjInit
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjInit(PrjStateT *prj) {
|
||||||
|
memset(prj, 0, sizeof(*prj));
|
||||||
|
prj->activeFileIdx = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjClose
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjClose(PrjStateT *prj) {
|
||||||
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
|
free(prj->files[i].buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(prj->files);
|
||||||
|
arrfree(prj->sourceMap);
|
||||||
|
prjInit(prj);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjLoad
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool prjLoad(PrjStateT *prj, const char *dbpPath) {
|
||||||
|
PrefsHandleT *h = prefsLoad(dbpPath);
|
||||||
|
|
||||||
|
if (!h) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
prjInit(prj);
|
||||||
|
snprintf(prj->projectPath, sizeof(prj->projectPath), "%s", dbpPath);
|
||||||
|
|
||||||
|
// Derive project directory
|
||||||
|
snprintf(prj->projectDir, sizeof(prj->projectDir), "%s", dbpPath);
|
||||||
|
char *sep = strrchr(prj->projectDir, '/');
|
||||||
|
char *sep2 = strrchr(prj->projectDir, '\\');
|
||||||
|
|
||||||
|
if (sep2 > sep) {
|
||||||
|
sep = sep2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sep) {
|
||||||
|
*sep = '\0';
|
||||||
|
} else {
|
||||||
|
prj->projectDir[0] = '.';
|
||||||
|
prj->projectDir[1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Project] section
|
||||||
|
const char *val;
|
||||||
|
|
||||||
|
val = prefsGetString(h, "Project", "Name", NULL);
|
||||||
|
if (val) { snprintf(prj->name, sizeof(prj->name), "%s", val); }
|
||||||
|
|
||||||
|
val = prefsGetString(h, "Project", "Author", NULL);
|
||||||
|
if (val) { snprintf(prj->author, sizeof(prj->author), "%s", val); }
|
||||||
|
|
||||||
|
val = prefsGetString(h, "Project", "Company", NULL);
|
||||||
|
if (val) { snprintf(prj->company, sizeof(prj->company), "%s", val); }
|
||||||
|
|
||||||
|
val = prefsGetString(h, "Project", "Version", NULL);
|
||||||
|
if (val) { snprintf(prj->version, sizeof(prj->version), "%s", val); }
|
||||||
|
|
||||||
|
val = prefsGetString(h, "Project", "Copyright", NULL);
|
||||||
|
if (val) { snprintf(prj->copyright, sizeof(prj->copyright), "%s", val); }
|
||||||
|
|
||||||
|
val = prefsGetString(h, "Project", "Description", NULL);
|
||||||
|
if (val) { snprintf(prj->description, sizeof(prj->description), "%s", val); }
|
||||||
|
|
||||||
|
val = prefsGetString(h, "Project", "Icon", NULL);
|
||||||
|
if (val) { snprintf(prj->iconPath, sizeof(prj->iconPath), "%s", val); }
|
||||||
|
|
||||||
|
// [Modules] section -- File0, File1, ...
|
||||||
|
for (int32_t i = 0; i < PRJ_MAX_FILES; i++) {
|
||||||
|
char key[16];
|
||||||
|
snprintf(key, sizeof(key), "File%d", (int)i);
|
||||||
|
val = prefsGetString(h, "Modules", key, NULL);
|
||||||
|
|
||||||
|
if (!val) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
prjAddFile(prj, val, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Forms] section -- File0, File1, ...
|
||||||
|
for (int32_t i = 0; i < PRJ_MAX_FILES; i++) {
|
||||||
|
char key[16];
|
||||||
|
snprintf(key, sizeof(key), "File%d", (int)i);
|
||||||
|
val = prefsGetString(h, "Forms", key, NULL);
|
||||||
|
|
||||||
|
if (!val) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
prjAddFile(prj, val, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Settings] section
|
||||||
|
val = prefsGetString(h, "Settings", "StartupForm", NULL);
|
||||||
|
if (val) { snprintf(prj->startupForm, sizeof(prj->startupForm), "%s", val); }
|
||||||
|
|
||||||
|
prefsClose(h);
|
||||||
|
prj->dirty = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjSave
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool prjSave(const PrjStateT *prj) {
|
||||||
|
if (prj->projectPath[0] == '\0') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrefsHandleT *h = prefsCreate();
|
||||||
|
|
||||||
|
if (!h) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Project] section
|
||||||
|
prefsSetString(h, "Project", "Name", prj->name);
|
||||||
|
|
||||||
|
if (prj->author[0]) { prefsSetString(h, "Project", "Author", prj->author); }
|
||||||
|
if (prj->company[0]) { prefsSetString(h, "Project", "Company", prj->company); }
|
||||||
|
if (prj->version[0]) { prefsSetString(h, "Project", "Version", prj->version); }
|
||||||
|
if (prj->copyright[0]) { prefsSetString(h, "Project", "Copyright", prj->copyright); }
|
||||||
|
if (prj->description[0]) { prefsSetString(h, "Project", "Description", prj->description); }
|
||||||
|
if (prj->iconPath[0]) { prefsSetString(h, "Project", "Icon", prj->iconPath); }
|
||||||
|
|
||||||
|
// [Modules] section
|
||||||
|
int32_t modIdx = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
|
if (!prj->files[i].isForm) {
|
||||||
|
char key[16];
|
||||||
|
snprintf(key, sizeof(key), "File%d", (int)modIdx++);
|
||||||
|
prefsSetString(h, "Modules", key, prj->files[i].path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prefsSetInt(h, "Modules", "Count", modIdx);
|
||||||
|
|
||||||
|
// [Forms] section
|
||||||
|
int32_t frmIdx = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
|
if (prj->files[i].isForm) {
|
||||||
|
char key[16];
|
||||||
|
snprintf(key, sizeof(key), "File%d", (int)frmIdx++);
|
||||||
|
prefsSetString(h, "Forms", key, prj->files[i].path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
prefsSetInt(h, "Forms", "Count", frmIdx);
|
||||||
|
|
||||||
|
// [Settings] section
|
||||||
|
prefsSetString(h, "Settings", "StartupForm", prj->startupForm);
|
||||||
|
|
||||||
|
bool ok = prefsSaveAs(h, prj->projectPath);
|
||||||
|
prefsClose(h);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjSaveAs
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool prjSaveAs(PrjStateT *prj, const char *dbpPath) {
|
||||||
|
snprintf(prj->projectPath, sizeof(prj->projectPath), "%s", dbpPath);
|
||||||
|
|
||||||
|
// Update project directory
|
||||||
|
snprintf(prj->projectDir, sizeof(prj->projectDir), "%s", dbpPath);
|
||||||
|
char *sep = strrchr(prj->projectDir, '/');
|
||||||
|
char *sep2 = strrchr(prj->projectDir, '\\');
|
||||||
|
|
||||||
|
if (sep2 > sep) {
|
||||||
|
sep = sep2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sep) {
|
||||||
|
*sep = '\0';
|
||||||
|
} else {
|
||||||
|
prj->projectDir[0] = '.';
|
||||||
|
prj->projectDir[1] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return prjSave(prj);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjNew
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjNew(PrjStateT *prj, const char *name, const char *directory) {
|
||||||
|
prjInit(prj);
|
||||||
|
snprintf(prj->name, sizeof(prj->name), "%s", name);
|
||||||
|
snprintf(prj->projectDir, sizeof(prj->projectDir), "%s", directory);
|
||||||
|
snprintf(prj->projectPath, sizeof(prj->projectPath), "%s/%s.dbp", directory, name);
|
||||||
|
prj->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjAddFile
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int32_t prjAddFile(PrjStateT *prj, const char *relativePath, bool isForm) {
|
||||||
|
PrjFileT entry;
|
||||||
|
memset(&entry, 0, sizeof(entry));
|
||||||
|
snprintf(entry.path, sizeof(entry.path), "%s", relativePath);
|
||||||
|
entry.isForm = isForm;
|
||||||
|
arrput(prj->files, entry);
|
||||||
|
prj->fileCount = (int32_t)arrlen(prj->files);
|
||||||
|
prj->dirty = true;
|
||||||
|
return prj->fileCount - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjRemoveFile
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjRemoveFile(PrjStateT *prj, int32_t idx) {
|
||||||
|
if (idx < 0 || idx >= prj->fileCount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(prj->files[idx].buffer);
|
||||||
|
arrdel(prj->files, idx);
|
||||||
|
prj->fileCount = (int32_t)arrlen(prj->files);
|
||||||
|
|
||||||
|
// Adjust active file index
|
||||||
|
if (prj->activeFileIdx == idx) {
|
||||||
|
prj->activeFileIdx = -1;
|
||||||
|
} else if (prj->activeFileIdx > idx) {
|
||||||
|
prj->activeFileIdx--;
|
||||||
|
}
|
||||||
|
|
||||||
|
prj->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjFullPath
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjFullPath(const PrjStateT *prj, int32_t fileIdx, char *outPath, int32_t outSize) {
|
||||||
|
if (fileIdx < 0 || fileIdx >= prj->fileCount) {
|
||||||
|
outPath[0] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(outPath, outSize, "%s/%s", prj->projectDir, prj->files[fileIdx].path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjMapLine
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool prjMapLine(const PrjStateT *prj, int32_t concatLine, int32_t *outFileIdx, int32_t *outLocalLine) {
|
||||||
|
for (int32_t i = 0; i < prj->sourceMapCount; i++) {
|
||||||
|
const PrjSourceMapT *m = &prj->sourceMap[i];
|
||||||
|
|
||||||
|
if (concatLine >= m->startLine && concatLine < m->startLine + m->lineCount) {
|
||||||
|
*outFileIdx = m->fileIdx;
|
||||||
|
*outLocalLine = concatLine - m->startLine + 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Project window callbacks
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void onPrjWinClose(WindowT *win) {
|
||||||
|
(void)win;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onTreeItemClick(WidgetT *w) {
|
||||||
|
if (!sPrj || !sOnClick) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t fileIdx = (int32_t)(intptr_t)w->userData;
|
||||||
|
|
||||||
|
if (fileIdx >= 0 && fileIdx < sPrj->fileCount) {
|
||||||
|
sOnClick(fileIdx, sPrj->files[fileIdx].isForm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjCreateWindow
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
WindowT *prjCreateWindow(AppContextT *ctx, PrjStateT *prj, PrjFileClickFnT onClick) {
|
||||||
|
sPrj = prj;
|
||||||
|
sOnClick = onClick;
|
||||||
|
|
||||||
|
sPrjWin = dvxCreateWindow(ctx, "Project", 0, 250, PRJ_WIN_W, PRJ_WIN_H, true);
|
||||||
|
|
||||||
|
if (!sPrjWin) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sPrjWin->onClose = onPrjWinClose;
|
||||||
|
|
||||||
|
WidgetT *root = wgtInitWindow(ctx, sPrjWin);
|
||||||
|
sTree = wgtTreeView(root);
|
||||||
|
sTree->weight = 100;
|
||||||
|
|
||||||
|
prjRebuildTree(prj);
|
||||||
|
return sPrjWin;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjDestroyWindow
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjDestroyWindow(AppContextT *ctx, WindowT *win) {
|
||||||
|
if (win) {
|
||||||
|
dvxDestroyWindow(ctx, win);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free label strings
|
||||||
|
if (sLabels) {
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(sLabels); i++) {
|
||||||
|
free(sLabels[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(sLabels);
|
||||||
|
sLabels = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sPrjWin = NULL;
|
||||||
|
sTree = NULL;
|
||||||
|
sPrj = NULL;
|
||||||
|
sOnClick = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjRebuildTree
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjRebuildTree(PrjStateT *prj) {
|
||||||
|
if (!sTree) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear existing items by removing all children
|
||||||
|
sTree->firstChild = NULL;
|
||||||
|
sTree->lastChild = NULL;
|
||||||
|
|
||||||
|
// Free old labels
|
||||||
|
if (sLabels) {
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(sLabels); i++) {
|
||||||
|
free(sLabels[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
arrfree(sLabels);
|
||||||
|
sLabels = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prj || prj->fileCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Project name as root
|
||||||
|
char *projLabel = strdup(prj->name[0] ? prj->name : "Project");
|
||||||
|
arrput(sLabels, projLabel);
|
||||||
|
WidgetT *projNode = wgtTreeItem(sTree, projLabel);
|
||||||
|
projNode->userData = (void *)(intptr_t)-1;
|
||||||
|
wgtTreeItemSetExpanded(projNode, true);
|
||||||
|
|
||||||
|
// Forms group
|
||||||
|
char *formsLabel = strdup("Forms");
|
||||||
|
arrput(sLabels, formsLabel);
|
||||||
|
WidgetT *formsNode = wgtTreeItem(projNode, formsLabel);
|
||||||
|
formsNode->userData = (void *)(intptr_t)-1;
|
||||||
|
wgtTreeItemSetExpanded(formsNode, true);
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
|
if (prj->files[i].isForm) {
|
||||||
|
char *label = strdup(prj->files[i].path);
|
||||||
|
arrput(sLabels, label);
|
||||||
|
WidgetT *item = wgtTreeItem(formsNode, label);
|
||||||
|
item->userData = (void *)(intptr_t)i;
|
||||||
|
item->onClick = onTreeItemClick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modules group
|
||||||
|
char *modsLabel = strdup("Modules");
|
||||||
|
arrput(sLabels, modsLabel);
|
||||||
|
WidgetT *modsNode = wgtTreeItem(projNode, modsLabel);
|
||||||
|
modsNode->userData = (void *)(intptr_t)-1;
|
||||||
|
wgtTreeItemSetExpanded(modsNode, true);
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
|
if (!prj->files[i].isForm) {
|
||||||
|
char *label = strdup(prj->files[i].path);
|
||||||
|
arrput(sLabels, label);
|
||||||
|
WidgetT *item = wgtTreeItem(modsNode, label);
|
||||||
|
item->userData = (void *)(intptr_t)i;
|
||||||
|
item->onClick = onTreeItemClick;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidate(sTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Project properties dialog
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define PPD_WIDTH 380
|
||||||
|
#define PPD_LABEL_W 96
|
||||||
|
#define PPD_BTN_W 70
|
||||||
|
#define PPD_BTN_H 24
|
||||||
|
#define PPD_DESC_H 60
|
||||||
|
|
||||||
|
static struct {
|
||||||
|
bool done;
|
||||||
|
bool accepted;
|
||||||
|
WidgetT *name;
|
||||||
|
WidgetT *author;
|
||||||
|
WidgetT *company;
|
||||||
|
WidgetT *version;
|
||||||
|
WidgetT *copyright;
|
||||||
|
WidgetT *description;
|
||||||
|
WidgetT *iconPreview;
|
||||||
|
char iconPath[DVX_MAX_PATH];
|
||||||
|
const char *appPath;
|
||||||
|
AppContextT *ctx;
|
||||||
|
PrjStateT *prj;
|
||||||
|
} sPpd;
|
||||||
|
|
||||||
|
static void ppdOnOk(WidgetT *w) {
|
||||||
|
(void)w;
|
||||||
|
|
||||||
|
// Validate icon path if set
|
||||||
|
if (sPpd.iconPath[0] && sPpd.prj) {
|
||||||
|
const char *iconText = sPpd.iconPath;
|
||||||
|
char fullPath[DVX_MAX_PATH * 2];
|
||||||
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", sPpd.prj->projectDir, iconText);
|
||||||
|
|
||||||
|
int32_t infoW = 0;
|
||||||
|
int32_t infoH = 0;
|
||||||
|
|
||||||
|
if (!dvxImageInfo(fullPath, &infoW, &infoH)) {
|
||||||
|
dvxMessageBox(sPpd.ctx, "Invalid Icon", "Could not read image file.", MB_OK | MB_ICONERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoW != 32 || infoH != 32) {
|
||||||
|
char msg[128];
|
||||||
|
snprintf(msg, sizeof(msg), "Icon must be 32x32 pixels.\nThis image is %dx%d.", (int)infoW, (int)infoH);
|
||||||
|
dvxMessageBox(sPpd.ctx, "Invalid Icon", msg, MB_OK | MB_ICONWARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sPpd.accepted = true;
|
||||||
|
sPpd.done = true;
|
||||||
|
}
|
||||||
|
static void ppdOnCancel(WidgetT *w) { (void)w; sPpd.accepted = false; sPpd.done = true; }
|
||||||
|
static void ppdOnClose(WindowT *win) { (void)win; sPpd.accepted = false; sPpd.done = true; }
|
||||||
|
|
||||||
|
|
||||||
|
static void ppdLoadIconPreview(void) {
|
||||||
|
if (!sPpd.iconPreview || !sPpd.ctx || !sPpd.prj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sPpd.iconPath[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *relPath = sPpd.iconPath;
|
||||||
|
|
||||||
|
char fullPath[DVX_MAX_PATH * 2];
|
||||||
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", sPpd.prj->projectDir, relPath);
|
||||||
|
|
||||||
|
// Verify the image is 32x32 before loading
|
||||||
|
int32_t infoW = 0;
|
||||||
|
int32_t infoH = 0;
|
||||||
|
|
||||||
|
if (!dvxImageInfo(fullPath, &infoW, &infoH)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoW != 32 || infoH != 32) {
|
||||||
|
char msg[128];
|
||||||
|
snprintf(msg, sizeof(msg), "Icon must be 32x32 pixels.\nThis image is %dx%d.", (int)infoW, (int)infoH);
|
||||||
|
dvxMessageBox(sPpd.ctx, "Invalid Icon", msg, MB_OK | MB_ICONWARNING);
|
||||||
|
sPpd.iconPath[0] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t w = 0;
|
||||||
|
int32_t h = 0;
|
||||||
|
int32_t pitch = 0;
|
||||||
|
uint8_t *data = dvxLoadImage(sPpd.ctx, fullPath, &w, &h, &pitch);
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
wgtImageSetData(sPpd.iconPreview, data, w, h, pitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void ppdOnBrowseIcon(WidgetT *w) {
|
||||||
|
(void)w;
|
||||||
|
|
||||||
|
FileFilterT filters[] = {
|
||||||
|
{ "Images (*.bmp;*.png;*.jpg;*.gif)", "*.bmp;*.png;*.jpg;*.gif" },
|
||||||
|
{ "All Files (*.*)", "*.*" }
|
||||||
|
};
|
||||||
|
|
||||||
|
char path[DVX_MAX_PATH];
|
||||||
|
|
||||||
|
if (dvxFileDialog(sPpd.ctx, "Select Icon", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
|
||||||
|
// Validate size using the full path before accepting
|
||||||
|
int32_t infoW = 0;
|
||||||
|
int32_t infoH = 0;
|
||||||
|
|
||||||
|
if (!dvxImageInfo(path, &infoW, &infoH)) {
|
||||||
|
dvxMessageBox(sPpd.ctx, "Invalid Icon", "Could not read image file.", MB_OK | MB_ICONERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoW != 32 || infoH != 32) {
|
||||||
|
char msg[128];
|
||||||
|
snprintf(msg, sizeof(msg), "Icon must be 32x32 pixels.\nThis image is %dx%d.", (int)infoW, (int)infoH);
|
||||||
|
dvxMessageBox(sPpd.ctx, "Invalid Icon", msg, MB_OK | MB_ICONWARNING);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The icon must be in the project directory so the relative
|
||||||
|
// path works when the project is reloaded.
|
||||||
|
const char *relPath = NULL;
|
||||||
|
int32_t dirLen = (int32_t)strlen(sPpd.prj->projectDir);
|
||||||
|
|
||||||
|
if (strncasecmp(path, sPpd.prj->projectDir, dirLen) == 0 &&
|
||||||
|
(path[dirLen] == '/' || path[dirLen] == '\\')) {
|
||||||
|
relPath = path + dirLen + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!relPath) {
|
||||||
|
int32_t result = dvxMessageBox(sPpd.ctx, "Copy Icon",
|
||||||
|
"The icon is outside the project directory.\nCopy it to the project?",
|
||||||
|
MB_YESNO | MB_ICONQUESTION);
|
||||||
|
|
||||||
|
if (result != ID_YES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get just the filename
|
||||||
|
const char *fname = strrchr(path, '/');
|
||||||
|
const char *fname2 = strrchr(path, '\\');
|
||||||
|
|
||||||
|
if (fname2 > fname) {
|
||||||
|
fname = fname2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fname = fname ? fname + 1 : path;
|
||||||
|
|
||||||
|
// Check if destination already exists
|
||||||
|
char destPath[DVX_MAX_PATH * 2];
|
||||||
|
snprintf(destPath, sizeof(destPath), "%s/%s", sPpd.prj->projectDir, fname);
|
||||||
|
|
||||||
|
FILE *existing = fopen(destPath, "rb");
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
fclose(existing);
|
||||||
|
|
||||||
|
char msg[DVX_MAX_PATH + 32];
|
||||||
|
snprintf(msg, sizeof(msg), "%s already exists.\nOverwrite it?", fname);
|
||||||
|
int32_t ow = dvxMessageBox(sPpd.ctx, "Overwrite", msg, MB_YESNO | MB_ICONQUESTION);
|
||||||
|
|
||||||
|
if (ow != ID_YES) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the file
|
||||||
|
FILE *src = fopen(path, "rb");
|
||||||
|
|
||||||
|
if (!src) {
|
||||||
|
dvxMessageBox(sPpd.ctx, "Error", "Could not read source file.", MB_OK | MB_ICONERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *dst = fopen(destPath, "wb");
|
||||||
|
|
||||||
|
if (!dst) {
|
||||||
|
fclose(src);
|
||||||
|
dvxMessageBox(sPpd.ctx, "Error", "Could not write to project directory.", MB_OK | MB_ICONERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[4096];
|
||||||
|
size_t n;
|
||||||
|
|
||||||
|
while ((n = fread(buf, 1, sizeof(buf), src)) > 0) {
|
||||||
|
fwrite(buf, 1, n, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(src);
|
||||||
|
fclose(dst);
|
||||||
|
|
||||||
|
relPath = fname;
|
||||||
|
}
|
||||||
|
|
||||||
|
snprintf(sPpd.iconPath, sizeof(sPpd.iconPath), "%s", relPath);
|
||||||
|
ppdLoadIconPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static WidgetT *ppdAddRow(WidgetT *parent, const char *labelText, const char *value, int32_t maxLen) {
|
||||||
|
WidgetT *row = wgtHBox(parent);
|
||||||
|
row->spacing = wgtPixels(4);
|
||||||
|
|
||||||
|
WidgetT *lbl = wgtLabel(row, labelText);
|
||||||
|
lbl->minW = wgtPixels(PPD_LABEL_W);
|
||||||
|
|
||||||
|
WidgetT *input = wgtTextInput(row, maxLen);
|
||||||
|
input->weight = 100;
|
||||||
|
wgtSetText(input, value);
|
||||||
|
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool prjPropertiesDialog(AppContextT *ctx, PrjStateT *prj, const char *appPath) {
|
||||||
|
if (!ctx || !prj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowT *win = dvxCreateWindowCentered(ctx, "Project Properties", PPD_WIDTH, 380, false);
|
||||||
|
|
||||||
|
if (!win) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
win->modal = true;
|
||||||
|
win->onClose = ppdOnClose;
|
||||||
|
win->maxW = win->w;
|
||||||
|
win->maxH = win->h;
|
||||||
|
|
||||||
|
sPpd.done = false;
|
||||||
|
sPpd.accepted = false;
|
||||||
|
sPpd.ctx = ctx;
|
||||||
|
sPpd.prj = prj;
|
||||||
|
sPpd.appPath = appPath;
|
||||||
|
|
||||||
|
WidgetT *root = wgtInitWindow(ctx, win);
|
||||||
|
|
||||||
|
if (!root) {
|
||||||
|
dvxDestroyWindow(ctx, win);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
root->spacing = wgtPixels(2);
|
||||||
|
|
||||||
|
sPpd.name = ppdAddRow(root, "Name:", prj->name, PRJ_MAX_NAME);
|
||||||
|
sPpd.author = ppdAddRow(root, "Author:", prj->author, PRJ_MAX_STRING);
|
||||||
|
sPpd.company = ppdAddRow(root, "Company:", prj->company, PRJ_MAX_STRING);
|
||||||
|
sPpd.version = ppdAddRow(root, "Version:", prj->version, PRJ_MAX_NAME);
|
||||||
|
sPpd.copyright = ppdAddRow(root, "Copyright:", prj->copyright, PRJ_MAX_STRING);
|
||||||
|
// Icon row: label + preview + Browse button
|
||||||
|
{
|
||||||
|
WidgetT *iconRow = wgtHBox(root);
|
||||||
|
iconRow->spacing = wgtPixels(4);
|
||||||
|
|
||||||
|
WidgetT *iconLbl = wgtLabel(iconRow, "Icon:");
|
||||||
|
iconLbl->minW = wgtPixels(PPD_LABEL_W);
|
||||||
|
|
||||||
|
// Load "noicon" placeholder from app resources
|
||||||
|
int32_t niW = 0;
|
||||||
|
int32_t niH = 0;
|
||||||
|
int32_t niP = 0;
|
||||||
|
uint8_t *noIconData = appPath ? dvxResLoadIcon(ctx, appPath, "noicon", &niW, &niH, &niP) : NULL;
|
||||||
|
|
||||||
|
if (noIconData) {
|
||||||
|
sPpd.iconPreview = wgtImage(iconRow, noIconData, niW, niH, niP);
|
||||||
|
} else {
|
||||||
|
uint8_t *placeholder = (uint8_t *)calloc(4, 1);
|
||||||
|
sPpd.iconPreview = wgtImage(iconRow, placeholder, 1, 1, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetT *browseBtn = wgtButton(iconRow, "Browse...");
|
||||||
|
browseBtn->onClick = ppdOnBrowseIcon;
|
||||||
|
|
||||||
|
snprintf(sPpd.iconPath, sizeof(sPpd.iconPath), "%s", prj->iconPath);
|
||||||
|
ppdLoadIconPreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description gets a taller text area
|
||||||
|
WidgetT *descRow = wgtHBox(root);
|
||||||
|
descRow->spacing = wgtPixels(4);
|
||||||
|
|
||||||
|
WidgetT *descLbl = wgtLabel(descRow, "Description:");
|
||||||
|
descLbl->minW = wgtPixels(PPD_LABEL_W);
|
||||||
|
|
||||||
|
sPpd.description = wgtTextArea(descRow, PRJ_MAX_DESC);
|
||||||
|
sPpd.description->weight = 100;
|
||||||
|
sPpd.description->minH = wgtPixels(PPD_DESC_H);
|
||||||
|
wgtSetText(sPpd.description, prj->description);
|
||||||
|
|
||||||
|
// OK / Cancel buttons
|
||||||
|
WidgetT *btnRow = wgtHBox(root);
|
||||||
|
btnRow->align = AlignCenterE;
|
||||||
|
|
||||||
|
WidgetT *okBtn = wgtButton(btnRow, "&OK");
|
||||||
|
okBtn->minW = wgtPixels(PPD_BTN_W);
|
||||||
|
okBtn->minH = wgtPixels(PPD_BTN_H);
|
||||||
|
okBtn->onClick = ppdOnOk;
|
||||||
|
|
||||||
|
WidgetT *cancelBtn = wgtButton(btnRow, "&Cancel");
|
||||||
|
cancelBtn->minW = wgtPixels(PPD_BTN_W);
|
||||||
|
cancelBtn->minH = wgtPixels(PPD_BTN_H);
|
||||||
|
cancelBtn->onClick = ppdOnCancel;
|
||||||
|
|
||||||
|
dvxFitWindow(ctx, win);
|
||||||
|
|
||||||
|
WindowT *prevModal = ctx->modalWindow;
|
||||||
|
ctx->modalWindow = win;
|
||||||
|
|
||||||
|
while (!sPpd.done && ctx->running) {
|
||||||
|
dvxUpdate(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sPpd.accepted) {
|
||||||
|
const char *s;
|
||||||
|
|
||||||
|
s = wgtGetText(sPpd.name);
|
||||||
|
if (s) { snprintf(prj->name, sizeof(prj->name), "%s", s); }
|
||||||
|
|
||||||
|
s = wgtGetText(sPpd.author);
|
||||||
|
if (s) { snprintf(prj->author, sizeof(prj->author), "%s", s); }
|
||||||
|
|
||||||
|
s = wgtGetText(sPpd.company);
|
||||||
|
if (s) { snprintf(prj->company, sizeof(prj->company), "%s", s); }
|
||||||
|
|
||||||
|
s = wgtGetText(sPpd.version);
|
||||||
|
if (s) { snprintf(prj->version, sizeof(prj->version), "%s", s); }
|
||||||
|
|
||||||
|
s = wgtGetText(sPpd.copyright);
|
||||||
|
if (s) { snprintf(prj->copyright, sizeof(prj->copyright), "%s", s); }
|
||||||
|
|
||||||
|
s = wgtGetText(sPpd.description);
|
||||||
|
if (s) { snprintf(prj->description, sizeof(prj->description), "%s", s); }
|
||||||
|
|
||||||
|
snprintf(prj->iconPath, sizeof(prj->iconPath), "%s", sPpd.iconPath);
|
||||||
|
|
||||||
|
prj->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx->modalWindow = prevModal;
|
||||||
|
dvxDestroyWindow(ctx, win);
|
||||||
|
|
||||||
|
return sPpd.accepted;
|
||||||
|
}
|
||||||
105
apps/dvxbasic/ide/ideProject.h
Normal file
105
apps/dvxbasic/ide/ideProject.h
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
// ideProject.h -- DVX BASIC project file management and project window
|
||||||
|
|
||||||
|
#ifndef IDE_PROJECT_H
|
||||||
|
#define IDE_PROJECT_H
|
||||||
|
|
||||||
|
#include "dvxApp.h"
|
||||||
|
#include "dvxTypes.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Constants
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
#define PRJ_MAX_NAME 32
|
||||||
|
#define PRJ_MAX_STRING 128
|
||||||
|
#define PRJ_MAX_DESC 512
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Project file entry
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char path[DVX_MAX_PATH]; // relative path (8.3 DOS name)
|
||||||
|
bool isForm; // true = .frm, false = .bas
|
||||||
|
char *buffer; // in-memory edit buffer (malloc'd, NULL = not loaded)
|
||||||
|
bool modified; // true = buffer has unsaved changes
|
||||||
|
} PrjFileT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Source map entry (for multi-file error reporting)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t startLine; // 1-based line in concatenated source
|
||||||
|
int32_t lineCount; // lines contributed by this file
|
||||||
|
int32_t fileIdx; // index in PrjStateT.files[]
|
||||||
|
} PrjSourceMapT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Project state
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[PRJ_MAX_NAME];
|
||||||
|
char projectPath[DVX_MAX_PATH]; // full path to .dbp file
|
||||||
|
char projectDir[DVX_MAX_PATH]; // directory containing .dbp
|
||||||
|
char startupForm[PRJ_MAX_NAME];
|
||||||
|
// Project metadata (for binary generation)
|
||||||
|
char author[PRJ_MAX_STRING];
|
||||||
|
char company[PRJ_MAX_STRING];
|
||||||
|
char version[PRJ_MAX_NAME];
|
||||||
|
char copyright[PRJ_MAX_STRING];
|
||||||
|
char description[PRJ_MAX_DESC];
|
||||||
|
char iconPath[DVX_MAX_PATH]; // relative path to icon BMP
|
||||||
|
PrjFileT *files; // stb_ds dynamic array
|
||||||
|
int32_t fileCount;
|
||||||
|
PrjSourceMapT *sourceMap; // stb_ds dynamic array
|
||||||
|
int32_t sourceMapCount;
|
||||||
|
bool dirty;
|
||||||
|
int32_t activeFileIdx; // index of file open in editor (-1 = none)
|
||||||
|
} PrjStateT;
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Project management
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjInit(PrjStateT *prj);
|
||||||
|
bool prjLoad(PrjStateT *prj, const char *dbpPath);
|
||||||
|
bool prjSave(const PrjStateT *prj);
|
||||||
|
bool prjSaveAs(PrjStateT *prj, const char *dbpPath);
|
||||||
|
void prjNew(PrjStateT *prj, const char *name, const char *directory);
|
||||||
|
void prjClose(PrjStateT *prj);
|
||||||
|
int32_t prjAddFile(PrjStateT *prj, const char *relativePath, bool isForm);
|
||||||
|
void prjRemoveFile(PrjStateT *prj, int32_t idx);
|
||||||
|
void prjFullPath(const PrjStateT *prj, int32_t fileIdx, char *outPath, int32_t outSize);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Source map -- translate concatenated line to file + local line
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Returns true if the line was found in the map. Sets outFileIdx and
|
||||||
|
// outLocalLine to the originating file and line within that file.
|
||||||
|
bool prjMapLine(const PrjStateT *prj, int32_t concatLine, int32_t *outFileIdx, int32_t *outLocalLine);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Project window UI
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
typedef void (*PrjFileClickFnT)(int32_t fileIdx, bool isForm);
|
||||||
|
|
||||||
|
WindowT *prjCreateWindow(AppContextT *ctx, PrjStateT *prj, PrjFileClickFnT onClick);
|
||||||
|
void prjDestroyWindow(AppContextT *ctx, WindowT *win);
|
||||||
|
void prjRebuildTree(PrjStateT *prj);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Project properties dialog
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
// Show a modal dialog for editing project metadata. Returns true if
|
||||||
|
// the user clicked OK (fields in prj are updated), false if cancelled.
|
||||||
|
bool prjPropertiesDialog(AppContextT *ctx, PrjStateT *prj, const char *appPath);
|
||||||
|
|
||||||
|
#endif // IDE_PROJECT_H
|
||||||
|
|
@ -288,12 +288,15 @@ static void onPropDblClick(WidgetT *w) {
|
||||||
DsgnControlT *ctrl = &sDs->form->controls[sDs->selectedIdx];
|
DsgnControlT *ctrl = &sDs->form->controls[sDs->selectedIdx];
|
||||||
|
|
||||||
if (strcasecmp(propName, "Name") == 0) {
|
if (strcasecmp(propName, "Name") == 0) {
|
||||||
|
char oldName[DSGN_MAX_NAME];
|
||||||
|
snprintf(oldName, sizeof(oldName), "%s", ctrl->name);
|
||||||
snprintf(ctrl->name, DSGN_MAX_NAME, "%.31s", newValue);
|
snprintf(ctrl->name, DSGN_MAX_NAME, "%.31s", newValue);
|
||||||
|
|
||||||
if (ctrl->widget) {
|
if (ctrl->widget) {
|
||||||
wgtSetName(ctrl->widget, ctrl->name);
|
wgtSetName(ctrl->widget, ctrl->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ideRenameInCode(oldName, ctrl->name);
|
||||||
prpRebuildTree(sDs);
|
prpRebuildTree(sDs);
|
||||||
} else if (strcasecmp(propName, "MinWidth") == 0) {
|
} else if (strcasecmp(propName, "MinWidth") == 0) {
|
||||||
ctrl->width = atoi(newValue);
|
ctrl->width = atoi(newValue);
|
||||||
|
|
@ -433,7 +436,23 @@ static void onPropDblClick(WidgetT *w) {
|
||||||
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
|
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (strcasecmp(propName, "Caption") == 0) {
|
if (strcasecmp(propName, "Name") == 0) {
|
||||||
|
char oldName[DSGN_MAX_NAME];
|
||||||
|
snprintf(oldName, sizeof(oldName), "%s", sDs->form->name);
|
||||||
|
|
||||||
|
// Length-clamped memcpy instead of strncpy/snprintf because
|
||||||
|
// GCC warns about both when source exceeds the buffer.
|
||||||
|
int32_t nl = (int32_t)strlen(newValue);
|
||||||
|
|
||||||
|
if (nl >= DSGN_MAX_NAME) {
|
||||||
|
nl = DSGN_MAX_NAME - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(sDs->form->name, newValue, nl);
|
||||||
|
sDs->form->name[nl] = '\0';
|
||||||
|
ideRenameInCode(oldName, sDs->form->name);
|
||||||
|
prpRebuildTree(sDs);
|
||||||
|
} else if (strcasecmp(propName, "Caption") == 0) {
|
||||||
snprintf(sDs->form->caption, DSGN_MAX_TEXT, "%s", newValue);
|
snprintf(sDs->form->caption, DSGN_MAX_TEXT, "%s", newValue);
|
||||||
|
|
||||||
if (sDs->formWin) {
|
if (sDs->formWin) {
|
||||||
|
|
|
||||||
BIN
apps/dvxbasic/noicon.bmp
(Stored with Git LFS)
Normal file
BIN
apps/dvxbasic/noicon.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
apps/dvxbasic/samples/ICON32.BMP
(Stored with Git LFS)
Normal file
BIN
apps/dvxbasic/samples/ICON32.BMP
(Stored with Git LFS)
Normal file
Binary file not shown.
18
apps/dvxbasic/samples/MULTI.DBP
Normal file
18
apps/dvxbasic/samples/MULTI.DBP
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
[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 =
|
||||||
10
apps/dvxbasic/samples/MULTI1.FRM
Normal file
10
apps/dvxbasic/samples/MULTI1.FRM
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
VERSION 1.00
|
||||||
|
Begin Form multi1
|
||||||
|
Caption = "Welcome to DVX BASIC!"
|
||||||
|
Layout = VBox
|
||||||
|
AutoSize = False
|
||||||
|
Resizable = True
|
||||||
|
Centered = True
|
||||||
|
Width = 400
|
||||||
|
Height = 300
|
||||||
|
End
|
||||||
|
|
@ -69,7 +69,6 @@ static void doNew(void);
|
||||||
static void doOpen(void);
|
static void doOpen(void);
|
||||||
static void doSave(void);
|
static void doSave(void);
|
||||||
static void doSaveAs(void);
|
static void doSaveAs(void);
|
||||||
static uint32_t hashText(const char *text);
|
|
||||||
static bool isDirty(void);
|
static bool isDirty(void);
|
||||||
static void markClean(void);
|
static void markClean(void);
|
||||||
static void onClose(WindowT *win);
|
static void onClose(WindowT *win);
|
||||||
|
|
@ -93,35 +92,18 @@ AppDescriptorT appDescriptor = {
|
||||||
// Dirty tracking
|
// Dirty tracking
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
// djb2-xor hash for dirty detection. Not cryptographic -- just a fast way
|
// Dirty tracking uses dvxTextHash from dvxApp.h.
|
||||||
// to detect changes without storing a full copy of the last-saved text.
|
|
||||||
// False negatives are theoretically possible but vanishingly unlikely for
|
|
||||||
// text edits. This avoids the memory cost of keeping a shadow buffer.
|
|
||||||
static uint32_t hashText(const char *text) {
|
|
||||||
if (!text) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t h = 5381;
|
|
||||||
|
|
||||||
while (*text) {
|
|
||||||
h = ((h << 5) + h) ^ (uint8_t)*text;
|
|
||||||
text++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static bool isDirty(void) {
|
static bool isDirty(void) {
|
||||||
const char *text = wgtGetText(sTextArea);
|
const char *text = wgtGetText(sTextArea);
|
||||||
return hashText(text) != sCleanHash;
|
return dvxTextHash(text) != sCleanHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void markClean(void) {
|
static void markClean(void) {
|
||||||
const char *text = wgtGetText(sTextArea);
|
const char *text = wgtGetText(sTextArea);
|
||||||
sCleanHash = hashText(text);
|
sCleanHash = dvxTextHash(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ static DxeAppContextT *sCtx = NULL;
|
||||||
static AppContextT *sAc = NULL;
|
static AppContextT *sAc = NULL;
|
||||||
static WindowT *sPmWindow = NULL;
|
static WindowT *sPmWindow = NULL;
|
||||||
static WidgetT *sStatusLabel = NULL;
|
static WidgetT *sStatusLabel = NULL;
|
||||||
|
static PrefsHandleT *sPrefs = NULL;
|
||||||
static bool sMinOnRun = false;
|
static bool sMinOnRun = false;
|
||||||
static AppEntryT sAppFiles[MAX_APP_FILES];
|
static AppEntryT sAppFiles[MAX_APP_FILES];
|
||||||
static int32_t sAppCount = 0;
|
static int32_t sAppCount = 0;
|
||||||
|
|
@ -341,8 +342,8 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
||||||
case CMD_MIN_ON_RUN:
|
case CMD_MIN_ON_RUN:
|
||||||
sMinOnRun = !sMinOnRun;
|
sMinOnRun = !sMinOnRun;
|
||||||
shellEnsureConfigDir(sCtx);
|
shellEnsureConfigDir(sCtx);
|
||||||
prefsSetBool("options", "minimizeOnRun", sMinOnRun);
|
prefsSetBool(sPrefs, "options", "minimizeOnRun", sMinOnRun);
|
||||||
prefsSave();
|
prefsSave(sPrefs);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_ABOUT:
|
case CMD_ABOUT:
|
||||||
|
|
@ -535,8 +536,8 @@ int32_t appMain(DxeAppContextT *ctx) {
|
||||||
// Load saved preferences
|
// Load saved preferences
|
||||||
char prefsPath[DVX_MAX_PATH];
|
char prefsPath[DVX_MAX_PATH];
|
||||||
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
|
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
|
||||||
prefsLoad(prefsPath);
|
sPrefs = prefsLoad(prefsPath);
|
||||||
sMinOnRun = prefsGetBool("options", "minimizeOnRun", false);
|
sMinOnRun = prefsGetBool(sPrefs, "options", "minimizeOnRun", false);
|
||||||
|
|
||||||
scanAppsDir();
|
scanAppsDir();
|
||||||
buildPmWindow();
|
buildPmWindow();
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@
|
||||||
; Supported color depths: 8, 15, 16, 24, 32
|
; Supported color depths: 8, 15, 16, 24, 32
|
||||||
|
|
||||||
[video]
|
[video]
|
||||||
width = 640
|
width = 1024
|
||||||
height = 480
|
height = 768
|
||||||
bpp = 16
|
bpp = 16
|
||||||
|
|
||||||
; Mouse settings.
|
; Mouse settings.
|
||||||
|
|
|
||||||
|
|
@ -3783,6 +3783,26 @@ void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// dvxTextHash
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
uint32_t dvxTextHash(const char *text) {
|
||||||
|
if (!text) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t h = 5381;
|
||||||
|
|
||||||
|
while (*text) {
|
||||||
|
h = ((h << 5) + h) ^ (uint8_t)*text;
|
||||||
|
text++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// dvxColorLabel
|
// dvxColorLabel
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -4057,6 +4077,29 @@ void dvxFreeImage(uint8_t *data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// dvxImageInfo
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool dvxImageInfo(const char *path, int32_t *outW, int32_t *outH) {
|
||||||
|
if (!path) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = 0;
|
||||||
|
int h = 0;
|
||||||
|
int comp = 0;
|
||||||
|
|
||||||
|
if (stbi_info(path, &w, &h, &comp)) {
|
||||||
|
if (outW) { *outW = w; }
|
||||||
|
if (outH) { *outH = h; }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// dvxGetBlitOps
|
// dvxGetBlitOps
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,10 @@ uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int
|
||||||
// Free a pixel buffer returned by dvxLoadImage.
|
// Free a pixel buffer returned by dvxLoadImage.
|
||||||
void dvxFreeImage(uint8_t *data);
|
void dvxFreeImage(uint8_t *data);
|
||||||
|
|
||||||
|
// Query image dimensions without decoding the full file.
|
||||||
|
// Returns true on success, false if the file can't be read.
|
||||||
|
bool dvxImageInfo(const char *path, int32_t *outW, int32_t *outH);
|
||||||
|
|
||||||
// Save native-format pixel data to a PNG file. The pixel data must be
|
// Save native-format pixel data to a PNG file. The pixel data must be
|
||||||
// in the display's native format (as returned by dvxLoadImage or
|
// in the display's native format (as returned by dvxLoadImage or
|
||||||
// captured from a content buffer). Returns 0 on success, -1 on failure.
|
// captured from a content buffer). Returns 0 on success, -1 on failure.
|
||||||
|
|
@ -338,4 +342,14 @@ bool dvxResLoadText(const char *dxePath, const char *resName, char *buf, int32_t
|
||||||
// data size in bytes.
|
// data size in bytes.
|
||||||
void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize);
|
void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Text hash for dirty tracking
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// djb2-xor hash for cheap dirty detection. Compare the hash at save
|
||||||
|
// time with the current hash to detect changes without keeping a
|
||||||
|
// shadow copy of the text. Not cryptographic.
|
||||||
|
|
||||||
|
uint32_t dvxTextHash(const char *text);
|
||||||
|
|
||||||
#endif // DVX_APP_H
|
#endif // DVX_APP_H
|
||||||
|
|
|
||||||
|
|
@ -1609,3 +1609,23 @@ bool dvxFileDialog(AppContextT *ctx, const char *title, int32_t flags, const cha
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// dvxPromptSave
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
int32_t dvxPromptSave(AppContextT *ctx, const char *title) {
|
||||||
|
int32_t result = dvxMessageBox(ctx, title ? title : "Save",
|
||||||
|
"Save changes?", MB_YESNOCANCEL | MB_ICONQUESTION);
|
||||||
|
|
||||||
|
if (result == ID_YES) {
|
||||||
|
return DVX_SAVE_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result == ID_NO) {
|
||||||
|
return DVX_SAVE_NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DVX_SAVE_CANCEL;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,4 +87,20 @@ bool dvxInputBox(AppContextT *ctx, const char *title, const char *prompt, const
|
||||||
// Returns true if the user clicked OK, false if cancelled.
|
// Returns true if the user clicked OK, false if cancelled.
|
||||||
bool dvxIntInputBox(AppContextT *ctx, const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal, int32_t step, int32_t *outVal);
|
bool dvxIntInputBox(AppContextT *ctx, const char *title, const char *prompt, int32_t defaultVal, int32_t minVal, int32_t maxVal, int32_t step, int32_t *outVal);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Save prompt helper
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Common "Save changes?" dialog for apps with unsaved data.
|
||||||
|
// Returns:
|
||||||
|
// DVX_SAVE_YES -- user wants to save (caller should save, then proceed)
|
||||||
|
// DVX_SAVE_NO -- user wants to discard (caller should proceed without saving)
|
||||||
|
// DVX_SAVE_CANCEL -- user cancelled (caller should abort the operation)
|
||||||
|
|
||||||
|
#define DVX_SAVE_YES 1
|
||||||
|
#define DVX_SAVE_NO 2
|
||||||
|
#define DVX_SAVE_CANCEL 3
|
||||||
|
|
||||||
|
int32_t dvxPromptSave(AppContextT *ctx, const char *title);
|
||||||
|
|
||||||
#endif // DVX_DIALOG_H
|
#endif // DVX_DIALOG_H
|
||||||
|
|
|
||||||
184
core/dvxPrefs.c
184
core/dvxPrefs.c
|
|
@ -1,8 +1,7 @@
|
||||||
// dvxPrefs.c -- INI-based preferences system (read/write)
|
// dvxPrefs.c -- INI-based preferences system (read/write)
|
||||||
//
|
//
|
||||||
// Custom INI parser and writer. Stores entries as a dynamic array of
|
// Handle-based: each PrefsHandleT holds its own entry array and file
|
||||||
// section/key/value triples using stb_ds. Preserves insertion order
|
// path. Multiple INI files can be open simultaneously.
|
||||||
// on save so the file remains human-readable.
|
|
||||||
|
|
||||||
#include "dvxPrefs.h"
|
#include "dvxPrefs.h"
|
||||||
|
|
||||||
|
|
@ -12,7 +11,6 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "dvxMem.h"
|
#include "dvxMem.h"
|
||||||
|
|
||||||
// stb_ds dynamic arrays (implementation lives in libtasks.a)
|
|
||||||
#include "thirdparty/stb_ds_wrap.h"
|
#include "thirdparty/stb_ds_wrap.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -30,8 +28,10 @@ typedef struct {
|
||||||
// key=NULL and value=the full line text (including the ; prefix).
|
// key=NULL and value=the full line text (including the ; prefix).
|
||||||
// Section headers have key=NULL and value=NULL.
|
// Section headers have key=NULL and value=NULL.
|
||||||
|
|
||||||
static PrefsEntryT *sEntries = NULL; // stb_ds dynamic array
|
struct PrefsHandleT {
|
||||||
static char *sFilePath = NULL; // path used by prefsLoad (for prefsSave)
|
PrefsEntryT *entries; // stb_ds dynamic array
|
||||||
|
char *filePath; // path used by prefsSave
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -64,7 +64,6 @@ static void freeEntry(PrefsEntryT *e) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Case-insensitive string compare
|
|
||||||
static int strcmpci(const char *a, const char *b) {
|
static int strcmpci(const char *a, const char *b) {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
|
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
|
||||||
|
|
@ -79,10 +78,9 @@ static int strcmpci(const char *a, const char *b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Find an entry by section+key (case-insensitive). Returns index or -1.
|
static int32_t findEntry(PrefsHandleT *h, const char *section, const char *key) {
|
||||||
static int32_t findEntry(const char *section, const char *key) {
|
for (int32_t i = 0; i < arrlen(h->entries); i++) {
|
||||||
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
PrefsEntryT *e = &h->entries[i];
|
||||||
PrefsEntryT *e = &sEntries[i];
|
|
||||||
|
|
||||||
if (e->key && e->section &&
|
if (e->key && e->section &&
|
||||||
strcmpci(e->section, section) == 0 &&
|
strcmpci(e->section, section) == 0 &&
|
||||||
|
|
@ -95,10 +93,9 @@ static int32_t findEntry(const char *section, const char *key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Find the index of a section header entry. Returns -1 if not found.
|
static int32_t findSection(PrefsHandleT *h, const char *section) {
|
||||||
static int32_t findSection(const char *section) {
|
for (int32_t i = 0; i < arrlen(h->entries); i++) {
|
||||||
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
PrefsEntryT *e = &h->entries[i];
|
||||||
PrefsEntryT *e = &sEntries[i];
|
|
||||||
|
|
||||||
if (!e->key && !e->value && e->section &&
|
if (!e->key && !e->value && e->section &&
|
||||||
strcmpci(e->section, section) == 0) {
|
strcmpci(e->section, section) == 0) {
|
||||||
|
|
@ -110,7 +107,6 @@ static int32_t findSection(const char *section) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Trim leading/trailing whitespace in place. Returns pointer into buf.
|
|
||||||
static char *trimInPlace(char *buf) {
|
static char *trimInPlace(char *buf) {
|
||||||
while (*buf == ' ' || *buf == '\t') {
|
while (*buf == ' ' || *buf == '\t') {
|
||||||
buf++;
|
buf++;
|
||||||
|
|
@ -127,18 +123,31 @@ static char *trimInPlace(char *buf) {
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// prefsFree
|
// prefsClose
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void prefsFree(void) {
|
void prefsClose(PrefsHandleT *h) {
|
||||||
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
if (!h) {
|
||||||
freeEntry(&sEntries[i]);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfree(sEntries);
|
for (int32_t i = 0; i < arrlen(h->entries); i++) {
|
||||||
sEntries = NULL;
|
freeEntry(&h->entries[i]);
|
||||||
free(sFilePath);
|
}
|
||||||
sFilePath = NULL;
|
|
||||||
|
arrfree(h->entries);
|
||||||
|
free(h->filePath);
|
||||||
|
free(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prefsCreate
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
PrefsHandleT *prefsCreate(void) {
|
||||||
|
PrefsHandleT *h = (PrefsHandleT *)calloc(1, sizeof(PrefsHandleT));
|
||||||
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -146,8 +155,8 @@ void prefsFree(void) {
|
||||||
// prefsGetBool
|
// prefsGetBool
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool prefsGetBool(const char *section, const char *key, bool defaultVal) {
|
bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool defaultVal) {
|
||||||
const char *val = prefsGetString(section, key, NULL);
|
const char *val = prefsGetString(h, section, key, NULL);
|
||||||
|
|
||||||
if (!val) {
|
if (!val) {
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
|
|
@ -171,8 +180,8 @@ bool prefsGetBool(const char *section, const char *key, bool defaultVal) {
|
||||||
// prefsGetInt
|
// prefsGetInt
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal) {
|
int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32_t defaultVal) {
|
||||||
const char *val = prefsGetString(section, key, NULL);
|
const char *val = prefsGetString(h, section, key, NULL);
|
||||||
|
|
||||||
if (!val) {
|
if (!val) {
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
|
|
@ -193,14 +202,18 @@ int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal) {
|
||||||
// prefsGetString
|
// prefsGetString
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
const char *prefsGetString(const char *section, const char *key, const char *defaultVal) {
|
const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key, const char *defaultVal) {
|
||||||
int32_t idx = findEntry(section, key);
|
if (!h) {
|
||||||
|
return defaultVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t idx = findEntry(h, section, key);
|
||||||
|
|
||||||
if (idx < 0) {
|
if (idx < 0) {
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sEntries[idx].value;
|
return h->entries[idx].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -208,24 +221,25 @@ const char *prefsGetString(const char *section, const char *key, const char *def
|
||||||
// prefsLoad
|
// prefsLoad
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool prefsLoad(const char *filename) {
|
PrefsHandleT *prefsLoad(const char *filename) {
|
||||||
prefsFree();
|
PrefsHandleT *h = prefsCreate();
|
||||||
|
|
||||||
// Always store the path so prefsSave can create the file
|
if (!h) {
|
||||||
// even if it doesn't exist yet.
|
return NULL;
|
||||||
sFilePath = dupStr(filename);
|
}
|
||||||
|
|
||||||
|
h->filePath = dupStr(filename);
|
||||||
|
|
||||||
FILE *fp = fopen(filename, "rb");
|
FILE *fp = fopen(filename, "rb");
|
||||||
|
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
return false;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
char line[512];
|
char line[512];
|
||||||
char *currentSection = dupStr("");
|
char *currentSection = dupStr("");
|
||||||
|
|
||||||
while (fgets(line, sizeof(line), fp)) {
|
while (fgets(line, sizeof(line), fp)) {
|
||||||
// Strip trailing whitespace/newline
|
|
||||||
char *end = line + strlen(line) - 1;
|
char *end = line + strlen(line) - 1;
|
||||||
|
|
||||||
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ' || *end == '\t')) {
|
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ' || *end == '\t')) {
|
||||||
|
|
@ -234,30 +248,26 @@ bool prefsLoad(const char *filename) {
|
||||||
|
|
||||||
char *p = line;
|
char *p = line;
|
||||||
|
|
||||||
// Skip leading whitespace
|
|
||||||
while (*p == ' ' || *p == '\t') {
|
while (*p == ' ' || *p == '\t') {
|
||||||
p++;
|
p++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blank line -- store as comment to preserve formatting
|
|
||||||
if (*p == '\0') {
|
if (*p == '\0') {
|
||||||
PrefsEntryT e = {0};
|
PrefsEntryT e = {0};
|
||||||
e.section = dupStr(currentSection);
|
e.section = dupStr(currentSection);
|
||||||
e.value = dupStr("");
|
e.value = dupStr("");
|
||||||
arrput(sEntries, e);
|
arrput(h->entries, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment line
|
|
||||||
if (*p == ';' || *p == '#') {
|
if (*p == ';' || *p == '#') {
|
||||||
PrefsEntryT e = {0};
|
PrefsEntryT e = {0};
|
||||||
e.section = dupStr(currentSection);
|
e.section = dupStr(currentSection);
|
||||||
e.value = dupStr(line);
|
e.value = dupStr(line);
|
||||||
arrput(sEntries, e);
|
arrput(h->entries, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section header
|
|
||||||
if (*p == '[') {
|
if (*p == '[') {
|
||||||
char *close = strchr(p, ']');
|
char *close = strchr(p, ']');
|
||||||
|
|
||||||
|
|
@ -268,13 +278,12 @@ bool prefsLoad(const char *filename) {
|
||||||
|
|
||||||
PrefsEntryT e = {0};
|
PrefsEntryT e = {0};
|
||||||
e.section = dupStr(currentSection);
|
e.section = dupStr(currentSection);
|
||||||
arrput(sEntries, e);
|
arrput(h->entries, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key=value
|
|
||||||
char *eq = strchr(p, '=');
|
char *eq = strchr(p, '=');
|
||||||
|
|
||||||
if (eq) {
|
if (eq) {
|
||||||
|
|
@ -284,13 +293,13 @@ bool prefsLoad(const char *filename) {
|
||||||
e.section = dupStr(currentSection);
|
e.section = dupStr(currentSection);
|
||||||
e.key = dupStr(trimInPlace(p));
|
e.key = dupStr(trimInPlace(p));
|
||||||
e.value = dupStr(trimInPlace(eq + 1));
|
e.value = dupStr(trimInPlace(eq + 1));
|
||||||
arrput(sEntries, e);
|
arrput(h->entries, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free(currentSection);
|
free(currentSection);
|
||||||
fclose(fp);
|
fclose(fp);
|
||||||
return true;
|
return h;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -298,12 +307,16 @@ bool prefsLoad(const char *filename) {
|
||||||
// prefsRemove
|
// prefsRemove
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void prefsRemove(const char *section, const char *key) {
|
void prefsRemove(PrefsHandleT *h, const char *section, const char *key) {
|
||||||
int32_t idx = findEntry(section, key);
|
if (!h) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t idx = findEntry(h, section, key);
|
||||||
|
|
||||||
if (idx >= 0) {
|
if (idx >= 0) {
|
||||||
freeEntry(&sEntries[idx]);
|
freeEntry(&h->entries[idx]);
|
||||||
arrdel(sEntries, idx);
|
arrdel(h->entries, idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,12 +325,12 @@ void prefsRemove(const char *section, const char *key) {
|
||||||
// prefsSave
|
// prefsSave
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool prefsSave(void) {
|
bool prefsSave(PrefsHandleT *h) {
|
||||||
if (!sFilePath) {
|
if (!h || !h->filePath) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return prefsSaveAs(sFilePath);
|
return prefsSaveAs(h, h->filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -325,29 +338,30 @@ bool prefsSave(void) {
|
||||||
// prefsSaveAs
|
// prefsSaveAs
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool prefsSaveAs(const char *filename) {
|
bool prefsSaveAs(PrefsHandleT *h, const char *filename) {
|
||||||
|
if (!h) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
FILE *fp = fopen(filename, "wb");
|
FILE *fp = fopen(filename, "wb");
|
||||||
|
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; i < arrlen(sEntries); i++) {
|
for (int32_t i = 0; i < arrlen(h->entries); i++) {
|
||||||
PrefsEntryT *e = &sEntries[i];
|
PrefsEntryT *e = &h->entries[i];
|
||||||
|
|
||||||
// Comment or blank line (key=NULL, value=text or empty)
|
|
||||||
if (!e->key && e->value) {
|
if (!e->key && e->value) {
|
||||||
fprintf(fp, "%s\r\n", e->value);
|
fprintf(fp, "%s\r\n", e->value);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Section header (key=NULL, value=NULL)
|
|
||||||
if (!e->key && !e->value) {
|
if (!e->key && !e->value) {
|
||||||
fprintf(fp, "[%s]\r\n", e->section);
|
fprintf(fp, "[%s]\r\n", e->section);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key=value
|
|
||||||
if (e->key && e->value) {
|
if (e->key && e->value) {
|
||||||
fprintf(fp, "%s = %s\r\n", e->key, e->value);
|
fprintf(fp, "%s = %s\r\n", e->key, e->value);
|
||||||
}
|
}
|
||||||
|
|
@ -362,8 +376,8 @@ bool prefsSaveAs(const char *filename) {
|
||||||
// prefsSetBool
|
// prefsSetBool
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void prefsSetBool(const char *section, const char *key, bool value) {
|
void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value) {
|
||||||
prefsSetString(section, key, value ? "true" : "false");
|
prefsSetString(h, section, key, value ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -371,10 +385,10 @@ void prefsSetBool(const char *section, const char *key, bool value) {
|
||||||
// prefsSetInt
|
// prefsSetInt
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void prefsSetInt(const char *section, const char *key, int32_t value) {
|
void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t value) {
|
||||||
char buf[32];
|
char buf[32];
|
||||||
snprintf(buf, sizeof(buf), "%ld", (long)value);
|
snprintf(buf, sizeof(buf), "%ld", (long)value);
|
||||||
prefsSetString(section, key, buf);
|
prefsSetString(h, section, key, buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -382,48 +396,45 @@ void prefsSetInt(const char *section, const char *key, int32_t value) {
|
||||||
// prefsSetString
|
// prefsSetString
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void prefsSetString(const char *section, const char *key, const char *value) {
|
void prefsSetString(PrefsHandleT *h, const char *section, const char *key, const char *value) {
|
||||||
int32_t idx = findEntry(section, key);
|
if (!h) {
|
||||||
|
|
||||||
if (idx >= 0) {
|
|
||||||
// Update existing entry
|
|
||||||
free(sEntries[idx].value);
|
|
||||||
sEntries[idx].value = dupStr(value);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find or create section header
|
int32_t idx = findEntry(h, section, key);
|
||||||
int32_t secIdx = findSection(section);
|
|
||||||
|
if (idx >= 0) {
|
||||||
|
free(h->entries[idx].value);
|
||||||
|
h->entries[idx].value = dupStr(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t secIdx = findSection(h, section);
|
||||||
|
|
||||||
if (secIdx < 0) {
|
if (secIdx < 0) {
|
||||||
// Add blank line before new section (unless file is empty)
|
if (arrlen(h->entries) > 0) {
|
||||||
if (arrlen(sEntries) > 0) {
|
|
||||||
PrefsEntryT blank = {0};
|
PrefsEntryT blank = {0};
|
||||||
blank.section = dupStr(section);
|
blank.section = dupStr(section);
|
||||||
blank.value = dupStr("");
|
blank.value = dupStr("");
|
||||||
arrput(sEntries, blank);
|
arrput(h->entries, blank);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add section header
|
|
||||||
PrefsEntryT secEntry = {0};
|
PrefsEntryT secEntry = {0};
|
||||||
secEntry.section = dupStr(section);
|
secEntry.section = dupStr(section);
|
||||||
arrput(sEntries, secEntry);
|
arrput(h->entries, secEntry);
|
||||||
secIdx = arrlen(sEntries) - 1;
|
secIdx = arrlen(h->entries) - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find insertion point: after last entry in this section
|
|
||||||
int32_t insertAt = secIdx + 1;
|
int32_t insertAt = secIdx + 1;
|
||||||
|
|
||||||
while (insertAt < arrlen(sEntries)) {
|
while (insertAt < arrlen(h->entries)) {
|
||||||
PrefsEntryT *e = &sEntries[insertAt];
|
PrefsEntryT *e = &h->entries[insertAt];
|
||||||
|
|
||||||
// Stop if we've hit a different section header
|
|
||||||
if (!e->key && !e->value && e->section &&
|
if (!e->key && !e->value && e->section &&
|
||||||
strcmpci(e->section, section) != 0) {
|
strcmpci(e->section, section) != 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop if we've hit an entry from a different section
|
|
||||||
if (e->section && strcmpci(e->section, section) != 0) {
|
if (e->section && strcmpci(e->section, section) != 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -431,10 +442,9 @@ void prefsSetString(const char *section, const char *key, const char *value) {
|
||||||
insertAt++;
|
insertAt++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert new entry
|
|
||||||
PrefsEntryT newEntry = {0};
|
PrefsEntryT newEntry = {0};
|
||||||
newEntry.section = dupStr(section);
|
newEntry.section = dupStr(section);
|
||||||
newEntry.key = dupStr(key);
|
newEntry.key = dupStr(key);
|
||||||
newEntry.value = dupStr(value);
|
newEntry.value = dupStr(value);
|
||||||
arrins(sEntries, insertAt, newEntry);
|
arrins(h->entries, insertAt, newEntry);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
// dvxPrefs.h -- INI-based preferences system (read/write)
|
// dvxPrefs.h -- INI-based preferences system (read/write)
|
||||||
//
|
//
|
||||||
// Loads a configuration file at startup and provides typed accessors
|
// Handle-based API: multiple INI files can be open simultaneously.
|
||||||
// with caller-supplied defaults. Values can be modified at runtime
|
// Each prefsOpen/prefsLoad returns a handle that must be passed to
|
||||||
// and saved back to disk. If the file is missing or a key is absent,
|
// all subsequent calls and freed with prefsClose when done.
|
||||||
// getters return the default silently.
|
|
||||||
|
|
||||||
#ifndef DVX_PREFS_H
|
#ifndef DVX_PREFS_H
|
||||||
#define DVX_PREFS_H
|
#define DVX_PREFS_H
|
||||||
|
|
@ -11,41 +10,47 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
// Load an INI file into memory. Returns true on success, false if the
|
// Opaque handle to a loaded preferences file.
|
||||||
// file could not be opened (all getters will return their defaults).
|
typedef struct PrefsHandleT PrefsHandleT;
|
||||||
// Only one file may be loaded at a time; calling again frees the previous.
|
|
||||||
bool prefsLoad(const char *filename);
|
|
||||||
|
|
||||||
// Save the current in-memory state back to the file that was loaded.
|
// Create an empty preferences handle (no file loaded). Useful for
|
||||||
// Returns true on success.
|
// building a new INI from scratch before saving.
|
||||||
bool prefsSave(void);
|
PrefsHandleT *prefsCreate(void);
|
||||||
|
|
||||||
// Save the current in-memory state to a specific file.
|
// Load an INI file into a new handle. Returns NULL on allocation
|
||||||
bool prefsSaveAs(const char *filename);
|
// failure. If the file doesn't exist, returns a valid empty handle
|
||||||
|
// (all getters return defaults) with the path stored for prefsSave.
|
||||||
|
PrefsHandleT *prefsLoad(const char *filename);
|
||||||
|
|
||||||
// Release all memory held by the preferences.
|
// Save the in-memory state back to the file that was loaded.
|
||||||
void prefsFree(void);
|
bool prefsSave(PrefsHandleT *h);
|
||||||
|
|
||||||
|
// Save the in-memory state to a specific file.
|
||||||
|
bool prefsSaveAs(PrefsHandleT *h, const char *filename);
|
||||||
|
|
||||||
|
// Release all memory held by the handle.
|
||||||
|
void prefsClose(PrefsHandleT *h);
|
||||||
|
|
||||||
// Retrieve a string value. Returns defaultVal if the key is not present.
|
// Retrieve a string value. Returns defaultVal if the key is not present.
|
||||||
// The returned pointer is valid until the key is modified or prefsFree().
|
// The returned pointer is valid until the key is modified or prefsClose.
|
||||||
const char *prefsGetString(const char *section, const char *key, const char *defaultVal);
|
const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key, const char *defaultVal);
|
||||||
|
|
||||||
// Retrieve an integer value.
|
// Retrieve an integer value.
|
||||||
int32_t prefsGetInt(const char *section, const char *key, int32_t defaultVal);
|
int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32_t defaultVal);
|
||||||
|
|
||||||
// Retrieve a boolean value. Recognises "true"/"yes"/"1" and "false"/"no"/"0".
|
// Retrieve a boolean value. Recognises "true"/"yes"/"1" and "false"/"no"/"0".
|
||||||
bool prefsGetBool(const char *section, const char *key, bool defaultVal);
|
bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool defaultVal);
|
||||||
|
|
||||||
// Set a string value. Creates the section and key if they don't exist.
|
// Set a string value. Creates the section and key if they don't exist.
|
||||||
void prefsSetString(const char *section, const char *key, const char *value);
|
void prefsSetString(PrefsHandleT *h, const char *section, const char *key, const char *value);
|
||||||
|
|
||||||
// Set an integer value.
|
// Set an integer value.
|
||||||
void prefsSetInt(const char *section, const char *key, int32_t value);
|
void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t value);
|
||||||
|
|
||||||
// Set a boolean value (stored as "true"/"false").
|
// Set a boolean value (stored as "true"/"false").
|
||||||
void prefsSetBool(const char *section, const char *key, bool value);
|
void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value);
|
||||||
|
|
||||||
// Remove a key from a section. No-op if not found.
|
// Remove a key from a section. No-op if not found.
|
||||||
void prefsRemove(const char *section, const char *key);
|
void prefsRemove(PrefsHandleT *h, const char *section, const char *key);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
3
core/thirdparty/stb_ds_impl.c
vendored
Normal file
3
core/thirdparty/stb_ds_impl.c
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
// stb_ds_impl.c -- stb_ds implementation for host test builds
|
||||||
|
#define STB_DS_IMPLEMENTATION
|
||||||
|
#include "stb_ds.h"
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static AppContextT sCtx;
|
static AppContextT sCtx;
|
||||||
|
static PrefsHandleT *sPrefs = NULL;
|
||||||
// setjmp buffer for crash recovery. The crash handler longjmps here to
|
// setjmp buffer for crash recovery. The crash handler longjmps here to
|
||||||
// return control to the shell's main loop after an app crashes.
|
// return control to the shell's main loop after an app crashes.
|
||||||
static jmp_buf sCrashJmp;
|
static jmp_buf sCrashJmp;
|
||||||
|
|
@ -211,11 +212,11 @@ int shellMain(int argc, char *argv[]) {
|
||||||
dvxLog("DVX Shell starting...");
|
dvxLog("DVX Shell starting...");
|
||||||
|
|
||||||
// Load preferences (missing file or keys silently use defaults)
|
// Load preferences (missing file or keys silently use defaults)
|
||||||
prefsLoad("CONFIG/DVX.INI");
|
sPrefs = prefsLoad("CONFIG/DVX.INI");
|
||||||
|
|
||||||
int32_t videoW = prefsGetInt("video", "width", 640);
|
int32_t videoW = prefsGetInt(sPrefs, "video", "width", 640);
|
||||||
int32_t videoH = prefsGetInt("video", "height", 480);
|
int32_t videoH = prefsGetInt(sPrefs, "video", "height", 480);
|
||||||
int32_t videoBpp = prefsGetInt("video", "bpp", 16);
|
int32_t videoBpp = prefsGetInt(sPrefs, "video", "bpp", 16);
|
||||||
dvxLog("Preferences: video %ldx%ld %ldbpp", (long)videoW, (long)videoH, (long)videoBpp);
|
dvxLog("Preferences: video %ldx%ld %ldbpp", (long)videoW, (long)videoH, (long)videoBpp);
|
||||||
|
|
||||||
// Initialize GUI
|
// Initialize GUI
|
||||||
|
|
@ -223,13 +224,13 @@ int shellMain(int argc, char *argv[]) {
|
||||||
|
|
||||||
if (result == 0) {
|
if (result == 0) {
|
||||||
// Apply mouse preferences
|
// Apply mouse preferences
|
||||||
const char *wheelStr = prefsGetString("mouse", "wheel", "normal");
|
const char *wheelStr = prefsGetString(sPrefs, "mouse", "wheel", "normal");
|
||||||
int32_t wheelDir = (strcmp(wheelStr, "reversed") == 0) ? -1 : 1;
|
int32_t wheelDir = (strcmp(wheelStr, "reversed") == 0) ? -1 : 1;
|
||||||
int32_t dblClick = prefsGetInt("mouse", "doubleclick", 500);
|
int32_t dblClick = prefsGetInt(sPrefs, "mouse", "doubleclick", 500);
|
||||||
|
|
||||||
// Map acceleration name to double-speed threshold (mickeys/sec).
|
// Map acceleration name to double-speed threshold (mickeys/sec).
|
||||||
// "off" sets a very high threshold so acceleration never triggers.
|
// "off" sets a very high threshold so acceleration never triggers.
|
||||||
const char *accelStr = prefsGetString("mouse", "acceleration", "medium");
|
const char *accelStr = prefsGetString(sPrefs, "mouse", "acceleration", "medium");
|
||||||
int32_t accelVal = 0;
|
int32_t accelVal = 0;
|
||||||
|
|
||||||
if (strcmp(accelStr, "off") == 0) {
|
if (strcmp(accelStr, "off") == 0) {
|
||||||
|
|
@ -249,7 +250,7 @@ int shellMain(int argc, char *argv[]) {
|
||||||
bool colorsLoaded = false;
|
bool colorsLoaded = false;
|
||||||
|
|
||||||
for (int32_t i = 0; i < ColorCountE; i++) {
|
for (int32_t i = 0; i < ColorCountE; i++) {
|
||||||
const char *val = prefsGetString("colors", dvxColorName((ColorIdE)i), NULL);
|
const char *val = prefsGetString(sPrefs, "colors", dvxColorName((ColorIdE)i), NULL);
|
||||||
|
|
||||||
if (val) {
|
if (val) {
|
||||||
int r;
|
int r;
|
||||||
|
|
@ -271,7 +272,7 @@ int shellMain(int argc, char *argv[]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply saved wallpaper mode and image
|
// Apply saved wallpaper mode and image
|
||||||
const char *wpMode = prefsGetString("desktop", "mode", "stretch");
|
const char *wpMode = prefsGetString(sPrefs, "desktop", "mode", "stretch");
|
||||||
|
|
||||||
if (strcmp(wpMode, "tile") == 0) {
|
if (strcmp(wpMode, "tile") == 0) {
|
||||||
sCtx.wallpaperMode = WallpaperTileE;
|
sCtx.wallpaperMode = WallpaperTileE;
|
||||||
|
|
@ -281,7 +282,7 @@ int shellMain(int argc, char *argv[]) {
|
||||||
sCtx.wallpaperMode = WallpaperStretchE;
|
sCtx.wallpaperMode = WallpaperStretchE;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *wpPath = prefsGetString("desktop", "wallpaper", NULL);
|
const char *wpPath = prefsGetString(sPrefs, "desktop", "wallpaper", NULL);
|
||||||
|
|
||||||
if (wpPath) {
|
if (wpPath) {
|
||||||
if (dvxSetWallpaper(&sCtx, wpPath)) {
|
if (dvxSetWallpaper(&sCtx, wpPath)) {
|
||||||
|
|
@ -343,7 +344,7 @@ int shellMain(int argc, char *argv[]) {
|
||||||
platformInstallCrashHandler(&sCrashJmp, &sCrashSignal, dvxLog);
|
platformInstallCrashHandler(&sCrashJmp, &sCrashSignal, dvxLog);
|
||||||
|
|
||||||
// Load the desktop app (configurable via [shell] desktop= in dvx.ini)
|
// Load the desktop app (configurable via [shell] desktop= in dvx.ini)
|
||||||
const char *desktopApp = prefsGetString("shell", "desktop", SHELL_DESKTOP_APP);
|
const char *desktopApp = prefsGetString(sPrefs, "shell", "desktop", SHELL_DESKTOP_APP);
|
||||||
int32_t desktopId = shellLoadApp(&sCtx, desktopApp);
|
int32_t desktopId = shellLoadApp(&sCtx, desktopApp);
|
||||||
|
|
||||||
if (desktopId < 0) {
|
if (desktopId < 0) {
|
||||||
|
|
@ -439,7 +440,8 @@ int shellMain(int argc, char *argv[]) {
|
||||||
|
|
||||||
tsShutdown();
|
tsShutdown();
|
||||||
dvxShutdown(&sCtx);
|
dvxShutdown(&sCtx);
|
||||||
prefsFree();
|
prefsClose(sPrefs);
|
||||||
|
sPrefs = NULL;
|
||||||
|
|
||||||
dvxLog("DVX Shell exited.");
|
dvxLog("DVX Shell exited.");
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,14 @@ BINDIR = ../bin
|
||||||
|
|
||||||
.PHONY: all clean
|
.PHONY: all clean
|
||||||
|
|
||||||
all: $(BINDIR)/dvxres $(BINDIR)/mktbicon
|
all: $(BINDIR)/dvxres $(BINDIR)/mkicon $(BINDIR)/mktbicon
|
||||||
|
|
||||||
$(BINDIR)/dvxres: dvxres.c ../core/dvxResource.c ../core/dvxResource.h | $(BINDIR)
|
$(BINDIR)/dvxres: dvxres.c ../core/dvxResource.c ../core/dvxResource.h | $(BINDIR)
|
||||||
$(CC) $(CFLAGS) -o $@ dvxres.c ../core/dvxResource.c
|
$(CC) $(CFLAGS) -o $@ dvxres.c ../core/dvxResource.c
|
||||||
|
|
||||||
|
$(BINDIR)/mkicon: mkicon.c | $(BINDIR)
|
||||||
|
$(CC) $(CFLAGS) -o $@ mkicon.c -lm
|
||||||
|
|
||||||
$(BINDIR)/mktbicon: mktbicon.c | $(BINDIR)
|
$(BINDIR)/mktbicon: mktbicon.c | $(BINDIR)
|
||||||
$(CC) $(CFLAGS) -o $@ mktbicon.c
|
$(CC) $(CFLAGS) -o $@ mktbicon.c
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,36 @@ static void vline(int x, int y0, int h, uint8_t r, uint8_t g, uint8_t b) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No-icon placeholder: grey square with red diagonal X
|
||||||
|
static void iconNoIcon(void) {
|
||||||
|
clear(192, 192, 192);
|
||||||
|
|
||||||
|
// Border
|
||||||
|
rect(0, 0, 32, 1, 128, 128, 128);
|
||||||
|
rect(0, 31, 32, 1, 128, 128, 128);
|
||||||
|
rect(0, 0, 1, 32, 128, 128, 128);
|
||||||
|
rect(31, 0, 1, 32, 128, 128, 128);
|
||||||
|
|
||||||
|
// Red X (two diagonal lines, 2px thick)
|
||||||
|
for (int i = 4; i < 28; i++) {
|
||||||
|
pixel(i, i, 200, 40, 40);
|
||||||
|
pixel(i + 1, i, 200, 40, 40);
|
||||||
|
pixel(31 - i, i, 200, 40, 40);
|
||||||
|
pixel(30 - i, i, 200, 40, 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
// "?" in the center
|
||||||
|
for (int x = 13; x <= 18; x++) { pixel(x, 8, 80, 80, 80); }
|
||||||
|
pixel(19, 9, 80, 80, 80);
|
||||||
|
pixel(19, 10, 80, 80, 80);
|
||||||
|
for (int x = 15; x <= 18; x++) { pixel(x, 11, 80, 80, 80); }
|
||||||
|
pixel(15, 12, 80, 80, 80);
|
||||||
|
pixel(15, 13, 80, 80, 80);
|
||||||
|
pixel(15, 15, 80, 80, 80);
|
||||||
|
pixel(16, 15, 80, 80, 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Clock icon: circle with hands
|
// Clock icon: circle with hands
|
||||||
static void iconClock(void) {
|
static void iconClock(void) {
|
||||||
clear(192, 192, 192);
|
clear(192, 192, 192);
|
||||||
|
|
@ -294,14 +324,16 @@ static void writeBmp(const char *path) {
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
fprintf(stderr, "Usage: mkicon <output.bmp> <type>\n");
|
fprintf(stderr, "Usage: mkicon <output.bmp> <type>\n");
|
||||||
fprintf(stderr, "Types: clock, notepad, cpanel, dvxdemo, imgview, basic\n");
|
fprintf(stderr, "Types: noicon, clock, notepad, cpanel, dvxdemo, imgview, basic\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *path = argv[1];
|
const char *path = argv[1];
|
||||||
const char *type = argv[2];
|
const char *type = argv[2];
|
||||||
|
|
||||||
if (strcmp(type, "clock") == 0) {
|
if (strcmp(type, "noicon") == 0) {
|
||||||
|
iconNoIcon();
|
||||||
|
} else if (strcmp(type, "clock") == 0) {
|
||||||
iconClock();
|
iconClock();
|
||||||
} else if (strcmp(type, "notepad") == 0) {
|
} else if (strcmp(type, "notepad") == 0) {
|
||||||
iconNotepad();
|
iconNotepad();
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue