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
|
||||
// ============================================================
|
||||
|
||||
#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
|
||||
|
|
|
|||
|
|
@ -2613,10 +2613,10 @@ static void parseDo(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
|
||||
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);
|
||||
|
||||
if (!win) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
win->visible = false;
|
||||
|
||||
WidgetT *root = wgtInitWindow(rt->ctx, win);
|
||||
|
||||
if (!root) {
|
||||
|
|
@ -898,6 +901,7 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
|
|||
}
|
||||
|
||||
form->window->visible = true;
|
||||
dvxRaiseWindow(rt->ctx, form->window);
|
||||
|
||||
if (form->frmAutoSize) {
|
||||
dvxFitWindow(rt->ctx, form->window);
|
||||
|
|
@ -1836,6 +1840,24 @@ static bool setIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propNam
|
|||
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:
|
||||
((void (*)(WidgetT *, int32_t))p->setFn)(w, (int32_t)basValToNumber(value));
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -220,6 +220,41 @@ void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
if (dsgnIsContainer(ctrl->typeName)) {
|
||||
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 updateProjectMenuState(void);
|
||||
static void saveActiveFile(void);
|
||||
static void saveCurProc(void);
|
||||
static bool saveCurProc(void);
|
||||
static void stashFormCode(void);
|
||||
static void showProc(int32_t procIdx);
|
||||
static int32_t toolbarBottom(void);
|
||||
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 **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 sEditorFileIdx = -1; // which project file owns sProcBufs (-1=none)
|
||||
|
||||
// Procedure table for Object/Event dropdowns
|
||||
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 **sEvtItems = NULL; // stb_ds dynamic array
|
||||
static bool sDropdownNavSuppressed = false;
|
||||
static bool sStopRequested = false;
|
||||
|
||||
// ============================================================
|
||||
// App descriptor
|
||||
|
|
@ -265,6 +268,7 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
|
||||
// Auto-load project for development/testing
|
||||
if (prjLoad(&sProject, "C:\\BIN\\APPS\\DVXBASIC\\MULTI.DBP")) {
|
||||
prjLoadAllFiles(&sProject, sAc);
|
||||
sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick);
|
||||
|
||||
if (sProjectWin) {
|
||||
|
|
@ -332,9 +336,10 @@ static void buildWindow(void) {
|
|||
wmAddMenuItem(fileMenu, "Open Pro&ject...", CMD_PRJ_OPEN);
|
||||
wmAddMenuItem(fileMenu, "Save Projec&t", CMD_PRJ_SAVE);
|
||||
wmAddMenuItem(fileMenu, "Close Projec&t", CMD_PRJ_CLOSE);
|
||||
wmAddMenuSeparator(fileMenu);
|
||||
wmAddMenuItem(fileMenu, "Project &Properties...", CMD_PRJ_PROPS);
|
||||
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 A&ll", CMD_SAVE_ALL);
|
||||
wmAddMenuSeparator(fileMenu);
|
||||
|
|
@ -439,54 +444,73 @@ static void buildWindow(void) {
|
|||
// Syntax colorizer callback for BASIC source code. Scans a single
|
||||
// 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[] = {
|
||||
"AND", "AS", "CALL", "CASE", "CLOSE", "CONST", "DATA", "DECLARE",
|
||||
"DEF", "DEFINT", "DEFLNG", "DEFSNG", "DEFDBL", "DEFSTR",
|
||||
"DIM", "DO", "DOEVENTS", "ELSE", "ELSEIF", "END", "ERASE",
|
||||
"EXIT", "FOR", "FUNCTION", "GET", "GOSUB", "GOTO", "HIDE",
|
||||
"IF", "IMP", "INPUT", "IS", "LET", "LIBRARY", "LINE", "LOAD",
|
||||
"LOOP", "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",
|
||||
"AND", "AS", "BYVAL", "CALL", "CASE", "CLOSE", "CONST",
|
||||
"DATA", "DECLARE", "DEF", "DEFDBL", "DEFINT", "DEFLNG",
|
||||
"DEFSNG", "DEFSTR", "DIM", "DO", "DOEVENTS",
|
||||
"ELSE", "ELSEIF", "END", "ERASE", "EXIT",
|
||||
"FOR", "FUNCTION",
|
||||
"GET", "GOSUB", "GOTO",
|
||||
"HIDE",
|
||||
"IF", "IMP", "INPUT", "IS",
|
||||
"LET", "LIBRARY", "LINE", "LOAD", "LOOP",
|
||||
"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
|
||||
};
|
||||
|
||||
char upper[32];
|
||||
|
||||
if (wordLen <= 0 || wordLen >= 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < wordLen; i++) {
|
||||
upper[i] = (char)toupper((unsigned char)word[i]);
|
||||
}
|
||||
|
||||
upper[wordLen] = '\0';
|
||||
static const char *types[] = {
|
||||
"BOOLEAN", "BYTE", "DOUBLE", "FALSE", "INTEGER",
|
||||
"LONG", "SINGLE", "STRING", "TRUE",
|
||||
NULL
|
||||
};
|
||||
|
||||
for (int32_t i = 0; keywords[i]; i++) {
|
||||
if (strcmp(upper, keywords[i]) == 0) {
|
||||
return true;
|
||||
shput(sSyntaxMap, keywords[i], 1);
|
||||
}
|
||||
|
||||
for (int32_t i = 0; types[i]; i++) {
|
||||
shput(sSyntaxMap, types[i], 6);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// classifyWord -- returns syntax color for an identifier.
|
||||
// Converts to uppercase once, then does a single hash lookup.
|
||||
|
||||
static bool isBasicType(const char *word, int32_t wordLen) {
|
||||
static const char *types[] = {
|
||||
"BOOLEAN", "BYTE", "DOUBLE", "INTEGER", "LONG", "SINGLE", "STRING",
|
||||
"TRUE", "FALSE",
|
||||
NULL
|
||||
};
|
||||
|
||||
static uint8_t classifyWord(const char *word, int32_t wordLen) {
|
||||
char upper[32];
|
||||
|
||||
if (wordLen <= 0 || wordLen >= 32) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < wordLen; i++) {
|
||||
|
|
@ -495,13 +519,15 @@ static bool isBasicType(const char *word, int32_t wordLen) {
|
|||
|
||||
upper[wordLen] = '\0';
|
||||
|
||||
for (int32_t i = 0; types[i]; i++) {
|
||||
if (strcmp(upper, types[i]) == 0) {
|
||||
return true;
|
||||
}
|
||||
initSyntaxMap();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint8_t c = 0; // default
|
||||
|
||||
if (isBasicKeyword(line + start, wordLen)) {
|
||||
c = 1; // SYNTAX_KEYWORD
|
||||
} else if (isBasicType(line + start, wordLen)) {
|
||||
c = 6; // SYNTAX_TYPE
|
||||
}
|
||||
uint8_t c = classifyWord(line + start, wordLen);
|
||||
|
||||
for (int32_t j = start; j < i; j++) {
|
||||
colors[j] = c;
|
||||
|
|
@ -678,23 +698,20 @@ static void compileAndRun(void) {
|
|||
int32_t srcLen = 0;
|
||||
|
||||
if (sProject.projectPath[0] != '\0' && sProject.fileCount > 0) {
|
||||
// Stash current editor state
|
||||
if (sProject.activeFileIdx >= 0) {
|
||||
PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
|
||||
// Stash current editor state to the file that owns the proc buffers
|
||||
if (sEditorFileIdx >= 0 && sEditorFileIdx < sProject.fileCount) {
|
||||
PrjFileT *edFile = &sProject.files[sEditorFileIdx];
|
||||
|
||||
if (!cur->isForm) {
|
||||
const char *fullSrc = getFullSource();
|
||||
free(cur->buffer);
|
||||
cur->buffer = fullSrc ? strdup(fullSrc) : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Stash form code if editing a form's code
|
||||
if (sDesigner.form && sCurProcIdx >= -1) {
|
||||
if (!edFile->isForm) {
|
||||
saveCurProc();
|
||||
free(sDesigner.form->code);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
const char *fullSrc = getFullSource();
|
||||
free(edFile->buffer);
|
||||
edFile->buffer = fullSrc ? strdup(fullSrc) : NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Stash form code if the editor has form code loaded
|
||||
stashFormCode();
|
||||
|
||||
// Concatenate all .bas files from buffers (or disk if not yet loaded)
|
||||
concatBuf = (char *)malloc(IDE_MAX_SOURCE);
|
||||
|
|
@ -710,7 +727,14 @@ static void compileAndRun(void) {
|
|||
sProject.sourceMap = NULL;
|
||||
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++) {
|
||||
// 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;
|
||||
char *diskBuf = NULL;
|
||||
|
||||
|
|
@ -935,6 +959,19 @@ static void runCached(void) {
|
|||
static void runModule(BasModuleT *mod) {
|
||||
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
|
||||
BasVmT *vm = basVmCreate();
|
||||
basVmLoadModule(vm, mod);
|
||||
|
|
@ -954,9 +991,21 @@ static void runModule(BasModuleT *mod) {
|
|||
// Load any .frm files from the same directory as the source
|
||||
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) {
|
||||
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;
|
||||
|
|
@ -966,6 +1015,7 @@ static void runModule(BasModuleT *mod) {
|
|||
|
||||
int32_t totalSteps = 0;
|
||||
BasVmResultE result;
|
||||
sStopRequested = false;
|
||||
|
||||
for (;;) {
|
||||
result = basVmRun(vm);
|
||||
|
|
@ -975,8 +1025,8 @@ static void runModule(BasModuleT *mod) {
|
|||
// Yield to DVX to keep the GUI responsive
|
||||
dvxUpdate(sAc);
|
||||
|
||||
// Stop if IDE window was closed or DVX is shutting down
|
||||
if (!sWin || !sAc->running) {
|
||||
// Stop if IDE window was closed, DVX is shutting down, or user hit Stop
|
||||
if (!sWin || !sAc->running || sStopRequested) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1000,8 +1050,9 @@ static void runModule(BasModuleT *mod) {
|
|||
// The program ends when all forms are unloaded (closed).
|
||||
if (result == BAS_VM_HALTED && formRt->formCount > 0) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1017,6 +1068,16 @@ static void runModule(BasModuleT *mod) {
|
|||
|
||||
basFormRtDestroy(formRt);
|
||||
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();
|
||||
}
|
||||
|
||||
// Stash form code before overwriting proc buffers
|
||||
stashFormCode();
|
||||
|
||||
// Parse into per-procedure buffers and show (General) section
|
||||
parseProcs(srcBuf);
|
||||
free(srcBuf);
|
||||
|
|
@ -1303,6 +1367,8 @@ static void ensureProject(const char *filePath) {
|
|||
sProject.dirty = false;
|
||||
sProject.activeFileIdx = 0;
|
||||
|
||||
prjLoadAllFiles(&sProject, sAc);
|
||||
|
||||
char title[300];
|
||||
snprintf(title, sizeof(title), "DVX BASIC - [%s]", sProject.name);
|
||||
|
||||
|
|
@ -1323,7 +1389,7 @@ static void loadFile(void) {
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -1358,6 +1424,7 @@ static void loadFile(void) {
|
|||
onPrjFileClick(0, true);
|
||||
} else {
|
||||
loadFilePath(path);
|
||||
sEditorFileIdx = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1381,15 +1448,14 @@ static void saveActiveFile(void) {
|
|||
char fullPath[DVX_MAX_PATH];
|
||||
prjFullPath(&sProject, idx, fullPath, sizeof(fullPath));
|
||||
|
||||
if (file->isForm && sDesigner.form) {
|
||||
// Save editor code back to form->code before saving
|
||||
if (sCurProcIdx >= -1) {
|
||||
saveCurProc();
|
||||
free(sDesigner.form->code);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
}
|
||||
if (file->isForm) {
|
||||
// Only serialize through the designer if it holds THIS form
|
||||
bool isDesignerForm = (sDesigner.form &&
|
||||
strcasecmp(sDesigner.form->name, file->formName) == 0);
|
||||
|
||||
if (isDesignerForm) {
|
||||
stashFormCode();
|
||||
|
||||
// Save form designer state to .frm file
|
||||
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
|
||||
|
||||
if (frmBuf) {
|
||||
|
|
@ -1408,9 +1474,26 @@ static void saveActiveFile(void) {
|
|||
|
||||
free(frmBuf);
|
||||
}
|
||||
} else if (!file->isForm) {
|
||||
// Save full source (splice current proc back first)
|
||||
const char *src = getFullSource();
|
||||
} 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;
|
||||
}
|
||||
|
||||
if (src) {
|
||||
FILE *f = fopen(fullPath, "w");
|
||||
|
|
@ -1472,14 +1555,22 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
}
|
||||
|
||||
if (fileIdx == sProject.activeFileIdx) {
|
||||
// Already active -- but ensure the right view is shown
|
||||
if (isForm) {
|
||||
switchToDesign();
|
||||
}
|
||||
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) {
|
||||
PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
|
||||
|
||||
if (cur->isForm && sDesigner.form) {
|
||||
// Save editor code back to form->code before serializing
|
||||
stashFormCode();
|
||||
|
||||
// Serialize form designer state to .frm text
|
||||
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
|
||||
|
||||
|
|
@ -1495,15 +1586,13 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
free(frmBuf);
|
||||
cur->buffer = NULL;
|
||||
}
|
||||
|
||||
cur->modified = true;
|
||||
}
|
||||
} else if (!cur->isForm) {
|
||||
// Stash full source (splice current proc back first)
|
||||
} else if (!cur->isForm && sEditorFileIdx == sProject.activeFileIdx) {
|
||||
// Stash full source (only if editor has this file's code)
|
||||
saveCurProc();
|
||||
const char *src = getFullSource();
|
||||
free(cur->buffer);
|
||||
cur->buffer = src ? strdup(src) : NULL;
|
||||
cur->modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1550,11 +1639,17 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
*dot = '\0';
|
||||
}
|
||||
|
||||
if (sFormWin) {
|
||||
dvxDestroyWindow(sAc, sFormWin);
|
||||
cleanupFormWin();
|
||||
}
|
||||
|
||||
if (sDesigner.form) {
|
||||
dsgnFree(&sDesigner);
|
||||
}
|
||||
|
||||
dsgnNewForm(&sDesigner, formName);
|
||||
snprintf(target->formName, sizeof(target->formName), "%s", sDesigner.form->name);
|
||||
target->modified = true;
|
||||
sProject.activeFileIdx = fileIdx;
|
||||
switchToDesign();
|
||||
|
|
@ -1583,6 +1678,12 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
frmSrc = diskBuf;
|
||||
}
|
||||
|
||||
// Close the old form designer window before loading a new form
|
||||
if (sFormWin) {
|
||||
dvxDestroyWindow(sAc, sFormWin);
|
||||
cleanupFormWin();
|
||||
}
|
||||
|
||||
if (sDesigner.form) {
|
||||
dsgnFree(&sDesigner);
|
||||
}
|
||||
|
|
@ -1590,10 +1691,16 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
dsgnLoadFrm(&sDesigner, frmSrc, (int32_t)strlen(frmSrc));
|
||||
free(diskBuf);
|
||||
|
||||
if (sDesigner.form) {
|
||||
snprintf(target->formName, sizeof(target->formName), "%s", sDesigner.form->name);
|
||||
}
|
||||
|
||||
sProject.activeFileIdx = fileIdx;
|
||||
switchToDesign();
|
||||
} else {
|
||||
// Load .bas file from buffer or disk
|
||||
stashFormCode();
|
||||
|
||||
if (!sCodeWin) {
|
||||
showCodeWindow();
|
||||
}
|
||||
|
|
@ -1624,6 +1731,7 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
sEditor->onChange = onEditorChange;
|
||||
}
|
||||
|
||||
sEditorFileIdx = fileIdx;
|
||||
sProject.activeFileIdx = fileIdx;
|
||||
}
|
||||
}
|
||||
|
|
@ -1724,6 +1832,8 @@ static void openProject(void) {
|
|||
return;
|
||||
}
|
||||
|
||||
prjLoadAllFiles(&sProject, sAc);
|
||||
|
||||
// Create and show project window
|
||||
if (!sProjectWin) {
|
||||
sProjectWin = prjCreateWindow(sAc, &sProject, onPrjFileClick);
|
||||
|
|
@ -1766,6 +1876,26 @@ static void closeProject(void) {
|
|||
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);
|
||||
|
||||
if (sProjectWin) {
|
||||
|
|
@ -1938,13 +2068,24 @@ void ideRenameInCode(const char *oldName, const char *newName) {
|
|||
}
|
||||
}
|
||||
|
||||
// Update form->code from the renamed buffers
|
||||
if (sDesigner.form) {
|
||||
// Update form->code from the renamed buffers (only if editor has this form's code)
|
||||
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);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
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)
|
||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||
// Skip the active file (already handled above)
|
||||
|
|
@ -2006,13 +2147,8 @@ void ideRenameInCode(const char *oldName, const char *newName) {
|
|||
// ============================================================
|
||||
|
||||
static void onCodeWinClose(WindowT *win) {
|
||||
// Stash code back to form->code before the window is destroyed.
|
||||
// This is just caching -- do not mark dirty.
|
||||
if (sDesigner.form && sCurProcIdx >= -1) {
|
||||
saveCurProc();
|
||||
free(sDesigner.form->code);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
}
|
||||
// Stash code back before the window is destroyed.
|
||||
stashFormCode();
|
||||
|
||||
dvxDestroyWindow(sAc, win);
|
||||
sCodeWin = NULL;
|
||||
|
|
@ -2043,11 +2179,19 @@ static void onProjectWinClose(WindowT *win) {
|
|||
// Load all .frm files listed in the current project into the
|
||||
// form runtime for execution.
|
||||
|
||||
static void loadFrmFile(BasFormRtT *rt, const char *frmPath) {
|
||||
FILE *f = fopen(frmPath, "r");
|
||||
static void loadFrmFiles(BasFormRtT *rt) {
|
||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||
if (!sProject.files[i].isForm) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char fullPath[DVX_MAX_PATH];
|
||||
prjFullPath(&sProject, i, fullPath, sizeof(fullPath));
|
||||
|
||||
FILE *f = fopen(fullPath, "r");
|
||||
|
||||
if (!f) {
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
|
|
@ -2056,30 +2200,26 @@ static void loadFrmFile(BasFormRtT *rt, const char *frmPath) {
|
|||
|
||||
if (size <= 0 || size >= IDE_MAX_SOURCE) {
|
||||
fclose(f);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
char *frmBuf = (char *)malloc(size + 1);
|
||||
|
||||
if (!frmBuf) {
|
||||
fclose(f);
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
|
||||
int32_t bytesRead = (int32_t)fread(frmBuf, 1, size, f);
|
||||
fclose(f);
|
||||
frmBuf[bytesRead] = '\0';
|
||||
|
||||
basFormRtLoadFrm(rt, frmBuf, bytesRead);
|
||||
BasFormT *form = basFormRtLoadFrm(rt, frmBuf, bytesRead);
|
||||
free(frmBuf);
|
||||
}
|
||||
|
||||
static void loadFrmFiles(BasFormRtT *rt) {
|
||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||
if (sProject.files[i].isForm) {
|
||||
char fullPath[DVX_MAX_PATH];
|
||||
prjFullPath(&sProject, i, fullPath, sizeof(fullPath));
|
||||
loadFrmFile(rt, fullPath);
|
||||
// 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;
|
||||
|
||||
case CMD_STOP:
|
||||
sStopRequested = true;
|
||||
if (sVm) {
|
||||
sVm->running = false;
|
||||
setStatus("Program stopped.");
|
||||
}
|
||||
setStatus("Program stopped.");
|
||||
break;
|
||||
|
||||
case CMD_CLEAR:
|
||||
|
|
@ -2569,19 +2710,7 @@ static void onEvtDropdownChange(WidgetT *w) {
|
|||
snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName);
|
||||
|
||||
arrput(sProcBufs, strdup(skeleton));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
showProc((int32_t)arrlen(sProcBufs) - 1);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -3048,9 +3177,10 @@ static void selectDropdowns(const char *objName, const char *evtName) {
|
|||
}
|
||||
|
||||
// Rebuild the event list for this object but suppress navigation
|
||||
bool savedSuppress = sDropdownNavSuppressed;
|
||||
sDropdownNavSuppressed = true;
|
||||
onObjDropdownChange(sObjDropdown);
|
||||
sDropdownNavSuppressed = false;
|
||||
sDropdownNavSuppressed = savedSuppress;
|
||||
|
||||
// Now select the specific event
|
||||
int32_t evtCount = (int32_t)arrlen(sEvtItems);
|
||||
|
|
@ -3100,8 +3230,10 @@ static void navigateToEventSub(void) {
|
|||
char subName[128];
|
||||
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 : "");
|
||||
sEditorFileIdx = sProject.activeFileIdx;
|
||||
|
||||
// Ensure code window is open
|
||||
if (!sCodeWin) {
|
||||
|
|
@ -3114,9 +3246,12 @@ static void navigateToEventSub(void) {
|
|||
|
||||
// Populate dropdown items without triggering navigation --
|
||||
// we navigate explicitly below after finding the target proc.
|
||||
{
|
||||
bool saved = sDropdownNavSuppressed;
|
||||
sDropdownNavSuppressed = true;
|
||||
updateDropdowns();
|
||||
sDropdownNavSuppressed = false;
|
||||
sDropdownNavSuppressed = saved;
|
||||
}
|
||||
|
||||
// Search for existing procedure
|
||||
int32_t procCount = (int32_t)arrlen(sProcTable);
|
||||
|
|
@ -3144,8 +3279,6 @@ static void navigateToEventSub(void) {
|
|||
|
||||
arrput(sProcBufs, strdup(skeleton));
|
||||
|
||||
updateDropdowns();
|
||||
|
||||
// Show the new procedure (it's the last one)
|
||||
switchToCode();
|
||||
showProc((int32_t)arrlen(sProcBufs) - 1);
|
||||
|
|
@ -3327,12 +3460,7 @@ static void switchToCode(void) {
|
|||
// ============================================================
|
||||
|
||||
static void switchToDesign(void) {
|
||||
// Save code back to form before switching
|
||||
if (sDesigner.form && sCurProcIdx >= -1) {
|
||||
saveCurProc();
|
||||
free(sDesigner.form->code);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
}
|
||||
stashFormCode();
|
||||
|
||||
// If already open, just bring to front
|
||||
if (sFormWin) {
|
||||
|
|
@ -3415,6 +3543,7 @@ static void switchToDesign(void) {
|
|||
}
|
||||
}
|
||||
|
||||
dvxInvalidateWindow(sAc, sFormWin);
|
||||
setStatus("Design view open.");
|
||||
}
|
||||
|
||||
|
|
@ -3426,7 +3555,7 @@ static void switchToDesign(void) {
|
|||
static void onTbOpen(WidgetT *w) { (void)w; loadFile(); }
|
||||
static void onTbSave(WidgetT *w) { (void)w; saveFile(); }
|
||||
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 onTbDesign(WidgetT *w) { (void)w; switchToDesign(); }
|
||||
|
||||
|
|
@ -3685,6 +3814,7 @@ static void freeProcBufs(void) {
|
|||
arrfree(sProcBufs);
|
||||
sProcBufs = NULL;
|
||||
sCurProcIdx = -2;
|
||||
sEditorFileIdx = -1;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -3956,19 +4086,42 @@ static char *extractNewProcs(const char *buf) {
|
|||
}
|
||||
|
||||
|
||||
// saveCurProc -- save editor contents back to the current buffer.
|
||||
// If the user typed a new Sub/Function in the General section,
|
||||
// it's automatically extracted into its own procedure buffer.
|
||||
// stashFormCode -- if the proc buffers belong to the designer's form,
|
||||
// save them back to form->code. Uses sEditorFileIdx to know which
|
||||
// file the proc buffers actually belong to.
|
||||
|
||||
static void saveCurProc(void) {
|
||||
if (!sEditor) {
|
||||
static void stashFormCode(void) {
|
||||
if (!sDesigner.form || sEditorFileIdx < 0) {
|
||||
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);
|
||||
|
||||
if (!edText) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sCurProcIdx == -1) {
|
||||
|
|
@ -3979,10 +4132,11 @@ static void saveCurProc(void) {
|
|||
sGeneralBuf = cleaned ? cleaned : strdup(edText);
|
||||
|
||||
if (cleaned) {
|
||||
// Update editor to show the cleaned General section
|
||||
wgtSetText(sEditor, sGeneralBuf);
|
||||
updateDropdowns();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} else if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcBufs)) {
|
||||
// Get the name of the current proc so we can identify its block
|
||||
// regardless of position in the editor.
|
||||
|
|
@ -4052,10 +4206,11 @@ static void saveCurProc(void) {
|
|||
free(sProcBufs[sCurProcIdx]);
|
||||
arrdel(sProcBufs, sCurProcIdx);
|
||||
sCurProcIdx = -2;
|
||||
updateDropdowns();
|
||||
return true;
|
||||
} else {
|
||||
free(sProcBufs[sCurProcIdx]);
|
||||
sProcBufs[sCurProcIdx] = strdup(edText);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// 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
|
||||
wgtSetText(sEditor, sProcBufs[sCurProcIdx]);
|
||||
updateDropdowns();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -4164,9 +4321,16 @@ static void showProc(int32_t procIdx) {
|
|||
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) {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include "dvxWm.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
#include "widgetDropdown.h"
|
||||
#include "widgetImage.h"
|
||||
#include "widgetLabel.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 onTreeItemDblClick(WidgetT *w);
|
||||
static bool validateIcon(const char *fullPath, bool showErrors);
|
||||
|
||||
// ============================================================
|
||||
// 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
|
||||
// ============================================================
|
||||
|
|
@ -460,26 +535,13 @@ void prjRebuildTree(PrjStateT *prj) {
|
|||
projNode->userData = (void *)(intptr_t)-1;
|
||||
wgtTreeItemSetExpanded(projNode, true);
|
||||
|
||||
// Forms group
|
||||
// Forms and Modules groups
|
||||
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 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");
|
||||
arrput(sLabels, modsLabel);
|
||||
WidgetT *modsNode = wgtTreeItem(projNode, modsLabel);
|
||||
|
|
@ -487,16 +549,14 @@ void prjRebuildTree(PrjStateT *prj) {
|
|||
wgtTreeItemSetExpanded(modsNode, 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(modsNode, label);
|
||||
WidgetT *item = wgtTreeItem(prj->files[i].isForm ? formsNode : modsNode, label);
|
||||
item->userData = (void *)(intptr_t)i;
|
||||
item->onDblClick = onTreeItemDblClick;
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(sTree);
|
||||
}
|
||||
|
|
@ -521,6 +581,8 @@ static struct {
|
|||
WidgetT *version;
|
||||
WidgetT *copyright;
|
||||
WidgetT *description;
|
||||
WidgetT *startupForm;
|
||||
const char **formNames; // stb_ds array of form name strings for startup dropdown
|
||||
WidgetT *iconPreview;
|
||||
char iconPath[DVX_MAX_PATH];
|
||||
const char *appPath;
|
||||
|
|
@ -533,22 +595,10 @@ static void ppdOnOk(WidgetT *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);
|
||||
snprintf(fullPath, sizeof(fullPath), "%s/%s", sPpd.prj->projectDir, sPpd.iconPath);
|
||||
|
||||
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);
|
||||
if (!validateIcon(fullPath, true)) {
|
||||
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; }
|
||||
|
||||
|
||||
// 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) {
|
||||
if (!sPpd.iconPreview || !sPpd.ctx || !sPpd.prj) {
|
||||
return;
|
||||
|
|
@ -574,18 +651,7 @@ static void ppdLoadIconPreview(void) {
|
|||
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);
|
||||
if (!validateIcon(fullPath, true)) {
|
||||
sPpd.iconPath[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
|
@ -612,19 +678,7 @@ static void ppdOnBrowseIcon(WidgetT *w) {
|
|||
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);
|
||||
if (!validateIcon(path, true)) {
|
||||
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.version = ppdAddRow(root, "Version:", prj->version, PRJ_MAX_NAME);
|
||||
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
|
||||
{
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
dvxDestroyWindow(ctx, win);
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ typedef struct {
|
|||
bool isForm; // true = .frm, false = .bas
|
||||
char *buffer; // in-memory edit buffer (malloc'd, NULL = not loaded)
|
||||
bool modified; // true = buffer has unsaved changes
|
||||
char formName[PRJ_MAX_NAME]; // form object name (from "Begin Form <name>")
|
||||
} PrjFileT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -73,6 +74,7 @@ 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 prjLoadAllFiles(PrjStateT *prj, AppContextT *ctx);
|
||||
void prjRemoveFile(PrjStateT *prj, int32_t idx);
|
||||
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_INT WGT_IFACE_INT
|
||||
#define PROP_TYPE_BOOL WGT_IFACE_BOOL
|
||||
#define PROP_TYPE_ENUM WGT_IFACE_ENUM
|
||||
#define PROP_TYPE_READONLY 255
|
||||
|
||||
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
|
||||
// ============================================================
|
||||
|
|
@ -260,6 +288,31 @@ static void onPropDblClick(WidgetT *w) {
|
|||
// Toggle boolean on double-click -- no input box
|
||||
bool cur = (strcasecmp(curValue, "True") == 0);
|
||||
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) {
|
||||
// Spinner dialog for integers
|
||||
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);
|
||||
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) {
|
||||
((void (*)(WidgetT *, int32_t))p->setFn)(ctrl->widget, atoi(newValue));
|
||||
} else if (p->type == WGT_IFACE_BOOL) {
|
||||
|
|
@ -885,6 +949,18 @@ void prpRefresh(DsgnStateT *ds) {
|
|||
if (p->type == WGT_IFACE_STRING && p->getFn) {
|
||||
const char *s = ((const char *(*)(WidgetT *))p->getFn)(ctrl->widget);
|
||||
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) {
|
||||
int32_t v = ((int32_t (*)(const WidgetT *))p->getFn)(ctrl->widget);
|
||||
snprintf(buf, sizeof(buf), "%d", (int)v);
|
||||
|
|
|
|||
|
|
@ -2534,6 +2534,11 @@ BasVmResultE basVmStep(BasVmT *vm) {
|
|||
// Halt
|
||||
// ============================================================
|
||||
|
||||
case OP_END:
|
||||
vm->running = false;
|
||||
vm->ended = true;
|
||||
return BAS_VM_HALTED;
|
||||
|
||||
case OP_HALT:
|
||||
vm->running = false;
|
||||
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 *sb = b.strVal ? b.strVal : basStringNew("", 0);
|
||||
int32_t newLen = sa->len + sb->len;
|
||||
BasStringT *cat = basStringNew("", 0);
|
||||
BasStringT *cat;
|
||||
|
||||
if (newLen > 0) {
|
||||
basStringUnref(cat);
|
||||
char *buf = (char *)malloc(newLen + 1);
|
||||
memcpy(buf, sa->data, sa->len);
|
||||
memcpy(buf + sa->len, sb->data, sb->len);
|
||||
buf[newLen] = '\0';
|
||||
cat = basStringNew(buf, newLen);
|
||||
free(buf);
|
||||
} else {
|
||||
cat = basStringNew("", 0);
|
||||
}
|
||||
|
||||
basValRelease(&a);
|
||||
|
|
|
|||
|
|
@ -248,6 +248,7 @@ typedef struct {
|
|||
// Execution
|
||||
int32_t pc; // program counter
|
||||
bool running;
|
||||
bool ended; // END statement executed -- program should terminate
|
||||
bool yielded;
|
||||
int32_t stepLimit; // max steps per basVmRun (0 = unlimited)
|
||||
int32_t stepCount; // steps executed in last basVmRun
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ static int32_t sAppCount = 0;
|
|||
int32_t appMain(DxeAppContextT *ctx);
|
||||
static void buildPmWindow(void);
|
||||
static void desktopUpdate(void);
|
||||
static void onAppButtonClick(WidgetT *w);
|
||||
static void onAppButtonDblClick(WidgetT *w);
|
||||
static void onPmClose(WindowT *win);
|
||||
static void onPmMenu(WindowT *win, int32_t menuId);
|
||||
static void scanAppsDir(void);
|
||||
|
|
@ -233,8 +233,7 @@ static void buildPmWindow(void) {
|
|||
}
|
||||
|
||||
btn->userData = &sAppFiles[i];
|
||||
btn->onDblClick = onAppButtonClick;
|
||||
btn->onClick = onAppButtonClick;
|
||||
btn->onDblClick = onAppButtonDblClick;
|
||||
|
||||
if (sAppFiles[i].tooltip[0]) {
|
||||
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
|
||||
// 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;
|
||||
|
||||
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 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;
|
||||
fg = ctx->colors.menuHighlightFg;
|
||||
}
|
||||
|
|
@ -1845,9 +1845,15 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
|
|||
} else {
|
||||
ctx->lastTitleClickTime = now;
|
||||
ctx->lastTitleClickId = win->id;
|
||||
|
||||
// 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;
|
||||
|
||||
case HIT_CLOSE:
|
||||
|
|
@ -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
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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
|
||||
// (plus chrome). Used for dialog boxes and other fixed-layout windows
|
||||
// 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_BOOL 2
|
||||
#define WGT_IFACE_FLOAT 3
|
||||
#define WGT_IFACE_ENUM 4 // int32_t with named values
|
||||
|
||||
// Method calling conventions (how the form runtime marshals args)
|
||||
#define WGT_SIG_VOID 0 // void fn(WidgetT *)
|
||||
|
|
@ -570,6 +571,7 @@ typedef struct {
|
|||
uint8_t type; // WGT_IFACE_*
|
||||
void *getFn; // getter function pointer (NULL if write-only)
|
||||
void *setFn; // setter function pointer (NULL if read-only)
|
||||
const char **enumNames; // WGT_IFACE_ENUM only: NULL-terminated array of value names
|
||||
} WgtPropDescT;
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Popup is closed
|
||||
if (key == (0x50 | 0x100) || key == ' ' || key == 0x0D) {
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
d->open = true;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = w;
|
||||
|
|
@ -134,6 +134,15 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
if (d->hoverIdx < d->scrollPos) {
|
||||
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)) {
|
||||
if (d->selectedIdx > 0) {
|
||||
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
|
||||
// ============================================================
|
||||
|
|
@ -160,8 +170,10 @@ static const struct {
|
|||
.setAlign = wgtLabelSetAlign
|
||||
};
|
||||
|
||||
static const char *sAlignNames[] = { "Left", "Center", "Right", NULL };
|
||||
|
||||
static const WgtPropDescT sProps[] = {
|
||||
{ "Alignment", WGT_IFACE_INT, NULL, (void *)wgtLabelSetAlign }
|
||||
{ "Alignment", WGT_IFACE_ENUM, (void *)wgtLabelGetAlign, (void *)wgtLabelSetAlign, sAlignNames }
|
||||
};
|
||||
|
||||
static const WgtIfaceT sIface = {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue