ProgMan now requires double clicks to launch. Enum type added to widgets. Huge amount of BASIC work.
This commit is contained in:
parent
17fe1840e3
commit
88746ec2ba
17 changed files with 807 additions and 261 deletions
|
|
@ -313,6 +313,7 @@
|
||||||
// Halt
|
// Halt
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
#define OP_HALT 0xFF
|
#define OP_END 0xFE // explicit END statement -- terminates program
|
||||||
|
#define OP_HALT 0xFF // implicit end of module
|
||||||
|
|
||||||
#endif // DVXBASIC_OPCODES_H
|
#endif // DVXBASIC_OPCODES_H
|
||||||
|
|
|
||||||
|
|
@ -2613,10 +2613,10 @@ static void parseDo(BasParserT *p) {
|
||||||
|
|
||||||
|
|
||||||
static void parseEnd(BasParserT *p) {
|
static void parseEnd(BasParserT *p) {
|
||||||
// END -- by itself = halt
|
// END -- by itself = terminate program
|
||||||
// END IF / END SUB / END FUNCTION / END SELECT are handled by their parsers
|
// END IF / END SUB / END FUNCTION / END SELECT are handled by their parsers
|
||||||
advance(p); // consume END
|
advance(p); // consume END
|
||||||
basEmit8(&p->cg, OP_HALT);
|
basEmit8(&p->cg, OP_END);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -491,12 +491,15 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Forms start hidden; code must call Show to make them visible
|
||||||
WindowT *win = dvxCreateWindowCentered(rt->ctx, formName, DEFAULT_FORM_W, DEFAULT_FORM_H, true);
|
WindowT *win = dvxCreateWindowCentered(rt->ctx, formName, DEFAULT_FORM_W, DEFAULT_FORM_H, true);
|
||||||
|
|
||||||
if (!win) {
|
if (!win) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
win->visible = false;
|
||||||
|
|
||||||
WidgetT *root = wgtInitWindow(rt->ctx, win);
|
WidgetT *root = wgtInitWindow(rt->ctx, win);
|
||||||
|
|
||||||
if (!root) {
|
if (!root) {
|
||||||
|
|
@ -898,6 +901,7 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
|
||||||
}
|
}
|
||||||
|
|
||||||
form->window->visible = true;
|
form->window->visible = true;
|
||||||
|
dvxRaiseWindow(rt->ctx, form->window);
|
||||||
|
|
||||||
if (form->frmAutoSize) {
|
if (form->frmAutoSize) {
|
||||||
dvxFitWindow(rt->ctx, form->window);
|
dvxFitWindow(rt->ctx, form->window);
|
||||||
|
|
@ -1836,6 +1840,24 @@ static bool setIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propNam
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case WGT_IFACE_ENUM:
|
||||||
|
if (p->enumNames && value.type == BAS_TYPE_STRING && value.strVal) {
|
||||||
|
// Map name to index
|
||||||
|
int32_t enumVal = 0;
|
||||||
|
|
||||||
|
for (int32_t en = 0; p->enumNames[en]; en++) {
|
||||||
|
if (strcasecmp(p->enumNames[en], value.strVal->data) == 0) {
|
||||||
|
enumVal = en;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
((void (*)(WidgetT *, int32_t))p->setFn)(w, enumVal);
|
||||||
|
} else {
|
||||||
|
((void (*)(WidgetT *, int32_t))p->setFn)(w, (int32_t)basValToNumber(value));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case WGT_IFACE_INT:
|
case WGT_IFACE_INT:
|
||||||
((void (*)(WidgetT *, int32_t))p->setFn)(w, (int32_t)basValToNumber(value));
|
((void (*)(WidgetT *, int32_t))p->setFn)(w, (int32_t)basValToNumber(value));
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,41 @@ void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) {
|
||||||
}
|
}
|
||||||
|
|
||||||
w->weight = ctrl->weight;
|
w->weight = ctrl->weight;
|
||||||
|
|
||||||
|
// Apply interface properties (Alignment, etc.) from FRM data
|
||||||
|
const char *wgtName = wgtFindByBasName(ctrl->typeName);
|
||||||
|
const WgtIfaceT *iface = wgtName ? wgtGetIface(wgtName) : NULL;
|
||||||
|
|
||||||
|
if (iface) {
|
||||||
|
for (int32_t pi = 0; pi < iface->propCount; pi++) {
|
||||||
|
const WgtPropDescT *p = &iface->props[pi];
|
||||||
|
|
||||||
|
if (!p->setFn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *val = getPropValue(ctrl, p->name);
|
||||||
|
|
||||||
|
if (!val) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p->type == WGT_IFACE_ENUM && p->enumNames) {
|
||||||
|
for (int32_t en = 0; p->enumNames[en]; en++) {
|
||||||
|
if (strcasecmp(p->enumNames[en], val) == 0) {
|
||||||
|
((void (*)(WidgetT *, int32_t))p->setFn)(w, en);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (p->type == WGT_IFACE_INT) {
|
||||||
|
((void (*)(WidgetT *, int32_t))p->setFn)(w, atoi(val));
|
||||||
|
} else if (p->type == WGT_IFACE_BOOL) {
|
||||||
|
((void (*)(WidgetT *, bool))p->setFn)(w, strcasecmp(val, "True") == 0);
|
||||||
|
} else if (p->type == WGT_IFACE_STRING) {
|
||||||
|
((void (*)(WidgetT *, const char *))p->setFn)(w, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -901,6 +936,58 @@ static int32_t saveControls(const DsgnFormT *form, char *buf, int32_t bufSize, i
|
||||||
pos += snprintf(buf + pos, bufSize - pos, "%s %s = \"%s\"\n", pad, ctrl->props[j].name, ctrl->props[j].value);
|
pos += snprintf(buf + pos, bufSize - pos, "%s %s = \"%s\"\n", pad, ctrl->props[j].name, ctrl->props[j].value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save interface properties (Alignment, etc.) read from the live widget
|
||||||
|
if (ctrl->widget) {
|
||||||
|
const char *wgtName = wgtFindByBasName(ctrl->typeName);
|
||||||
|
const WgtIfaceT *iface = wgtName ? wgtGetIface(wgtName) : NULL;
|
||||||
|
|
||||||
|
if (iface) {
|
||||||
|
for (int32_t j = 0; j < iface->propCount; j++) {
|
||||||
|
const WgtPropDescT *p = &iface->props[j];
|
||||||
|
|
||||||
|
if (!p->getFn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip if already saved as a custom prop
|
||||||
|
bool already = false;
|
||||||
|
|
||||||
|
for (int32_t k = 0; k < ctrl->propCount; k++) {
|
||||||
|
if (strcasecmp(ctrl->props[k].name, p->name) == 0) {
|
||||||
|
already = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (already) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (p->type == WGT_IFACE_ENUM && p->enumNames) {
|
||||||
|
int32_t v = ((int32_t (*)(const WidgetT *))p->getFn)(ctrl->widget);
|
||||||
|
const char *name = NULL;
|
||||||
|
|
||||||
|
for (int32_t en = 0; p->enumNames[en]; en++) {
|
||||||
|
if (en == v) {
|
||||||
|
name = p->enumNames[en];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
pos += snprintf(buf + pos, bufSize - pos, "%s %s = %s\n", pad, p->name, name);
|
||||||
|
}
|
||||||
|
} else if (p->type == WGT_IFACE_INT) {
|
||||||
|
int32_t v = ((int32_t (*)(const WidgetT *))p->getFn)(ctrl->widget);
|
||||||
|
pos += snprintf(buf + pos, bufSize - pos, "%s %s = %d\n", pad, p->name, (int)v);
|
||||||
|
} else if (p->type == WGT_IFACE_BOOL) {
|
||||||
|
bool v = ((bool (*)(const WidgetT *))p->getFn)(ctrl->widget);
|
||||||
|
pos += snprintf(buf + pos, bufSize - pos, "%s %s = %s\n", pad, p->name, v ? "True" : "False");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Recursively output children of this container
|
// Recursively output children of this container
|
||||||
if (dsgnIsContainer(ctrl->typeName)) {
|
if (dsgnIsContainer(ctrl->typeName)) {
|
||||||
pos = saveControls(form, buf, bufSize, pos, ctrl->name, indent + 1);
|
pos = saveControls(form, buf, bufSize, pos, ctrl->name, indent + 1);
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,8 @@ static void loadFile(void);
|
||||||
static void parseProcs(const char *source);
|
static void parseProcs(const char *source);
|
||||||
static void updateProjectMenuState(void);
|
static void updateProjectMenuState(void);
|
||||||
static void saveActiveFile(void);
|
static void saveActiveFile(void);
|
||||||
static void saveCurProc(void);
|
static bool saveCurProc(void);
|
||||||
|
static void stashFormCode(void);
|
||||||
static void showProc(int32_t procIdx);
|
static void showProc(int32_t procIdx);
|
||||||
static int32_t toolbarBottom(void);
|
static int32_t toolbarBottom(void);
|
||||||
static void loadFilePath(const char *path);
|
static void loadFilePath(const char *path);
|
||||||
|
|
@ -197,6 +198,7 @@ static int32_t sOutputLen = 0;
|
||||||
static char *sGeneralBuf = NULL; // (General) section: module-level code
|
static char *sGeneralBuf = NULL; // (General) section: module-level code
|
||||||
static char **sProcBufs = NULL; // stb_ds array: one buffer per procedure
|
static char **sProcBufs = NULL; // stb_ds array: one buffer per procedure
|
||||||
static int32_t sCurProcIdx = -2; // which buffer is in the editor (-1=General, -2=none)
|
static int32_t sCurProcIdx = -2; // which buffer is in the editor (-1=General, -2=none)
|
||||||
|
static int32_t sEditorFileIdx = -1; // which project file owns sProcBufs (-1=none)
|
||||||
|
|
||||||
// Procedure table for Object/Event dropdowns
|
// Procedure table for Object/Event dropdowns
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|
@ -209,6 +211,7 @@ static IdeProcEntryT *sProcTable = NULL; // stb_ds dynamic array
|
||||||
static const char **sObjItems = NULL; // stb_ds dynamic array
|
static const char **sObjItems = NULL; // stb_ds dynamic array
|
||||||
static const char **sEvtItems = NULL; // stb_ds dynamic array
|
static const char **sEvtItems = NULL; // stb_ds dynamic array
|
||||||
static bool sDropdownNavSuppressed = false;
|
static bool sDropdownNavSuppressed = false;
|
||||||
|
static bool sStopRequested = false;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// App descriptor
|
// App descriptor
|
||||||
|
|
@ -265,6 +268,7 @@ int32_t appMain(DxeAppContextT *ctx) {
|
||||||
|
|
||||||
// Auto-load project for development/testing
|
// Auto-load project for development/testing
|
||||||
if (prjLoad(&sProject, "C:\\BIN\\APPS\\DVXBASIC\\MULTI.DBP")) {
|
if (prjLoad(&sProject, "C:\\BIN\\APPS\\DVXBASIC\\MULTI.DBP")) {
|
||||||
|
prjLoadAllFiles(&sProject, sAc);
|
||||||
sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick);
|
sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick);
|
||||||
|
|
||||||
if (sProjectWin) {
|
if (sProjectWin) {
|
||||||
|
|
@ -332,9 +336,10 @@ static void buildWindow(void) {
|
||||||
wmAddMenuItem(fileMenu, "Open Pro&ject...", CMD_PRJ_OPEN);
|
wmAddMenuItem(fileMenu, "Open Pro&ject...", CMD_PRJ_OPEN);
|
||||||
wmAddMenuItem(fileMenu, "Save Projec&t", CMD_PRJ_SAVE);
|
wmAddMenuItem(fileMenu, "Save Projec&t", CMD_PRJ_SAVE);
|
||||||
wmAddMenuItem(fileMenu, "Close Projec&t", CMD_PRJ_CLOSE);
|
wmAddMenuItem(fileMenu, "Close Projec&t", CMD_PRJ_CLOSE);
|
||||||
|
wmAddMenuSeparator(fileMenu);
|
||||||
wmAddMenuItem(fileMenu, "Project &Properties...", CMD_PRJ_PROPS);
|
wmAddMenuItem(fileMenu, "Project &Properties...", CMD_PRJ_PROPS);
|
||||||
wmAddMenuSeparator(fileMenu);
|
wmAddMenuSeparator(fileMenu);
|
||||||
wmAddMenuItem(fileMenu, "&Open File...\tCtrl+O", CMD_OPEN);
|
wmAddMenuItem(fileMenu, "&Add File...\tCtrl+O", CMD_OPEN);
|
||||||
wmAddMenuItem(fileMenu, "&Save File\tCtrl+S", CMD_SAVE);
|
wmAddMenuItem(fileMenu, "&Save File\tCtrl+S", CMD_SAVE);
|
||||||
wmAddMenuItem(fileMenu, "Save A&ll", CMD_SAVE_ALL);
|
wmAddMenuItem(fileMenu, "Save A&ll", CMD_SAVE_ALL);
|
||||||
wmAddMenuSeparator(fileMenu);
|
wmAddMenuSeparator(fileMenu);
|
||||||
|
|
@ -439,54 +444,73 @@ static void buildWindow(void) {
|
||||||
// Syntax colorizer callback for BASIC source code. Scans a single
|
// Syntax colorizer callback for BASIC source code. Scans a single
|
||||||
// line and fills the colors array with syntax color indices.
|
// line and fills the colors array with syntax color indices.
|
||||||
|
|
||||||
static bool isBasicKeyword(const char *word, int32_t wordLen) {
|
// Hash-based keyword/type lookup using stb_ds.
|
||||||
|
// Key = uppercase word, value = syntax color (1=keyword, 6=type).
|
||||||
|
// Built once on first use, then O(1) per lookup.
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *key;
|
||||||
|
uint8_t value;
|
||||||
|
} SyntaxMapEntryT;
|
||||||
|
|
||||||
|
static SyntaxMapEntryT *sSyntaxMap = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
static void initSyntaxMap(void) {
|
||||||
|
if (sSyntaxMap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sh_new_arena(sSyntaxMap);
|
||||||
|
|
||||||
static const char *keywords[] = {
|
static const char *keywords[] = {
|
||||||
"AND", "AS", "CALL", "CASE", "CLOSE", "CONST", "DATA", "DECLARE",
|
"AND", "AS", "BYVAL", "CALL", "CASE", "CLOSE", "CONST",
|
||||||
"DEF", "DEFINT", "DEFLNG", "DEFSNG", "DEFDBL", "DEFSTR",
|
"DATA", "DECLARE", "DEF", "DEFDBL", "DEFINT", "DEFLNG",
|
||||||
"DIM", "DO", "DOEVENTS", "ELSE", "ELSEIF", "END", "ERASE",
|
"DEFSNG", "DEFSTR", "DIM", "DO", "DOEVENTS",
|
||||||
"EXIT", "FOR", "FUNCTION", "GET", "GOSUB", "GOTO", "HIDE",
|
"ELSE", "ELSEIF", "END", "ERASE", "EXIT",
|
||||||
"IF", "IMP", "INPUT", "IS", "LET", "LIBRARY", "LINE", "LOAD",
|
"FOR", "FUNCTION",
|
||||||
"LOOP", "MOD", "MSGBOX", "NEXT", "NOT", "ON", "OPEN", "OPTION",
|
"GET", "GOSUB", "GOTO",
|
||||||
"OR", "PRINT", "PUT", "RANDOMIZE", "READ", "REDIM", "RESTORE",
|
"HIDE",
|
||||||
"RESUME", "RETURN", "SEEK", "SELECT", "SHARED", "SHELL", "SHOW",
|
"IF", "IMP", "INPUT", "IS",
|
||||||
"SLEEP", "STATIC", "STEP", "STOP", "SUB", "SWAP", "THEN", "TO",
|
"LET", "LIBRARY", "LINE", "LOAD", "LOOP",
|
||||||
"TYPE", "UNLOAD", "UNTIL", "WEND", "WHILE", "WRITE", "XOR",
|
"ME", "MOD", "MSGBOX",
|
||||||
|
"NEXT", "NOT",
|
||||||
|
"ON", "OPEN", "OPTION", "OR",
|
||||||
|
"PRINT", "PUT",
|
||||||
|
"RANDOMIZE", "READ", "REDIM", "RESTORE", "RESUME", "RETURN",
|
||||||
|
"SEEK", "SELECT", "SHARED", "SHELL", "SHOW", "SLEEP",
|
||||||
|
"STATIC", "STEP", "STOP", "SUB", "SWAP",
|
||||||
|
"THEN", "TO", "TYPE",
|
||||||
|
"UNLOAD", "UNTIL",
|
||||||
|
"WEND", "WHILE", "WRITE",
|
||||||
|
"XOR",
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
char upper[32];
|
static const char *types[] = {
|
||||||
|
"BOOLEAN", "BYTE", "DOUBLE", "FALSE", "INTEGER",
|
||||||
if (wordLen <= 0 || wordLen >= 32) {
|
"LONG", "SINGLE", "STRING", "TRUE",
|
||||||
return false;
|
NULL
|
||||||
}
|
};
|
||||||
|
|
||||||
for (int32_t i = 0; i < wordLen; i++) {
|
|
||||||
upper[i] = (char)toupper((unsigned char)word[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
upper[wordLen] = '\0';
|
|
||||||
|
|
||||||
for (int32_t i = 0; keywords[i]; i++) {
|
for (int32_t i = 0; keywords[i]; i++) {
|
||||||
if (strcmp(upper, keywords[i]) == 0) {
|
shput(sSyntaxMap, keywords[i], 1);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
for (int32_t i = 0; types[i]; i++) {
|
||||||
|
shput(sSyntaxMap, types[i], 6);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool isBasicType(const char *word, int32_t wordLen) {
|
// classifyWord -- returns syntax color for an identifier.
|
||||||
static const char *types[] = {
|
// Converts to uppercase once, then does a single hash lookup.
|
||||||
"BOOLEAN", "BYTE", "DOUBLE", "INTEGER", "LONG", "SINGLE", "STRING",
|
|
||||||
"TRUE", "FALSE",
|
|
||||||
NULL
|
|
||||||
};
|
|
||||||
|
|
||||||
|
static uint8_t classifyWord(const char *word, int32_t wordLen) {
|
||||||
char upper[32];
|
char upper[32];
|
||||||
|
|
||||||
if (wordLen <= 0 || wordLen >= 32) {
|
if (wordLen <= 0 || wordLen >= 32) {
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; i < wordLen; i++) {
|
for (int32_t i = 0; i < wordLen; i++) {
|
||||||
|
|
@ -495,13 +519,15 @@ static bool isBasicType(const char *word, int32_t wordLen) {
|
||||||
|
|
||||||
upper[wordLen] = '\0';
|
upper[wordLen] = '\0';
|
||||||
|
|
||||||
for (int32_t i = 0; types[i]; i++) {
|
initSyntaxMap();
|
||||||
if (strcmp(upper, types[i]) == 0) {
|
|
||||||
return true;
|
int32_t idx = shgeti(sSyntaxMap, upper);
|
||||||
}
|
|
||||||
|
if (idx >= 0) {
|
||||||
|
return sSyntaxMap[idx].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -562,13 +588,7 @@ static void basicColorize(const char *line, int32_t lineLen, uint8_t *colors, vo
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t c = 0; // default
|
uint8_t c = classifyWord(line + start, wordLen);
|
||||||
|
|
||||||
if (isBasicKeyword(line + start, wordLen)) {
|
|
||||||
c = 1; // SYNTAX_KEYWORD
|
|
||||||
} else if (isBasicType(line + start, wordLen)) {
|
|
||||||
c = 6; // SYNTAX_TYPE
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int32_t j = start; j < i; j++) {
|
for (int32_t j = start; j < i; j++) {
|
||||||
colors[j] = c;
|
colors[j] = c;
|
||||||
|
|
@ -678,23 +698,20 @@ static void compileAndRun(void) {
|
||||||
int32_t srcLen = 0;
|
int32_t srcLen = 0;
|
||||||
|
|
||||||
if (sProject.projectPath[0] != '\0' && sProject.fileCount > 0) {
|
if (sProject.projectPath[0] != '\0' && sProject.fileCount > 0) {
|
||||||
// Stash current editor state
|
// Stash current editor state to the file that owns the proc buffers
|
||||||
if (sProject.activeFileIdx >= 0) {
|
if (sEditorFileIdx >= 0 && sEditorFileIdx < sProject.fileCount) {
|
||||||
PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
|
PrjFileT *edFile = &sProject.files[sEditorFileIdx];
|
||||||
|
|
||||||
if (!cur->isForm) {
|
if (!edFile->isForm) {
|
||||||
|
saveCurProc();
|
||||||
const char *fullSrc = getFullSource();
|
const char *fullSrc = getFullSource();
|
||||||
free(cur->buffer);
|
free(edFile->buffer);
|
||||||
cur->buffer = fullSrc ? strdup(fullSrc) : NULL;
|
edFile->buffer = fullSrc ? strdup(fullSrc) : NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stash form code if editing a form's code
|
// Stash form code if the editor has form code loaded
|
||||||
if (sDesigner.form && sCurProcIdx >= -1) {
|
stashFormCode();
|
||||||
saveCurProc();
|
|
||||||
free(sDesigner.form->code);
|
|
||||||
sDesigner.form->code = strdup(getFullSource());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenate all .bas files from buffers (or disk if not yet loaded)
|
// Concatenate all .bas files from buffers (or disk if not yet loaded)
|
||||||
concatBuf = (char *)malloc(IDE_MAX_SOURCE);
|
concatBuf = (char *)malloc(IDE_MAX_SOURCE);
|
||||||
|
|
@ -710,7 +727,14 @@ static void compileAndRun(void) {
|
||||||
sProject.sourceMap = NULL;
|
sProject.sourceMap = NULL;
|
||||||
sProject.sourceMapCount = 0;
|
sProject.sourceMapCount = 0;
|
||||||
|
|
||||||
|
// Two passes: .bas modules first (so CONST declarations are
|
||||||
|
// available), then .frm code sections.
|
||||||
|
for (int32_t pass = 0; pass < 2; pass++)
|
||||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||||
|
// Pass 0: modules only. Pass 1: forms only.
|
||||||
|
if (pass == 0 && sProject.files[i].isForm) { continue; }
|
||||||
|
if (pass == 1 && !sProject.files[i].isForm) { continue; }
|
||||||
|
|
||||||
const char *fileSrc = NULL;
|
const char *fileSrc = NULL;
|
||||||
char *diskBuf = NULL;
|
char *diskBuf = NULL;
|
||||||
|
|
||||||
|
|
@ -935,6 +959,19 @@ static void runCached(void) {
|
||||||
static void runModule(BasModuleT *mod) {
|
static void runModule(BasModuleT *mod) {
|
||||||
setStatus("Running...");
|
setStatus("Running...");
|
||||||
|
|
||||||
|
// Hide IDE windows while the program runs
|
||||||
|
bool hadFormWin = sFormWin && sFormWin->visible;
|
||||||
|
bool hadToolbox = sToolboxWin && sToolboxWin->visible;
|
||||||
|
bool hadProps = sPropsWin && sPropsWin->visible;
|
||||||
|
bool hadCodeWin = sCodeWin && sCodeWin->visible;
|
||||||
|
bool hadPrjWin = sProjectWin && sProjectWin->visible;
|
||||||
|
|
||||||
|
if (sFormWin) { sFormWin->visible = false; dvxInvalidateWindow(sAc, sFormWin); }
|
||||||
|
if (sToolboxWin) { sToolboxWin->visible = false; dvxInvalidateWindow(sAc, sToolboxWin); }
|
||||||
|
if (sPropsWin) { sPropsWin->visible = false; dvxInvalidateWindow(sAc, sPropsWin); }
|
||||||
|
if (sCodeWin) { sCodeWin->visible = false; dvxInvalidateWindow(sAc, sCodeWin); }
|
||||||
|
if (sProjectWin) { sProjectWin->visible = false; dvxInvalidateWindow(sAc, sProjectWin); }
|
||||||
|
|
||||||
// Create VM
|
// Create VM
|
||||||
BasVmT *vm = basVmCreate();
|
BasVmT *vm = basVmCreate();
|
||||||
basVmLoadModule(vm, mod);
|
basVmLoadModule(vm, mod);
|
||||||
|
|
@ -954,9 +991,21 @@ static void runModule(BasModuleT *mod) {
|
||||||
// Load any .frm files from the same directory as the source
|
// Load any .frm files from the same directory as the source
|
||||||
loadFrmFiles(formRt);
|
loadFrmFiles(formRt);
|
||||||
|
|
||||||
// Auto-show the first form (like VB3's startup form)
|
// Auto-show the startup form (or first form if none specified).
|
||||||
|
// Other forms remain hidden until code calls Show.
|
||||||
if (formRt->formCount > 0) {
|
if (formRt->formCount > 0) {
|
||||||
basFormRtShowForm(formRt, &formRt->forms[0], false);
|
BasFormT *startupForm = &formRt->forms[0];
|
||||||
|
|
||||||
|
if (sProject.startupForm[0]) {
|
||||||
|
for (int32_t i = 0; i < formRt->formCount; i++) {
|
||||||
|
if (strcasecmp(formRt->forms[i].name, sProject.startupForm) == 0) {
|
||||||
|
startupForm = &formRt->forms[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
basFormRtShowForm(formRt, startupForm, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
sVm = vm;
|
sVm = vm;
|
||||||
|
|
@ -966,6 +1015,7 @@ static void runModule(BasModuleT *mod) {
|
||||||
|
|
||||||
int32_t totalSteps = 0;
|
int32_t totalSteps = 0;
|
||||||
BasVmResultE result;
|
BasVmResultE result;
|
||||||
|
sStopRequested = false;
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
result = basVmRun(vm);
|
result = basVmRun(vm);
|
||||||
|
|
@ -975,8 +1025,8 @@ static void runModule(BasModuleT *mod) {
|
||||||
// Yield to DVX to keep the GUI responsive
|
// Yield to DVX to keep the GUI responsive
|
||||||
dvxUpdate(sAc);
|
dvxUpdate(sAc);
|
||||||
|
|
||||||
// Stop if IDE window was closed or DVX is shutting down
|
// Stop if IDE window was closed, DVX is shutting down, or user hit Stop
|
||||||
if (!sWin || !sAc->running) {
|
if (!sWin || !sAc->running || sStopRequested) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1000,8 +1050,9 @@ static void runModule(BasModuleT *mod) {
|
||||||
// The program ends when all forms are unloaded (closed).
|
// The program ends when all forms are unloaded (closed).
|
||||||
if (result == BAS_VM_HALTED && formRt->formCount > 0) {
|
if (result == BAS_VM_HALTED && formRt->formCount > 0) {
|
||||||
setStatus("Running (event loop)...");
|
setStatus("Running (event loop)...");
|
||||||
|
sStopRequested = false;
|
||||||
|
|
||||||
while (sWin && sAc->running && formRt->formCount > 0) {
|
while (sWin && sAc->running && formRt->formCount > 0 && !sStopRequested && !vm->ended) {
|
||||||
dvxUpdate(sAc);
|
dvxUpdate(sAc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1017,6 +1068,16 @@ static void runModule(BasModuleT *mod) {
|
||||||
|
|
||||||
basFormRtDestroy(formRt);
|
basFormRtDestroy(formRt);
|
||||||
basVmDestroy(vm);
|
basVmDestroy(vm);
|
||||||
|
|
||||||
|
// Restore IDE windows
|
||||||
|
if (hadFormWin && sFormWin) { sFormWin->visible = true; dvxInvalidateWindow(sAc, sFormWin); }
|
||||||
|
if (hadToolbox && sToolboxWin) { sToolboxWin->visible = true; dvxInvalidateWindow(sAc, sToolboxWin); }
|
||||||
|
if (hadProps && sPropsWin) { sPropsWin->visible = true; dvxInvalidateWindow(sAc, sPropsWin); }
|
||||||
|
if (hadCodeWin && sCodeWin) { sCodeWin->visible = true; dvxInvalidateWindow(sAc, sCodeWin); }
|
||||||
|
if (hadPrjWin && sProjectWin) { sProjectWin->visible = true; dvxInvalidateWindow(sAc, sProjectWin); }
|
||||||
|
|
||||||
|
// Repaint to clear destroyed runtime forms and restore designer
|
||||||
|
dvxUpdate(sAc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -1191,6 +1252,9 @@ static void loadFilePath(const char *path) {
|
||||||
showCodeWindow();
|
showCodeWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stash form code before overwriting proc buffers
|
||||||
|
stashFormCode();
|
||||||
|
|
||||||
// Parse into per-procedure buffers and show (General) section
|
// Parse into per-procedure buffers and show (General) section
|
||||||
parseProcs(srcBuf);
|
parseProcs(srcBuf);
|
||||||
free(srcBuf);
|
free(srcBuf);
|
||||||
|
|
@ -1303,6 +1367,8 @@ static void ensureProject(const char *filePath) {
|
||||||
sProject.dirty = false;
|
sProject.dirty = false;
|
||||||
sProject.activeFileIdx = 0;
|
sProject.activeFileIdx = 0;
|
||||||
|
|
||||||
|
prjLoadAllFiles(&sProject, sAc);
|
||||||
|
|
||||||
char title[300];
|
char title[300];
|
||||||
snprintf(title, sizeof(title), "DVX BASIC - [%s]", sProject.name);
|
snprintf(title, sizeof(title), "DVX BASIC - [%s]", sProject.name);
|
||||||
|
|
||||||
|
|
@ -1323,7 +1389,7 @@ static void loadFile(void) {
|
||||||
|
|
||||||
char path[DVX_MAX_PATH];
|
char path[DVX_MAX_PATH];
|
||||||
|
|
||||||
if (!dvxFileDialog(sAc, "Open BASIC File", FD_OPEN, NULL, filters, 3, path, sizeof(path))) {
|
if (!dvxFileDialog(sAc, "Add File", FD_OPEN, NULL, filters, 3, path, sizeof(path))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1358,6 +1424,7 @@ static void loadFile(void) {
|
||||||
onPrjFileClick(0, true);
|
onPrjFileClick(0, true);
|
||||||
} else {
|
} else {
|
||||||
loadFilePath(path);
|
loadFilePath(path);
|
||||||
|
sEditorFileIdx = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1381,36 +1448,52 @@ static void saveActiveFile(void) {
|
||||||
char fullPath[DVX_MAX_PATH];
|
char fullPath[DVX_MAX_PATH];
|
||||||
prjFullPath(&sProject, idx, fullPath, sizeof(fullPath));
|
prjFullPath(&sProject, idx, fullPath, sizeof(fullPath));
|
||||||
|
|
||||||
if (file->isForm && sDesigner.form) {
|
if (file->isForm) {
|
||||||
// Save editor code back to form->code before saving
|
// Only serialize through the designer if it holds THIS form
|
||||||
if (sCurProcIdx >= -1) {
|
bool isDesignerForm = (sDesigner.form &&
|
||||||
saveCurProc();
|
strcasecmp(sDesigner.form->name, file->formName) == 0);
|
||||||
free(sDesigner.form->code);
|
|
||||||
sDesigner.form->code = strdup(getFullSource());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save form designer state to .frm file
|
if (isDesignerForm) {
|
||||||
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
|
stashFormCode();
|
||||||
|
|
||||||
if (frmBuf) {
|
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
|
||||||
int32_t frmLen = dsgnSaveFrm(&sDesigner, frmBuf, IDE_MAX_SOURCE);
|
|
||||||
|
|
||||||
if (frmLen > 0) {
|
if (frmBuf) {
|
||||||
FILE *f = fopen(fullPath, "w");
|
int32_t frmLen = dsgnSaveFrm(&sDesigner, frmBuf, IDE_MAX_SOURCE);
|
||||||
|
|
||||||
if (f) {
|
if (frmLen > 0) {
|
||||||
fwrite(frmBuf, 1, frmLen, f);
|
FILE *f = fopen(fullPath, "w");
|
||||||
fclose(f);
|
|
||||||
sDesigner.form->dirty = false;
|
if (f) {
|
||||||
file->modified = false;
|
fwrite(frmBuf, 1, frmLen, f);
|
||||||
|
fclose(f);
|
||||||
|
sDesigner.form->dirty = false;
|
||||||
|
file->modified = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
free(frmBuf);
|
free(frmBuf);
|
||||||
|
}
|
||||||
|
} else if (file->buffer) {
|
||||||
|
// Not the active designer form -- save from stashed buffer
|
||||||
|
FILE *f = fopen(fullPath, "w");
|
||||||
|
|
||||||
|
if (f) {
|
||||||
|
fputs(file->buffer, f);
|
||||||
|
fclose(f);
|
||||||
|
file->modified = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Save .bas file -- use editor if it has this file, else use buffer
|
||||||
|
const char *src = NULL;
|
||||||
|
|
||||||
|
if (sEditorFileIdx == idx) {
|
||||||
|
saveCurProc();
|
||||||
|
src = getFullSource();
|
||||||
|
} else {
|
||||||
|
src = file->buffer;
|
||||||
}
|
}
|
||||||
} else if (!file->isForm) {
|
|
||||||
// Save full source (splice current proc back first)
|
|
||||||
const char *src = getFullSource();
|
|
||||||
|
|
||||||
if (src) {
|
if (src) {
|
||||||
FILE *f = fopen(fullPath, "w");
|
FILE *f = fopen(fullPath, "w");
|
||||||
|
|
@ -1472,14 +1555,22 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileIdx == sProject.activeFileIdx) {
|
if (fileIdx == sProject.activeFileIdx) {
|
||||||
|
// Already active -- but ensure the right view is shown
|
||||||
|
if (isForm) {
|
||||||
|
switchToDesign();
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stash current active file's contents into its buffer
|
// Stash current active file's contents into its buffer.
|
||||||
|
// This is just caching -- do not mark modified.
|
||||||
if (sProject.activeFileIdx >= 0) {
|
if (sProject.activeFileIdx >= 0) {
|
||||||
PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
|
PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
|
||||||
|
|
||||||
if (cur->isForm && sDesigner.form) {
|
if (cur->isForm && sDesigner.form) {
|
||||||
|
// Save editor code back to form->code before serializing
|
||||||
|
stashFormCode();
|
||||||
|
|
||||||
// Serialize form designer state to .frm text
|
// Serialize form designer state to .frm text
|
||||||
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
|
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
|
||||||
|
|
||||||
|
|
@ -1495,15 +1586,13 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
||||||
free(frmBuf);
|
free(frmBuf);
|
||||||
cur->buffer = NULL;
|
cur->buffer = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
cur->modified = true;
|
|
||||||
}
|
}
|
||||||
} else if (!cur->isForm) {
|
} else if (!cur->isForm && sEditorFileIdx == sProject.activeFileIdx) {
|
||||||
// Stash full source (splice current proc back first)
|
// Stash full source (only if editor has this file's code)
|
||||||
|
saveCurProc();
|
||||||
const char *src = getFullSource();
|
const char *src = getFullSource();
|
||||||
free(cur->buffer);
|
free(cur->buffer);
|
||||||
cur->buffer = src ? strdup(src) : NULL;
|
cur->buffer = src ? strdup(src) : NULL;
|
||||||
cur->modified = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1550,11 +1639,17 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
||||||
*dot = '\0';
|
*dot = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sFormWin) {
|
||||||
|
dvxDestroyWindow(sAc, sFormWin);
|
||||||
|
cleanupFormWin();
|
||||||
|
}
|
||||||
|
|
||||||
if (sDesigner.form) {
|
if (sDesigner.form) {
|
||||||
dsgnFree(&sDesigner);
|
dsgnFree(&sDesigner);
|
||||||
}
|
}
|
||||||
|
|
||||||
dsgnNewForm(&sDesigner, formName);
|
dsgnNewForm(&sDesigner, formName);
|
||||||
|
snprintf(target->formName, sizeof(target->formName), "%s", sDesigner.form->name);
|
||||||
target->modified = true;
|
target->modified = true;
|
||||||
sProject.activeFileIdx = fileIdx;
|
sProject.activeFileIdx = fileIdx;
|
||||||
switchToDesign();
|
switchToDesign();
|
||||||
|
|
@ -1583,6 +1678,12 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
||||||
frmSrc = diskBuf;
|
frmSrc = diskBuf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close the old form designer window before loading a new form
|
||||||
|
if (sFormWin) {
|
||||||
|
dvxDestroyWindow(sAc, sFormWin);
|
||||||
|
cleanupFormWin();
|
||||||
|
}
|
||||||
|
|
||||||
if (sDesigner.form) {
|
if (sDesigner.form) {
|
||||||
dsgnFree(&sDesigner);
|
dsgnFree(&sDesigner);
|
||||||
}
|
}
|
||||||
|
|
@ -1590,10 +1691,16 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
||||||
dsgnLoadFrm(&sDesigner, frmSrc, (int32_t)strlen(frmSrc));
|
dsgnLoadFrm(&sDesigner, frmSrc, (int32_t)strlen(frmSrc));
|
||||||
free(diskBuf);
|
free(diskBuf);
|
||||||
|
|
||||||
|
if (sDesigner.form) {
|
||||||
|
snprintf(target->formName, sizeof(target->formName), "%s", sDesigner.form->name);
|
||||||
|
}
|
||||||
|
|
||||||
sProject.activeFileIdx = fileIdx;
|
sProject.activeFileIdx = fileIdx;
|
||||||
switchToDesign();
|
switchToDesign();
|
||||||
} else {
|
} else {
|
||||||
// Load .bas file from buffer or disk
|
// Load .bas file from buffer or disk
|
||||||
|
stashFormCode();
|
||||||
|
|
||||||
if (!sCodeWin) {
|
if (!sCodeWin) {
|
||||||
showCodeWindow();
|
showCodeWindow();
|
||||||
}
|
}
|
||||||
|
|
@ -1624,6 +1731,7 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
||||||
sEditor->onChange = onEditorChange;
|
sEditor->onChange = onEditorChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sEditorFileIdx = fileIdx;
|
||||||
sProject.activeFileIdx = fileIdx;
|
sProject.activeFileIdx = fileIdx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1724,6 +1832,8 @@ static void openProject(void) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prjLoadAllFiles(&sProject, sAc);
|
||||||
|
|
||||||
// Create and show project window
|
// Create and show project window
|
||||||
if (!sProjectWin) {
|
if (!sProjectWin) {
|
||||||
sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick);
|
sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick);
|
||||||
|
|
@ -1766,6 +1876,26 @@ static void closeProject(void) {
|
||||||
prjSave(&sProject);
|
prjSave(&sProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close designer windows
|
||||||
|
if (sFormWin) {
|
||||||
|
dvxDestroyWindow(sAc, sFormWin);
|
||||||
|
cleanupFormWin();
|
||||||
|
}
|
||||||
|
|
||||||
|
dsgnFree(&sDesigner);
|
||||||
|
|
||||||
|
// Close code editor
|
||||||
|
if (sCodeWin) {
|
||||||
|
dvxDestroyWindow(sAc, sCodeWin);
|
||||||
|
sCodeWin = NULL;
|
||||||
|
sEditor = NULL;
|
||||||
|
sObjDropdown = NULL;
|
||||||
|
sEvtDropdown = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
freeProcBufs();
|
||||||
|
|
||||||
|
// Close project window
|
||||||
prjClose(&sProject);
|
prjClose(&sProject);
|
||||||
|
|
||||||
if (sProjectWin) {
|
if (sProjectWin) {
|
||||||
|
|
@ -1938,13 +2068,24 @@ void ideRenameInCode(const char *oldName, const char *newName) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update form->code from the renamed buffers
|
// Update form->code from the renamed buffers (only if editor has this form's code)
|
||||||
if (sDesigner.form) {
|
if (sDesigner.form && sEditorFileIdx >= 0 && sEditorFileIdx < sProject.fileCount &&
|
||||||
|
sProject.files[sEditorFileIdx].isForm &&
|
||||||
|
strcasecmp(sProject.files[sEditorFileIdx].formName, sDesigner.form->name) == 0) {
|
||||||
free(sDesigner.form->code);
|
free(sDesigner.form->code);
|
||||||
sDesigner.form->code = strdup(getFullSource());
|
sDesigner.form->code = strdup(getFullSource());
|
||||||
sDesigner.form->dirty = true;
|
sDesigner.form->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update cached formName if the active file is a form being renamed
|
||||||
|
if (sProject.activeFileIdx >= 0 && sProject.activeFileIdx < sProject.fileCount) {
|
||||||
|
PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
|
||||||
|
|
||||||
|
if (cur->isForm && strcasecmp(cur->formName, oldName) == 0) {
|
||||||
|
snprintf(cur->formName, sizeof(cur->formName), "%s", newName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Rename in all project .bas file buffers (and non-active .frm code)
|
// Rename in all project .bas file buffers (and non-active .frm code)
|
||||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||||
// Skip the active file (already handled above)
|
// Skip the active file (already handled above)
|
||||||
|
|
@ -2006,13 +2147,8 @@ void ideRenameInCode(const char *oldName, const char *newName) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void onCodeWinClose(WindowT *win) {
|
static void onCodeWinClose(WindowT *win) {
|
||||||
// Stash code back to form->code before the window is destroyed.
|
// Stash code back before the window is destroyed.
|
||||||
// This is just caching -- do not mark dirty.
|
stashFormCode();
|
||||||
if (sDesigner.form && sCurProcIdx >= -1) {
|
|
||||||
saveCurProc();
|
|
||||||
free(sDesigner.form->code);
|
|
||||||
sDesigner.form->code = strdup(getFullSource());
|
|
||||||
}
|
|
||||||
|
|
||||||
dvxDestroyWindow(sAc, win);
|
dvxDestroyWindow(sAc, win);
|
||||||
sCodeWin = NULL;
|
sCodeWin = NULL;
|
||||||
|
|
@ -2043,43 +2179,47 @@ static void onProjectWinClose(WindowT *win) {
|
||||||
// Load all .frm files listed in the current project into the
|
// Load all .frm files listed in the current project into the
|
||||||
// form runtime for execution.
|
// form runtime for execution.
|
||||||
|
|
||||||
static void loadFrmFile(BasFormRtT *rt, const char *frmPath) {
|
|
||||||
FILE *f = fopen(frmPath, "r");
|
|
||||||
|
|
||||||
if (!f) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek(f, 0, SEEK_END);
|
|
||||||
long size = ftell(f);
|
|
||||||
fseek(f, 0, SEEK_SET);
|
|
||||||
|
|
||||||
if (size <= 0 || size >= IDE_MAX_SOURCE) {
|
|
||||||
fclose(f);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
char *frmBuf = (char *)malloc(size + 1);
|
|
||||||
|
|
||||||
if (!frmBuf) {
|
|
||||||
fclose(f);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t bytesRead = (int32_t)fread(frmBuf, 1, size, f);
|
|
||||||
fclose(f);
|
|
||||||
frmBuf[bytesRead] = '\0';
|
|
||||||
|
|
||||||
basFormRtLoadFrm(rt, frmBuf, bytesRead);
|
|
||||||
free(frmBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void loadFrmFiles(BasFormRtT *rt) {
|
static void loadFrmFiles(BasFormRtT *rt) {
|
||||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||||
if (sProject.files[i].isForm) {
|
if (!sProject.files[i].isForm) {
|
||||||
char fullPath[DVX_MAX_PATH];
|
continue;
|
||||||
prjFullPath(&sProject, i, fullPath, sizeof(fullPath));
|
}
|
||||||
loadFrmFile(rt, fullPath);
|
|
||||||
|
char fullPath[DVX_MAX_PATH];
|
||||||
|
prjFullPath(&sProject, i, fullPath, sizeof(fullPath));
|
||||||
|
|
||||||
|
FILE *f = fopen(fullPath, "r");
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long size = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (size <= 0 || size >= IDE_MAX_SOURCE) {
|
||||||
|
fclose(f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *frmBuf = (char *)malloc(size + 1);
|
||||||
|
|
||||||
|
if (!frmBuf) {
|
||||||
|
fclose(f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t bytesRead = (int32_t)fread(frmBuf, 1, size, f);
|
||||||
|
fclose(f);
|
||||||
|
frmBuf[bytesRead] = '\0';
|
||||||
|
|
||||||
|
BasFormT *form = basFormRtLoadFrm(rt, frmBuf, bytesRead);
|
||||||
|
free(frmBuf);
|
||||||
|
|
||||||
|
// Cache the form object name in the project file entry
|
||||||
|
if (form && form->name[0]) {
|
||||||
|
snprintf(sProject.files[i].formName, sizeof(sProject.files[i].formName), "%s", form->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2297,10 +2437,11 @@ static void onMenu(WindowT *win, int32_t menuId) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_STOP:
|
case CMD_STOP:
|
||||||
|
sStopRequested = true;
|
||||||
if (sVm) {
|
if (sVm) {
|
||||||
sVm->running = false;
|
sVm->running = false;
|
||||||
setStatus("Program stopped.");
|
|
||||||
}
|
}
|
||||||
|
setStatus("Program stopped.");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CMD_CLEAR:
|
case CMD_CLEAR:
|
||||||
|
|
@ -2569,19 +2710,7 @@ static void onEvtDropdownChange(WidgetT *w) {
|
||||||
snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName);
|
snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName);
|
||||||
|
|
||||||
arrput(sProcBufs, strdup(skeleton));
|
arrput(sProcBufs, strdup(skeleton));
|
||||||
|
showProc((int32_t)arrlen(sProcBufs) - 1);
|
||||||
updateDropdowns();
|
|
||||||
|
|
||||||
// Show the new proc (last in the list)
|
|
||||||
procCount = (int32_t)arrlen(sProcTable);
|
|
||||||
|
|
||||||
for (int32_t i = 0; i < procCount; i++) {
|
|
||||||
if (strcasecmp(sProcTable[i].objName, selObj) == 0 &&
|
|
||||||
strcasecmp(sProcTable[i].evtName, evtName) == 0) {
|
|
||||||
showProc(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3048,9 +3177,10 @@ static void selectDropdowns(const char *objName, const char *evtName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuild the event list for this object but suppress navigation
|
// Rebuild the event list for this object but suppress navigation
|
||||||
|
bool savedSuppress = sDropdownNavSuppressed;
|
||||||
sDropdownNavSuppressed = true;
|
sDropdownNavSuppressed = true;
|
||||||
onObjDropdownChange(sObjDropdown);
|
onObjDropdownChange(sObjDropdown);
|
||||||
sDropdownNavSuppressed = false;
|
sDropdownNavSuppressed = savedSuppress;
|
||||||
|
|
||||||
// Now select the specific event
|
// Now select the specific event
|
||||||
int32_t evtCount = (int32_t)arrlen(sEvtItems);
|
int32_t evtCount = (int32_t)arrlen(sEvtItems);
|
||||||
|
|
@ -3100,8 +3230,10 @@ static void navigateToEventSub(void) {
|
||||||
char subName[128];
|
char subName[128];
|
||||||
snprintf(subName, sizeof(subName), "%s_%s", ctrlName, eventName);
|
snprintf(subName, sizeof(subName), "%s_%s", ctrlName, eventName);
|
||||||
|
|
||||||
// Parse the form's code into per-procedure buffers
|
// Stash any existing editor code, then load this form's code
|
||||||
|
stashFormCode();
|
||||||
parseProcs(sDesigner.form->code ? sDesigner.form->code : "");
|
parseProcs(sDesigner.form->code ? sDesigner.form->code : "");
|
||||||
|
sEditorFileIdx = sProject.activeFileIdx;
|
||||||
|
|
||||||
// Ensure code window is open
|
// Ensure code window is open
|
||||||
if (!sCodeWin) {
|
if (!sCodeWin) {
|
||||||
|
|
@ -3114,9 +3246,12 @@ static void navigateToEventSub(void) {
|
||||||
|
|
||||||
// Populate dropdown items without triggering navigation --
|
// Populate dropdown items without triggering navigation --
|
||||||
// we navigate explicitly below after finding the target proc.
|
// we navigate explicitly below after finding the target proc.
|
||||||
sDropdownNavSuppressed = true;
|
{
|
||||||
updateDropdowns();
|
bool saved = sDropdownNavSuppressed;
|
||||||
sDropdownNavSuppressed = false;
|
sDropdownNavSuppressed = true;
|
||||||
|
updateDropdowns();
|
||||||
|
sDropdownNavSuppressed = saved;
|
||||||
|
}
|
||||||
|
|
||||||
// Search for existing procedure
|
// Search for existing procedure
|
||||||
int32_t procCount = (int32_t)arrlen(sProcTable);
|
int32_t procCount = (int32_t)arrlen(sProcTable);
|
||||||
|
|
@ -3144,8 +3279,6 @@ static void navigateToEventSub(void) {
|
||||||
|
|
||||||
arrput(sProcBufs, strdup(skeleton));
|
arrput(sProcBufs, strdup(skeleton));
|
||||||
|
|
||||||
updateDropdowns();
|
|
||||||
|
|
||||||
// Show the new procedure (it's the last one)
|
// Show the new procedure (it's the last one)
|
||||||
switchToCode();
|
switchToCode();
|
||||||
showProc((int32_t)arrlen(sProcBufs) - 1);
|
showProc((int32_t)arrlen(sProcBufs) - 1);
|
||||||
|
|
@ -3327,12 +3460,7 @@ static void switchToCode(void) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
static void switchToDesign(void) {
|
static void switchToDesign(void) {
|
||||||
// Save code back to form before switching
|
stashFormCode();
|
||||||
if (sDesigner.form && sCurProcIdx >= -1) {
|
|
||||||
saveCurProc();
|
|
||||||
free(sDesigner.form->code);
|
|
||||||
sDesigner.form->code = strdup(getFullSource());
|
|
||||||
}
|
|
||||||
|
|
||||||
// If already open, just bring to front
|
// If already open, just bring to front
|
||||||
if (sFormWin) {
|
if (sFormWin) {
|
||||||
|
|
@ -3415,6 +3543,7 @@ static void switchToDesign(void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dvxInvalidateWindow(sAc, sFormWin);
|
||||||
setStatus("Design view open.");
|
setStatus("Design view open.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3426,7 +3555,7 @@ static void switchToDesign(void) {
|
||||||
static void onTbOpen(WidgetT *w) { (void)w; loadFile(); }
|
static void onTbOpen(WidgetT *w) { (void)w; loadFile(); }
|
||||||
static void onTbSave(WidgetT *w) { (void)w; saveFile(); }
|
static void onTbSave(WidgetT *w) { (void)w; saveFile(); }
|
||||||
static void onTbRun(WidgetT *w) { (void)w; compileAndRun(); }
|
static void onTbRun(WidgetT *w) { (void)w; compileAndRun(); }
|
||||||
static void onTbStop(WidgetT *w) { (void)w; if (sVm) { sVm->running = false; setStatus("Program stopped."); } }
|
static void onTbStop(WidgetT *w) { (void)w; sStopRequested = true; if (sVm) { sVm->running = false; } setStatus("Program stopped."); }
|
||||||
static void onTbCode(WidgetT *w) { (void)w; switchToCode(); }
|
static void onTbCode(WidgetT *w) { (void)w; switchToCode(); }
|
||||||
static void onTbDesign(WidgetT *w) { (void)w; switchToDesign(); }
|
static void onTbDesign(WidgetT *w) { (void)w; switchToDesign(); }
|
||||||
|
|
||||||
|
|
@ -3683,8 +3812,9 @@ static void freeProcBufs(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
arrfree(sProcBufs);
|
arrfree(sProcBufs);
|
||||||
sProcBufs = NULL;
|
sProcBufs = NULL;
|
||||||
sCurProcIdx = -2;
|
sCurProcIdx = -2;
|
||||||
|
sEditorFileIdx = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -3956,19 +4086,42 @@ static char *extractNewProcs(const char *buf) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// saveCurProc -- save editor contents back to the current buffer.
|
// stashFormCode -- if the proc buffers belong to the designer's form,
|
||||||
// If the user typed a new Sub/Function in the General section,
|
// save them back to form->code. Uses sEditorFileIdx to know which
|
||||||
// it's automatically extracted into its own procedure buffer.
|
// file the proc buffers actually belong to.
|
||||||
|
|
||||||
static void saveCurProc(void) {
|
static void stashFormCode(void) {
|
||||||
if (!sEditor) {
|
if (!sDesigner.form || sEditorFileIdx < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sEditorFileIdx >= sProject.fileCount || !sProject.files[sEditorFileIdx].isForm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcasecmp(sProject.files[sEditorFileIdx].formName, sDesigner.form->name) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCurProc();
|
||||||
|
free(sDesigner.form->code);
|
||||||
|
sDesigner.form->code = strdup(getFullSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// saveCurProc -- save editor contents back to the current buffer.
|
||||||
|
// Returns true if the proc list was modified (skeleton discarded or
|
||||||
|
// new procs extracted), meaning sProcBufs indices may have shifted.
|
||||||
|
|
||||||
|
static bool saveCurProc(void) {
|
||||||
|
if (!sEditor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const char *edText = wgtGetText(sEditor);
|
const char *edText = wgtGetText(sEditor);
|
||||||
|
|
||||||
if (!edText) {
|
if (!edText) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sCurProcIdx == -1) {
|
if (sCurProcIdx == -1) {
|
||||||
|
|
@ -3979,10 +4132,11 @@ static void saveCurProc(void) {
|
||||||
sGeneralBuf = cleaned ? cleaned : strdup(edText);
|
sGeneralBuf = cleaned ? cleaned : strdup(edText);
|
||||||
|
|
||||||
if (cleaned) {
|
if (cleaned) {
|
||||||
// Update editor to show the cleaned General section
|
|
||||||
wgtSetText(sEditor, sGeneralBuf);
|
wgtSetText(sEditor, sGeneralBuf);
|
||||||
updateDropdowns();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
} else if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcBufs)) {
|
} else if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcBufs)) {
|
||||||
// Get the name of the current proc so we can identify its block
|
// Get the name of the current proc so we can identify its block
|
||||||
// regardless of position in the editor.
|
// regardless of position in the editor.
|
||||||
|
|
@ -4052,10 +4206,11 @@ static void saveCurProc(void) {
|
||||||
free(sProcBufs[sCurProcIdx]);
|
free(sProcBufs[sCurProcIdx]);
|
||||||
arrdel(sProcBufs, sCurProcIdx);
|
arrdel(sProcBufs, sCurProcIdx);
|
||||||
sCurProcIdx = -2;
|
sCurProcIdx = -2;
|
||||||
updateDropdowns();
|
return true;
|
||||||
} else {
|
} else {
|
||||||
free(sProcBufs[sCurProcIdx]);
|
free(sProcBufs[sCurProcIdx]);
|
||||||
sProcBufs[sCurProcIdx] = strdup(edText);
|
sProcBufs[sCurProcIdx] = strdup(edText);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Multiple proc blocks in the editor. Find the one matching
|
// Multiple proc blocks in the editor. Find the one matching
|
||||||
|
|
@ -4150,10 +4305,12 @@ static void saveCurProc(void) {
|
||||||
|
|
||||||
// Update editor to show only this proc
|
// Update editor to show only this proc
|
||||||
wgtSetText(sEditor, sProcBufs[sCurProcIdx]);
|
wgtSetText(sEditor, sProcBufs[sCurProcIdx]);
|
||||||
updateDropdowns();
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4164,9 +4321,16 @@ static void showProc(int32_t procIdx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save whatever is currently in the editor
|
// Save whatever is currently in the editor.
|
||||||
|
// If a buffer was deleted (empty skeleton discard), adjust the
|
||||||
|
// target index since arrdel shifts everything after it.
|
||||||
if (sCurProcIdx >= -1) {
|
if (sCurProcIdx >= -1) {
|
||||||
saveCurProc();
|
int32_t deletedIdx = sCurProcIdx;
|
||||||
|
bool changed = saveCurProc();
|
||||||
|
|
||||||
|
if (changed && deletedIdx >= 0 && procIdx > deletedIdx) {
|
||||||
|
procIdx--;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suppress onChange while loading -- setting text is not a user edit
|
// Suppress onChange while loading -- setting text is not a user edit
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
#include "dvxWm.h"
|
#include "dvxWm.h"
|
||||||
#include "widgetBox.h"
|
#include "widgetBox.h"
|
||||||
#include "widgetButton.h"
|
#include "widgetButton.h"
|
||||||
|
#include "widgetDropdown.h"
|
||||||
#include "widgetImage.h"
|
#include "widgetImage.h"
|
||||||
#include "widgetLabel.h"
|
#include "widgetLabel.h"
|
||||||
#include "widgetTextInput.h"
|
#include "widgetTextInput.h"
|
||||||
|
|
@ -64,6 +65,7 @@ static char **sLabels = NULL; // stb_ds array of strdup'd strings
|
||||||
|
|
||||||
static void onPrjWinClose(WindowT *win);
|
static void onPrjWinClose(WindowT *win);
|
||||||
static void onTreeItemDblClick(WidgetT *w);
|
static void onTreeItemDblClick(WidgetT *w);
|
||||||
|
static bool validateIcon(const char *fullPath, bool showErrors);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// prjInit
|
// prjInit
|
||||||
|
|
@ -296,6 +298,79 @@ int32_t prjAddFile(PrjStateT *prj, const char *relativePath, bool isForm) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// prjLoadAllFiles -- read all project files into memory buffers
|
||||||
|
// and extract form names from .frm files.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void prjLoadAllFiles(PrjStateT *prj, AppContextT *ctx) {
|
||||||
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
|
if (prj->files[i].buffer) {
|
||||||
|
continue; // already loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
char fullPath[DVX_MAX_PATH];
|
||||||
|
prjFullPath(prj, i, fullPath, sizeof(fullPath));
|
||||||
|
|
||||||
|
FILE *f = fopen(fullPath, "r");
|
||||||
|
|
||||||
|
if (!f) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long size = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (size <= 0) {
|
||||||
|
fclose(f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *buf = (char *)malloc(size + 1);
|
||||||
|
|
||||||
|
if (!buf) {
|
||||||
|
fclose(f);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t bytesRead = (int32_t)fread(buf, 1, size, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[bytesRead] = '\0';
|
||||||
|
|
||||||
|
prj->files[i].buffer = buf;
|
||||||
|
|
||||||
|
// Extract form name from .frm files
|
||||||
|
if (prj->files[i].isForm) {
|
||||||
|
const char *pos = buf;
|
||||||
|
|
||||||
|
while (*pos) {
|
||||||
|
while (*pos == ' ' || *pos == '\t') { pos++; }
|
||||||
|
|
||||||
|
if (strncasecmp(pos, "Begin Form ", 11) == 0) {
|
||||||
|
const char *np = pos + 11;
|
||||||
|
while (*np == ' ' || *np == '\t') { np++; }
|
||||||
|
int32_t n = 0;
|
||||||
|
while (*np && *np != ' ' && *np != '\t' && *np != '\r' && *np != '\n' && n < PRJ_MAX_NAME - 1) {
|
||||||
|
prj->files[i].formName[n++] = *np++;
|
||||||
|
}
|
||||||
|
prj->files[i].formName[n] = '\0';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*pos && *pos != '\n') { pos++; }
|
||||||
|
if (*pos == '\n') { pos++; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yield between files to keep the UI responsive
|
||||||
|
if (ctx) {
|
||||||
|
dvxUpdate(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// prjRemoveFile
|
// prjRemoveFile
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -460,26 +535,13 @@ void prjRebuildTree(PrjStateT *prj) {
|
||||||
projNode->userData = (void *)(intptr_t)-1;
|
projNode->userData = (void *)(intptr_t)-1;
|
||||||
wgtTreeItemSetExpanded(projNode, true);
|
wgtTreeItemSetExpanded(projNode, true);
|
||||||
|
|
||||||
// Forms group
|
// Forms and Modules groups
|
||||||
char *formsLabel = strdup("Forms");
|
char *formsLabel = strdup("Forms");
|
||||||
arrput(sLabels, formsLabel);
|
arrput(sLabels, formsLabel);
|
||||||
WidgetT *formsNode = wgtTreeItem(projNode, formsLabel);
|
WidgetT *formsNode = wgtTreeItem(projNode, formsLabel);
|
||||||
formsNode->userData = (void *)(intptr_t)-1;
|
formsNode->userData = (void *)(intptr_t)-1;
|
||||||
wgtTreeItemSetExpanded(formsNode, true);
|
wgtTreeItemSetExpanded(formsNode, true);
|
||||||
|
|
||||||
for (int32_t i = 0; i < prj->fileCount; i++) {
|
|
||||||
if (prj->files[i].isForm) {
|
|
||||||
char buf[DVX_MAX_PATH + 4];
|
|
||||||
snprintf(buf, sizeof(buf), "%s%s", prj->files[i].path, prj->files[i].modified ? " *" : "");
|
|
||||||
char *label = strdup(buf);
|
|
||||||
arrput(sLabels, label);
|
|
||||||
WidgetT *item = wgtTreeItem(formsNode, label);
|
|
||||||
item->userData = (void *)(intptr_t)i;
|
|
||||||
item->onDblClick = onTreeItemDblClick;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Modules group
|
|
||||||
char *modsLabel = strdup("Modules");
|
char *modsLabel = strdup("Modules");
|
||||||
arrput(sLabels, modsLabel);
|
arrput(sLabels, modsLabel);
|
||||||
WidgetT *modsNode = wgtTreeItem(projNode, modsLabel);
|
WidgetT *modsNode = wgtTreeItem(projNode, modsLabel);
|
||||||
|
|
@ -487,15 +549,13 @@ void prjRebuildTree(PrjStateT *prj) {
|
||||||
wgtTreeItemSetExpanded(modsNode, true);
|
wgtTreeItemSetExpanded(modsNode, true);
|
||||||
|
|
||||||
for (int32_t i = 0; i < prj->fileCount; i++) {
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
if (!prj->files[i].isForm) {
|
char buf[DVX_MAX_PATH + 4];
|
||||||
char buf[DVX_MAX_PATH + 4];
|
snprintf(buf, sizeof(buf), "%s%s", prj->files[i].path, prj->files[i].modified ? " *" : "");
|
||||||
snprintf(buf, sizeof(buf), "%s%s", prj->files[i].path, prj->files[i].modified ? " *" : "");
|
char *label = strdup(buf);
|
||||||
char *label = strdup(buf);
|
arrput(sLabels, label);
|
||||||
arrput(sLabels, label);
|
WidgetT *item = wgtTreeItem(prj->files[i].isForm ? formsNode : modsNode, label);
|
||||||
WidgetT *item = wgtTreeItem(modsNode, label);
|
item->userData = (void *)(intptr_t)i;
|
||||||
item->userData = (void *)(intptr_t)i;
|
item->onDblClick = onTreeItemDblClick;
|
||||||
item->onDblClick = onTreeItemDblClick;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wgtInvalidate(sTree);
|
wgtInvalidate(sTree);
|
||||||
|
|
@ -521,6 +581,8 @@ static struct {
|
||||||
WidgetT *version;
|
WidgetT *version;
|
||||||
WidgetT *copyright;
|
WidgetT *copyright;
|
||||||
WidgetT *description;
|
WidgetT *description;
|
||||||
|
WidgetT *startupForm;
|
||||||
|
const char **formNames; // stb_ds array of form name strings for startup dropdown
|
||||||
WidgetT *iconPreview;
|
WidgetT *iconPreview;
|
||||||
char iconPath[DVX_MAX_PATH];
|
char iconPath[DVX_MAX_PATH];
|
||||||
const char *appPath;
|
const char *appPath;
|
||||||
|
|
@ -533,22 +595,10 @@ static void ppdOnOk(WidgetT *w) {
|
||||||
|
|
||||||
// Validate icon path if set
|
// Validate icon path if set
|
||||||
if (sPpd.iconPath[0] && sPpd.prj) {
|
if (sPpd.iconPath[0] && sPpd.prj) {
|
||||||
const char *iconText = sPpd.iconPath;
|
|
||||||
char fullPath[DVX_MAX_PATH * 2];
|
char fullPath[DVX_MAX_PATH * 2];
|
||||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", sPpd.prj->projectDir, iconText);
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", sPpd.prj->projectDir, sPpd.iconPath);
|
||||||
|
|
||||||
int32_t infoW = 0;
|
if (!validateIcon(fullPath, true)) {
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -560,6 +610,33 @@ static void ppdOnCancel(WidgetT *w) { (void)w; sPpd.accepted = false; sPpd.done
|
||||||
static void ppdOnClose(WindowT *win) { (void)win; sPpd.accepted = false; sPpd.done = true; }
|
static void ppdOnClose(WindowT *win) { (void)win; sPpd.accepted = false; sPpd.done = true; }
|
||||||
|
|
||||||
|
|
||||||
|
// validateIcon -- check that an image file is a valid 32x32 icon.
|
||||||
|
// Returns true if valid. Shows an error dialog and returns false if not.
|
||||||
|
|
||||||
|
static bool validateIcon(const char *fullPath, bool showErrors) {
|
||||||
|
int32_t infoW = 0;
|
||||||
|
int32_t infoH = 0;
|
||||||
|
|
||||||
|
if (!dvxImageInfo(fullPath, &infoW, &infoH)) {
|
||||||
|
if (showErrors) {
|
||||||
|
dvxMessageBox(sPpd.ctx, "Invalid Icon", "Could not read image file.", MB_OK | MB_ICONERROR);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (infoW != 32 || infoH != 32) {
|
||||||
|
if (showErrors) {
|
||||||
|
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 false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void ppdLoadIconPreview(void) {
|
static void ppdLoadIconPreview(void) {
|
||||||
if (!sPpd.iconPreview || !sPpd.ctx || !sPpd.prj) {
|
if (!sPpd.iconPreview || !sPpd.ctx || !sPpd.prj) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -574,18 +651,7 @@ static void ppdLoadIconPreview(void) {
|
||||||
char fullPath[DVX_MAX_PATH * 2];
|
char fullPath[DVX_MAX_PATH * 2];
|
||||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", sPpd.prj->projectDir, relPath);
|
snprintf(fullPath, sizeof(fullPath), "%s/%s", sPpd.prj->projectDir, relPath);
|
||||||
|
|
||||||
// Verify the image is 32x32 before loading
|
if (!validateIcon(fullPath, true)) {
|
||||||
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';
|
sPpd.iconPath[0] = '\0';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -612,19 +678,7 @@ static void ppdOnBrowseIcon(WidgetT *w) {
|
||||||
char path[DVX_MAX_PATH];
|
char path[DVX_MAX_PATH];
|
||||||
|
|
||||||
if (dvxFileDialog(sPpd.ctx, "Select Icon", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
|
if (dvxFileDialog(sPpd.ctx, "Select Icon", FD_OPEN, NULL, filters, 2, path, sizeof(path))) {
|
||||||
// Validate size using the full path before accepting
|
if (!validateIcon(path, true)) {
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -761,6 +815,53 @@ bool prjPropertiesDialog(AppContextT *ctx, PrjStateT *prj, const char *appPath)
|
||||||
sPpd.company = ppdAddRow(root, "Company:", prj->company, PRJ_MAX_STRING);
|
sPpd.company = ppdAddRow(root, "Company:", prj->company, PRJ_MAX_STRING);
|
||||||
sPpd.version = ppdAddRow(root, "Version:", prj->version, PRJ_MAX_NAME);
|
sPpd.version = ppdAddRow(root, "Version:", prj->version, PRJ_MAX_NAME);
|
||||||
sPpd.copyright = ppdAddRow(root, "Copyright:", prj->copyright, PRJ_MAX_STRING);
|
sPpd.copyright = ppdAddRow(root, "Copyright:", prj->copyright, PRJ_MAX_STRING);
|
||||||
|
|
||||||
|
// Startup form dropdown
|
||||||
|
{
|
||||||
|
WidgetT *sfRow = wgtHBox(root);
|
||||||
|
sfRow->spacing = wgtPixels(4);
|
||||||
|
|
||||||
|
WidgetT *sfLbl = wgtLabel(sfRow, "Startup Form:");
|
||||||
|
sfLbl->minW = wgtPixels(PPD_LABEL_W);
|
||||||
|
|
||||||
|
sPpd.startupForm = wgtDropdown(sfRow);
|
||||||
|
sPpd.startupForm->weight = 100;
|
||||||
|
|
||||||
|
// Populate with form names from the project
|
||||||
|
sPpd.formNames = NULL;
|
||||||
|
int32_t selectedIdx = 0;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < prj->fileCount; i++) {
|
||||||
|
if (!prj->files[i].isForm) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the cached form object name, fall back to filename
|
||||||
|
const char *name = prj->files[i].formName;
|
||||||
|
char fallback[PRJ_MAX_NAME];
|
||||||
|
|
||||||
|
if (!name[0]) {
|
||||||
|
snprintf(fallback, sizeof(fallback), "%s", prj->files[i].path);
|
||||||
|
char *dot = strrchr(fallback, '.');
|
||||||
|
if (dot) { *dot = '\0'; }
|
||||||
|
name = fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
arrput(sPpd.formNames, strdup(name));
|
||||||
|
|
||||||
|
if (strcasecmp(name, prj->startupForm) == 0) {
|
||||||
|
selectedIdx = (int32_t)arrlen(sPpd.formNames) - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t formCount = (int32_t)arrlen(sPpd.formNames);
|
||||||
|
wgtDropdownSetItems(sPpd.startupForm, sPpd.formNames, formCount);
|
||||||
|
|
||||||
|
if (formCount > 0) {
|
||||||
|
wgtDropdownSetSelected(sPpd.startupForm, selectedIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Icon row: label + preview + Browse button
|
// Icon row: label + preview + Browse button
|
||||||
{
|
{
|
||||||
WidgetT *iconRow = wgtHBox(root);
|
WidgetT *iconRow = wgtHBox(root);
|
||||||
|
|
@ -847,9 +948,25 @@ bool prjPropertiesDialog(AppContextT *ctx, PrjStateT *prj, const char *appPath)
|
||||||
|
|
||||||
snprintf(prj->iconPath, sizeof(prj->iconPath), "%s", sPpd.iconPath);
|
snprintf(prj->iconPath, sizeof(prj->iconPath), "%s", sPpd.iconPath);
|
||||||
|
|
||||||
|
// Read startup form from dropdown
|
||||||
|
if (sPpd.startupForm && sPpd.formNames) {
|
||||||
|
int32_t sfIdx = wgtDropdownGetSelected(sPpd.startupForm);
|
||||||
|
|
||||||
|
if (sfIdx >= 0 && sfIdx < (int32_t)arrlen(sPpd.formNames)) {
|
||||||
|
snprintf(prj->startupForm, sizeof(prj->startupForm), "%s", sPpd.formNames[sfIdx]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prj->dirty = true;
|
prj->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Free form name strings
|
||||||
|
for (int32_t i = 0; i < (int32_t)arrlen(sPpd.formNames); i++) {
|
||||||
|
free((char *)sPpd.formNames[i]);
|
||||||
|
}
|
||||||
|
arrfree(sPpd.formNames);
|
||||||
|
sPpd.formNames = NULL;
|
||||||
|
|
||||||
ctx->modalWindow = prevModal;
|
ctx->modalWindow = prevModal;
|
||||||
dvxDestroyWindow(ctx, win);
|
dvxDestroyWindow(ctx, win);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ typedef struct {
|
||||||
bool isForm; // true = .frm, false = .bas
|
bool isForm; // true = .frm, false = .bas
|
||||||
char *buffer; // in-memory edit buffer (malloc'd, NULL = not loaded)
|
char *buffer; // in-memory edit buffer (malloc'd, NULL = not loaded)
|
||||||
bool modified; // true = buffer has unsaved changes
|
bool modified; // true = buffer has unsaved changes
|
||||||
|
char formName[PRJ_MAX_NAME]; // form object name (from "Begin Form <name>")
|
||||||
} PrjFileT;
|
} PrjFileT;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -73,6 +74,7 @@ bool prjSaveAs(PrjStateT *prj, const char *dbpPath);
|
||||||
void prjNew(PrjStateT *prj, const char *name, const char *directory);
|
void prjNew(PrjStateT *prj, const char *name, const char *directory);
|
||||||
void prjClose(PrjStateT *prj);
|
void prjClose(PrjStateT *prj);
|
||||||
int32_t prjAddFile(PrjStateT *prj, const char *relativePath, bool isForm);
|
int32_t prjAddFile(PrjStateT *prj, const char *relativePath, bool isForm);
|
||||||
|
void prjLoadAllFiles(PrjStateT *prj, AppContextT *ctx);
|
||||||
void prjRemoveFile(PrjStateT *prj, int32_t idx);
|
void prjRemoveFile(PrjStateT *prj, int32_t idx);
|
||||||
void prjFullPath(const PrjStateT *prj, int32_t fileIdx, char *outPath, int32_t outSize);
|
void prjFullPath(const PrjStateT *prj, int32_t fileIdx, char *outPath, int32_t outSize);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,7 @@ static void onPrpClose(WindowT *win) {
|
||||||
#define PROP_TYPE_STRING WGT_IFACE_STRING
|
#define PROP_TYPE_STRING WGT_IFACE_STRING
|
||||||
#define PROP_TYPE_INT WGT_IFACE_INT
|
#define PROP_TYPE_INT WGT_IFACE_INT
|
||||||
#define PROP_TYPE_BOOL WGT_IFACE_BOOL
|
#define PROP_TYPE_BOOL WGT_IFACE_BOOL
|
||||||
|
#define PROP_TYPE_ENUM WGT_IFACE_ENUM
|
||||||
#define PROP_TYPE_READONLY 255
|
#define PROP_TYPE_READONLY 255
|
||||||
|
|
||||||
static uint8_t getPropType(const char *propName, const char *typeName) {
|
static uint8_t getPropType(const char *propName, const char *typeName) {
|
||||||
|
|
@ -145,6 +146,33 @@ static uint8_t getPropType(const char *propName, const char *typeName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static const WgtPropDescT *findIfaceProp(const char *typeName, const char *propName) {
|
||||||
|
if (!typeName || !typeName[0]) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *wgtName = wgtFindByBasName(typeName);
|
||||||
|
|
||||||
|
if (!wgtName) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WgtIfaceT *iface = wgtGetIface(wgtName);
|
||||||
|
|
||||||
|
if (!iface) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < iface->propCount; i++) {
|
||||||
|
if (strcasecmp(iface->props[i].name, propName) == 0) {
|
||||||
|
return &iface->props[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// cascadeToChildren
|
// cascadeToChildren
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -260,6 +288,31 @@ static void onPropDblClick(WidgetT *w) {
|
||||||
// Toggle boolean on double-click -- no input box
|
// Toggle boolean on double-click -- no input box
|
||||||
bool cur = (strcasecmp(curValue, "True") == 0);
|
bool cur = (strcasecmp(curValue, "True") == 0);
|
||||||
snprintf(newValue, sizeof(newValue), "%s", cur ? "False" : "True");
|
snprintf(newValue, sizeof(newValue), "%s", cur ? "False" : "True");
|
||||||
|
} else if (propType == PROP_TYPE_ENUM) {
|
||||||
|
// Enum: cycle to next value on double-click
|
||||||
|
const WgtPropDescT *pd = findIfaceProp(ctrlTypeName, propName);
|
||||||
|
|
||||||
|
if (!pd || !pd->enumNames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find current value and advance to next
|
||||||
|
int32_t enumCount = 0;
|
||||||
|
int32_t curIdx = 0;
|
||||||
|
|
||||||
|
while (pd->enumNames[enumCount]) {
|
||||||
|
if (strcasecmp(pd->enumNames[enumCount], curValue) == 0) {
|
||||||
|
curIdx = enumCount;
|
||||||
|
}
|
||||||
|
enumCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enumCount == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t nextIdx = (curIdx + 1) % enumCount;
|
||||||
|
snprintf(newValue, sizeof(newValue), "%s", pd->enumNames[nextIdx]);
|
||||||
} else if (propType == PROP_TYPE_INT) {
|
} else if (propType == PROP_TYPE_INT) {
|
||||||
// Spinner dialog for integers
|
// Spinner dialog for integers
|
||||||
char prompt[128];
|
char prompt[128];
|
||||||
|
|
@ -387,6 +440,17 @@ static void onPropDblClick(WidgetT *w) {
|
||||||
((void (*)(WidgetT *, const char *))p->setFn)(ctrl->widget, ctrl->props[ctrl->propCount].value);
|
((void (*)(WidgetT *, const char *))p->setFn)(ctrl->widget, ctrl->props[ctrl->propCount].value);
|
||||||
ctrl->propCount++;
|
ctrl->propCount++;
|
||||||
}
|
}
|
||||||
|
} else if (p->type == WGT_IFACE_ENUM && p->enumNames) {
|
||||||
|
int32_t enumVal = 0;
|
||||||
|
|
||||||
|
for (int32_t en = 0; p->enumNames[en]; en++) {
|
||||||
|
if (strcasecmp(p->enumNames[en], newValue) == 0) {
|
||||||
|
enumVal = en;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
((void (*)(WidgetT *, int32_t))p->setFn)(ctrl->widget, enumVal);
|
||||||
} else if (p->type == WGT_IFACE_INT) {
|
} else if (p->type == WGT_IFACE_INT) {
|
||||||
((void (*)(WidgetT *, int32_t))p->setFn)(ctrl->widget, atoi(newValue));
|
((void (*)(WidgetT *, int32_t))p->setFn)(ctrl->widget, atoi(newValue));
|
||||||
} else if (p->type == WGT_IFACE_BOOL) {
|
} else if (p->type == WGT_IFACE_BOOL) {
|
||||||
|
|
@ -885,6 +949,18 @@ void prpRefresh(DsgnStateT *ds) {
|
||||||
if (p->type == WGT_IFACE_STRING && p->getFn) {
|
if (p->type == WGT_IFACE_STRING && p->getFn) {
|
||||||
const char *s = ((const char *(*)(WidgetT *))p->getFn)(ctrl->widget);
|
const char *s = ((const char *(*)(WidgetT *))p->getFn)(ctrl->widget);
|
||||||
addPropRow(p->name, s ? s : "");
|
addPropRow(p->name, s ? s : "");
|
||||||
|
} else if (p->type == WGT_IFACE_ENUM && p->getFn && p->enumNames) {
|
||||||
|
int32_t v = ((int32_t (*)(const WidgetT *))p->getFn)(ctrl->widget);
|
||||||
|
const char *name = NULL;
|
||||||
|
|
||||||
|
for (int32_t k = 0; p->enumNames[k]; k++) {
|
||||||
|
if (k == v) {
|
||||||
|
name = p->enumNames[k];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addPropRow(p->name, name ? name : "?");
|
||||||
} else if (p->type == WGT_IFACE_INT && p->getFn) {
|
} else if (p->type == WGT_IFACE_INT && p->getFn) {
|
||||||
int32_t v = ((int32_t (*)(const WidgetT *))p->getFn)(ctrl->widget);
|
int32_t v = ((int32_t (*)(const WidgetT *))p->getFn)(ctrl->widget);
|
||||||
snprintf(buf, sizeof(buf), "%d", (int)v);
|
snprintf(buf, sizeof(buf), "%d", (int)v);
|
||||||
|
|
|
||||||
|
|
@ -2534,6 +2534,11 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
||||||
// Halt
|
// Halt
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
case OP_END:
|
||||||
|
vm->running = false;
|
||||||
|
vm->ended = true;
|
||||||
|
return BAS_VM_HALTED;
|
||||||
|
|
||||||
case OP_HALT:
|
case OP_HALT:
|
||||||
vm->running = false;
|
vm->running = false;
|
||||||
return BAS_VM_HALTED;
|
return BAS_VM_HALTED;
|
||||||
|
|
@ -2974,16 +2979,17 @@ static BasVmResultE execArith(BasVmT *vm, uint8_t op) {
|
||||||
BasStringT *sa = a.strVal ? a.strVal : basStringNew("", 0);
|
BasStringT *sa = a.strVal ? a.strVal : basStringNew("", 0);
|
||||||
BasStringT *sb = b.strVal ? b.strVal : basStringNew("", 0);
|
BasStringT *sb = b.strVal ? b.strVal : basStringNew("", 0);
|
||||||
int32_t newLen = sa->len + sb->len;
|
int32_t newLen = sa->len + sb->len;
|
||||||
BasStringT *cat = basStringNew("", 0);
|
BasStringT *cat;
|
||||||
|
|
||||||
if (newLen > 0) {
|
if (newLen > 0) {
|
||||||
basStringUnref(cat);
|
|
||||||
char *buf = (char *)malloc(newLen + 1);
|
char *buf = (char *)malloc(newLen + 1);
|
||||||
memcpy(buf, sa->data, sa->len);
|
memcpy(buf, sa->data, sa->len);
|
||||||
memcpy(buf + sa->len, sb->data, sb->len);
|
memcpy(buf + sa->len, sb->data, sb->len);
|
||||||
buf[newLen] = '\0';
|
buf[newLen] = '\0';
|
||||||
cat = basStringNew(buf, newLen);
|
cat = basStringNew(buf, newLen);
|
||||||
free(buf);
|
free(buf);
|
||||||
|
} else {
|
||||||
|
cat = basStringNew("", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
basValRelease(&a);
|
basValRelease(&a);
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,7 @@ typedef struct {
|
||||||
// Execution
|
// Execution
|
||||||
int32_t pc; // program counter
|
int32_t pc; // program counter
|
||||||
bool running;
|
bool running;
|
||||||
|
bool ended; // END statement executed -- program should terminate
|
||||||
bool yielded;
|
bool yielded;
|
||||||
int32_t stepLimit; // max steps per basVmRun (0 = unlimited)
|
int32_t stepLimit; // max steps per basVmRun (0 = unlimited)
|
||||||
int32_t stepCount; // steps executed in last basVmRun
|
int32_t stepCount; // steps executed in last basVmRun
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ static int32_t sAppCount = 0;
|
||||||
int32_t appMain(DxeAppContextT *ctx);
|
int32_t appMain(DxeAppContextT *ctx);
|
||||||
static void buildPmWindow(void);
|
static void buildPmWindow(void);
|
||||||
static void desktopUpdate(void);
|
static void desktopUpdate(void);
|
||||||
static void onAppButtonClick(WidgetT *w);
|
static void onAppButtonDblClick(WidgetT *w);
|
||||||
static void onPmClose(WindowT *win);
|
static void onPmClose(WindowT *win);
|
||||||
static void onPmMenu(WindowT *win, int32_t menuId);
|
static void onPmMenu(WindowT *win, int32_t menuId);
|
||||||
static void scanAppsDir(void);
|
static void scanAppsDir(void);
|
||||||
|
|
@ -233,8 +233,7 @@ static void buildPmWindow(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
btn->userData = &sAppFiles[i];
|
btn->userData = &sAppFiles[i];
|
||||||
btn->onDblClick = onAppButtonClick;
|
btn->onDblClick = onAppButtonDblClick;
|
||||||
btn->onClick = onAppButtonClick;
|
|
||||||
|
|
||||||
if (sAppFiles[i].tooltip[0]) {
|
if (sAppFiles[i].tooltip[0]) {
|
||||||
wgtSetTooltip(btn, sAppFiles[i].tooltip);
|
wgtSetTooltip(btn, sAppFiles[i].tooltip);
|
||||||
|
|
@ -277,7 +276,7 @@ static void desktopUpdate(void) {
|
||||||
|
|
||||||
// Widget click handler for app grid buttons. userData was set to the
|
// Widget click handler for app grid buttons. userData was set to the
|
||||||
// AppEntryT pointer during window construction, giving us the .app path.
|
// AppEntryT pointer during window construction, giving us the .app path.
|
||||||
static void onAppButtonClick(WidgetT *w) {
|
static void onAppButtonDblClick(WidgetT *w) {
|
||||||
AppEntryT *entry = (AppEntryT *)w->userData;
|
AppEntryT *entry = (AppEntryT *)w->userData;
|
||||||
|
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
|
|
|
||||||
|
|
@ -1546,9 +1546,9 @@ static void drawPopupLevel(AppContextT *ctx, DisplayT *d, const BlitOpsT *ops, c
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t bg = ctx->colors.menuBg;
|
uint32_t bg = ctx->colors.menuBg;
|
||||||
uint32_t fg = ctx->colors.menuFg;
|
uint32_t fg = item->enabled ? ctx->colors.menuFg : ctx->colors.windowShadow;
|
||||||
|
|
||||||
if (k == hoverItem) {
|
if (k == hoverItem && item->enabled) {
|
||||||
bg = ctx->colors.menuHighlightBg;
|
bg = ctx->colors.menuHighlightBg;
|
||||||
fg = ctx->colors.menuHighlightFg;
|
fg = ctx->colors.menuHighlightFg;
|
||||||
}
|
}
|
||||||
|
|
@ -1845,7 +1845,13 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
|
||||||
} else {
|
} else {
|
||||||
ctx->lastTitleClickTime = now;
|
ctx->lastTitleClickTime = now;
|
||||||
ctx->lastTitleClickId = win->id;
|
ctx->lastTitleClickId = win->id;
|
||||||
wmDragBegin(&ctx->stack, hitIdx, mx, my);
|
|
||||||
|
// Don't start a drag on a maximized window --
|
||||||
|
// dragging clears the maximized flag, which
|
||||||
|
// prevents the double-click restore from working.
|
||||||
|
if (!win->maximized) {
|
||||||
|
wmDragBegin(&ctx->stack, hitIdx, mx, my);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
@ -3942,6 +3948,21 @@ void dvxDestroyWindow(AppContextT *ctx, WindowT *win) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// dvxRaiseWindow
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void dvxRaiseWindow(AppContextT *ctx, WindowT *win) {
|
||||||
|
for (int32_t i = 0; i < ctx->stack.count; i++) {
|
||||||
|
if (ctx->stack.windows[i] == win) {
|
||||||
|
wmRaiseWindow(&ctx->stack, &ctx->dirty, i);
|
||||||
|
wmSetFocus(&ctx->stack, &ctx->dirty, ctx->stack.count - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// dvxFitWindow
|
// dvxFitWindow
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,9 @@ WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w,
|
||||||
// Destroy a window, free all its resources, and dirty its former region.
|
// Destroy a window, free all its resources, and dirty its former region.
|
||||||
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
|
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
|
||||||
|
|
||||||
|
// Raise a window to the top of the z-order and give it focus.
|
||||||
|
void dvxRaiseWindow(AppContextT *ctx, WindowT *win);
|
||||||
|
|
||||||
// Resize a window to exactly fit its widget tree's computed minimum size
|
// Resize a window to exactly fit its widget tree's computed minimum size
|
||||||
// (plus chrome). Used for dialog boxes and other fixed-layout windows
|
// (plus chrome). Used for dialog boxes and other fixed-layout windows
|
||||||
// where the window should shrink-wrap its content.
|
// where the window should shrink-wrap its content.
|
||||||
|
|
|
||||||
|
|
@ -552,6 +552,7 @@ const void *wgtGetApi(const char *name);
|
||||||
#define WGT_IFACE_INT 1
|
#define WGT_IFACE_INT 1
|
||||||
#define WGT_IFACE_BOOL 2
|
#define WGT_IFACE_BOOL 2
|
||||||
#define WGT_IFACE_FLOAT 3
|
#define WGT_IFACE_FLOAT 3
|
||||||
|
#define WGT_IFACE_ENUM 4 // int32_t with named values
|
||||||
|
|
||||||
// Method calling conventions (how the form runtime marshals args)
|
// Method calling conventions (how the form runtime marshals args)
|
||||||
#define WGT_SIG_VOID 0 // void fn(WidgetT *)
|
#define WGT_SIG_VOID 0 // void fn(WidgetT *)
|
||||||
|
|
@ -566,10 +567,11 @@ const void *wgtGetApi(const char *name);
|
||||||
|
|
||||||
// Property descriptor
|
// Property descriptor
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char *name; // BASIC property name (e.g. "Caption", "Value")
|
const char *name; // BASIC property name (e.g. "Caption", "Value")
|
||||||
uint8_t type; // WGT_IFACE_*
|
uint8_t type; // WGT_IFACE_*
|
||||||
void *getFn; // getter function pointer (NULL if write-only)
|
void *getFn; // getter function pointer (NULL if write-only)
|
||||||
void *setFn; // setter function pointer (NULL if read-only)
|
void *setFn; // setter function pointer (NULL if read-only)
|
||||||
|
const char **enumNames; // WGT_IFACE_ENUM only: NULL-terminated array of value names
|
||||||
} WgtPropDescT;
|
} WgtPropDescT;
|
||||||
|
|
||||||
// Method descriptor
|
// Method descriptor
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,30 @@ void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *f
|
||||||
|
|
||||||
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, false);
|
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw scroll indicators if the list extends beyond visible area
|
||||||
|
if (itemCount > visibleItems) {
|
||||||
|
int32_t cx = popX + popW / 2;
|
||||||
|
uint32_t arrowC = colors->menuHighlightBg;
|
||||||
|
|
||||||
|
// Up triangle (point at top, wide at bottom)
|
||||||
|
if (scrollPos > 0) {
|
||||||
|
int32_t ty = popY + 2;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < 3; i++) {
|
||||||
|
drawHLine(d, ops, cx - i, ty + i, 1 + i * 2, arrowC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down triangle (wide at top, point at bottom)
|
||||||
|
if (scrollPos + visibleItems < itemCount) {
|
||||||
|
int32_t by = popY + popH - 4;
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < 3; i++) {
|
||||||
|
drawHLine(d, ops, cx - i, by - i, 1 + i * 2, arrowC);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Popup is closed
|
// Popup is closed
|
||||||
if (key == (0x50 | 0x100) || key == ' ' || key == 0x0D) {
|
if (key == ' ' || key == 0x0D) {
|
||||||
d->open = true;
|
d->open = true;
|
||||||
d->hoverIdx = d->selectedIdx;
|
d->hoverIdx = d->selectedIdx;
|
||||||
sOpenPopup = w;
|
sOpenPopup = w;
|
||||||
|
|
@ -134,6 +134,15 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (d->hoverIdx < d->scrollPos) {
|
if (d->hoverIdx < d->scrollPos) {
|
||||||
d->scrollPos = d->hoverIdx;
|
d->scrollPos = d->hoverIdx;
|
||||||
}
|
}
|
||||||
|
} else if (key == (0x50 | 0x100)) {
|
||||||
|
// Down arrow: cycle selection forward (wheel-friendly)
|
||||||
|
if (d->selectedIdx < d->itemCount - 1) {
|
||||||
|
d->selectedIdx++;
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (key == (0x48 | 0x100)) {
|
} else if (key == (0x48 | 0x100)) {
|
||||||
if (d->selectedIdx > 0) {
|
if (d->selectedIdx > 0) {
|
||||||
d->selectedIdx--;
|
d->selectedIdx--;
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,16 @@ void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int32_t wgtLabelGetAlign(const WidgetT *w) {
|
||||||
|
if (w && w->type == sTypeId) {
|
||||||
|
LabelDataT *d = (LabelDataT *)w->data;
|
||||||
|
return (int32_t)d->textAlign;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// DXE registration
|
// DXE registration
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -160,8 +170,10 @@ static const struct {
|
||||||
.setAlign = wgtLabelSetAlign
|
.setAlign = wgtLabelSetAlign
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const char *sAlignNames[] = { "Left", "Center", "Right", NULL };
|
||||||
|
|
||||||
static const WgtPropDescT sProps[] = {
|
static const WgtPropDescT sProps[] = {
|
||||||
{ "Alignment", WGT_IFACE_INT, NULL, (void *)wgtLabelSetAlign }
|
{ "Alignment", WGT_IFACE_ENUM, (void *)wgtLabelGetAlign, (void *)wgtLabelSetAlign, sAlignNames }
|
||||||
};
|
};
|
||||||
|
|
||||||
static const WgtIfaceT sIface = {
|
static const WgtIfaceT sIface = {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue