Working on BASIC code editor.
This commit is contained in:
parent
82939c3f27
commit
1fb8e2a387
12 changed files with 745 additions and 115 deletions
|
|
@ -800,6 +800,11 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
|||
}
|
||||
}
|
||||
|
||||
// Fire the Load event now that the form and controls are ready
|
||||
if (form) {
|
||||
basFormRtFireEvent(rt, form, form->name, "Load");
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
|
|
@ -870,8 +875,6 @@ void basFormRtShowForm(void *ctx, void *formRef, bool modal) {
|
|||
if (modal) {
|
||||
rt->ctx->modalWindow = form->window;
|
||||
}
|
||||
|
||||
basFormRtFireEvent(rt, form, form->name, "Load");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ const char *dsgnDefaultEvent(const char *typeName) {
|
|||
void dsgnFree(DsgnStateT *ds) {
|
||||
if (ds->form) {
|
||||
arrfree(ds->form->controls);
|
||||
free(ds->form->code);
|
||||
free(ds->form);
|
||||
ds->form = NULL;
|
||||
}
|
||||
|
|
@ -433,6 +434,28 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
|
|||
curCtrl = NULL;
|
||||
} else {
|
||||
inForm = false;
|
||||
|
||||
// Everything after the form's closing End is code
|
||||
if (pos < end) {
|
||||
// Skip leading blank lines
|
||||
const char *codeStart = pos;
|
||||
|
||||
while (codeStart < end && (*codeStart == '\r' || *codeStart == '\n' || *codeStart == ' ' || *codeStart == '\t')) {
|
||||
codeStart++;
|
||||
}
|
||||
|
||||
if (codeStart < end) {
|
||||
int32_t codeLen = (int32_t)(end - codeStart);
|
||||
form->code = (char *)malloc(codeLen + 1);
|
||||
|
||||
if (form->code) {
|
||||
memcpy(form->code, codeStart, codeLen);
|
||||
form->code[codeLen] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break; // done parsing
|
||||
}
|
||||
|
||||
continue;
|
||||
|
|
@ -919,6 +942,31 @@ int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
|
|||
pos = saveControls(ds->form, buf, bufSize, pos, "", 1);
|
||||
|
||||
pos += snprintf(buf + pos, bufSize - pos, "End\n");
|
||||
|
||||
// Append code section if present
|
||||
if (ds->form->code && ds->form->code[0]) {
|
||||
int32_t codeLen = (int32_t)strlen(ds->form->code);
|
||||
int32_t avail = bufSize - pos - 2; // room for \n prefix and \n suffix
|
||||
|
||||
if (avail > 0) {
|
||||
buf[pos++] = '\n';
|
||||
|
||||
if (codeLen > avail) {
|
||||
codeLen = avail;
|
||||
}
|
||||
|
||||
memcpy(buf + pos, ds->form->code, codeLen);
|
||||
pos += codeLen;
|
||||
|
||||
// Ensure trailing newline
|
||||
if (pos > 0 && buf[pos - 1] != '\n' && pos < bufSize - 1) {
|
||||
buf[pos++] = '\n';
|
||||
}
|
||||
|
||||
buf[pos] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ typedef struct {
|
|||
DsgnControlT *controls; // stb_ds dynamic array
|
||||
bool dirty;
|
||||
WidgetT *contentBox; // VBox parent for live widgets
|
||||
char *code; // BASIC code section (malloc'd, after End block)
|
||||
} DsgnFormT;
|
||||
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -99,7 +99,13 @@ static void buildWindow(void);
|
|||
static void clearOutput(void);
|
||||
static void compileAndRun(void);
|
||||
static void ensureProject(const char *filePath);
|
||||
static void freeProcBufs(void);
|
||||
static const char *getFullSource(void);
|
||||
static void loadFile(void);
|
||||
static void parseProcs(const char *source);
|
||||
static void saveActiveFile(void);
|
||||
static void saveCurProc(void);
|
||||
static void showProc(int32_t procIdx);
|
||||
static int32_t toolbarBottom(void);
|
||||
static void loadFilePath(const char *path);
|
||||
static void newProject(void);
|
||||
|
|
@ -112,6 +118,7 @@ static bool hasUnsavedData(void);
|
|||
static bool promptAndSave(void);
|
||||
static void cleanupFormWin(void);
|
||||
static void onClose(WindowT *win);
|
||||
static void onCodeWinClose(WindowT *win);
|
||||
static void onContentFocus(WindowT *win);
|
||||
static void onFormWinClose(WindowT *win);
|
||||
static void onFormWinResize(WindowT *win, int32_t newW, int32_t newH);
|
||||
|
|
@ -180,6 +187,13 @@ static char sOutputBuf[IDE_MAX_OUTPUT];
|
|||
static int32_t sOutputLen = 0;
|
||||
static char sFilePath[DVX_MAX_PATH];
|
||||
|
||||
// Procedure view state -- the editor shows one procedure at a time.
|
||||
// Each procedure is stored in its own malloc'd buffer. The editor
|
||||
// swaps directly between buffers with no splicing needed.
|
||||
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)
|
||||
|
||||
// Procedure table for Object/Event dropdowns
|
||||
typedef struct {
|
||||
char objName[64];
|
||||
|
|
@ -589,9 +603,30 @@ static void clearOutput(void) {
|
|||
// ============================================================
|
||||
|
||||
static void compileAndRun(void) {
|
||||
// Save all files before compiling if Save on Run is enabled
|
||||
// Save all dirty files before compiling if Save on Run is enabled
|
||||
if (sWin && sWin->menuBar && wmMenuItemIsChecked(sWin->menuBar, CMD_SAVE_ON_RUN)) {
|
||||
saveFile();
|
||||
if (sProject.activeFileIdx >= 0) {
|
||||
saveActiveFile();
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||
if (i == sProject.activeFileIdx) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (sProject.files[i].modified && sProject.files[i].buffer) {
|
||||
char fullPath[DVX_MAX_PATH];
|
||||
prjFullPath(&sProject, i, fullPath, sizeof(fullPath));
|
||||
|
||||
FILE *f = fopen(fullPath, "w");
|
||||
|
||||
if (f) {
|
||||
fputs(sProject.files[i].buffer, f);
|
||||
fclose(f);
|
||||
sProject.files[i].modified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clearOutput();
|
||||
|
|
@ -606,12 +641,22 @@ static void compileAndRun(void) {
|
|||
int32_t srcLen = 0;
|
||||
|
||||
if (sProject.projectPath[0] != '\0' && sProject.fileCount > 0) {
|
||||
// Stash current editor contents into the active file's buffer
|
||||
if (sProject.activeFileIdx >= 0 && sEditor) {
|
||||
// Stash current editor state
|
||||
if (sProject.activeFileIdx >= 0) {
|
||||
PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
|
||||
const char *edSrc = wgtGetText(sEditor);
|
||||
free(cur->buffer);
|
||||
cur->buffer = edSrc ? strdup(edSrc) : NULL;
|
||||
|
||||
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) {
|
||||
saveCurProc();
|
||||
free(sDesigner.form->code);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
}
|
||||
|
||||
// Concatenate all .bas files from buffers (or disk if not yet loaded)
|
||||
|
|
@ -629,13 +674,41 @@ static void compileAndRun(void) {
|
|||
sProject.sourceMapCount = 0;
|
||||
|
||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||
if (sProject.files[i].isForm) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char *fileSrc = sProject.files[i].buffer;
|
||||
const char *fileSrc = NULL;
|
||||
char *diskBuf = NULL;
|
||||
|
||||
if (sProject.files[i].isForm) {
|
||||
// For .frm files, extract just the code section.
|
||||
// If this is the active form in the designer, use form->code.
|
||||
if (sDesigner.form && i == sProject.activeFileIdx) {
|
||||
fileSrc = sDesigner.form->code;
|
||||
} else if (sProject.files[i].buffer) {
|
||||
// Extract code from the stashed .frm text (after "End\n")
|
||||
const char *buf = sProject.files[i].buffer;
|
||||
const char *endTag = strstr(buf, "\nEnd\n");
|
||||
|
||||
if (!endTag) {
|
||||
endTag = strstr(buf, "\nEnd\r\n");
|
||||
}
|
||||
|
||||
if (endTag) {
|
||||
endTag += 5;
|
||||
|
||||
while (*endTag == '\r' || *endTag == '\n') {
|
||||
endTag++;
|
||||
}
|
||||
|
||||
if (*endTag) {
|
||||
fileSrc = endTag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no code found from memory, fall through to disk read
|
||||
} else {
|
||||
fileSrc = sProject.files[i].buffer;
|
||||
}
|
||||
|
||||
if (!fileSrc) {
|
||||
// Not yet loaded into memory -- read from disk
|
||||
char fullPath[DVX_MAX_PATH];
|
||||
|
|
@ -658,6 +731,27 @@ static void compileAndRun(void) {
|
|||
int32_t br = (int32_t)fread(diskBuf, 1, size, f);
|
||||
diskBuf[br] = '\0';
|
||||
fileSrc = diskBuf;
|
||||
|
||||
// For .frm from disk, extract code section
|
||||
if (sProject.files[i].isForm) {
|
||||
const char *endTag = strstr(fileSrc, "\nEnd\n");
|
||||
|
||||
if (!endTag) {
|
||||
endTag = strstr(fileSrc, "\nEnd\r\n");
|
||||
}
|
||||
|
||||
if (endTag) {
|
||||
endTag += 5;
|
||||
|
||||
while (*endTag == '\r' || *endTag == '\n') {
|
||||
endTag++;
|
||||
}
|
||||
|
||||
fileSrc = endTag;
|
||||
} else {
|
||||
fileSrc = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -708,8 +802,8 @@ static void compileAndRun(void) {
|
|||
src = concatBuf;
|
||||
srcLen = pos;
|
||||
} else {
|
||||
// No project files -- compile whatever is in the editor
|
||||
src = wgtGetText(sEditor);
|
||||
// No project files -- compile the full source
|
||||
src = getFullSource();
|
||||
|
||||
if (!src || *src == '\0') {
|
||||
setStatus("No source code to run.");
|
||||
|
|
@ -823,6 +917,11 @@ 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)
|
||||
if (formRt->formCount > 0) {
|
||||
basFormRtShowForm(formRt, &formRt->forms[0], false);
|
||||
}
|
||||
|
||||
sVm = vm;
|
||||
|
||||
// Run in slices of 10000 steps, yielding to DVX between slices
|
||||
|
|
@ -1037,9 +1136,8 @@ static void loadFilePath(const char *path) {
|
|||
showCodeWindow();
|
||||
}
|
||||
|
||||
if (sEditor) {
|
||||
wgtSetText(sEditor, sSourceBuf);
|
||||
}
|
||||
// Parse into per-procedure buffers and show (General) section
|
||||
parseProcs(sSourceBuf);
|
||||
|
||||
snprintf(sFilePath, sizeof(sFilePath), "%s", path);
|
||||
|
||||
|
|
@ -1050,6 +1148,7 @@ static void loadFilePath(const char *path) {
|
|||
|
||||
dsgnFree(&sDesigner);
|
||||
updateDropdowns();
|
||||
showProc(-1); // show (General) section
|
||||
setStatus("File loaded.");
|
||||
}
|
||||
|
||||
|
|
@ -1118,10 +1217,10 @@ static void ensureProject(const char *filePath) {
|
|||
snprintf(frmPath, sizeof(frmPath), "%s", filePath);
|
||||
char *frmDot = strrchr(frmPath, '.');
|
||||
|
||||
if (frmDot) {
|
||||
strcpy(frmDot, ".frm");
|
||||
} else {
|
||||
strcat(frmPath, ".frm");
|
||||
if (frmDot && (frmDot - frmPath) + 4 < DVX_MAX_PATH) {
|
||||
snprintf(frmDot, sizeof(frmPath) - (frmDot - frmPath), ".frm");
|
||||
} else if ((int32_t)strlen(frmPath) + 4 < DVX_MAX_PATH) {
|
||||
snprintf(frmPath + strlen(frmPath), sizeof(frmPath) - strlen(frmPath), ".frm");
|
||||
}
|
||||
|
||||
FILE *frmFile = fopen(frmPath, "r");
|
||||
|
|
@ -1202,6 +1301,13 @@ static void saveActiveFile(void) {
|
|||
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());
|
||||
}
|
||||
|
||||
// Save form designer state to .frm file
|
||||
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
|
||||
|
||||
|
|
@ -1221,9 +1327,9 @@ static void saveActiveFile(void) {
|
|||
|
||||
free(frmBuf);
|
||||
}
|
||||
} else if (!file->isForm && sEditor) {
|
||||
// Save code editor contents to .bas file
|
||||
const char *src = wgtGetText(sEditor);
|
||||
} else if (!file->isForm) {
|
||||
// Save full source (splice current proc back first)
|
||||
const char *src = getFullSource();
|
||||
|
||||
if (src) {
|
||||
FILE *f = fopen(fullPath, "w");
|
||||
|
|
@ -1335,9 +1441,9 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
|
||||
cur->modified = true;
|
||||
}
|
||||
} else if (!cur->isForm && sEditor) {
|
||||
// Stash code editor text
|
||||
const char *src = wgtGetText(sEditor);
|
||||
} else if (!cur->isForm) {
|
||||
// Stash full source (splice current proc back first)
|
||||
const char *src = getFullSource();
|
||||
free(cur->buffer);
|
||||
cur->buffer = src ? strdup(src) : NULL;
|
||||
cur->modified = true;
|
||||
|
|
@ -1436,9 +1542,9 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
}
|
||||
|
||||
if (target->buffer) {
|
||||
if (sEditor) {
|
||||
wgtSetText(sEditor, target->buffer);
|
||||
}
|
||||
parseProcs(target->buffer);
|
||||
updateDropdowns();
|
||||
showProc(-1);
|
||||
} else {
|
||||
char fullPath[DVX_MAX_PATH];
|
||||
prjFullPath(&sProject, fileIdx, fullPath, sizeof(fullPath));
|
||||
|
|
@ -1448,9 +1554,11 @@ static void onPrjFileClick(int32_t fileIdx, bool isForm) {
|
|||
if (f) {
|
||||
fclose(f);
|
||||
loadFilePath(fullPath);
|
||||
} else if (sEditor) {
|
||||
// File doesn't exist yet -- start with empty editor
|
||||
wgtSetText(sEditor, "");
|
||||
} else {
|
||||
// File doesn't exist yet -- start with empty source
|
||||
parseProcs("");
|
||||
updateDropdowns();
|
||||
showProc(-1);
|
||||
target->modified = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1735,28 +1843,46 @@ void ideRenameInCode(const char *oldName, const char *newName) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Rename in the active editor
|
||||
if (sEditor && sProject.activeFileIdx >= 0 &&
|
||||
!sProject.files[sProject.activeFileIdx].isForm) {
|
||||
const char *edText = wgtGetText(sEditor);
|
||||
// Rename in the per-procedure buffers (form code currently being edited)
|
||||
if (sGeneralBuf) {
|
||||
char *replaced = renameInBuffer(sGeneralBuf, oldName, newName);
|
||||
|
||||
if (edText) {
|
||||
char *replaced = renameInBuffer(edText, oldName, newName);
|
||||
if (replaced) {
|
||||
free(sGeneralBuf);
|
||||
sGeneralBuf = replaced;
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sProcBufs); i++) {
|
||||
if (sProcBufs[i]) {
|
||||
char *replaced = renameInBuffer(sProcBufs[i], oldName, newName);
|
||||
|
||||
if (replaced) {
|
||||
wgtSetText(sEditor, replaced);
|
||||
free(replaced);
|
||||
free(sProcBufs[i]);
|
||||
sProcBufs[i] = replaced;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rename in all project .bas file buffers
|
||||
for (int32_t i = 0; i < sProject.fileCount; i++) {
|
||||
if (sProject.files[i].isForm) {
|
||||
continue;
|
||||
// Update the editor if it's showing a procedure
|
||||
if (sEditor && sCurProcIdx >= -1) {
|
||||
if (sCurProcIdx == -1 && sGeneralBuf) {
|
||||
wgtSetText(sEditor, sGeneralBuf);
|
||||
} else if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcBufs)) {
|
||||
wgtSetText(sEditor, sProcBufs[sCurProcIdx]);
|
||||
}
|
||||
}
|
||||
|
||||
// Skip the active file (already handled via editor)
|
||||
// Update form->code from the renamed buffers
|
||||
if (sDesigner.form) {
|
||||
free(sDesigner.form->code);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
sDesigner.form->dirty = true;
|
||||
}
|
||||
|
||||
// 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)
|
||||
if (i == sProject.activeFileIdx) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1804,6 +1930,34 @@ void ideRenameInCode(const char *oldName, const char *newName) {
|
|||
sProject.files[i].modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh dropdowns to reflect renamed procedures
|
||||
updateDropdowns();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// onCodeWinClose -- user closed the code window via X button
|
||||
// ============================================================
|
||||
|
||||
static void onCodeWinClose(WindowT *win) {
|
||||
// Stash code back to form->code before the window is destroyed
|
||||
if (sDesigner.form && sCurProcIdx >= -1) {
|
||||
saveCurProc();
|
||||
free(sDesigner.form->code);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
sDesigner.form->dirty = true;
|
||||
}
|
||||
|
||||
dvxDestroyWindow(sAc, win);
|
||||
sCodeWin = NULL;
|
||||
sEditor = NULL;
|
||||
sObjDropdown = NULL;
|
||||
sEvtDropdown = NULL;
|
||||
|
||||
if (sLastFocusWin == win) {
|
||||
sLastFocusWin = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1821,9 +1975,8 @@ static void onProjectWinClose(WindowT *win) {
|
|||
// loadFrmFiles
|
||||
// ============================================================
|
||||
//
|
||||
// Try to load a .frm file with the same base name as the loaded
|
||||
// .bas source file. For example, if the user loaded "clickme.bas",
|
||||
// this looks for "clickme.frm" in the same directory.
|
||||
// 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");
|
||||
|
|
@ -1852,14 +2005,7 @@ static void loadFrmFile(BasFormRtT *rt, const char *frmPath) {
|
|||
fclose(f);
|
||||
frmBuf[bytesRead] = '\0';
|
||||
|
||||
BasFormT *form = basFormRtLoadFrm(rt, frmBuf, bytesRead);
|
||||
|
||||
if (form) {
|
||||
int32_t pos = sOutputLen;
|
||||
int32_t n = snprintf(sOutputBuf + pos, IDE_MAX_OUTPUT - pos, "Loaded form: %s\n", form->name);
|
||||
sOutputLen += n;
|
||||
}
|
||||
|
||||
basFormRtLoadFrm(rt, frmBuf, bytesRead);
|
||||
free(frmBuf);
|
||||
}
|
||||
|
||||
|
|
@ -2016,6 +2162,8 @@ static void onClose(WindowT *win) {
|
|||
|
||||
dsgnFree(&sDesigner);
|
||||
|
||||
freeProcBufs();
|
||||
|
||||
arrfree(sProcTable);
|
||||
arrfree(sObjItems);
|
||||
arrfree(sEvtItems);
|
||||
|
|
@ -2297,7 +2445,7 @@ static void onEvtDropdownChange(WidgetT *w) {
|
|||
for (int32_t i = 0; i < procCount; i++) {
|
||||
if (strcasecmp(sProcTable[i].objName, selObj) == 0) {
|
||||
if (matchIdx == evtIdx) {
|
||||
wgtTextAreaGoToLine(sEditor, sProcTable[i].lineNum);
|
||||
showProc(i);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2528,9 +2676,89 @@ static int32_t onFormWinCursorQuery(WindowT *win, int32_t x, int32_t y) {
|
|||
}
|
||||
|
||||
|
||||
// navigateToEventSub -- open code editor at the default event sub for the
|
||||
// selected control (or form). Creates the sub skeleton if it doesn't exist.
|
||||
// Code is stored in the .frm file's code section (sDesigner.form->code).
|
||||
|
||||
static void navigateToEventSub(void) {
|
||||
if (!sDesigner.form) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine control name and default event
|
||||
const char *ctrlName = NULL;
|
||||
const char *eventName = NULL;
|
||||
|
||||
if (sDesigner.selectedIdx >= 0 &&
|
||||
sDesigner.selectedIdx < (int32_t)arrlen(sDesigner.form->controls)) {
|
||||
DsgnControlT *ctrl = &sDesigner.form->controls[sDesigner.selectedIdx];
|
||||
ctrlName = ctrl->name;
|
||||
eventName = dsgnDefaultEvent(ctrl->typeName);
|
||||
} else {
|
||||
ctrlName = sDesigner.form->name;
|
||||
eventName = dsgnDefaultEvent("Form");
|
||||
}
|
||||
|
||||
if (!ctrlName || !eventName) {
|
||||
return;
|
||||
}
|
||||
|
||||
char subName[128];
|
||||
snprintf(subName, sizeof(subName), "%s_%s", ctrlName, eventName);
|
||||
|
||||
// Parse the form's code into per-procedure buffers
|
||||
parseProcs(sDesigner.form->code ? sDesigner.form->code : "");
|
||||
|
||||
// Ensure code window is open
|
||||
if (!sCodeWin) {
|
||||
showCodeWindow();
|
||||
}
|
||||
|
||||
if (!sEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateDropdowns();
|
||||
|
||||
// Search for existing procedure
|
||||
int32_t procCount = (int32_t)arrlen(sProcTable);
|
||||
|
||||
for (int32_t i = 0; i < procCount; i++) {
|
||||
char fullName[128];
|
||||
snprintf(fullName, sizeof(fullName), "%s_%s", sProcTable[i].objName, sProcTable[i].evtName);
|
||||
|
||||
if (strcasecmp(fullName, subName) == 0) {
|
||||
switchToCode();
|
||||
showProc(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Not found -- create a new sub and add it as a procedure buffer
|
||||
char skeleton[256];
|
||||
snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName);
|
||||
|
||||
char *newBuf = strdup(skeleton);
|
||||
arrput(sProcBufs, newBuf);
|
||||
|
||||
sDesigner.form->dirty = true;
|
||||
|
||||
// Save code back to form
|
||||
free(sDesigner.form->code);
|
||||
sDesigner.form->code = strdup(getFullSource());
|
||||
|
||||
updateDropdowns();
|
||||
|
||||
// Show the new procedure (it's the last one)
|
||||
switchToCode();
|
||||
showProc((int32_t)arrlen(sProcBufs) - 1);
|
||||
}
|
||||
|
||||
|
||||
static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||
(void)win;
|
||||
static int32_t lastButtons = 0;
|
||||
|
||||
bool wasDown = (lastButtons & MOUSE_LEFT) != 0;
|
||||
bool isDown = (buttons & MOUSE_LEFT) != 0;
|
||||
|
||||
|
|
@ -2539,15 +2767,15 @@ static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons)
|
|||
return;
|
||||
}
|
||||
|
||||
if (isDown) {
|
||||
int32_t prevCount = sDesigner.form ? (int32_t)arrlen(sDesigner.form->controls) : 0;
|
||||
bool wasDirty = sDesigner.form ? sDesigner.form->dirty : false;
|
||||
bool drag = wasDown;
|
||||
dsgnOnMouse(&sDesigner, x, y, drag);
|
||||
if (isDown && !wasDown) {
|
||||
// Detect double-click using the system-wide setting
|
||||
int32_t clicks = multiClickDetect(x, y);
|
||||
|
||||
// Rebuild tree if controls were added, removed, or reordered
|
||||
int32_t newCount = sDesigner.form ? (int32_t)arrlen(sDesigner.form->controls) : 0;
|
||||
bool nowDirty = sDesigner.form ? sDesigner.form->dirty : false;
|
||||
int32_t prevCount = (int32_t)arrlen(sDesigner.form->controls);
|
||||
bool wasDirty = sDesigner.form->dirty;
|
||||
dsgnOnMouse(&sDesigner, x, y, false);
|
||||
int32_t newCount = (int32_t)arrlen(sDesigner.form->controls);
|
||||
bool nowDirty = sDesigner.form->dirty;
|
||||
|
||||
if (newCount != prevCount || (nowDirty && !wasDirty)) {
|
||||
prpRebuildTree(&sDesigner);
|
||||
|
|
@ -2558,7 +2786,20 @@ static void onFormWinMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons)
|
|||
if (sFormWin) {
|
||||
dvxInvalidateWindow(sAc, sFormWin);
|
||||
}
|
||||
} else if (wasDown) {
|
||||
|
||||
if (clicks >= 2 && sDesigner.activeTool[0] == '\0') {
|
||||
navigateToEventSub();
|
||||
}
|
||||
} else if (isDown && wasDown) {
|
||||
// Drag
|
||||
dsgnOnMouse(&sDesigner, x, y, true);
|
||||
prpRefresh(&sDesigner);
|
||||
|
||||
if (sFormWin) {
|
||||
dvxInvalidateWindow(sAc, sFormWin);
|
||||
}
|
||||
} else if (!isDown && wasDown) {
|
||||
// Release
|
||||
dsgnOnMouse(&sDesigner, x, y, false);
|
||||
prpRefresh(&sDesigner);
|
||||
|
||||
|
|
@ -2647,11 +2888,34 @@ static void onFormWinClose(WindowT *win) {
|
|||
// ============================================================
|
||||
|
||||
static void switchToCode(void) {
|
||||
if (sFormWin) {
|
||||
dvxDestroyWindow(sAc, sFormWin);
|
||||
cleanupFormWin();
|
||||
// Stash form data
|
||||
if (sDesigner.form && sProject.activeFileIdx >= 0) {
|
||||
PrjFileT *cur = &sProject.files[sProject.activeFileIdx];
|
||||
|
||||
if (cur->isForm) {
|
||||
char *frmBuf = (char *)malloc(IDE_MAX_SOURCE);
|
||||
|
||||
if (frmBuf) {
|
||||
int32_t frmLen = dsgnSaveFrm(&sDesigner, frmBuf, IDE_MAX_SOURCE);
|
||||
|
||||
free(cur->buffer);
|
||||
|
||||
if (frmLen > 0) {
|
||||
frmBuf[frmLen] = '\0';
|
||||
cur->buffer = frmBuf;
|
||||
} else {
|
||||
free(frmBuf);
|
||||
cur->buffer = NULL;
|
||||
}
|
||||
|
||||
cur->modified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't destroy the form window -- allow both code and design
|
||||
// to be open simultaneously, like VB3.
|
||||
|
||||
setStatus("Code view.");
|
||||
}
|
||||
|
||||
|
|
@ -2661,6 +2925,13 @@ 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());
|
||||
}
|
||||
|
||||
// If already open, just bring to front
|
||||
if (sFormWin) {
|
||||
return;
|
||||
|
|
@ -2673,10 +2944,10 @@ static void switchToDesign(void) {
|
|||
snprintf(frmPath, sizeof(frmPath), "%s", sFilePath);
|
||||
char *dot = strrchr(frmPath, '.');
|
||||
|
||||
if (dot) {
|
||||
strcpy(dot, ".frm");
|
||||
} else {
|
||||
strcat(frmPath, ".frm");
|
||||
if (dot && (dot - frmPath) + 4 < DVX_MAX_PATH) {
|
||||
snprintf(dot, sizeof(frmPath) - (dot - frmPath), ".frm");
|
||||
} else if ((int32_t)strlen(frmPath) + 4 < DVX_MAX_PATH) {
|
||||
snprintf(frmPath + strlen(frmPath), sizeof(frmPath) - strlen(frmPath), ".frm");
|
||||
}
|
||||
|
||||
FILE *f = fopen(frmPath, "r");
|
||||
|
|
@ -2800,14 +3071,20 @@ static void showCodeWindow(void) {
|
|||
return; // already open
|
||||
}
|
||||
|
||||
int32_t codeY = sWin ? sWin->y + sWin->h + 2 : 60;
|
||||
int32_t codeY = toolbarBottom();
|
||||
int32_t codeH = sAc->display.height - codeY - 122;
|
||||
|
||||
sCodeWin = dvxCreateWindow(sAc, "Code", 0, codeY, sAc->display.width, codeH, true);
|
||||
|
||||
// Ensure position is below the toolbar (dvxCreateWindow may adjust)
|
||||
if (sCodeWin) {
|
||||
sCodeWin->y = codeY;
|
||||
}
|
||||
|
||||
if (sCodeWin) {
|
||||
sCodeWin->onMenu = onMenu;
|
||||
sCodeWin->onFocus = onContentFocus;
|
||||
sCodeWin->onClose = onCodeWinClose;
|
||||
sCodeWin->accelTable = sWin ? sWin->accelTable : NULL;
|
||||
sLastFocusWin = sCodeWin;
|
||||
|
||||
|
|
@ -2928,27 +3205,254 @@ static void setStatus(const char *text) {
|
|||
// the Object and Event dropdowns. Procedure names are split on
|
||||
// '_' into ObjectName and EventName (e.g. "Command1_Click").
|
||||
|
||||
// freeProcBufs -- release all procedure buffers
|
||||
|
||||
static void freeProcBufs(void) {
|
||||
free(sGeneralBuf);
|
||||
sGeneralBuf = NULL;
|
||||
|
||||
for (int32_t i = 0; i < (int32_t)arrlen(sProcBufs); i++) {
|
||||
free(sProcBufs[i]);
|
||||
}
|
||||
|
||||
arrfree(sProcBufs);
|
||||
sProcBufs = NULL;
|
||||
sCurProcIdx = -2;
|
||||
}
|
||||
|
||||
|
||||
// parseProcs -- split source into (General) + per-procedure buffers
|
||||
|
||||
static void parseProcs(const char *source) {
|
||||
freeProcBufs();
|
||||
|
||||
if (!source) {
|
||||
sGeneralBuf = strdup("");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *pos = source;
|
||||
const char *genEnd = source; // end of (General) section
|
||||
|
||||
while (*pos) {
|
||||
const char *lineStart = pos;
|
||||
|
||||
// Skip leading whitespace
|
||||
const char *trimmed = pos;
|
||||
|
||||
while (*trimmed == ' ' || *trimmed == '\t') {
|
||||
trimmed++;
|
||||
}
|
||||
|
||||
bool isSub = (strncasecmp(trimmed, "SUB ", 4) == 0);
|
||||
bool isFunc = (strncasecmp(trimmed, "FUNCTION ", 9) == 0);
|
||||
|
||||
if (isSub || isFunc) {
|
||||
// On first proc, mark end of (General) section
|
||||
if (arrlen(sProcBufs) == 0) {
|
||||
genEnd = lineStart;
|
||||
}
|
||||
|
||||
// Find End Sub / End Function
|
||||
const char *endTag = isSub ? "END SUB" : "END FUNCTION";
|
||||
int32_t endTagLen = isSub ? 7 : 12;
|
||||
const char *scan = pos;
|
||||
|
||||
// Advance past the Sub/Function line
|
||||
while (*scan && *scan != '\n') { scan++; }
|
||||
if (*scan == '\n') { scan++; }
|
||||
|
||||
// Scan for End Sub/Function
|
||||
while (*scan) {
|
||||
const char *sl = scan;
|
||||
|
||||
while (*sl == ' ' || *sl == '\t') { sl++; }
|
||||
|
||||
if (strncasecmp(sl, endTag, endTagLen) == 0) {
|
||||
while (*scan && *scan != '\n') { scan++; }
|
||||
if (*scan == '\n') { scan++; }
|
||||
break;
|
||||
}
|
||||
|
||||
while (*scan && *scan != '\n') { scan++; }
|
||||
if (*scan == '\n') { scan++; }
|
||||
}
|
||||
|
||||
// Extract this procedure
|
||||
int32_t procLen = (int32_t)(scan - lineStart);
|
||||
char *procBuf = (char *)malloc(procLen + 1);
|
||||
|
||||
if (procBuf) {
|
||||
memcpy(procBuf, lineStart, procLen);
|
||||
procBuf[procLen] = '\0';
|
||||
}
|
||||
|
||||
arrput(sProcBufs, procBuf);
|
||||
pos = scan;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Advance to next line
|
||||
while (*pos && *pos != '\n') { pos++; }
|
||||
if (*pos == '\n') { pos++; }
|
||||
}
|
||||
|
||||
// Extract (General) section
|
||||
int32_t genLen = (int32_t)(genEnd - source);
|
||||
|
||||
// Trim trailing blank lines
|
||||
while (genLen > 0 && (source[genLen - 1] == '\n' || source[genLen - 1] == '\r' ||
|
||||
source[genLen - 1] == ' ' || source[genLen - 1] == '\t')) {
|
||||
genLen--;
|
||||
}
|
||||
|
||||
sGeneralBuf = (char *)malloc(genLen + 2);
|
||||
|
||||
if (sGeneralBuf) {
|
||||
memcpy(sGeneralBuf, source, genLen);
|
||||
sGeneralBuf[genLen] = '\n';
|
||||
sGeneralBuf[genLen + 1] = '\0';
|
||||
|
||||
if (genLen == 0) {
|
||||
sGeneralBuf[0] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// saveCurProc -- save editor contents back to the current buffer
|
||||
|
||||
static void saveCurProc(void) {
|
||||
if (!sEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *edText = wgtGetText(sEditor);
|
||||
|
||||
if (!edText) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sCurProcIdx == -1) {
|
||||
free(sGeneralBuf);
|
||||
sGeneralBuf = strdup(edText);
|
||||
} else if (sCurProcIdx >= 0 && sCurProcIdx < (int32_t)arrlen(sProcBufs)) {
|
||||
free(sProcBufs[sCurProcIdx]);
|
||||
sProcBufs[sCurProcIdx] = strdup(edText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// showProc -- display a procedure buffer in the editor
|
||||
|
||||
static void showProc(int32_t procIdx) {
|
||||
if (!sEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save whatever is currently in the editor
|
||||
if (sCurProcIdx >= -1) {
|
||||
saveCurProc();
|
||||
}
|
||||
|
||||
if (procIdx == -1) {
|
||||
wgtSetText(sEditor, sGeneralBuf ? sGeneralBuf : "");
|
||||
sCurProcIdx = -1;
|
||||
} else if (procIdx >= 0 && procIdx < (int32_t)arrlen(sProcBufs)) {
|
||||
wgtSetText(sEditor, sProcBufs[procIdx] ? sProcBufs[procIdx] : "");
|
||||
sCurProcIdx = procIdx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// getFullSource -- reassemble all buffers into one source string
|
||||
// Caller must free the returned buffer.
|
||||
|
||||
static char *sFullSourceCache = NULL;
|
||||
|
||||
static const char *getFullSource(void) {
|
||||
saveCurProc();
|
||||
|
||||
free(sFullSourceCache);
|
||||
|
||||
// Calculate total length
|
||||
int32_t totalLen = 0;
|
||||
|
||||
if (sGeneralBuf && sGeneralBuf[0]) {
|
||||
totalLen += (int32_t)strlen(sGeneralBuf);
|
||||
totalLen += 2; // blank line separator
|
||||
}
|
||||
|
||||
int32_t procCount = (int32_t)arrlen(sProcBufs);
|
||||
|
||||
for (int32_t i = 0; i < procCount; i++) {
|
||||
if (sProcBufs[i]) {
|
||||
totalLen += (int32_t)strlen(sProcBufs[i]);
|
||||
totalLen += 1; // newline separator
|
||||
}
|
||||
}
|
||||
|
||||
sFullSourceCache = (char *)malloc(totalLen + 1);
|
||||
|
||||
if (!sFullSourceCache) {
|
||||
return "";
|
||||
}
|
||||
|
||||
int32_t pos = 0;
|
||||
|
||||
if (sGeneralBuf && sGeneralBuf[0]) {
|
||||
int32_t len = (int32_t)strlen(sGeneralBuf);
|
||||
memcpy(sFullSourceCache + pos, sGeneralBuf, len);
|
||||
pos += len;
|
||||
|
||||
if (pos > 0 && sFullSourceCache[pos - 1] != '\n') {
|
||||
sFullSourceCache[pos++] = '\n';
|
||||
}
|
||||
|
||||
sFullSourceCache[pos++] = '\n';
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < procCount; i++) {
|
||||
if (sProcBufs[i]) {
|
||||
int32_t len = (int32_t)strlen(sProcBufs[i]);
|
||||
memcpy(sFullSourceCache + pos, sProcBufs[i], len);
|
||||
pos += len;
|
||||
|
||||
if (pos > 0 && sFullSourceCache[pos - 1] != '\n') {
|
||||
sFullSourceCache[pos++] = '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sFullSourceCache[pos] = '\0';
|
||||
return sFullSourceCache;
|
||||
}
|
||||
|
||||
|
||||
static void updateDropdowns(void) {
|
||||
// Reset dynamic arrays
|
||||
arrsetlen(sProcTable, 0);
|
||||
arrsetlen(sObjItems, 0);
|
||||
arrsetlen(sEvtItems, 0);
|
||||
|
||||
if (!sEditor || !sObjDropdown || !sEvtDropdown) {
|
||||
if (!sObjDropdown || !sEvtDropdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char *src = wgtGetText(sEditor);
|
||||
// Scan the reassembled full source
|
||||
const char *src = getFullSource();
|
||||
|
||||
if (!src) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Scan line by line for SUB / FUNCTION
|
||||
const char *pos = src;
|
||||
int32_t lineNum = 1;
|
||||
const char *pos = src;
|
||||
int32_t lineNum = 1;
|
||||
|
||||
while (*pos) {
|
||||
const char *lineStart = pos;
|
||||
|
||||
// Skip leading whitespace
|
||||
while (*pos == ' ' || *pos == '\t') {
|
||||
pos++;
|
||||
|
|
@ -2961,12 +3465,10 @@ static void updateDropdowns(void) {
|
|||
if (isSub || isFunc) {
|
||||
pos += isSub ? 4 : 9;
|
||||
|
||||
// Skip whitespace after keyword
|
||||
while (*pos == ' ' || *pos == '\t') {
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Extract procedure name
|
||||
char procName[64];
|
||||
int32_t nameLen = 0;
|
||||
|
||||
|
|
@ -2976,7 +3478,40 @@ static void updateDropdowns(void) {
|
|||
|
||||
procName[nameLen] = '\0';
|
||||
|
||||
// Split on '_' into object + event
|
||||
// Find End Sub / End Function
|
||||
const char *endTag = isSub ? "END SUB" : "END FUNCTION";
|
||||
int32_t endTagLen = isSub ? 7 : 12;
|
||||
const char *scan = pos;
|
||||
|
||||
while (*scan) {
|
||||
const char *sl = scan;
|
||||
|
||||
while (*sl == ' ' || *sl == '\t') {
|
||||
sl++;
|
||||
}
|
||||
|
||||
if (strncasecmp(sl, endTag, endTagLen) == 0) {
|
||||
// Advance past the End line
|
||||
while (*scan && *scan != '\n') {
|
||||
scan++;
|
||||
}
|
||||
|
||||
if (*scan == '\n') {
|
||||
scan++;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
while (*scan && *scan != '\n') {
|
||||
scan++;
|
||||
}
|
||||
|
||||
if (*scan == '\n') {
|
||||
scan++;
|
||||
}
|
||||
}
|
||||
|
||||
IdeProcEntryT entry;
|
||||
memset(&entry, 0, sizeof(entry));
|
||||
entry.lineNum = lineNum;
|
||||
|
|
@ -2999,6 +3534,18 @@ static void updateDropdowns(void) {
|
|||
}
|
||||
|
||||
arrput(sProcTable, entry);
|
||||
|
||||
// Skip to end of this proc (already scanned)
|
||||
pos = scan;
|
||||
|
||||
// Count lines we skipped
|
||||
for (const char *c = lineStart; c < scan; c++) {
|
||||
if (*c == '\n') {
|
||||
lineNum++;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Advance to end of line
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ static char **sLabels = NULL; // stb_ds array of strdup'd strings
|
|||
// ============================================================
|
||||
|
||||
static void onPrjWinClose(WindowT *win);
|
||||
static void onTreeItemClick(WidgetT *w);
|
||||
static void onTreeItemDblClick(WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// prjInit
|
||||
|
|
@ -362,7 +362,7 @@ static void onPrjWinClose(WindowT *win) {
|
|||
}
|
||||
|
||||
|
||||
static void onTreeItemClick(WidgetT *w) {
|
||||
static void onTreeItemDblClick(WidgetT *w) {
|
||||
if (!sPrj || !sOnClick) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -473,7 +473,7 @@ void prjRebuildTree(PrjStateT *prj) {
|
|||
arrput(sLabels, label);
|
||||
WidgetT *item = wgtTreeItem(formsNode, label);
|
||||
item->userData = (void *)(intptr_t)i;
|
||||
item->onClick = onTreeItemClick;
|
||||
item->onDblClick = onTreeItemDblClick;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -490,7 +490,7 @@ void prjRebuildTree(PrjStateT *prj) {
|
|||
arrput(sLabels, label);
|
||||
WidgetT *item = wgtTreeItem(modsNode, label);
|
||||
item->userData = (void *)(intptr_t)i;
|
||||
item->onClick = onTreeItemClick;
|
||||
item->onDblClick = onTreeItemDblClick;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
#define CMD_TILE_H 202
|
||||
#define CMD_TILE_V 203
|
||||
#define CMD_MIN_ON_RUN 104
|
||||
#define CMD_RESTORE_ALONE 105
|
||||
#define CMD_ABOUT 300
|
||||
#define CMD_TASK_MGR 301
|
||||
#define CMD_SYSINFO 302
|
||||
|
|
@ -99,7 +100,8 @@ static AppContextT *sAc = NULL;
|
|||
static WindowT *sPmWindow = NULL;
|
||||
static WidgetT *sStatusLabel = NULL;
|
||||
static PrefsHandleT *sPrefs = NULL;
|
||||
static bool sMinOnRun = false;
|
||||
static bool sMinOnRun = false;
|
||||
static bool sRestoreAlone = false;
|
||||
static AppEntryT sAppFiles[MAX_APP_FILES];
|
||||
static int32_t sAppCount = 0;
|
||||
|
||||
|
|
@ -164,10 +166,11 @@ static void buildPmWindow(void) {
|
|||
MenuT *fileMenu = wmAddMenu(menuBar, "&File");
|
||||
wmAddMenuItem(fileMenu, "&Run...", CMD_RUN);
|
||||
wmAddMenuSeparator(fileMenu);
|
||||
wmAddMenuItem(fileMenu, "E&xit Shell", CMD_EXIT);
|
||||
wmAddMenuItem(fileMenu, "E&xit DVX", CMD_EXIT);
|
||||
|
||||
MenuT *optMenu = wmAddMenu(menuBar, "&Options");
|
||||
wmAddMenuCheckItem(optMenu, "&Minimize on Run", CMD_MIN_ON_RUN, sMinOnRun);
|
||||
wmAddMenuCheckItem(optMenu, "&Restore when Alone", CMD_RESTORE_ALONE, sRestoreAlone);
|
||||
|
||||
MenuT *windowMenu = wmAddMenu(menuBar, "&Window");
|
||||
wmAddMenuItem(windowMenu, "&Cascade", CMD_CASCADE);
|
||||
|
|
@ -176,7 +179,7 @@ static void buildPmWindow(void) {
|
|||
wmAddMenuItem(windowMenu, "Tile &Vertically", CMD_TILE_V);
|
||||
|
||||
MenuT *helpMenu = wmAddMenu(menuBar, "&Help");
|
||||
wmAddMenuItem(helpMenu, "&About DVX Shell...", CMD_ABOUT);
|
||||
wmAddMenuItem(helpMenu, "&About DVX...", CMD_ABOUT);
|
||||
wmAddMenuItem(helpMenu, "&System Information...", CMD_SYSINFO);
|
||||
wmAddMenuSeparator(helpMenu);
|
||||
wmAddMenuItem(helpMenu, "&Task Manager\tCtrl+Esc", CMD_TASK_MGR);
|
||||
|
|
@ -262,6 +265,13 @@ static void buildPmWindow(void) {
|
|||
// (Task Manager refresh is handled by the shell's shellDesktopUpdate.)
|
||||
static void desktopUpdate(void) {
|
||||
updateStatusText();
|
||||
|
||||
// Auto-restore if we're the only running app and minimized
|
||||
if (sRestoreAlone && sPmWindow && sPmWindow->minimized) {
|
||||
if (shellRunningAppCount() <= 1) {
|
||||
wmRestoreMinimized(&sAc->stack, &sAc->dirty, &sAc->display, sPmWindow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -288,7 +298,7 @@ static void onAppButtonClick(WidgetT *w) {
|
|||
// shellTerminateAllApps() to gracefully tear down all loaded DXEs.
|
||||
static void onPmClose(WindowT *win) {
|
||||
(void)win;
|
||||
int32_t result = dvxMessageBox(sAc, "Exit Shell", "Are you sure you want to exit DVX Shell?", MB_YESNO | MB_ICONQUESTION);
|
||||
int32_t result = dvxMessageBox(sAc, "Exit DVX", "Are you sure you want to exit DVX?", MB_YESNO | MB_ICONQUESTION);
|
||||
|
||||
if (result == ID_YES) {
|
||||
dvxQuit(sAc);
|
||||
|
|
@ -346,6 +356,13 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
|
|||
prefsSave(sPrefs);
|
||||
break;
|
||||
|
||||
case CMD_RESTORE_ALONE:
|
||||
sRestoreAlone = !sRestoreAlone;
|
||||
shellEnsureConfigDir(sCtx);
|
||||
prefsSetBool(sPrefs, "options", "restoreAlone", sRestoreAlone);
|
||||
prefsSave(sPrefs);
|
||||
break;
|
||||
|
||||
case CMD_ABOUT:
|
||||
showAboutDialog();
|
||||
break;
|
||||
|
|
@ -464,8 +481,8 @@ static void scanAppsDirRecurse(const char *dirPath) {
|
|||
|
||||
|
||||
static void showAboutDialog(void) {
|
||||
dvxMessageBox(sAc, "About DVX Shell",
|
||||
"DVX Shell 1.0\nA DOS Visual eXecutive desktop shell for DJGPP/DPMI. Using DXE3 dynamic loading for application modules.",
|
||||
dvxMessageBox(sAc, "About DVX",
|
||||
"DVX 1.0 - \"DOS Visual eXecutive\" GUI System.\n\n\"We have Windows at home.\"\n\nCopyright 2026 Scott Duensing\nKangaroo Punch Studios\nhttps://kangaroopunch.com",
|
||||
MB_OK | MB_ICONINFO);
|
||||
}
|
||||
|
||||
|
|
@ -537,7 +554,8 @@ int32_t appMain(DxeAppContextT *ctx) {
|
|||
char prefsPath[DVX_MAX_PATH];
|
||||
shellConfigPath(sCtx, "progman.ini", prefsPath, sizeof(prefsPath));
|
||||
sPrefs = prefsLoad(prefsPath);
|
||||
sMinOnRun = prefsGetBool(sPrefs, "options", "minimizeOnRun", false);
|
||||
sMinOnRun = prefsGetBool(sPrefs, "options", "minimizeOnRun", false);
|
||||
sRestoreAlone = prefsGetBool(sPrefs, "options", "restoreAlone", false);
|
||||
|
||||
scanAppsDir();
|
||||
buildPmWindow();
|
||||
|
|
|
|||
|
|
@ -1773,12 +1773,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
|
|||
if (ctx->lastIconClickId == iconWin->id &&
|
||||
(now - ctx->lastIconClickTime) < ctx->dblClickTicks) {
|
||||
// Double-click: restore minimized window
|
||||
// Dirty the entire icon area (may span multiple rows)
|
||||
int32_t iconY;
|
||||
int32_t iconH;
|
||||
wmMinimizedIconRect(&ctx->stack, &ctx->display, &iconY, &iconH);
|
||||
dirtyListAdd(&ctx->dirty, 0, iconY, ctx->display.width, iconH);
|
||||
wmRestoreMinimized(&ctx->stack, &ctx->dirty, iconWin);
|
||||
wmRestoreMinimized(&ctx->stack, &ctx->dirty, &ctx->display, iconWin);
|
||||
ctx->lastIconClickId = -1;
|
||||
} else {
|
||||
// First click -- record for double-click detection
|
||||
|
|
|
|||
|
|
@ -575,6 +575,12 @@ static void wordWrapDraw(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fo
|
|||
drawTextN(d, ops, font, x, curY, text, lineLen, fg, bg, true);
|
||||
curY += lineH;
|
||||
text += lineLen;
|
||||
|
||||
// Skip the newline that ended this line (if any) so the
|
||||
// explicit \n handler at the top doesn't double-advance.
|
||||
if (*text == '\n') {
|
||||
text++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -631,6 +637,10 @@ static int32_t wordWrapHeight(const BitmapFontT *font, const char *text, int32_t
|
|||
|
||||
lines++;
|
||||
text += lineLen;
|
||||
|
||||
if (*text == '\n') {
|
||||
text++;
|
||||
}
|
||||
}
|
||||
|
||||
if (lines == 0) {
|
||||
|
|
|
|||
20
core/dvxWm.c
20
core/dvxWm.c
|
|
@ -2403,21 +2403,21 @@ void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *
|
|||
// wmRestoreMinimized
|
||||
// ============================================================
|
||||
//
|
||||
// Restores a minimized window: clears the minimized flag, raises the window
|
||||
// to the top of the z-order, and gives it focus. No content buffer
|
||||
// reallocation is needed because the buffer was preserved while minimized.
|
||||
//
|
||||
// The icon strip area is implicitly dirtied by the raise/focus operations
|
||||
// and by the window area dirty at the end. The compositor repaints the
|
||||
// entire icon strip on any dirty rect that intersects it, so icon positions
|
||||
// are always correct after restore even though we don't dirty the icon
|
||||
// strip explicitly.
|
||||
// Restores a minimized window: clears the minimized flag, dirties the
|
||||
// icon strip so the old icon is erased, raises the window to the top
|
||||
// of the z-order, and gives it focus.
|
||||
|
||||
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, WindowT *win) {
|
||||
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win) {
|
||||
if (!win->minimized) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Dirty the icon strip area so the old minimized icon is erased
|
||||
int32_t iconY;
|
||||
int32_t iconH;
|
||||
wmMinimizedIconRect(stack, d, &iconY, &iconH);
|
||||
dirtyListAdd(dl, 0, iconY, d->width, iconH);
|
||||
|
||||
win->minimized = false;
|
||||
|
||||
for (int32_t i = 0; i < stack->count; i++) {
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ void wmMinimizedIconRect(const WindowStackT *stack, const DisplayT *d, int32_t *
|
|||
void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
|
||||
|
||||
// Restore a minimized window
|
||||
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, WindowT *win);
|
||||
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
|
||||
|
||||
// Load an icon image for a window (converts to display pixel format)
|
||||
int32_t wmSetIcon(WindowT *win, const char *path, const DisplayT *d);
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ static void onTmSwitchTo(WidgetT *w) {
|
|||
|
||||
if (win->appId == app->appId) {
|
||||
if (win->minimized) {
|
||||
wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, win);
|
||||
wmRestoreMinimized(&sCtx->stack, &sCtx->dirty, &sCtx->display, win);
|
||||
}
|
||||
|
||||
wmRaiseWindow(&sCtx->stack, &sCtx->dirty, j);
|
||||
|
|
|
|||
|
|
@ -1192,12 +1192,20 @@ void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
|||
item->onChange(item);
|
||||
}
|
||||
} else {
|
||||
if (item->onClick) {
|
||||
int32_t clicks = multiClickDetect(vx, vy);
|
||||
|
||||
if (clicks >= 2 && item->onDblClick) {
|
||||
item->onDblClick(item);
|
||||
} else if (item->onClick) {
|
||||
item->onClick(item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (item->onClick) {
|
||||
int32_t clicks = multiClickDetect(vx, vy);
|
||||
|
||||
if (clicks >= 2 && item->onDblClick) {
|
||||
item->onDblClick(item);
|
||||
} else if (item->onClick) {
|
||||
item->onClick(item);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue