Major code cleanup.

This commit is contained in:
Scott Duensing 2026-04-17 19:36:05 -05:00
parent e6305db3b5
commit 48fb1c30ae
162 changed files with 30429 additions and 33139 deletions

View file

@ -3251,11 +3251,6 @@ shellConfigPath(ctx, "settings.ini", path, sizeof(path));
<h2>dvxSql -- SQL Database Interface</h2> <h2>dvxSql -- SQL Database Interface</h2>
<p>High-level wrapper around SQLite3 for DVX applications. Manages database connections and result set cursors via integer handles so BASIC code never touches raw pointers. All handles are 1-based; 0 indicates an error or invalid handle.</p> <p>High-level wrapper around SQLite3 for DVX applications. Manages database connections and result set cursors via integer handles so BASIC code never touches raw pointers. All handles are 1-based; 0 indicates an error or invalid handle.</p>
<p>Header: sql/dvxSql.h</p> <p>Header: sql/dvxSql.h</p>
<h3>Limits</h3>
<pre> Constant Value Description
-------- ----- -----------
MAX_DBS 16 Maximum number of simultaneously open databases.
MAX_CURSORS 64 Maximum number of simultaneously open result set cursors.</pre>
<h3>Handle Model</h3> <h3>Handle Model</h3>
<p>Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it.</p> <p>Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it.</p>
<p><a href="#sql.db">Database Operations</a></p> <p><a href="#sql.db">Database Operations</a></p>
@ -3868,7 +3863,6 @@ dvxSqlExec(db, sql);</code></pre>
PKT_SUCCESS 0 Operation succeeded. PKT_SUCCESS 0 Operation succeeded.
PKT_ERR_INVALID_PORT -1 Invalid COM port index. PKT_ERR_INVALID_PORT -1 Invalid COM port index.
PKT_ERR_NOT_OPEN -2 Connection is not open. PKT_ERR_NOT_OPEN -2 Connection is not open.
PKT_ERR_ALREADY_OPEN -3 Connection is already open.
PKT_ERR_WOULD_BLOCK -4 Operation would block. PKT_ERR_WOULD_BLOCK -4 Operation would block.
PKT_ERR_OVERFLOW -5 Buffer overflow. PKT_ERR_OVERFLOW -5 Buffer overflow.
PKT_ERR_INVALID_PARAM -6 Invalid parameter. PKT_ERR_INVALID_PARAM -6 Invalid parameter.

View file

@ -1,6 +1,8 @@
[Project] [Project]
Name = BASIC Demo Name = BASIC Demo
Author = DVX Project Author = Scott Duensing
Publisher = Kangaroo Punch Studios
Copyright = Copyright 2026 Scott Duensing
Description = Comprehensive tour of DVX BASIC features Description = Comprehensive tour of DVX BASIC features
Icon = ICON32.BMP Icon = ICON32.BMP

View file

@ -77,9 +77,15 @@ AppDescriptorT appDescriptor = {
.priority = TS_PRIORITY_LOW .priority = TS_PRIORITY_LOW
}; };
// ============================================================
// Callbacks (fire in task 0 during dvxUpdate) // The shell calls appShutdown (if exported) when force-killing an app via
// ============================================================ // Task Manager or during shell shutdown. For main-loop apps, this is the
// signal to break out of the main loop. Without this, shellForceKillApp
// would have to terminate the task forcibly, potentially leaking resources.
void appShutdown(void) {
sState.quit = true;
}
// Setting quit = true tells the main loop (running in a separate task) to // Setting quit = true tells the main loop (running in a separate task) to
// exit. The main loop then destroys the window and returns from appMain, // exit. The main loop then destroys the window and returns from appMain,
@ -135,9 +141,6 @@ static void onPaint(WindowT *win, RectT *dirty) {
drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true); drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true);
} }
// ============================================================
// Time update
// ============================================================
static void updateTime(void) { static void updateTime(void) {
time_t now = time(NULL); time_t now = time(NULL);
@ -162,21 +165,6 @@ static void updateTime(void) {
sState.lastUpdate = now; sState.lastUpdate = now;
} }
// ============================================================
// Shutdown hook (optional DXE export)
// ============================================================
// The shell calls appShutdown (if exported) when force-killing an app via
// Task Manager or during shell shutdown. For main-loop apps, this is the
// signal to break out of the main loop. Without this, shellForceKillApp
// would have to terminate the task forcibly, potentially leaking resources.
void appShutdown(void) {
sState.quit = true;
}
// ============================================================
// Entry point (runs in its own task)
// ============================================================
// This runs in its own task. The shell creates the task before calling // This runs in its own task. The shell creates the task before calling
// appMain, and reaps it when appMain returns. // appMain, and reaps it when appMain returns.

View file

@ -1,5 +1,7 @@
# clock.res -- Resource manifest for Clock # clock.res -- Resource manifest for Clock
icon32 icon icon32.bmp icon32 icon icon32.bmp
name text "Clock" name text "Clock"
author text "DVX Project" author text "Scott Duensing"
copyright text "Copyright 2026 Scott Duensing"
publisher text "Kangaroo Punch Studios"
description text "Digital clock with date display" description text "Digital clock with date display"

View file

@ -59,18 +59,6 @@
#define THEME_DIR "CONFIG/THEMES" #define THEME_DIR "CONFIG/THEMES"
#define THEME_EXT ".THM" #define THEME_EXT ".THM"
// ============================================================
// App descriptor
// ============================================================
AppDescriptorT appDescriptor = {
.name = "Control Panel",
.hasMainLoop = false,
.multiInstance = false,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_NORMAL
};
// ============================================================ // ============================================================
// Entry types for dynamic lists // Entry types for dynamic lists
// ============================================================ // ============================================================
@ -100,6 +88,7 @@ static int32_t sSavedVideoW;
static int32_t sSavedVideoH; static int32_t sSavedVideoH;
static int32_t sSavedVideoBpp; static int32_t sSavedVideoBpp;
static WallpaperModeE sSavedWpMode; static WallpaperModeE sSavedWpMode;
static char sSavedWallpaperPath[DVX_MAX_PATH];
// Mouse tab widgets // Mouse tab widgets
static WidgetT *sWheelDrop = NULL; static WidgetT *sWheelDrop = NULL;
@ -135,6 +124,7 @@ static const VideoModeInfoT *sVideoModes = NULL; // points into ctx, not owned
static int32_t sVideoCount = 0; static int32_t sVideoCount = 0;
static const char **sVideoLabels = NULL; // stb_ds dynamic array static const char **sVideoLabels = NULL; // stb_ds dynamic array
static WidgetT *sVideoList = NULL; static WidgetT *sVideoList = NULL;
static int32_t sVideoConfirmResult = -1;
// Theme list // Theme list
static FileEntryT *sThemeEntries = NULL; // stb_ds dynamic array static FileEntryT *sThemeEntries = NULL; // stb_ds dynamic array
@ -146,22 +136,17 @@ static WidgetT *sThemeList = NULL;
// ============================================================ // ============================================================
int32_t appMain(DxeAppContextT *ctx); int32_t appMain(DxeAppContextT *ctx);
static void buildMouseTab(WidgetT *page); static void applyMouseConfig(void);
static void buildColorsTab(WidgetT *page); static void buildColorsTab(WidgetT *page);
static void buildDesktopTab(WidgetT *page); static void buildDesktopTab(WidgetT *page);
static void buildMouseTab(WidgetT *page);
static void buildVideoTab(WidgetT *page); static void buildVideoTab(WidgetT *page);
static int32_t mapAccelName(const char *name); static int32_t mapAccelName(const char *name);
static const char *mapAccelValue(int32_t val); static const char *mapAccelValue(int32_t val);
static void onAccelChange(WidgetT *w); static void onAccelChange(WidgetT *w);
static void onSpeedSlider(WidgetT *w);
static void updateSpeedLabel(void);
static void updateWheelStepLabel(void);
static void applyMouseConfig(void);
static void onApplyTheme(WidgetT *w); static void onApplyTheme(WidgetT *w);
static void onBrowseTheme(WidgetT *w);
static void onResetColors(WidgetT *w);
static void onApplyWallpaper(WidgetT *w); static void onApplyWallpaper(WidgetT *w);
static void onWallpaperMode(WidgetT *w); static void onBrowseTheme(WidgetT *w);
static void onCancel(WidgetT *w); static void onCancel(WidgetT *w);
static void onChooseWallpaper(WidgetT *w); static void onChooseWallpaper(WidgetT *w);
static void onClearWallpaper(WidgetT *w); static void onClearWallpaper(WidgetT *w);
@ -171,23 +156,59 @@ static void onColorSlider(WidgetT *w);
static void onDblClickSlider(WidgetT *w); static void onDblClickSlider(WidgetT *w);
static void onDblClickTest(WidgetT *w); static void onDblClickTest(WidgetT *w);
static void onOk(WidgetT *w); static void onOk(WidgetT *w);
static void onResetColors(WidgetT *w);
static void onSaveTheme(WidgetT *w); static void onSaveTheme(WidgetT *w);
static void onSpeedSlider(WidgetT *w);
static void onVideoApply(WidgetT *w); static void onVideoApply(WidgetT *w);
static void onVideoConfirmClose(WindowT *win);
static void onVideoConfirmNo(WidgetT *w);
static void onVideoConfirmYes(WidgetT *w);
static void onWallpaperMode(WidgetT *w);
static void onWheelChange(WidgetT *w); static void onWheelChange(WidgetT *w);
static void onWheelStepSlider(WidgetT *w); static void onWheelStepSlider(WidgetT *w);
static void saveSnapshot(void);
static void restoreSnapshot(void); static void restoreSnapshot(void);
static void scanWallpapers(void); static void saveSnapshot(void);
static void scanThemes(void); static void scanThemes(void);
static void scanWallpapers(void);
static void updateColorSliders(void); static void updateColorSliders(void);
static void updateDblClickLabel(void); static void updateDblClickLabel(void);
static void updateSpeedLabel(void);
static void updateSwatch(void); static void updateSwatch(void);
static void updateWheelStepLabel(void);
// ============================================================ // ============================================================
// buildColorsTab // App descriptor
// ============================================================ // ============================================================
AppDescriptorT appDescriptor = {
.name = "Control Panel",
.hasMainLoop = false,
.multiInstance = false,
.stackSize = SHELL_STACK_DEFAULT,
.priority = TS_PRIORITY_NORMAL
};
static void applyMouseConfig(void) {
int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
int32_t accelVal = mapAccelName(accelName);
// Slider is inverted: low value = slow (high mickeys), high value = fast (low mickeys)
// Slider range 2..32. Mickey ratio = 34 - sliderValue, so:
// slider 2 -> 32 mickeys/8px (slowest)
// slider 8 -> 26 (near default)
// slider 32 -> 2 mickeys/8px (fastest)
int32_t speedVal = 34 - wgtSliderGetValue(sSpeedSldr);
int32_t wheelStep = wgtSliderGetValue(sWheelStepSldr);
dvxSetMouseConfig(sAc, dir, dbl, accelVal, speedVal, wheelStep);
}
static void buildColorsTab(WidgetT *page) { static void buildColorsTab(WidgetT *page) {
// Color list on the left, sliders on the right // Color list on the left, sliders on the right
WidgetT *hbox = wgtHBox(page); WidgetT *hbox = wgtHBox(page);
@ -277,10 +298,6 @@ static void buildColorsTab(WidgetT *page) {
} }
// ============================================================
// buildDesktopTab
// ============================================================
static void buildDesktopTab(WidgetT *page) { static void buildDesktopTab(WidgetT *page) {
wgtLabel(page, "Wallpaper:"); wgtLabel(page, "Wallpaper:");
@ -318,10 +335,6 @@ static void buildDesktopTab(WidgetT *page) {
} }
// ============================================================
// buildMouseTab
// ============================================================
static void buildMouseTab(WidgetT *page) { static void buildMouseTab(WidgetT *page) {
page->spacing = wgtPixels(6); page->spacing = wgtPixels(6);
@ -370,7 +383,7 @@ static void buildMouseTab(WidgetT *page) {
sDblClickSldr->weight = 100; sDblClickSldr->weight = 100;
sDblClickSldr->onChange = onDblClickSlider; sDblClickSldr->onChange = onDblClickSlider;
int32_t dblMs = prefsGetInt(sPrefs, "mouse", "doubleclick", 500); int32_t dblMs = prefsGetInt(sPrefs, "mouse", "doubleclick", MOUSE_DBLCLICK_DEFAULT_MS);
wgtSliderSetValue(sDblClickSldr, dblMs); wgtSliderSetValue(sDblClickSldr, dblMs);
wgtLabel(dblRow, "Slow "); wgtLabel(dblRow, "Slow ");
sDblClickLbl = wgtLabel(dblRow, ""); sDblClickLbl = wgtLabel(dblRow, "");
@ -390,7 +403,7 @@ static void buildMouseTab(WidgetT *page) {
sAccelDrop->onChange = onAccelChange; sAccelDrop->onChange = onAccelChange;
wgtDropdownSetItems(sAccelDrop, accelItems, 4); wgtDropdownSetItems(sAccelDrop, accelItems, 4);
const char *accelStr = prefsGetString(sPrefs, "mouse", "acceleration", "medium"); const char *accelStr = prefsGetString(sPrefs, "mouse", "acceleration", MOUSE_ACCEL_DEFAULT);
if (strcmp(accelStr, "off") == 0) { if (strcmp(accelStr, "off") == 0) {
wgtDropdownSetSelected(sAccelDrop, 0); wgtDropdownSetSelected(sAccelDrop, 0);
@ -415,7 +428,7 @@ static void buildMouseTab(WidgetT *page) {
sSpeedSldr->weight = 100; sSpeedSldr->weight = 100;
sSpeedSldr->onChange = onSpeedSlider; sSpeedSldr->onChange = onSpeedSlider;
int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", 8); int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", MOUSE_SPEED_DEFAULT);
wgtSliderSetValue(sSpeedSldr, speed); wgtSliderSetValue(sSpeedSldr, speed);
wgtLabel(speedRow, "Fast "); wgtLabel(speedRow, "Fast ");
sSpeedLbl = wgtLabel(speedRow, ""); sSpeedLbl = wgtLabel(speedRow, "");
@ -436,10 +449,6 @@ static void buildMouseTab(WidgetT *page) {
} }
// ============================================================
// buildVideoTab
// ============================================================
static void buildVideoTab(WidgetT *page) { static void buildVideoTab(WidgetT *page) {
wgtLabel(page, "Available Video Modes:"); wgtLabel(page, "Available Video Modes:");
@ -490,11 +499,6 @@ static void buildVideoTab(WidgetT *page) {
} }
// ============================================================
// mapAccelName / mapAccelValue
// ============================================================
static int32_t mapAccelName(const char *name) { static int32_t mapAccelName(const char *name) {
if (strcmp(name, "off") == 0) return 10000; if (strcmp(name, "off") == 0) return 10000;
if (strcmp(name, "low") == 0) return 100; if (strcmp(name, "low") == 0) return 100;
@ -515,57 +519,107 @@ static const char *mapAccelValue(int32_t idx) {
} }
// ============================================================
// Callbacks -- Mouse tab
// ============================================================
static void onWheelChange(WidgetT *w) {
(void)w;
applyMouseConfig();
}
static void onWheelStepSlider(WidgetT *w) {
(void)w;
updateWheelStepLabel();
applyMouseConfig();
}
static void onDblClickSlider(WidgetT *w) {
(void)w;
updateDblClickLabel();
applyMouseConfig();
}
static void onDblClickTest(WidgetT *w) {
(void)w;
static int32_t count = 0;
count++;
static char buf[48];
snprintf(buf, sizeof(buf), "Double-click detected! (%ld)", (long)count);
wgtSetText(sDblTestLbl, buf);
}
static void onAccelChange(WidgetT *w) { static void onAccelChange(WidgetT *w) {
(void)w; (void)w;
applyMouseConfig(); applyMouseConfig();
} }
static void onSpeedSlider(WidgetT *w) { static void onApplyTheme(WidgetT *w) {
(void)w; (void)w;
updateSpeedLabel();
applyMouseConfig(); int32_t sel = wgtDropdownGetSelected(sThemeList);
if (sel < 0 || sel >= arrlen(sThemeEntries)) {
return;
}
dvxLoadTheme(sAc, sThemeEntries[sel].path);
updateColorSliders();
} }
// ============================================================ static void onApplyWallpaper(WidgetT *w) {
// Callbacks -- Colors tab (void)w;
// ============================================================
int32_t sel = wgtListBoxGetSelected(sWpaperList);
if (sel < 0 || sel >= arrlen(sWpaperEntries)) {
return;
}
if (dvxSetWallpaper(sAc, sWpaperEntries[sel].path)) {
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sWpaperEntries[sel].path);
wgtSetText(sWallpaperLbl, sWallpaperPath);
} else {
dvxErrorBox(sAc, NULL, "Could not load wallpaper image.");
}
}
static void onBrowseTheme(WidgetT *w) {
(void)w;
FileFilterT filters[] = {
{ "\1" },
{ "\1" }
};
char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Load Theme", FD_OPEN, THEME_DIR, filters, 2, path, sizeof(path))) {
dvxLoadTheme(sAc, path);
updateColorSliders();
}
}
static void onCancel(WidgetT *w) {
(void)w;
restoreSnapshot();
prefsClose(sPrefs);
sPrefs = NULL;
dvxDestroyWindow(sAc, sWin);
sWin = NULL;
}
static void onChooseWallpaper(WidgetT *w) {
(void)w;
FileFilterT filters[] = {
{ "\1" },
{ "\1" }
};
char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Choose Wallpaper", FD_OPEN, "CONFIG/WPAPER", filters, 2, path, sizeof(path))) {
if (dvxSetWallpaper(sAc, path)) {
strncpy(sWallpaperPath, path, sizeof(sWallpaperPath) - 1);
sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0';
wgtSetText(sWallpaperLbl, sWallpaperPath);
} else {
dvxErrorBox(sAc, NULL, "Could not load wallpaper image.");
}
}
}
static void onClearWallpaper(WidgetT *w) {
(void)w;
dvxSetWallpaper(sAc, NULL);
sWallpaperPath[0] = '\0';
wgtSetText(sWallpaperLbl, "(none)");
}
static void onClose(WindowT *win) {
restoreSnapshot();
prefsClose(sPrefs);
sPrefs = NULL;
dvxDestroyWindow(sAc, win);
sWin = NULL;
}
static void onColorSelect(WidgetT *w) { static void onColorSelect(WidgetT *w) {
(void)w; (void)w;
@ -602,33 +656,81 @@ static void onColorSlider(WidgetT *w) {
} }
static void onApplyTheme(WidgetT *w) { static void onDblClickSlider(WidgetT *w) {
(void)w; (void)w;
updateDblClickLabel();
int32_t sel = wgtDropdownGetSelected(sThemeList); applyMouseConfig();
if (sel < 0 || sel >= arrlen(sThemeEntries)) {
return;
}
dvxLoadTheme(sAc, sThemeEntries[sel].path);
updateColorSliders();
} }
static void onBrowseTheme(WidgetT *w) { static void onDblClickTest(WidgetT *w) {
(void)w;
static int32_t count = 0;
count++;
static char buf[48];
snprintf(buf, sizeof(buf), "Double-click detected! (%ld)", (long)count);
wgtSetText(sDblTestLbl, buf);
}
static void onOk(WidgetT *w) {
(void)w; (void)w;
FileFilterT filters[] = { // Save mouse settings
{ "\1" }, int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop);
{ "\1" } prefsSetString(sPrefs, "mouse", "wheel", wheelSel == 1 ? "reversed" : MOUSE_WHEEL_DIR_DEFAULT);
}; prefsSetInt(sPrefs, "mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
char path[DVX_MAX_PATH]; prefsSetString(sPrefs, "mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop)));
prefsSetInt(sPrefs, "mouse", "speed", wgtSliderGetValue(sSpeedSldr));
prefsSetInt(sPrefs, "mouse", "wheelspeed", wgtSliderGetValue(sWheelStepSldr));
if (dvxFileDialog(sAc, "Load Theme", FD_OPEN, THEME_DIR, filters, 2, path, sizeof(path))) { // Save colors to INI
dvxLoadTheme(sAc, path); for (int32_t i = 0; i < ColorCountE; i++) {
updateColorSliders(); uint8_t r;
uint8_t g;
uint8_t b;
dvxGetColor(sAc, (ColorIdE)i, &r, &g, &b);
char val[16];
snprintf(val, sizeof(val), "%d,%d,%d", r, g, b);
prefsSetString(sPrefs, "colors", dvxColorName((ColorIdE)i), val);
} }
// Save desktop settings
if (sWallpaperPath[0]) {
prefsSetString(sPrefs, "desktop", "wallpaper", sWallpaperPath);
} else {
prefsRemove(sPrefs, "desktop", "wallpaper");
}
const char *modeStr = "stretch";
if (sAc->wallpaperMode == WallpaperTileE) {
modeStr = "tile";
} else if (sAc->wallpaperMode == WallpaperCenterE) {
modeStr = "center";
}
prefsSetString(sPrefs, "desktop", "mode", modeStr);
// Save video settings
prefsSetInt(sPrefs, "video", "width", sAc->display.width);
prefsSetInt(sPrefs, "video", "height", sAc->display.height);
prefsSetInt(sPrefs, "video", "bpp", sAc->display.format.bitsPerPixel);
prefsSave(sPrefs);
prefsClose(sPrefs);
sPrefs = NULL;
dvxDestroyWindow(sAc, sWin);
sWin = NULL;
}
static void onResetColors(WidgetT *w) {
(void)w;
dvxResetColorScheme(sAc);
updateColorSliders();
} }
@ -649,94 +751,10 @@ static void onSaveTheme(WidgetT *w) {
} }
static void onResetColors(WidgetT *w) { static void onSpeedSlider(WidgetT *w) {
(void)w; (void)w;
dvxResetColorScheme(sAc); updateSpeedLabel();
updateColorSliders(); applyMouseConfig();
}
// ============================================================
// Callbacks -- Desktop tab
// ============================================================
static void onApplyWallpaper(WidgetT *w) {
(void)w;
int32_t sel = wgtListBoxGetSelected(sWpaperList);
if (sel < 0 || sel >= arrlen(sWpaperEntries)) {
return;
}
if (dvxSetWallpaper(sAc, sWpaperEntries[sel].path)) {
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sWpaperEntries[sel].path);
wgtSetText(sWallpaperLbl, sWallpaperPath);
} else {
dvxMessageBox(sAc, "Error", "Could not load wallpaper image.", MB_OK | MB_ICONERROR);
}
}
static void onChooseWallpaper(WidgetT *w) {
(void)w;
FileFilterT filters[] = {
{ "\1" },
{ "\1" }
};
char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Choose Wallpaper", FD_OPEN, "CONFIG/WPAPER", filters, 2, path, sizeof(path))) {
if (dvxSetWallpaper(sAc, path)) {
strncpy(sWallpaperPath, path, sizeof(sWallpaperPath) - 1);
sWallpaperPath[sizeof(sWallpaperPath) - 1] = '\0';
wgtSetText(sWallpaperLbl, sWallpaperPath);
} else {
dvxMessageBox(sAc, "Error", "Could not load wallpaper image.", MB_OK | MB_ICONERROR);
}
}
}
static void onClearWallpaper(WidgetT *w) {
(void)w;
dvxSetWallpaper(sAc, NULL);
sWallpaperPath[0] = '\0';
wgtSetText(sWallpaperLbl, "(none)");
}
static void onWallpaperMode(WidgetT *w) {
int32_t sel = wgtDropdownGetSelected(w);
if (sel >= 0 && sel <= 2) {
dvxSetWallpaperMode(sAc, (WallpaperModeE)sel);
}
}
// ============================================================
// Callbacks -- Video tab
// ============================================================
static int32_t sVideoConfirmResult = -1;
static void onVideoConfirmYes(WidgetT *w) {
(void)w;
sVideoConfirmResult = 1;
}
static void onVideoConfirmNo(WidgetT *w) {
(void)w;
sVideoConfirmResult = 0;
}
static void onVideoConfirmClose(WindowT *win) {
(void)win;
sVideoConfirmResult = 0;
} }
@ -772,7 +790,7 @@ static void onVideoApply(WidgetT *w) {
} }
if (dvxChangeVideoMode(sAc, m->w, m->h, m->bpp) != 0) { if (dvxChangeVideoMode(sAc, m->w, m->h, m->bpp) != 0) {
dvxMessageBox(sAc, "Error", "Failed to change video mode.", MB_OK | MB_ICONERROR); dvxErrorBox(sAc, NULL, "Failed to change video mode.");
sInProgress = false; sInProgress = false;
return; return;
} }
@ -843,103 +861,43 @@ static void onVideoApply(WidgetT *w) {
} }
// ============================================================ static void onVideoConfirmClose(WindowT *win) {
// Callbacks -- OK / Cancel / Close (void)win;
// ============================================================ sVideoConfirmResult = 0;
}
static void onOk(WidgetT *w) {
static void onVideoConfirmNo(WidgetT *w) {
(void)w; (void)w;
sVideoConfirmResult = 0;
// Save mouse settings
int32_t wheelSel = wgtDropdownGetSelected(sWheelDrop);
prefsSetString(sPrefs, "mouse", "wheel", wheelSel == 1 ? "reversed" : "normal");
prefsSetInt(sPrefs, "mouse", "doubleclick", wgtSliderGetValue(sDblClickSldr));
prefsSetString(sPrefs, "mouse", "acceleration", mapAccelValue(wgtDropdownGetSelected(sAccelDrop)));
prefsSetInt(sPrefs, "mouse", "speed", wgtSliderGetValue(sSpeedSldr));
prefsSetInt(sPrefs, "mouse", "wheelspeed", wgtSliderGetValue(sWheelStepSldr));
// Save colors to INI
for (int32_t i = 0; i < ColorCountE; i++) {
uint8_t r;
uint8_t g;
uint8_t b;
dvxGetColor(sAc, (ColorIdE)i, &r, &g, &b);
char val[16];
snprintf(val, sizeof(val), "%d,%d,%d", r, g, b);
prefsSetString(sPrefs, "colors", dvxColorName((ColorIdE)i), val);
}
// Save desktop settings
if (sWallpaperPath[0]) {
prefsSetString(sPrefs, "desktop", "wallpaper", sWallpaperPath);
} else {
prefsRemove(sPrefs, "desktop", "wallpaper");
}
const char *modeStr = "stretch";
if (sAc->wallpaperMode == WallpaperTileE) {
modeStr = "tile";
} else if (sAc->wallpaperMode == WallpaperCenterE) {
modeStr = "center";
}
prefsSetString(sPrefs, "desktop", "mode", modeStr);
// Save video settings
prefsSetInt(sPrefs, "video", "width", sAc->display.width);
prefsSetInt(sPrefs, "video", "height", sAc->display.height);
prefsSetInt(sPrefs, "video", "bpp", sAc->display.format.bitsPerPixel);
prefsSave(sPrefs);
prefsClose(sPrefs);
sPrefs = NULL;
dvxDestroyWindow(sAc, sWin);
sWin = NULL;
} }
static void onCancel(WidgetT *w) { static void onVideoConfirmYes(WidgetT *w) {
(void)w; (void)w;
restoreSnapshot(); sVideoConfirmResult = 1;
prefsClose(sPrefs);
sPrefs = NULL;
dvxDestroyWindow(sAc, sWin);
sWin = NULL;
} }
static void onClose(WindowT *win) { static void onWallpaperMode(WidgetT *w) {
restoreSnapshot(); int32_t sel = wgtDropdownGetSelected(w);
prefsClose(sPrefs);
sPrefs = NULL; if (sel >= 0 && sel <= 2) {
dvxDestroyWindow(sAc, win); dvxSetWallpaperMode(sAc, (WallpaperModeE)sel);
sWin = NULL; }
} }
// ============================================================ static void onWheelChange(WidgetT *w) {
// saveSnapshot / restoreSnapshot (void)w;
// ============================================================ applyMouseConfig();
}
static char sSavedWallpaperPath[DVX_MAX_PATH];
static void saveSnapshot(void) { static void onWheelStepSlider(WidgetT *w) {
memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb)); (void)w;
sSavedWheelDir = sAc->wheelDirection; updateWheelStepLabel();
sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC); applyMouseConfig();
sSavedAccel = wgtDropdownGetSelected(sAccelDrop);
sSavedSpeed = wgtSliderGetValue(sSpeedSldr);
sSavedWheelStep = sAc->wheelStep;
sSavedVideoW = sAc->display.width;
sSavedVideoH = sAc->display.height;
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
// Save the active wallpaper path and mode from the context
snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath);
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath);
sSavedWpMode = sAc->wallpaperMode;
} }
@ -975,9 +933,23 @@ static void restoreSnapshot(void) {
} }
// ============================================================ static void saveSnapshot(void) {
// scanThemes memcpy(sSavedColorRgb, sAc->colorRgb, sizeof(sSavedColorRgb));
// ============================================================ sSavedWheelDir = sAc->wheelDirection;
sSavedDblClick = (int32_t)(sAc->dblClickTicks * 1000 / CLOCKS_PER_SEC);
sSavedAccel = wgtDropdownGetSelected(sAccelDrop);
sSavedSpeed = wgtSliderGetValue(sSpeedSldr);
sSavedWheelStep = sAc->wheelStep;
sSavedVideoW = sAc->display.width;
sSavedVideoH = sAc->display.height;
sSavedVideoBpp = sAc->display.format.bitsPerPixel;
// Save the active wallpaper path and mode from the context
snprintf(sSavedWallpaperPath, sizeof(sSavedWallpaperPath), "%s", sAc->wallpaperPath);
snprintf(sWallpaperPath, sizeof(sWallpaperPath), "%s", sAc->wallpaperPath);
sSavedWpMode = sAc->wallpaperMode;
}
static void scanThemes(void) { static void scanThemes(void) {
arrsetlen(sThemeEntries, 0); arrsetlen(sThemeEntries, 0);
@ -1007,7 +979,7 @@ static void scanThemes(void) {
memcpy(entry.name, ent->d_name, nameLen); memcpy(entry.name, ent->d_name, nameLen);
entry.name[nameLen] = '\0'; entry.name[nameLen] = '\0';
snprintf(entry.path, sizeof(entry.path), "%s%c%s", THEME_DIR, DVX_PATH_SEP, entry.name); snprintf(entry.path, sizeof(entry.path), "%s" DVX_PATH_SEP "%s", THEME_DIR, entry.name);
arrput(sThemeEntries, entry); arrput(sThemeEntries, entry);
} }
@ -1020,10 +992,6 @@ static void scanThemes(void) {
} }
// ============================================================
// scanWallpapers
// ============================================================
static void scanWallpapers(void) { static void scanWallpapers(void) {
arrsetlen(sWpaperEntries, 0); arrsetlen(sWpaperEntries, 0);
arrsetlen(sWpaperLabels, 0); arrsetlen(sWpaperLabels, 0);
@ -1062,7 +1030,7 @@ static void scanWallpapers(void) {
memcpy(entry.name, ent->d_name, nl); memcpy(entry.name, ent->d_name, nl);
entry.name[nl] = '\0'; entry.name[nl] = '\0';
snprintf(entry.path, sizeof(entry.path), "%s%c%s", WPAPER_DIR, DVX_PATH_SEP, entry.name); snprintf(entry.path, sizeof(entry.path), "%s" DVX_PATH_SEP "%s", WPAPER_DIR, entry.name);
arrput(sWpaperEntries, entry); arrput(sWpaperEntries, entry);
} }
@ -1075,10 +1043,6 @@ static void scanWallpapers(void) {
} }
// ============================================================
// updateColorSliders
// ============================================================
static void updateColorSliders(void) { static void updateColorSliders(void) {
int32_t sel = wgtListBoxGetSelected(sColorList); int32_t sel = wgtListBoxGetSelected(sColorList);
@ -1108,10 +1072,6 @@ static void updateColorSliders(void) {
} }
// ============================================================
// updateDblClickLabel
// ============================================================
static void updateDblClickLabel(void) { static void updateDblClickLabel(void) {
static char buf[32]; static char buf[32];
snprintf(buf, sizeof(buf), "%ld ms", (long)wgtSliderGetValue(sDblClickSldr)); snprintf(buf, sizeof(buf), "%ld ms", (long)wgtSliderGetValue(sDblClickSldr));
@ -1119,10 +1079,6 @@ static void updateDblClickLabel(void) {
} }
// ============================================================
// updateSpeedLabel
// ============================================================
static void updateSpeedLabel(void) { static void updateSpeedLabel(void) {
static char buf[32]; static char buf[32];
int32_t val = wgtSliderGetValue(sSpeedSldr); int32_t val = wgtSliderGetValue(sSpeedSldr);
@ -1131,45 +1087,6 @@ static void updateSpeedLabel(void) {
} }
// ============================================================
// updateWheelStepLabel
// ============================================================
static void updateWheelStepLabel(void) {
static char buf[32];
int32_t val = wgtSliderGetValue(sWheelStepSldr);
snprintf(buf, sizeof(buf), "%ld", (long)val);
wgtSetText(sWheelStepLbl, buf);
}
// ============================================================
// applyMouseConfig -- gather current widget values and apply
// ============================================================
static void applyMouseConfig(void) {
int32_t dir = (wgtDropdownGetSelected(sWheelDrop) == 1) ? -1 : 1;
int32_t dbl = wgtSliderGetValue(sDblClickSldr);
const char *accelName = mapAccelValue(wgtDropdownGetSelected(sAccelDrop));
int32_t accelVal = mapAccelName(accelName);
// Slider is inverted: low value = slow (high mickeys), high value = fast (low mickeys)
// Slider range 2..32. Mickey ratio = 34 - sliderValue, so:
// slider 2 -> 32 mickeys/8px (slowest)
// slider 8 -> 26 (near default)
// slider 32 -> 2 mickeys/8px (fastest)
int32_t speedVal = 34 - wgtSliderGetValue(sSpeedSldr);
int32_t wheelStep = wgtSliderGetValue(sWheelStepSldr);
dvxSetMouseConfig(sAc, dir, dbl, accelVal, speedVal, wheelStep);
}
// ============================================================
// updateSwatch
// ============================================================
static void updateSwatch(void) { static void updateSwatch(void) {
if (!sColorSwatch) { if (!sColorSwatch) {
return; return;
@ -1184,14 +1101,18 @@ static void updateSwatch(void) {
} }
// ============================================================ static void updateWheelStepLabel(void) {
// appMain static char buf[32];
// ============================================================ int32_t val = wgtSliderGetValue(sWheelStepSldr);
snprintf(buf, sizeof(buf), "%ld", (long)val);
wgtSetText(sWheelStepLbl, buf);
}
int32_t appMain(DxeAppContextT *ctx) { int32_t appMain(DxeAppContextT *ctx) {
sCtx = ctx; sCtx = ctx;
sAc = ctx->shellCtx; sAc = ctx->shellCtx;
sPrefs = prefsLoad("CONFIG/DVX.INI"); sPrefs = prefsLoad(DVX_INI_PATH);
int32_t winX = (sAc->display.width - CP_WIN_W) / 2; int32_t winX = (sAc->display.width - CP_WIN_W) / 2;
int32_t winY = (sAc->display.height - CP_WIN_H) / 2; int32_t winY = (sAc->display.height - CP_WIN_H) / 2;

View file

@ -1,5 +1,7 @@
# cpanel.res -- Resource manifest for Control Panel # cpanel.res -- Resource manifest for Control Panel
icon32 icon icon32.bmp icon32 icon icon32.bmp
name text "Control Panel" name text "Control Panel"
author text "DVX Project" author text "Scott Duensing"
copyright text "Copyright 2026 Scott Duensing"
publisher text "Kangaroo Punch Studios"
description text "System settings and preferences" description text "System settings and preferences"

View file

@ -26,7 +26,7 @@ RT_TARGETDIR = $(LIBSDIR)/kpunch/basrt
RT_TARGET = $(RT_TARGETDIR)/basrt.lib RT_TARGET = $(RT_TARGETDIR)/basrt.lib
# Compiler objects (only needed by the IDE) # Compiler objects (only needed by the IDE)
COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o $(OBJDIR)/strip.o $(OBJDIR)/obfuscate.o $(OBJDIR)/compact.o COMP_OBJS = $(OBJDIR)/lexer.o $(OBJDIR)/parser.o $(OBJDIR)/codegen.o $(OBJDIR)/symtab.o $(OBJDIR)/strip.o $(OBJDIR)/obfuscate.o $(OBJDIR)/compact.o $(OBJDIR)/basBuild.o
# IDE app objects # IDE app objects
IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o IDE_OBJS = $(OBJDIR)/ideMain.o $(OBJDIR)/ideDesigner.o $(OBJDIR)/ideMenuEditor.o $(OBJDIR)/ideProject.o $(OBJDIR)/ideToolbox.o $(OBJDIR)/ideProperties.o
@ -48,15 +48,15 @@ TEST_QUICK = $(HOSTDIR)/test_quick
TEST_COMPACT = $(HOSTDIR)/test_compact TEST_COMPACT = $(HOSTDIR)/test_compact
STB_DS_IMPL = ../../../libs/kpunch/libdvx/thirdparty/stb_ds_impl.c STB_DS_IMPL = ../../../libs/kpunch/libdvx/thirdparty/stb_ds_impl.c
PLATFORM_CHDIR = ../../../libs/kpunch/libdvx/platform/dvxPlatformChdir.c PLATFORM_UTIL = ../../../libs/kpunch/libdvx/platform/dvxPlatformUtil.c
TEST_COMPILER_SRCS = test_compiler.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_CHDIR) $(STB_DS_IMPL) TEST_COMPILER_SRCS = test_compiler.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
TEST_VM_SRCS = test_vm.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_CHDIR) $(STB_DS_IMPL) TEST_VM_SRCS = test_vm.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
TEST_LEX_SRCS = test_lex.c compiler/lexer.c TEST_LEX_SRCS = test_lex.c compiler/lexer.c
TEST_QUICK_SRCS = test_quick.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_CHDIR) $(STB_DS_IMPL) TEST_QUICK_SRCS = test_quick.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
TEST_COMPACT_SRCS = test_compact.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_CHDIR) $(STB_DS_IMPL) TEST_COMPACT_SRCS = test_compact.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
# Command-line compiler (host tool) # Command-line compiler (host tool)
BASCOMP_SRCS = stub/bascomp.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/obfuscate.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c ../../../libs/kpunch/libdvx/dvxPrefs.c ../../../libs/kpunch/libdvx/dvxResource.c $(PLATFORM_CHDIR) $(STB_DS_IMPL) BASCOMP_SRCS = stub/bascomp.c basBuild.c compiler/lexer.c compiler/parser.c compiler/codegen.c compiler/symtab.c compiler/strip.c compiler/obfuscate.c compiler/compact.c runtime/vm.c runtime/values.c runtime/serialize.c ../../../libs/kpunch/libdvx/dvxPrefs.c ../../../libs/kpunch/libdvx/dvxResource.c $(PLATFORM_UTIL) $(STB_DS_IMPL)
BASCOMP_TARGET = $(HOSTDIR)/bascomp BASCOMP_TARGET = $(HOSTDIR)/bascomp
# DOS command-line compiler # DOS command-line compiler
@ -68,7 +68,7 @@ SYSTEMDIR = ../../../../bin/system
.PHONY: all clean tests .PHONY: all clean tests
all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(STUB_TARGET) $(APP_TARGET) $(BASCOMP_TARGET) $(SYSTEMDIR)/BASCOMP.EXE $(SYSTEMDIR)/BASSTUB.APP all: $(RT_TARGET) $(RT_TARGETDIR)/basrt.dep $(STUB_TARGET) $(APP_TARGET) $(BASCOMP_TARGET) $(SYSTEMDIR)/BASCOMP.EXE
tests: $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK) $(TEST_COMPACT) tests: $(TEST_COMPILER) $(TEST_VM) $(TEST_LEX) $(TEST_QUICK) $(TEST_COMPACT)
@ -87,21 +87,19 @@ $(TEST_QUICK): $(TEST_QUICK_SRCS) | $(HOSTDIR)
$(TEST_COMPACT): $(TEST_COMPACT_SRCS) | $(HOSTDIR) $(TEST_COMPACT): $(TEST_COMPACT_SRCS) | $(HOSTDIR)
$(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_COMPACT_SRCS) -lm $(HOSTCC) $(HOSTCFLAGS) -o $@ $(TEST_COMPACT_SRCS) -lm
# Host command-line compiler (stub deployed alongside) # Host command-line compiler -- basstub.app is appended as a STUB
# resource so bascomp is self-contained (no BASSTUB.APP companion file).
$(BASCOMP_TARGET): $(BASCOMP_SRCS) $(STUB_TARGET) | $(HOSTDIR) $(BASCOMP_TARGET): $(BASCOMP_SRCS) $(STUB_TARGET) | $(HOSTDIR)
$(HOSTCC) $(HOSTCFLAGS) -DBASCOMP_STANDALONE -I../../../tools -o $@ $(BASCOMP_SRCS) -lm $(HOSTCC) $(HOSTCFLAGS) -DBASCOMP_STANDALONE -I../../../tools -o $@ $(BASCOMP_SRCS) -lm
cp $(STUB_TARGET) $(HOSTDIR)/BASSTUB.APP $(DVXRES) add $@ STUB binary @$(STUB_TARGET)
# DOS command-line compiler # DOS command-line compiler (same STUB embed as the host build)
$(SYSTEMDIR)/BASCOMP.EXE: $(BASCOMP_SRCS) | $(SYSTEMDIR) $(SYSTEMDIR)/BASCOMP.EXE: $(BASCOMP_SRCS) $(STUB_TARGET) | $(SYSTEMDIR)
$(DOSCC) $(DOSCFLAGS) -DBASCOMP_STANDALONE -I../../../tools -o $(SYSTEMDIR)/bascomp.exe $(BASCOMP_SRCS) -lm $(DOSCC) $(DOSCFLAGS) -DBASCOMP_STANDALONE -I../../../tools -o $(SYSTEMDIR)/bascomp.exe $(BASCOMP_SRCS) -lm
$(EXE2COFF) $(SYSTEMDIR)/bascomp.exe $(EXE2COFF) $(SYSTEMDIR)/bascomp.exe
cat $(CWSDSTUB) $(SYSTEMDIR)/bascomp > $@ cat $(CWSDSTUB) $(SYSTEMDIR)/bascomp > $@
rm -f $(SYSTEMDIR)/bascomp $(SYSTEMDIR)/bascomp.exe rm -f $(SYSTEMDIR)/bascomp $(SYSTEMDIR)/bascomp.exe
$(DVXRES) add $@ STUB binary @$(STUB_TARGET)
# Deploy stub alongside DOS compiler
$(SYSTEMDIR)/BASSTUB.APP: $(STUB_TARGET) | $(SYSTEMDIR)
cp $(STUB_TARGET) $@
$(HOSTDIR): $(HOSTDIR):
mkdir -p $(HOSTDIR) mkdir -p $(HOSTDIR)
@ -147,6 +145,9 @@ $(OBJDIR)/obfuscate.o: compiler/obfuscate.c compiler/obfuscate.h runtime/vm.h ru
$(OBJDIR)/compact.o: compiler/compact.c compiler/compact.h compiler/opcodes.h runtime/vm.h | $(OBJDIR) $(OBJDIR)/compact.o: compiler/compact.c compiler/compact.h compiler/opcodes.h runtime/vm.h | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/basBuild.o: basBuild.c basBuild.h basRes.h | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR)/basstub.o: stub/basstub.c runtime/vm.h runtime/serialize.h formrt/formrt.h | $(OBJDIR) $(OBJDIR)/basstub.o: stub/basstub.c runtime/vm.h runtime/serialize.h formrt/formrt.h | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<

View file

@ -0,0 +1,116 @@
// basBuild.c -- shared "emit .app resource set" step
//
// See basBuild.h for the public contract. This file used to live inline
// in two places: ideMain.c (the IDE's Make Executable path) and
// bascomp.c (the standalone command-line compiler). Both implementations
// were essentially identical, so they are now consolidated here.
//
// The canonical emit order is:
// 1. BAS_RES_NAME (always, falls back to "BASIC App")
// 2. BAS_RES_AUTHOR / PUBLISHER / VERSION / COPYRIGHT / DESCRIPTION
// (each skipped if empty)
// 3. BAS_RES_ICON32 (file via iconPath, else iconData bytes)
// 4. BAS_RES_HELPFILE (filename only, if helpFile set)
// 5. BAS_RES_MODULE (bytecode)
// 6. BAS_RES_DEBUG (optional)
// 7. FORM0, FORM1, ... (one per spec->formCount)
#include "basBuild.h"
#include "basRes.h"
#include "../../../libs/kpunch/libdvx/dvxRes.h"
#include "../../../libs/kpunch/libdvx/platform/dvxPlat.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ------------------------------------------------------------
// Internal helpers
// ------------------------------------------------------------
static void appendText(const char *path, const char *name, const char *value) {
if (!value || !value[0]) {
return;
}
dvxResAppend(path, name, DVX_RES_TEXT, value, (uint32_t)strlen(value) + 1);
}
static void emitIcon(const char *path, const BasBuildSpecT *spec) {
// If a disk path was given, load it and embed. Otherwise use the
// pre-loaded bytes (used by the IDE for its "noicon" fallback).
if (spec->iconPath && spec->iconPath[0]) {
int32_t iconLen = 0;
char *iconData = platformReadFile(spec->iconPath, &iconLen);
if (iconData) {
dvxResAppend(path, BAS_RES_ICON32, DVX_RES_ICON, iconData, (uint32_t)iconLen);
free(iconData);
}
return;
}
if (spec->iconData && spec->iconSize > 0) {
dvxResAppend(path, BAS_RES_ICON32, DVX_RES_ICON, spec->iconData, (uint32_t)spec->iconSize);
}
}
// ------------------------------------------------------------
// Public API
// ------------------------------------------------------------
int32_t basBuildEmitResources(const char *outPath, const BasBuildSpecT *spec) {
if (!outPath || !spec) {
return -1;
}
// Project metadata. Name is required -- fall back to a generic label
// so the compiled app always has something sensible to display.
const char *projName = (spec->projName && spec->projName[0]) ? spec->projName : "BASIC App";
appendText(outPath, BAS_RES_NAME, projName);
appendText(outPath, BAS_RES_AUTHOR, spec->author);
appendText(outPath, BAS_RES_PUBLISHER, spec->publisher);
appendText(outPath, BAS_RES_VERSION, spec->version);
appendText(outPath, BAS_RES_COPYRIGHT, spec->copyright);
appendText(outPath, BAS_RES_DESCRIPTION, spec->description);
// Icon.
emitIcon(outPath, spec);
// Help file name (just the basename -- stub resolves it next to the app).
if (spec->helpFile && spec->helpFile[0]) {
const char *helpBase = platformPathBaseName(spec->helpFile);
appendText(outPath, BAS_RES_HELPFILE, helpBase);
}
// Bytecode module.
if (spec->moduleData && spec->moduleLen > 0) {
dvxResAppend(outPath, BAS_RES_MODULE, DVX_RES_BINARY, spec->moduleData, (uint32_t)spec->moduleLen);
}
// Optional debug info.
if (spec->debugData && spec->debugLen > 0) {
dvxResAppend(outPath, BAS_RES_DEBUG, DVX_RES_BINARY, spec->debugData, (uint32_t)spec->debugLen);
}
// Form resources. Callers pre-strip / pre-obfuscate as needed; we just
// write them out as FORM0, FORM1, ...
for (int32_t i = 0; i < spec->formCount; i++) {
if (!spec->formData || !spec->formData[i] || !spec->formLens || spec->formLens[i] <= 0) {
continue;
}
char resName[16];
snprintf(resName, sizeof(resName), "FORM%ld", (long)i);
dvxResAppend(outPath, resName, DVX_RES_BINARY, spec->formData[i], (uint32_t)spec->formLens[i]);
}
return 0;
}

View file

@ -0,0 +1,65 @@
// basBuild.h -- shared "emit .app resource set" step
//
// Both the DVX BASIC IDE (ideMain.c) and the standalone command-line
// compiler (bascomp.c) need to attach the same group of resources to
// an output .app file after writing the stub DXE. This header exposes
// a single function that takes the metadata, bytecode, debug info and
// form texts and appends all resources in the canonical order.
//
// Callers are still responsible for:
// - writing the stub to outPath before calling us
// - copying the .hlp file next to outPath (if any)
// - freeing any buffers they passed in via BasBuildSpecT
//
// Resource names use BAS_RES_* from basRes.h so both callers stay in
// sync automatically.
#ifndef BAS_BUILD_H
#define BAS_BUILD_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
// Basic metadata (any NULL/empty is skipped).
const char *projName;
const char *author;
const char *publisher;
const char *version;
const char *copyright;
const char *description;
const char *helpFile; // just the filename, not a path
// Icon: either a file to load and embed, OR pre-loaded bytes.
// If iconPath is set (non-NULL and non-empty) it is read from disk.
// Otherwise iconData / iconSize are used (may themselves be NULL/0).
const char *iconPath;
const void *iconData;
int32_t iconSize;
// Bytecode module and optional debug info.
const void *moduleData;
int32_t moduleLen;
const void *debugData; // may be NULL
int32_t debugLen;
// Forms: parallel arrays of formCount entries, each a text blob.
// formData[i] points at formLens[i] bytes of (already stripped /
// possibly obfuscated) form source to embed as resource FORMi.
int32_t formCount;
const uint8_t *const *formData;
const int32_t *formLens;
} BasBuildSpecT;
// Append the full resource set to outPath. Returns 0 on success, non-zero
// on failure. The stub must already have been written to outPath.
int32_t basBuildEmitResources(const char *outPath, const BasBuildSpecT *spec);
#ifdef __cplusplus
}
#endif
#endif // BAS_BUILD_H

View file

@ -0,0 +1,56 @@
// basRes.h -- single source of truth for BASIC project / binary layout
//
// Keys used in .dbp project files and names of resources written into the
// compiled .app DXE. These were previously scattered as raw string literals
// across the IDE (ideMain.c, ideProject.c) and the standalone compiler
// (bascomp.c), making it easy for one side to drift from the other.
// All readers and writers now include this header.
#ifndef BAS_RES_H
#define BAS_RES_H
// ------------------------------------------------------------
// [Project] section of a .dbp file
// ------------------------------------------------------------
#define BAS_INI_SECTION_PROJECT "Project"
#define BAS_INI_KEY_NAME "Name"
#define BAS_INI_KEY_AUTHOR "Author"
#define BAS_INI_KEY_PUBLISHER "Publisher"
#define BAS_INI_KEY_VERSION "Version"
#define BAS_INI_KEY_COPYRIGHT "Copyright"
#define BAS_INI_KEY_DESCRIPTION "Description"
#define BAS_INI_KEY_ICON "Icon"
#define BAS_INI_KEY_HELPFILE "HelpFile"
// [Settings] / [Modules] / [Forms] sections of a .dbp file
#define BAS_INI_SECTION_SETTINGS "Settings"
#define BAS_INI_KEY_STARTUPFORM "StartupForm"
#define BAS_INI_SECTION_MODULES "Modules"
#define BAS_INI_SECTION_FORMS "Forms"
// ------------------------------------------------------------
// Resource names inside a compiled .app DXE
// ------------------------------------------------------------
// Text metadata -- mirrors the [Project] keys above but lowercase
// (resource names are case-sensitive; apps query these at runtime).
#define BAS_RES_NAME "name"
#define BAS_RES_AUTHOR "author"
#define BAS_RES_PUBLISHER "publisher"
#define BAS_RES_VERSION "version"
#define BAS_RES_COPYRIGHT "copyright"
#define BAS_RES_DESCRIPTION "description"
#define BAS_RES_HELPFILE "helpfile"
// Icon -- 32x32 application icon
#define BAS_RES_ICON32 "icon32"
// Binary payload -- bytecode + debug info
#define BAS_RES_MODULE "MODULE"
#define BAS_RES_DEBUG "DEBUG"
// Stub DXE embedded in the IDE app for release builds
#define BAS_RES_STUB "STUB"
#endif // BAS_RES_H

View file

@ -0,0 +1,17 @@
// basEvents.h -- canonical list of BASIC event suffixes
//
// A procedure whose name ends with one of these suffixes is treated as an
// event handler (Ctrl_Load, Form_Click, etc). Referenced by the stripper
// (to retain handlers in release builds), the obfuscator (to preserve event
// naming in rewritten forms), and the IDE (to populate the Object/Event
// dropdowns). Keep them in one place so adding a new event only touches
// one file.
#ifndef BAS_EVENTS_H
#define BAS_EVENTS_H
// NULL-terminated list of event suffixes. Case-insensitive match.
// Declared extern here, defined once in strip.c.
extern const char *basEventSuffixes[];
#endif // BAS_EVENTS_H

View file

@ -10,21 +10,23 @@
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
// ============================================================
// basAddData
// ============================================================
bool basAddData(BasCodeGenT *cg, BasValueT val) { // Function prototypes (alphabetical)
BasValueT copy = basValCopy(val); uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len);
arrput(cg->dataPool, copy); bool basAddData(BasCodeGenT *cg, BasValueT val);
cg->dataCount = (int32_t)arrlen(cg->dataPool); void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName);
return true; BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg);
} BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab);
void basCodeGenFree(BasCodeGenT *cg);
void basCodeGenInit(BasCodeGenT *cg);
// ============================================================ int32_t basCodePos(const BasCodeGenT *cg);
// basAddConstant void basEmit16(BasCodeGenT *cg, int16_t v);
// ============================================================ void basEmit8(BasCodeGenT *cg, uint8_t b);
void basEmitDouble(BasCodeGenT *cg, double v);
void basEmitFloat(BasCodeGenT *cg, float v);
void basEmitU16(BasCodeGenT *cg, uint16_t v);
const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name);
void basPatch16(BasCodeGenT *cg, int32_t pos, int16_t val);
uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) { uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) {
// Check if this string is already in the pool // Check if this string is already in the pool
@ -42,9 +44,13 @@ uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) {
} }
// ============================================================ bool basAddData(BasCodeGenT *cg, BasValueT val) {
// basCodeGenAddDebugVar BasValueT copy = basValCopy(val);
// ============================================================ arrput(cg->dataPool, copy);
cg->dataCount = (int32_t)arrlen(cg->dataPool);
return true;
}
void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName) { void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName) {
BasDebugVarT dv; BasDebugVarT dv;
@ -63,10 +69,6 @@ void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uin
} }
// ============================================================
// basCodeGenBuildModule
// ============================================================
BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) { BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
BasModuleT *mod = (BasModuleT *)calloc(1, sizeof(BasModuleT)); BasModuleT *mod = (BasModuleT *)calloc(1, sizeof(BasModuleT));
@ -172,10 +174,6 @@ BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
} }
// ============================================================
// basCodeGenBuildModuleWithProcs
// ============================================================
BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab) { BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab) {
BasModuleT *mod = basCodeGenBuildModule(cg); BasModuleT *mod = basCodeGenBuildModule(cg);
@ -189,7 +187,7 @@ BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab) {
int32_t procCount = 0; int32_t procCount = 0;
for (int32_t i = 0; i < tab->count; i++) { for (int32_t i = 0; i < tab->count; i++) {
BasSymbolT *s = &tab->symbols[i]; BasSymbolT *s = tab->symbols[i];
if ((s->kind == SYM_SUB || s->kind == SYM_FUNCTION) && s->isDefined && !s->isExtern) { if ((s->kind == SYM_SUB || s->kind == SYM_FUNCTION) && s->isDefined && !s->isExtern) {
procCount++; procCount++;
@ -209,7 +207,7 @@ BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab) {
int32_t idx = 0; int32_t idx = 0;
for (int32_t i = 0; i < tab->count; i++) { for (int32_t i = 0; i < tab->count; i++) {
BasSymbolT *s = &tab->symbols[i]; BasSymbolT *s = tab->symbols[i];
if ((s->kind == SYM_SUB || s->kind == SYM_FUNCTION) && s->isDefined && !s->isExtern) { if ((s->kind == SYM_SUB || s->kind == SYM_FUNCTION) && s->isDefined && !s->isExtern) {
BasProcEntryT *p = &mod->procs[idx++]; BasProcEntryT *p = &mod->procs[idx++];
@ -228,10 +226,6 @@ BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab) {
} }
// ============================================================
// basCodeGenFree
// ============================================================
void basCodeGenFree(BasCodeGenT *cg) { void basCodeGenFree(BasCodeGenT *cg) {
for (int32_t i = 0; i < cg->constCount; i++) { for (int32_t i = 0; i < cg->constCount; i++) {
basStringUnref(cg->constants[i]); basStringUnref(cg->constants[i]);
@ -267,38 +261,16 @@ void basCodeGenFree(BasCodeGenT *cg) {
} }
// ============================================================
// basCodeGenInit
// ============================================================
void basCodeGenInit(BasCodeGenT *cg) { void basCodeGenInit(BasCodeGenT *cg) {
memset(cg, 0, sizeof(*cg)); memset(cg, 0, sizeof(*cg));
} }
// ============================================================
// basCodePos
// ============================================================
int32_t basCodePos(const BasCodeGenT *cg) { int32_t basCodePos(const BasCodeGenT *cg) {
return cg->codeLen; return cg->codeLen;
} }
// ============================================================
// basEmit8
// ============================================================
void basEmit8(BasCodeGenT *cg, uint8_t b) {
arrput(cg->code, b);
cg->codeLen = (int32_t)arrlen(cg->code);
}
// ============================================================
// basEmit16
// ============================================================
void basEmit16(BasCodeGenT *cg, int16_t v) { void basEmit16(BasCodeGenT *cg, int16_t v) {
uint8_t buf[2]; uint8_t buf[2];
memcpy(buf, &v, 2); memcpy(buf, &v, 2);
@ -308,9 +280,11 @@ void basEmit16(BasCodeGenT *cg, int16_t v) {
} }
// ============================================================ void basEmit8(BasCodeGenT *cg, uint8_t b) {
// basEmitDouble arrput(cg->code, b);
// ============================================================ cg->codeLen = (int32_t)arrlen(cg->code);
}
void basEmitDouble(BasCodeGenT *cg, double v) { void basEmitDouble(BasCodeGenT *cg, double v) {
uint8_t buf[sizeof(double)]; uint8_t buf[sizeof(double)];
@ -324,10 +298,6 @@ void basEmitDouble(BasCodeGenT *cg, double v) {
} }
// ============================================================
// basEmitFloat
// ============================================================
void basEmitFloat(BasCodeGenT *cg, float v) { void basEmitFloat(BasCodeGenT *cg, float v) {
uint8_t buf[sizeof(float)]; uint8_t buf[sizeof(float)];
memcpy(buf, &v, sizeof(float)); memcpy(buf, &v, sizeof(float));
@ -340,10 +310,6 @@ void basEmitFloat(BasCodeGenT *cg, float v) {
} }
// ============================================================
// basEmitU16
// ============================================================
void basEmitU16(BasCodeGenT *cg, uint16_t v) { void basEmitU16(BasCodeGenT *cg, uint16_t v) {
uint8_t buf[2]; uint8_t buf[2];
memcpy(buf, &v, 2); memcpy(buf, &v, 2);
@ -353,10 +319,6 @@ void basEmitU16(BasCodeGenT *cg, uint16_t v) {
} }
// ============================================================
// basModuleFindProc
// ============================================================
const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name) { const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name) {
if (!mod || !mod->procs || !name) { if (!mod || !mod->procs || !name) {
return NULL; return NULL;
@ -372,12 +334,6 @@ const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name)
} }
// ============================================================
// basPatch16
// ============================================================
void basPatch16(BasCodeGenT *cg, int32_t pos, int16_t val) { void basPatch16(BasCodeGenT *cg, int32_t pos, int16_t val) {
if (pos >= 0 && pos + 2 <= cg->codeLen) { if (pos >= 0 && pos + 2 <= cg->codeLen) {
memcpy(&cg->code[pos], &val, 2); memcpy(&cg->code[pos], &val, 2);

View file

@ -25,330 +25,19 @@
#include <string.h> #include <string.h>
// ============================================================ // Function prototypes (alphabetical)
// Opcode operand size table int32_t basCompactBytecode(BasModuleT *mod);
// ============================================================ static int32_t *buildRemap(const uint8_t *code, int32_t codeLen, int32_t *outNewLen);
// Returns operand byte count (excluding the 1-byte opcode), or -1 if unknown. static bool isGosubPush(const uint8_t *code, int32_t codeLen, int32_t pos);
static int32_t opOperandSize(uint8_t op);
static int32_t opOperandSize(uint8_t op) { static int16_t readI16LE(const uint8_t *p);
switch (op) { static int32_t readI32LE(const uint8_t *p);
// No operand bytes static uint16_t readU16LE(const uint8_t *p);
case OP_NOP: static bool remapAbsU16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffset, uint16_t oldAddr, const int32_t *remap, int32_t newCodeLen);
case OP_PUSH_TRUE: case OP_PUSH_FALSE: static bool remapRelI16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffset, int16_t oldOffset, int32_t oldPcAfter, int32_t newPcAfter, const int32_t *remap, int32_t codeLen, int32_t newCodeLen, bool allowZero);
case OP_POP: case OP_DUP: static void writeI16LE(uint8_t *p, int16_t v);
case OP_LOAD_REF: case OP_STORE_REF: static void writeI32LE(uint8_t *p, int32_t v);
case OP_ADD_INT: case OP_SUB_INT: case OP_MUL_INT: static void writeU16LE(uint8_t *p, uint16_t v);
case OP_IDIV_INT: case OP_MOD_INT: case OP_NEG_INT:
case OP_ADD_FLT: case OP_SUB_FLT: case OP_MUL_FLT:
case OP_DIV_FLT: case OP_NEG_FLT: case OP_POW:
case OP_STR_CONCAT: case OP_STR_LEFT: case OP_STR_RIGHT:
case OP_STR_MID: case OP_STR_MID2: case OP_STR_LEN:
case OP_STR_INSTR: case OP_STR_INSTR3:
case OP_STR_UCASE: case OP_STR_LCASE:
case OP_STR_TRIM: case OP_STR_LTRIM: case OP_STR_RTRIM:
case OP_STR_CHR: case OP_STR_ASC: case OP_STR_SPACE:
case OP_CMP_EQ: case OP_CMP_NE: case OP_CMP_LT:
case OP_CMP_GT: case OP_CMP_LE: case OP_CMP_GE:
case OP_AND: case OP_OR: case OP_NOT:
case OP_XOR: case OP_EQV: case OP_IMP:
case OP_GOSUB_RET: case OP_RET: case OP_RET_VAL:
case OP_FOR_POP:
case OP_CONV_INT_FLT: case OP_CONV_FLT_INT:
case OP_CONV_INT_STR: case OP_CONV_STR_INT:
case OP_CONV_FLT_STR: case OP_CONV_STR_FLT:
case OP_CONV_INT_LONG: case OP_CONV_LONG_INT:
case OP_PRINT: case OP_PRINT_NL: case OP_PRINT_TAB:
case OP_INPUT:
case OP_FILE_CLOSE: case OP_FILE_PRINT: case OP_FILE_INPUT:
case OP_FILE_EOF: case OP_FILE_LINE_INPUT:
case OP_LOAD_PROP: case OP_STORE_PROP:
case OP_LOAD_FORM: case OP_UNLOAD_FORM:
case OP_HIDE_FORM: case OP_DO_EVENTS:
case OP_MSGBOX: case OP_INPUTBOX: case OP_ME_REF:
case OP_CREATE_CTRL: case OP_FIND_CTRL: case OP_FIND_CTRL_IDX:
case OP_CREATE_CTRL_EX:
case OP_ERASE:
case OP_RESUME: case OP_RESUME_NEXT:
case OP_RAISE_ERR: case OP_ERR_NUM: case OP_ERR_CLEAR:
case OP_MATH_ABS: case OP_MATH_INT: case OP_MATH_FIX:
case OP_MATH_SGN: case OP_MATH_SQR: case OP_MATH_SIN:
case OP_MATH_COS: case OP_MATH_TAN: case OP_MATH_ATN:
case OP_MATH_LOG: case OP_MATH_EXP: case OP_MATH_RND:
case OP_MATH_RANDOMIZE:
case OP_RGB:
case OP_GET_RED: case OP_GET_GREEN: case OP_GET_BLUE:
case OP_STR_VAL: case OP_STR_STRF: case OP_STR_HEX:
case OP_STR_STRING:
case OP_MATH_TIMER: case OP_DATE_STR: case OP_TIME_STR:
case OP_SLEEP: case OP_ENVIRON:
case OP_READ_DATA: case OP_RESTORE:
case OP_FILE_WRITE: case OP_FILE_WRITE_SEP: case OP_FILE_WRITE_NL:
case OP_FILE_GET: case OP_FILE_PUT: case OP_FILE_SEEK:
case OP_FILE_LOF: case OP_FILE_LOC: case OP_FILE_FREEFILE:
case OP_FILE_INPUT_N:
case OP_STR_MID_ASGN: case OP_PRINT_USING:
case OP_PRINT_TAB_N: case OP_PRINT_SPC_N:
case OP_FORMAT: case OP_SHELL:
case OP_APP_PATH: case OP_APP_CONFIG: case OP_APP_DATA:
case OP_INI_READ: case OP_INI_WRITE:
case OP_FS_KILL: case OP_FS_NAME: case OP_FS_FILECOPY:
case OP_FS_MKDIR: case OP_FS_RMDIR: case OP_FS_CHDIR:
case OP_FS_CHDRIVE: case OP_FS_CURDIR: case OP_FS_DIR:
case OP_FS_DIR_NEXT: case OP_FS_FILELEN:
case OP_FS_GETATTR: case OP_FS_SETATTR:
case OP_CREATE_FORM: case OP_SET_EVENT: case OP_REMOVE_CTRL:
case OP_END: case OP_HALT:
return 0;
case OP_LOAD_ARRAY: case OP_STORE_ARRAY:
case OP_PRINT_SPC: case OP_FILE_OPEN:
case OP_CALL_METHOD: case OP_SHOW_FORM:
case OP_LBOUND: case OP_UBOUND:
case OP_COMPARE_MODE:
return 1;
case OP_PUSH_INT16: case OP_PUSH_STR:
case OP_LOAD_LOCAL: case OP_STORE_LOCAL:
case OP_LOAD_GLOBAL: case OP_STORE_GLOBAL:
case OP_LOAD_FIELD: case OP_STORE_FIELD:
case OP_PUSH_LOCAL_ADDR: case OP_PUSH_GLOBAL_ADDR:
case OP_JMP: case OP_JMP_TRUE: case OP_JMP_FALSE:
case OP_CTRL_REF:
case OP_LOAD_FORM_VAR: case OP_STORE_FORM_VAR:
case OP_PUSH_FORM_ADDR:
case OP_DIM_ARRAY: case OP_REDIM:
case OP_ON_ERROR:
case OP_STR_FIXLEN:
case OP_LINE:
return 2;
case OP_STORE_ARRAY_FIELD:
case OP_FOR_INIT:
return 3;
case OP_PUSH_INT32: case OP_PUSH_FLT32:
case OP_CALL:
return 4;
case OP_FOR_NEXT:
return 5;
case OP_CALL_EXTERN:
return 6;
case OP_PUSH_FLT64:
return 8;
default:
return -1;
}
}
// ============================================================
// Little-endian helpers (bytecode is always LE regardless of host)
// ============================================================
static int16_t readI16LE(const uint8_t *p) {
return (int16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static uint16_t readU16LE(const uint8_t *p) {
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
static int32_t readI32LE(const uint8_t *p) {
return (int32_t)((uint32_t)p[0] |
((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24));
}
static void writeI16LE(uint8_t *p, int16_t v) {
uint16_t u = (uint16_t)v;
p[0] = (uint8_t)(u & 0xFF);
p[1] = (uint8_t)((u >> 8) & 0xFF);
}
static void writeU16LE(uint8_t *p, uint16_t v) {
p[0] = (uint8_t)(v & 0xFF);
p[1] = (uint8_t)((v >> 8) & 0xFF);
}
static void writeI32LE(uint8_t *p, int32_t v) {
uint32_t u = (uint32_t)v;
p[0] = (uint8_t)(u & 0xFF);
p[1] = (uint8_t)((u >> 8) & 0xFF);
p[2] = (uint8_t)((u >> 16) & 0xFF);
p[3] = (uint8_t)((u >> 24) & 0xFF);
}
// ============================================================
// Walk bytecode, build remap
// ============================================================
//
// remap[oldPos] = newPos for every byte position in [0, oldCodeLen].
// For OP_LINE bytes (removed): remap points at where the NEXT instruction
// starts in the new code.
// Final entry remap[oldCodeLen] = newCodeLen.
//
// Returns malloc'd array of size (oldCodeLen + 1), or NULL on failure.
static int32_t *buildRemap(const uint8_t *code, int32_t codeLen, int32_t *outNewLen) {
int32_t *remap = (int32_t *)malloc((codeLen + 1) * sizeof(int32_t));
if (!remap) {
return NULL;
}
int32_t oldPc = 0;
int32_t newPc = 0;
while (oldPc < codeLen) {
uint8_t op = code[oldPc];
int32_t operand = opOperandSize(op);
if (operand < 0) {
free(remap);
return NULL;
}
int32_t instSize = 1 + operand;
if (oldPc + instSize > codeLen) {
free(remap);
return NULL;
}
if (op == OP_LINE) {
// These bytes are removed; they map to where the next instruction starts.
for (int32_t i = 0; i < instSize; i++) {
remap[oldPc + i] = newPc;
}
} else {
for (int32_t i = 0; i < instSize; i++) {
remap[oldPc + i] = newPc + i;
}
newPc += instSize;
}
oldPc += instSize;
}
if (oldPc != codeLen) {
free(remap);
return NULL;
}
remap[codeLen] = newPc;
*outNewLen = newPc;
return remap;
}
// ============================================================
// GOSUB pattern detection
// ============================================================
//
// GOSUB emits:
// oldPc: OP_PUSH_INT32 (1 byte)
// oldPc+1: int32 value V (4 bytes)
// oldPc+5: OP_JMP (1 byte)
// oldPc+6: int16 offset (2 bytes)
// oldPc+8: <next instruction>
// The invariant is V == oldPc + 8 (the pushed return address).
//
// Returns true if the given position is the start of such a pattern.
static bool isGosubPush(const uint8_t *code, int32_t codeLen, int32_t pos) {
if (pos + 8 > codeLen) {
return false;
}
if (code[pos] != OP_PUSH_INT32) {
return false;
}
if (code[pos + 5] != OP_JMP) {
return false;
}
int32_t value = readI32LE(code + pos + 1);
return value == pos + 8;
}
// ============================================================
// Apply remap to a single instruction's operand
// ============================================================
//
// Returns true on success, false if an offset overflows int16 or a
// target doesn't land on a valid instruction in the old code.
static bool remapAbsU16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffset, uint16_t oldAddr, const int32_t *remap, int32_t newCodeLen) {
int32_t newAddr = remap[oldAddr];
if (newAddr < 0 || newAddr > newCodeLen) {
return false;
}
if (newAddr > 0xFFFF) {
return false;
}
writeU16LE(newCode + newOpPos + operandOffset, (uint16_t)newAddr);
return true;
}
// Rewrite a relative int16 offset.
// oldOpPos, oldPcAfter: position of opcode and PC after reading offset
// newOpPos, newPcAfter: same in the new code
// operandOffset: byte offset from opcode to the int16 operand
// oldOffset: the offset as stored in the old code
//
// Handles ON_ERROR's special case (offset == 0 means "disable").
// For ON_ERROR the caller passes allowZero=true; the zero is preserved as-is.
static bool remapRelI16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffset, int16_t oldOffset, int32_t oldPcAfter, int32_t newPcAfter, const int32_t *remap, int32_t codeLen, int32_t newCodeLen, bool allowZero) {
if (allowZero && oldOffset == 0) {
writeI16LE(newCode + newOpPos + operandOffset, 0);
return true;
}
int32_t oldTarget = oldPcAfter + oldOffset;
if (oldTarget < 0 || oldTarget > codeLen) {
return false;
}
int32_t newTarget = remap[oldTarget];
if (newTarget < 0 || newTarget > newCodeLen) {
return false;
}
int32_t newOffset = newTarget - newPcAfter;
if (newOffset < -32768 || newOffset > 32767) {
return false;
}
writeI16LE(newCode + newOpPos + operandOffset, (int16_t)newOffset);
return true;
}
// ============================================================
// basCompactBytecode
// ============================================================
int32_t basCompactBytecode(BasModuleT *mod) { int32_t basCompactBytecode(BasModuleT *mod) {
if (!mod || !mod->code || mod->codeLen <= 0) { if (!mod || !mod->code || mod->codeLen <= 0) {
@ -562,3 +251,318 @@ int32_t basCompactBytecode(BasModuleT *mod) {
free(remap); free(remap);
return removed; return removed;
} }
// ============================================================
// Walk bytecode, build remap
// ============================================================
//
// remap[oldPos] = newPos for every byte position in [0, oldCodeLen].
// For OP_LINE bytes (removed): remap points at where the NEXT instruction
// starts in the new code.
// Final entry remap[oldCodeLen] = newCodeLen.
//
// Returns malloc'd array of size (oldCodeLen + 1), or NULL on failure.
static int32_t *buildRemap(const uint8_t *code, int32_t codeLen, int32_t *outNewLen) {
int32_t *remap = (int32_t *)malloc((codeLen + 1) * sizeof(int32_t));
if (!remap) {
return NULL;
}
int32_t oldPc = 0;
int32_t newPc = 0;
while (oldPc < codeLen) {
uint8_t op = code[oldPc];
int32_t operand = opOperandSize(op);
if (operand < 0) {
free(remap);
return NULL;
}
int32_t instSize = 1 + operand;
if (oldPc + instSize > codeLen) {
free(remap);
return NULL;
}
if (op == OP_LINE) {
// These bytes are removed; they map to where the next instruction starts.
for (int32_t i = 0; i < instSize; i++) {
remap[oldPc + i] = newPc;
}
} else {
for (int32_t i = 0; i < instSize; i++) {
remap[oldPc + i] = newPc + i;
}
newPc += instSize;
}
oldPc += instSize;
}
if (oldPc != codeLen) {
free(remap);
return NULL;
}
remap[codeLen] = newPc;
*outNewLen = newPc;
return remap;
}
// ============================================================
// GOSUB pattern detection
// ============================================================
//
// GOSUB emits:
// oldPc: OP_PUSH_INT32 (1 byte)
// oldPc+1: int32 value V (4 bytes)
// oldPc+5: OP_JMP (1 byte)
// oldPc+6: int16 offset (2 bytes)
// oldPc+8: <next instruction>
// The invariant is V == oldPc + 8 (the pushed return address).
//
// Returns true if the given position is the start of such a pattern.
static bool isGosubPush(const uint8_t *code, int32_t codeLen, int32_t pos) {
if (pos + 8 > codeLen) {
return false;
}
if (code[pos] != OP_PUSH_INT32) {
return false;
}
if (code[pos + 5] != OP_JMP) {
return false;
}
int32_t value = readI32LE(code + pos + 1);
return value == pos + 8;
}
// ============================================================
// Opcode operand size table
// ============================================================
// Returns operand byte count (excluding the 1-byte opcode), or -1 if unknown.
static int32_t opOperandSize(uint8_t op) {
switch (op) {
// No operand bytes
case OP_NOP:
case OP_PUSH_TRUE: case OP_PUSH_FALSE:
case OP_POP: case OP_DUP:
case OP_LOAD_REF: case OP_STORE_REF:
case OP_ADD_INT: case OP_SUB_INT: case OP_MUL_INT:
case OP_IDIV_INT: case OP_MOD_INT: case OP_NEG_INT:
case OP_ADD_FLT: case OP_SUB_FLT: case OP_MUL_FLT:
case OP_DIV_FLT: case OP_NEG_FLT: case OP_POW:
case OP_STR_CONCAT: case OP_STR_LEFT: case OP_STR_RIGHT:
case OP_STR_MID: case OP_STR_MID2: case OP_STR_LEN:
case OP_STR_INSTR: case OP_STR_INSTR3:
case OP_STR_UCASE: case OP_STR_LCASE:
case OP_STR_TRIM: case OP_STR_LTRIM: case OP_STR_RTRIM:
case OP_STR_CHR: case OP_STR_ASC: case OP_STR_SPACE:
case OP_CMP_EQ: case OP_CMP_NE: case OP_CMP_LT:
case OP_CMP_GT: case OP_CMP_LE: case OP_CMP_GE:
case OP_AND: case OP_OR: case OP_NOT:
case OP_XOR: case OP_EQV: case OP_IMP:
case OP_GOSUB_RET: case OP_RET: case OP_RET_VAL:
case OP_FOR_POP:
case OP_CONV_INT_FLT: case OP_CONV_FLT_INT:
case OP_CONV_INT_STR: case OP_CONV_STR_INT:
case OP_CONV_FLT_STR: case OP_CONV_STR_FLT:
case OP_CONV_INT_LONG: case OP_CONV_LONG_INT:
case OP_PRINT: case OP_PRINT_NL: case OP_PRINT_TAB:
case OP_INPUT:
case OP_FILE_CLOSE: case OP_FILE_PRINT: case OP_FILE_INPUT:
case OP_FILE_EOF: case OP_FILE_LINE_INPUT:
case OP_LOAD_PROP: case OP_STORE_PROP:
case OP_LOAD_FORM: case OP_UNLOAD_FORM:
case OP_HIDE_FORM: case OP_DO_EVENTS:
case OP_MSGBOX: case OP_INPUTBOX: case OP_ME_REF:
case OP_CREATE_CTRL: case OP_FIND_CTRL: case OP_FIND_CTRL_IDX:
case OP_CREATE_CTRL_EX:
case OP_ERASE:
case OP_RESUME: case OP_RESUME_NEXT:
case OP_RAISE_ERR: case OP_ERR_NUM: case OP_ERR_CLEAR:
case OP_MATH_ABS: case OP_MATH_INT: case OP_MATH_FIX:
case OP_MATH_SGN: case OP_MATH_SQR: case OP_MATH_SIN:
case OP_MATH_COS: case OP_MATH_TAN: case OP_MATH_ATN:
case OP_MATH_LOG: case OP_MATH_EXP: case OP_MATH_RND:
case OP_MATH_RANDOMIZE:
case OP_RGB:
case OP_GET_RED: case OP_GET_GREEN: case OP_GET_BLUE:
case OP_STR_VAL: case OP_STR_STRF: case OP_STR_HEX:
case OP_STR_STRING:
case OP_MATH_TIMER: case OP_DATE_STR: case OP_TIME_STR:
case OP_SLEEP: case OP_ENVIRON:
case OP_READ_DATA: case OP_RESTORE:
case OP_FILE_WRITE: case OP_FILE_WRITE_SEP: case OP_FILE_WRITE_NL:
case OP_FILE_GET: case OP_FILE_PUT: case OP_FILE_SEEK:
case OP_FILE_LOF: case OP_FILE_LOC: case OP_FILE_FREEFILE:
case OP_FILE_INPUT_N:
case OP_STR_MID_ASGN: case OP_PRINT_USING:
case OP_PRINT_TAB_N: case OP_PRINT_SPC_N:
case OP_FORMAT: case OP_SHELL:
case OP_APP_PATH: case OP_APP_CONFIG: case OP_APP_DATA:
case OP_INI_READ: case OP_INI_WRITE:
case OP_FS_KILL: case OP_FS_NAME: case OP_FS_FILECOPY:
case OP_FS_MKDIR: case OP_FS_RMDIR: case OP_FS_CHDIR:
case OP_FS_CHDRIVE: case OP_FS_CURDIR: case OP_FS_DIR:
case OP_FS_DIR_NEXT: case OP_FS_FILELEN:
case OP_FS_GETATTR: case OP_FS_SETATTR:
case OP_CREATE_FORM: case OP_SET_EVENT: case OP_REMOVE_CTRL:
case OP_END: case OP_HALT:
return 0;
case OP_LOAD_ARRAY: case OP_STORE_ARRAY:
case OP_PRINT_SPC: case OP_FILE_OPEN:
case OP_CALL_METHOD: case OP_SHOW_FORM:
case OP_LBOUND: case OP_UBOUND:
case OP_COMPARE_MODE:
return 1;
case OP_PUSH_INT16: case OP_PUSH_STR:
case OP_LOAD_LOCAL: case OP_STORE_LOCAL:
case OP_LOAD_GLOBAL: case OP_STORE_GLOBAL:
case OP_LOAD_FIELD: case OP_STORE_FIELD:
case OP_PUSH_LOCAL_ADDR: case OP_PUSH_GLOBAL_ADDR:
case OP_JMP: case OP_JMP_TRUE: case OP_JMP_FALSE:
case OP_CTRL_REF:
case OP_LOAD_FORM_VAR: case OP_STORE_FORM_VAR:
case OP_PUSH_FORM_ADDR:
case OP_DIM_ARRAY: case OP_REDIM:
case OP_ON_ERROR:
case OP_STR_FIXLEN:
case OP_LINE:
return 2;
case OP_STORE_ARRAY_FIELD:
case OP_FOR_INIT:
return 3;
case OP_PUSH_INT32: case OP_PUSH_FLT32:
case OP_CALL:
return 4;
case OP_FOR_NEXT:
return 5;
case OP_CALL_EXTERN:
return 6;
case OP_PUSH_FLT64:
return 8;
default:
return -1;
}
}
// ============================================================
// Little-endian helpers (bytecode is always LE regardless of host)
// ============================================================
static int16_t readI16LE(const uint8_t *p) {
return (int16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static int32_t readI32LE(const uint8_t *p) {
return (int32_t)((uint32_t)p[0] |
((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24));
}
static uint16_t readU16LE(const uint8_t *p) {
return (uint16_t)p[0] | ((uint16_t)p[1] << 8);
}
// ============================================================
// Apply remap to a single instruction's operand
// ============================================================
//
// Returns true on success, false if an offset overflows int16 or a
// target doesn't land on a valid instruction in the old code.
static bool remapAbsU16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffset, uint16_t oldAddr, const int32_t *remap, int32_t newCodeLen) {
int32_t newAddr = remap[oldAddr];
if (newAddr < 0 || newAddr > newCodeLen) {
return false;
}
if (newAddr > 0xFFFF) {
return false;
}
writeU16LE(newCode + newOpPos + operandOffset, (uint16_t)newAddr);
return true;
}
// Rewrite a relative int16 offset.
// oldOpPos, oldPcAfter: position of opcode and PC after reading offset
// newOpPos, newPcAfter: same in the new code
// operandOffset: byte offset from opcode to the int16 operand
// oldOffset: the offset as stored in the old code
//
// Handles ON_ERROR's special case (offset == 0 means "disable").
// For ON_ERROR the caller passes allowZero=true; the zero is preserved as-is.
static bool remapRelI16(uint8_t *newCode, int32_t newOpPos, int32_t operandOffset, int16_t oldOffset, int32_t oldPcAfter, int32_t newPcAfter, const int32_t *remap, int32_t codeLen, int32_t newCodeLen, bool allowZero) {
if (allowZero && oldOffset == 0) {
writeI16LE(newCode + newOpPos + operandOffset, 0);
return true;
}
int32_t oldTarget = oldPcAfter + oldOffset;
if (oldTarget < 0 || oldTarget > codeLen) {
return false;
}
int32_t newTarget = remap[oldTarget];
if (newTarget < 0 || newTarget > newCodeLen) {
return false;
}
int32_t newOffset = newTarget - newPcAfter;
if (newOffset < -32768 || newOffset > 32767) {
return false;
}
writeI16LE(newCode + newOpPos + operandOffset, (int16_t)newOffset);
return true;
}
static void writeI16LE(uint8_t *p, int16_t v) {
uint16_t u = (uint16_t)v;
p[0] = (uint8_t)(u & 0xFF);
p[1] = (uint8_t)((u >> 8) & 0xFF);
}
static void writeI32LE(uint8_t *p, int32_t v) {
uint32_t u = (uint32_t)v;
p[0] = (uint8_t)(u & 0xFF);
p[1] = (uint8_t)((u >> 8) & 0xFF);
p[2] = (uint8_t)((u >> 16) & 0xFF);
p[3] = (uint8_t)((u >> 24) & 0xFF);
}
static void writeU16LE(uint8_t *p, uint16_t v) {
p[0] = (uint8_t)(v & 0xFF);
p[1] = (uint8_t)((v >> 8) & 0xFF);
}

View file

@ -151,28 +151,28 @@ static const KeywordEntryT sKeywords[] = {
#define KEYWORD_COUNT (sizeof(sKeywords) / sizeof(sKeywords[0]) - 1) #define KEYWORD_COUNT (sizeof(sKeywords) / sizeof(sKeywords[0]) - 1)
// ============================================================
// Prototypes
// ============================================================
static char advance(BasLexerT *lex); // Function prototypes (alphabetical)
static bool atEnd(const BasLexerT *lex); static char advance(BasLexerT *lex);
static bool atEnd(const BasLexerT *lex);
void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen);
const char *basLexerKeywordAt(int32_t i);
BasKeywordClassE basLexerKeywordClass(int32_t i);
int32_t basLexerKeywordCount(void);
BasTokenTypeE basLexerNext(BasLexerT *lex);
BasTokenTypeE basLexerPeek(const BasLexerT *lex);
const char *basTokenName(BasTokenTypeE type);
static BasTokenTypeE lookupKeyword(const char *text, int32_t len); static BasTokenTypeE lookupKeyword(const char *text, int32_t len);
static char peek(const BasLexerT *lex); static char peek(const BasLexerT *lex);
static char peekNext(const BasLexerT *lex); static char peekNext(const BasLexerT *lex);
static void setError(BasLexerT *lex, const char *msg); static void setError(BasLexerT *lex, const char *msg);
static void skipLineComment(BasLexerT *lex); static void skipLineComment(BasLexerT *lex);
static void skipWhitespace(BasLexerT *lex); static void skipWhitespace(BasLexerT *lex);
static BasTokenTypeE tokenizeHexLiteral(BasLexerT *lex); static BasTokenTypeE tokenizeHexLiteral(BasLexerT *lex);
static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex); static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex);
static BasTokenTypeE tokenizeNumber(BasLexerT *lex); static BasTokenTypeE tokenizeNumber(BasLexerT *lex);
static BasTokenTypeE tokenizeString(BasLexerT *lex); static BasTokenTypeE tokenizeString(BasLexerT *lex);
static char upperChar(char c); static char upperChar(char c);
// ============================================================
// advance
// ============================================================
static char advance(BasLexerT *lex) { static char advance(BasLexerT *lex) {
if (atEnd(lex)) { if (atEnd(lex)) {
@ -192,19 +192,11 @@ static char advance(BasLexerT *lex) {
} }
// ============================================================
// atEnd
// ============================================================
static bool atEnd(const BasLexerT *lex) { static bool atEnd(const BasLexerT *lex) {
return lex->pos >= lex->sourceLen; return lex->pos >= lex->sourceLen;
} }
// ============================================================
// basLexerInit
// ============================================================
void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen) { void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen) {
memset(lex, 0, sizeof(*lex)); memset(lex, 0, sizeof(*lex));
lex->source = source; lex->source = source;
@ -218,9 +210,44 @@ void basLexerInit(BasLexerT *lex, const char *source, int32_t sourceLen) {
} }
// ============================================================ const char *basLexerKeywordAt(int32_t i) {
// basLexerNext if (i < 0 || i >= (int32_t)KEYWORD_COUNT) {
// ============================================================ return NULL;
}
return sKeywords[i].text;
}
BasKeywordClassE basLexerKeywordClass(int32_t i) {
if (i < 0 || i >= (int32_t)KEYWORD_COUNT) {
return BAS_KW_CLASS_OTHER;
}
switch (sKeywords[i].type) {
case TOK_BOOLEAN:
case TOK_DOUBLE:
case TOK_INTEGER:
case TOK_LONG:
case TOK_SINGLE:
case TOK_STRING_KW:
return BAS_KW_CLASS_TYPE;
case TOK_TRUE_KW:
case TOK_FALSE_KW:
case TOK_NOTHING:
return BAS_KW_CLASS_LITERAL;
default:
return BAS_KW_CLASS_OTHER;
}
}
int32_t basLexerKeywordCount(void) {
return (int32_t)KEYWORD_COUNT;
}
BasTokenTypeE basLexerNext(BasLexerT *lex) { BasTokenTypeE basLexerNext(BasLexerT *lex) {
skipWhitespace(lex); skipWhitespace(lex);
@ -408,19 +435,11 @@ BasTokenTypeE basLexerNext(BasLexerT *lex) {
} }
// ============================================================
// basLexerPeek
// ============================================================
BasTokenTypeE basLexerPeek(const BasLexerT *lex) { BasTokenTypeE basLexerPeek(const BasLexerT *lex) {
return lex->token.type; return lex->token.type;
} }
// ============================================================
// basTokenName
// ============================================================
const char *basTokenName(BasTokenTypeE type) { const char *basTokenName(BasTokenTypeE type) {
switch (type) { switch (type) {
case TOK_INT_LIT: return "integer"; case TOK_INT_LIT: return "integer";
@ -465,10 +484,6 @@ const char *basTokenName(BasTokenTypeE type) {
} }
// ============================================================
// lookupKeyword
// ============================================================
static BasTokenTypeE lookupKeyword(const char *text, int32_t len) { static BasTokenTypeE lookupKeyword(const char *text, int32_t len) {
// Case-insensitive keyword lookup // Case-insensitive keyword lookup
for (int32_t i = 0; i < (int32_t)KEYWORD_COUNT; i++) { for (int32_t i = 0; i < (int32_t)KEYWORD_COUNT; i++) {
@ -497,10 +512,6 @@ static BasTokenTypeE lookupKeyword(const char *text, int32_t len) {
} }
// ============================================================
// peek
// ============================================================
static char peek(const BasLexerT *lex) { static char peek(const BasLexerT *lex) {
if (atEnd(lex)) { if (atEnd(lex)) {
return '\0'; return '\0';
@ -510,10 +521,6 @@ static char peek(const BasLexerT *lex) {
} }
// ============================================================
// peekNext
// ============================================================
static char peekNext(const BasLexerT *lex) { static char peekNext(const BasLexerT *lex) {
if (lex->pos + 1 >= lex->sourceLen) { if (lex->pos + 1 >= lex->sourceLen) {
return '\0'; return '\0';
@ -523,19 +530,11 @@ static char peekNext(const BasLexerT *lex) {
} }
// ============================================================
// setError
// ============================================================
static void setError(BasLexerT *lex, const char *msg) { static void setError(BasLexerT *lex, const char *msg) {
snprintf(lex->error, sizeof(lex->error), "Line %d, Col %d: %s", (int)lex->line, (int)lex->col, msg); snprintf(lex->error, sizeof(lex->error), "Line %d, Col %d: %s", (int)lex->line, (int)lex->col, msg);
} }
// ============================================================
// skipLineComment
// ============================================================
static void skipLineComment(BasLexerT *lex) { static void skipLineComment(BasLexerT *lex) {
while (!atEnd(lex) && peek(lex) != '\n' && peek(lex) != '\r') { while (!atEnd(lex) && peek(lex) != '\n' && peek(lex) != '\r') {
advance(lex); advance(lex);
@ -543,14 +542,10 @@ static void skipLineComment(BasLexerT *lex) {
} }
// ============================================================
// skipWhitespace
// ============================================================
// //
// Skips spaces and tabs. Does NOT skip newlines (they are tokens). // Skips spaces and tabs. Does NOT skip newlines (they are tokens).
// Handles line continuation: underscore followed by newline joins // Handles line continuation: underscore followed by newline joins
// the next line to the current logical line. // the next line to the current logical line.
static void skipWhitespace(BasLexerT *lex) { static void skipWhitespace(BasLexerT *lex) {
while (!atEnd(lex)) { while (!atEnd(lex)) {
char c = peek(lex); char c = peek(lex);
@ -595,10 +590,6 @@ static void skipWhitespace(BasLexerT *lex) {
} }
// ============================================================
// tokenizeHexLiteral
// ============================================================
static BasTokenTypeE tokenizeHexLiteral(BasLexerT *lex) { static BasTokenTypeE tokenizeHexLiteral(BasLexerT *lex) {
advance(lex); // skip & advance(lex); // skip &
advance(lex); // skip H advance(lex); // skip H
@ -641,10 +632,6 @@ static BasTokenTypeE tokenizeHexLiteral(BasLexerT *lex) {
} }
// ============================================================
// tokenizeIdentOrKeyword
// ============================================================
static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex) { static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex) {
int32_t idx = 0; int32_t idx = 0;
@ -714,10 +701,6 @@ static BasTokenTypeE tokenizeIdentOrKeyword(BasLexerT *lex) {
} }
// ============================================================
// tokenizeNumber
// ============================================================
static BasTokenTypeE tokenizeNumber(BasLexerT *lex) { static BasTokenTypeE tokenizeNumber(BasLexerT *lex) {
int32_t idx = 0; int32_t idx = 0;
bool hasDecimal = false; bool hasDecimal = false;
@ -814,10 +797,6 @@ static BasTokenTypeE tokenizeNumber(BasLexerT *lex) {
} }
// ============================================================
// tokenizeString
// ============================================================
static BasTokenTypeE tokenizeString(BasLexerT *lex) { static BasTokenTypeE tokenizeString(BasLexerT *lex) {
advance(lex); // skip opening quote advance(lex); // skip opening quote
@ -847,10 +826,6 @@ static BasTokenTypeE tokenizeString(BasLexerT *lex) {
} }
// ============================================================
// upperChar
// ============================================================
static char upperChar(char c) { static char upperChar(char c) {
if (c >= 'a' && c <= 'z') { if (c >= 'a' && c <= 'z') {
return c - 32; return c - 32;

View file

@ -242,4 +242,29 @@ BasTokenTypeE basLexerPeek(const BasLexerT *lex);
// Return human-readable name for a token type. // Return human-readable name for a token type.
const char *basTokenName(BasTokenTypeE type); const char *basTokenName(BasTokenTypeE type);
// ============================================================
// Keyword iteration
// ============================================================
//
// The lexer's internal keyword table is the one source of truth.
// Other modules (e.g. the IDE syntax highlighter) iterate through
// it here instead of keeping their own parallel list.
typedef enum {
BAS_KW_CLASS_OTHER = 0, // control-flow / statement / operator keyword
BAS_KW_CLASS_TYPE = 1, // INTEGER, LONG, SINGLE, DOUBLE, STRING, BOOLEAN
BAS_KW_CLASS_LITERAL = 2 // TRUE, FALSE, NOTHING
} BasKeywordClassE;
// Number of keywords in the table (excluding the trailing NULL sentinel
// and any duplicate dollar-suffixed aliases like DIR$/DIR).
int32_t basLexerKeywordCount(void);
// Return the text of keyword i (uppercase). i must be in [0, count).
// Duplicates such as DIR and DIR$ are both returned at their own index.
const char *basLexerKeywordAt(int32_t i);
// Classify the keyword at index i.
BasKeywordClassE basLexerKeywordClass(int32_t i);
#endif // DVXBASIC_LEXER_H #endif // DVXBASIC_LEXER_H

View file

@ -3,6 +3,7 @@
// See obfuscate.h for the high-level description. // See obfuscate.h for the high-level description.
#include "obfuscate.h" #include "obfuscate.h"
#include "basEvents.h"
#include "../runtime/values.h" #include "../runtime/values.h"
#include <ctype.h> #include <ctype.h>
@ -10,21 +11,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
// ============================================================
// Event suffix list (must match strip.c)
// ============================================================
static const char *sEventSuffixes[] = {
"Load", "Unload", "QueryUnload", "Resize", "Activate", "Deactivate",
"Click", "DblClick", "Change", "Timer",
"GotFocus", "LostFocus",
"KeyPress", "KeyDown", "KeyUp",
"MouseDown", "MouseUp", "MouseMove",
"Scroll", "Reposition", "Validate",
NULL
};
// ============================================================ // ============================================================
// Name map // Name map
// ============================================================ // ============================================================
@ -41,113 +27,165 @@ typedef struct {
} NameMapT; } NameMapT;
static void nameMapInit(NameMapT *m) { // Function prototypes (alphabetical)
m->entries = NULL; void basObfuscateNames(BasModuleT *mod, const char **frmTexts, const int32_t *frmLens, int32_t frmCount, BasObfFrmT *outFrms);
m->count = 0; int32_t basStripFrmComments(const char *src, int32_t srcLen, uint8_t *outBuf, int32_t outCap);
m->cap = 0; static void collectNamesFromFrm(const char *text, int32_t len, NameMapT *map);
} static int32_t findFormEndPos(const char *text, int32_t len);
static bool isEventSuffix(const char *suffix);
static bool isIdentChar(int c);
static void nameMapFree(NameMapT *m) { static bool isValidIdent(const char *name);
for (int32_t i = 0; i < m->count; i++) { static const char *nameMapAdd(NameMapT *m, const char *name);
free(m->entries[i].orig); static void nameMapFree(NameMapT *m);
free(m->entries[i].mapped); static void nameMapInit(NameMapT *m);
} static const char *nameMapLookup(const NameMapT *m, const char *name);
static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize);
free(m->entries); static void replaceConstant(BasModuleT *mod, int32_t idx, const char *newText);
m->entries = NULL; static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *map, uint8_t *out, int32_t outCap);
m->count = 0; static void rewriteModuleConstants(BasModuleT *mod, const NameMapT *map);
m->cap = 0; static void rewriteModuleFormVars(BasModuleT *mod, const NameMapT *map);
} static void rewriteModuleProcs(BasModuleT *mod, const NameMapT *map);
static const char *skipWhitespace(const char *p, const char *end);
// Look up an original name (case-insensitive). Returns mapped name or NULL.
static const char *nameMapLookup(const NameMapT *m, const char *name) {
for (int32_t i = 0; i < m->count; i++) {
if (strcasecmp(m->entries[i].orig, name) == 0) {
return m->entries[i].mapped;
}
}
return NULL;
}
// Add a name if not already present. Returns mapped name.
static const char *nameMapAdd(NameMapT *m, const char *name) {
const char *existing = nameMapLookup(m, name);
if (existing) {
return existing;
}
if (m->count >= m->cap) {
int32_t newCap = m->cap == 0 ? 16 : m->cap * 2;
NameEntryT *newEntries = realloc(m->entries, newCap * sizeof(NameEntryT));
if (!newEntries) {
return NULL;
}
m->entries = newEntries;
m->cap = newCap;
}
char mapped[16];
snprintf(mapped, sizeof(mapped), "C%ld", (long)(m->count + 1));
m->entries[m->count].orig = strdup(name);
m->entries[m->count].mapped = strdup(mapped);
m->count++;
return m->entries[m->count - 1].mapped;
}
// ============================================================ // ============================================================
// .frm parsing helpers // Top-level entry point
// ============================================================ // ============================================================
void basObfuscateNames(BasModuleT *mod, const char **frmTexts, const int32_t *frmLens, int32_t frmCount, BasObfFrmT *outFrms) {
// Skip ASCII whitespace. Returns pointer past whitespace. if (!mod || frmCount < 0) {
static const char *skipWhitespace(const char *p, const char *end) { return;
while (p < end && (*p == ' ' || *p == '\t')) {
p++;
} }
return p; NameMapT map;
} nameMapInit(&map);
// Pass 1: collect all names from all .frm texts
// Copy next whitespace-delimited token into buf. Returns pointer after token. for (int32_t i = 0; i < frmCount; i++) {
static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize) { if (frmTexts[i] && frmLens[i] > 0) {
int32_t len = 0; collectNamesFromFrm(frmTexts[i], frmLens[i], &map);
while (p < end && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && len < bufSize - 1) {
buf[len++] = *p++;
}
buf[len] = '\0';
return p;
}
// Check if name is a valid identifier (letters, digits, underscore, starts non-digit)
static bool isValidIdent(const char *name) {
if (!name || !*name) {
return false;
}
if (!isalpha((unsigned char)name[0]) && name[0] != '_') {
return false;
}
for (const char *p = name; *p; p++) {
if (!isalnum((unsigned char)*p) && *p != '_') {
return false;
} }
} }
return true; // Pass 2: rewrite each .frm
for (int32_t i = 0; i < frmCount; i++) {
outFrms[i].data = NULL;
outFrms[i].len = 0;
if (!frmTexts[i] || frmLens[i] <= 0) {
continue;
}
int32_t strippedLen = findFormEndPos(frmTexts[i], frmLens[i]);
// Allocate generous output buffer (mapped names are usually shorter
// than originals, but allow for growth and a trailing newline).
int32_t outCap = strippedLen + 1024;
uint8_t *outBuf = malloc(outCap);
if (!outBuf) {
continue;
}
int32_t outLen = rewriteFrmText(frmTexts[i], strippedLen, &map, outBuf, outCap);
// Ensure trailing newline
if (outLen > 0 && outBuf[outLen - 1] != '\n' && outLen < outCap) {
outBuf[outLen++] = '\n';
}
outFrms[i].data = outBuf;
outFrms[i].len = outLen;
}
// Pass 3: rewrite module
rewriteModuleConstants(mod, &map);
rewriteModuleProcs(mod, &map);
rewriteModuleFormVars(mod, &map);
nameMapFree(&map);
}
int32_t basStripFrmComments(const char *src, int32_t srcLen, uint8_t *outBuf, int32_t outCap) {
if (!src || srcLen <= 0 || !outBuf || outCap <= 0) {
return 0;
}
int32_t outLen = 0;
int32_t i = 0;
while (i < srcLen) {
int32_t lineStart = i;
while (i < srcLen && src[i] != '\n' && src[i] != '\r') {
i++;
}
int32_t lineEnd = i;
if (i < srcLen && src[i] == '\r') {
i++;
}
if (i < srcLen && src[i] == '\n') {
i++;
}
// Scan for first unquoted ' (comment start).
bool inStr = false;
int32_t commentStart = -1;
for (int32_t j = lineStart; j < lineEnd; j++) {
char c = src[j];
if (c == '"') {
inStr = !inStr;
} else if (c == '\'' && !inStr) {
commentStart = j;
break;
}
}
int32_t contentEnd = (commentStart >= 0) ? commentStart : lineEnd;
// Check for whole-line REM. Find first non-whitespace position.
int32_t firstNonWs = lineStart;
while (firstNonWs < contentEnd && (src[firstNonWs] == ' ' || src[firstNonWs] == '\t')) {
firstNonWs++;
}
if (contentEnd - firstNonWs >= 3 &&
strncasecmp(src + firstNonWs, "REM", 3) == 0 &&
(contentEnd - firstNonWs == 3 ||
src[firstNonWs + 3] == ' ' ||
src[firstNonWs + 3] == '\t')) {
contentEnd = firstNonWs;
}
// Trim trailing whitespace.
while (contentEnd > lineStart && (src[contentEnd - 1] == ' ' || src[contentEnd - 1] == '\t')) {
contentEnd--;
}
// Drop lines that have no non-whitespace content.
if (contentEnd <= firstNonWs) {
continue;
}
// Strip leading whitespace -- the form parser trims per line
// anyway, so shipping indentation just bloats the embedded resource.
int32_t writeLen = contentEnd - firstNonWs;
if (outLen + writeLen + 1 >= outCap) {
break;
}
memcpy(outBuf + outLen, src + firstNonWs, writeLen);
outLen += writeLen;
outBuf[outLen++] = '\n';
}
return outLen;
} }
@ -267,6 +305,18 @@ static int32_t findFormEndPos(const char *text, int32_t len) {
} }
// Check if suffix is a known event name.
static bool isEventSuffix(const char *suffix) {
for (int32_t i = 0; basEventSuffixes[i]; i++) {
if (strcasecmp(suffix, basEventSuffixes[i]) == 0) {
return true;
}
}
return false;
}
// ============================================================ // ============================================================
// Pass 3: rewrite .frm text with mapped names // Pass 3: rewrite .frm text with mapped names
// ============================================================ // ============================================================
@ -277,6 +327,119 @@ static bool isIdentChar(int c) {
} }
// Check if name is a valid identifier (letters, digits, underscore, starts non-digit)
static bool isValidIdent(const char *name) {
if (!name || !*name) {
return false;
}
if (!isalpha((unsigned char)name[0]) && name[0] != '_') {
return false;
}
for (const char *p = name; *p; p++) {
if (!isalnum((unsigned char)*p) && *p != '_') {
return false;
}
}
return true;
}
// Add a name if not already present. Returns mapped name.
static const char *nameMapAdd(NameMapT *m, const char *name) {
const char *existing = nameMapLookup(m, name);
if (existing) {
return existing;
}
if (m->count >= m->cap) {
int32_t newCap = m->cap == 0 ? 16 : m->cap * 2;
NameEntryT *newEntries = realloc(m->entries, newCap * sizeof(NameEntryT));
if (!newEntries) {
return NULL;
}
m->entries = newEntries;
m->cap = newCap;
}
char mapped[16];
snprintf(mapped, sizeof(mapped), "C%ld", (long)(m->count + 1));
m->entries[m->count].orig = strdup(name);
m->entries[m->count].mapped = strdup(mapped);
m->count++;
return m->entries[m->count - 1].mapped;
}
static void nameMapFree(NameMapT *m) {
for (int32_t i = 0; i < m->count; i++) {
free(m->entries[i].orig);
free(m->entries[i].mapped);
}
free(m->entries);
m->entries = NULL;
m->count = 0;
m->cap = 0;
}
static void nameMapInit(NameMapT *m) {
m->entries = NULL;
m->count = 0;
m->cap = 0;
}
// Look up an original name (case-insensitive). Returns mapped name or NULL.
static const char *nameMapLookup(const NameMapT *m, const char *name) {
for (int32_t i = 0; i < m->count; i++) {
if (strcasecmp(m->entries[i].orig, name) == 0) {
return m->entries[i].mapped;
}
}
return NULL;
}
// Copy next whitespace-delimited token into buf. Returns pointer after token.
static const char *readToken(const char *p, const char *end, char *buf, int32_t bufSize) {
int32_t len = 0;
while (p < end && *p != ' ' && *p != '\t' && *p != '\r' && *p != '\n' && len < bufSize - 1) {
buf[len++] = *p++;
}
buf[len] = '\0';
return p;
}
// ============================================================
// Module rewriting
// ============================================================
// Replace the contents of a constant pool entry with a new string.
static void replaceConstant(BasModuleT *mod, int32_t idx, const char *newText) {
BasStringT *newStr = basStringNew(newText, (int32_t)strlen(newText));
if (!newStr) {
return;
}
basStringUnref(mod->constants[idx]);
mod->constants[idx] = newStr;
}
// Scan text; for each identifier found outside of strings, if it's in // Scan text; for each identifier found outside of strings, if it's in
// the map, emit the mapped name instead. Output to out (returns bytes written). // the map, emit the mapped name instead. Output to out (returns bytes written).
static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *map, uint8_t *out, int32_t outCap) { static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *map, uint8_t *out, int32_t outCap) {
@ -344,23 +507,6 @@ static int32_t rewriteFrmText(const char *src, int32_t srcLen, const NameMapT *m
} }
// ============================================================
// Module rewriting
// ============================================================
// Replace the contents of a constant pool entry with a new string.
static void replaceConstant(BasModuleT *mod, int32_t idx, const char *newText) {
BasStringT *newStr = basStringNew(newText, (int32_t)strlen(newText));
if (!newStr) {
return;
}
basStringUnref(mod->constants[idx]);
mod->constants[idx] = newStr;
}
static void rewriteModuleConstants(BasModuleT *mod, const NameMapT *map) { static void rewriteModuleConstants(BasModuleT *mod, const NameMapT *map) {
for (int32_t i = 0; i < mod->constCount; i++) { for (int32_t i = 0; i < mod->constCount; i++) {
const BasStringT *s = mod->constants[i]; const BasStringT *s = mod->constants[i];
@ -378,15 +524,15 @@ static void rewriteModuleConstants(BasModuleT *mod, const NameMapT *map) {
} }
// Check if suffix is a known event name. static void rewriteModuleFormVars(BasModuleT *mod, const NameMapT *map) {
static bool isEventSuffix(const char *suffix) { for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
for (int32_t i = 0; sEventSuffixes[i]; i++) { BasFormVarInfoT *fv = &mod->formVarInfo[i];
if (strcasecmp(suffix, sEventSuffixes[i]) == 0) { const char *mapped = nameMapLookup(map, fv->formName);
return true;
if (mapped) {
snprintf(fv->formName, sizeof(fv->formName), "%s", mapped);
} }
} }
return false;
} }
@ -433,160 +579,15 @@ static void rewriteModuleProcs(BasModuleT *mod, const NameMapT *map) {
} }
static void rewriteModuleFormVars(BasModuleT *mod, const NameMapT *map) { // ============================================================
for (int32_t i = 0; i < mod->formVarInfoCount; i++) { // .frm parsing helpers
BasFormVarInfoT *fv = &mod->formVarInfo[i]; // ============================================================
const char *mapped = nameMapLookup(map, fv->formName);
if (mapped) { // Skip ASCII whitespace. Returns pointer past whitespace.
snprintf(fv->formName, sizeof(fv->formName), "%s", mapped); static const char *skipWhitespace(const char *p, const char *end) {
} while (p < end && (*p == ' ' || *p == '\t')) {
p++;
} }
}
return p;
// ============================================================
// basStripFrmComments
// ============================================================
int32_t basStripFrmComments(const char *src, int32_t srcLen, uint8_t *outBuf, int32_t outCap) {
if (!src || srcLen <= 0 || !outBuf || outCap <= 0) {
return 0;
}
int32_t outLen = 0;
int32_t i = 0;
while (i < srcLen) {
int32_t lineStart = i;
while (i < srcLen && src[i] != '\n' && src[i] != '\r') {
i++;
}
int32_t lineEnd = i;
if (i < srcLen && src[i] == '\r') {
i++;
}
if (i < srcLen && src[i] == '\n') {
i++;
}
// Scan for first unquoted ' (comment start).
bool inStr = false;
int32_t commentStart = -1;
for (int32_t j = lineStart; j < lineEnd; j++) {
char c = src[j];
if (c == '"') {
inStr = !inStr;
} else if (c == '\'' && !inStr) {
commentStart = j;
break;
}
}
int32_t contentEnd = (commentStart >= 0) ? commentStart : lineEnd;
// Check for whole-line REM. Find first non-whitespace position.
int32_t firstNonWs = lineStart;
while (firstNonWs < contentEnd && (src[firstNonWs] == ' ' || src[firstNonWs] == '\t')) {
firstNonWs++;
}
if (contentEnd - firstNonWs >= 3 &&
strncasecmp(src + firstNonWs, "REM", 3) == 0 &&
(contentEnd - firstNonWs == 3 ||
src[firstNonWs + 3] == ' ' ||
src[firstNonWs + 3] == '\t')) {
contentEnd = firstNonWs;
}
// Trim trailing whitespace.
while (contentEnd > lineStart && (src[contentEnd - 1] == ' ' || src[contentEnd - 1] == '\t')) {
contentEnd--;
}
// Drop lines that have no non-whitespace content.
if (contentEnd <= firstNonWs) {
continue;
}
// Strip leading whitespace -- the form parser trims per line
// anyway, so shipping indentation just bloats the embedded resource.
int32_t writeLen = contentEnd - firstNonWs;
if (outLen + writeLen + 1 >= outCap) {
break;
}
memcpy(outBuf + outLen, src + firstNonWs, writeLen);
outLen += writeLen;
outBuf[outLen++] = '\n';
}
return outLen;
}
// ============================================================
// Top-level entry point
// ============================================================
void basObfuscateNames(BasModuleT *mod, const char **frmTexts, const int32_t *frmLens, int32_t frmCount, BasObfFrmT *outFrms) {
if (!mod || frmCount < 0) {
return;
}
NameMapT map;
nameMapInit(&map);
// Pass 1: collect all names from all .frm texts
for (int32_t i = 0; i < frmCount; i++) {
if (frmTexts[i] && frmLens[i] > 0) {
collectNamesFromFrm(frmTexts[i], frmLens[i], &map);
}
}
// Pass 2: rewrite each .frm
for (int32_t i = 0; i < frmCount; i++) {
outFrms[i].data = NULL;
outFrms[i].len = 0;
if (!frmTexts[i] || frmLens[i] <= 0) {
continue;
}
int32_t strippedLen = findFormEndPos(frmTexts[i], frmLens[i]);
// Allocate generous output buffer (mapped names are usually shorter
// than originals, but allow for growth and a trailing newline).
int32_t outCap = strippedLen + 1024;
uint8_t *outBuf = malloc(outCap);
if (!outBuf) {
continue;
}
int32_t outLen = rewriteFrmText(frmTexts[i], strippedLen, &map, outBuf, outCap);
// Ensure trailing newline
if (outLen > 0 && outBuf[outLen - 1] != '\n' && outLen < outCap) {
outBuf[outLen++] = '\n';
}
outFrms[i].data = outBuf;
outFrms[i].len = outLen;
}
// Pass 3: rewrite module
rewriteModuleConstants(mod, &map);
rewriteModuleProcs(mod, &map);
rewriteModuleFormVars(mod, &map);
nameMapFree(&map);
} }

File diff suppressed because it is too large Load diff

View file

@ -12,6 +12,7 @@
// bytecode compaction and offset rewriting). // bytecode compaction and offset rewriting).
#include "strip.h" #include "strip.h"
#include "basEvents.h"
#include "../runtime/values.h" #include "../runtime/values.h"
#include <stdio.h> #include <stdio.h>
@ -21,8 +22,9 @@
// Events fired by name via basFormRtFireEvent* in formrt.c. Any proc // Events fired by name via basFormRtFireEvent* in formrt.c. Any proc
// ending in "_<EventName>" must keep its name so the dispatcher can // ending in "_<EventName>" must keep its name so the dispatcher can
// find it. // find it. Declared in basEvents.h; defined here as the single source
static const char *sEventSuffixes[] = { // of truth.
const char *basEventSuffixes[] = {
"Load", "Unload", "QueryUnload", "Resize", "Activate", "Deactivate", "Load", "Unload", "QueryUnload", "Resize", "Activate", "Deactivate",
"Click", "DblClick", "Change", "Timer", "Click", "DblClick", "Change", "Timer",
"GotFocus", "LostFocus", "GotFocus", "LostFocus",
@ -33,36 +35,10 @@ static const char *sEventSuffixes[] = {
}; };
static bool nameEndsWithEventSuffix(const char *name) { // Function prototypes (alphabetical)
const char *underscore = strrchr(name, '_'); void basStripModule(BasModuleT *mod);
static bool nameEndsWithEventSuffix(const char *name);
if (!underscore) { static bool nameInConstantPool(const BasModuleT *mod, const char *name);
return false;
}
const char *suffix = underscore + 1;
for (int32_t i = 0; sEventSuffixes[i]; i++) {
if (strcasecmp(suffix, sEventSuffixes[i]) == 0) {
return true;
}
}
return false;
}
static bool nameInConstantPool(const BasModuleT *mod, const char *name) {
for (int32_t i = 0; i < mod->constCount; i++) {
const BasStringT *s = mod->constants[i];
if (s && strcasecmp(s->data, name) == 0) {
return true;
}
}
return false;
}
void basStripModule(BasModuleT *mod) { void basStripModule(BasModuleT *mod) {
@ -109,3 +85,35 @@ void basStripModule(BasModuleT *mod) {
snprintf(proc->name, sizeof(proc->name), "F%ld", (long)nextMangled++); snprintf(proc->name, sizeof(proc->name), "F%ld", (long)nextMangled++);
} }
} }
static bool nameEndsWithEventSuffix(const char *name) {
const char *underscore = strrchr(name, '_');
if (!underscore) {
return false;
}
const char *suffix = underscore + 1;
for (int32_t i = 0; basEventSuffixes[i]; i++) {
if (strcasecmp(suffix, basEventSuffixes[i]) == 0) {
return true;
}
}
return false;
}
static bool nameInConstantPool(const BasModuleT *mod, const char *name) {
for (int32_t i = 0; i < mod->constCount; i++) {
const BasStringT *s = mod->constants[i];
if (s && strcasecmp(s->data, name) == 0) {
return true;
}
}
return false;
}

View file

@ -7,30 +7,18 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
// ============================================================
// Case-insensitive name comparison
// ============================================================
static bool namesEqual(const char *a, const char *b) { // Function prototypes (alphabetical)
while (*a && *b) { BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType);
char ca = *a >= 'a' && *a <= 'z' ? *a - 32 : *a; int32_t basSymTabAllocSlot(BasSymTabT *tab);
char cb = *b >= 'a' && *b <= 'z' ? *b - 32 : *b; void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName);
void basSymTabEnterLocal(BasSymTabT *tab);
if (ca != cb) { BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name);
return false; BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name);
} void basSymTabInit(BasSymTabT *tab);
int32_t basSymTabLeaveFormScope(BasSymTabT *tab);
a++; void basSymTabLeaveLocal(BasSymTabT *tab);
b++; static bool namesEqual(const char *a, const char *b);
}
return *a == *b;
}
// ============================================================
// basSymTabAdd
// ============================================================
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) { BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) {
// Determine scope: local > form > global. // Determine scope: local > form > global.
@ -47,20 +35,21 @@ BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, ui
// Check for duplicate in current scope (skip ended form symbols) // Check for duplicate in current scope (skip ended form symbols)
for (int32_t i = 0; i < tab->count; i++) { for (int32_t i = 0; i < tab->count; i++) {
if (tab->symbols[i].formScopeEnded) { if (tab->symbols[i]->formScopeEnded) {
continue; continue;
} }
if (tab->symbols[i].scope == scope && namesEqual(tab->symbols[i].name, name)) { if (tab->symbols[i]->scope == scope && namesEqual(tab->symbols[i]->name, name)) {
return NULL; // duplicate return NULL; // duplicate
} }
} }
BasSymbolT entry; BasSymbolT *sym = (BasSymbolT *)calloc(1, sizeof(BasSymbolT));
memset(&entry, 0, sizeof(entry));
arrput(tab->symbols, entry); if (!sym) {
tab->count = (int32_t)arrlen(tab->symbols); return NULL;
BasSymbolT *sym = &tab->symbols[tab->count - 1]; }
strncpy(sym->name, name, BAS_MAX_SYMBOL_NAME - 1); strncpy(sym->name, name, BAS_MAX_SYMBOL_NAME - 1);
sym->name[BAS_MAX_SYMBOL_NAME - 1] = '\0'; sym->name[BAS_MAX_SYMBOL_NAME - 1] = '\0';
sym->kind = kind; sym->kind = kind;
@ -73,14 +62,12 @@ BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, ui
sym->formName[BAS_MAX_SYMBOL_NAME - 1] = '\0'; sym->formName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
} }
arrput(tab->symbols, sym);
tab->count = (int32_t)arrlen(tab->symbols);
return sym; return sym;
} }
// ============================================================
// basSymTabAllocSlot
// ============================================================
int32_t basSymTabAllocSlot(BasSymTabT *tab) { int32_t basSymTabAllocSlot(BasSymTabT *tab) {
if (tab->inLocalScope) { if (tab->inLocalScope) {
return tab->nextLocalIdx++; return tab->nextLocalIdx++;
@ -94,9 +81,14 @@ int32_t basSymTabAllocSlot(BasSymTabT *tab) {
} }
// ============================================================ void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName) {
// basSymTabEnterLocal tab->inFormScope = true;
// ============================================================ strncpy(tab->formScopeName, formName, BAS_MAX_SYMBOL_NAME - 1);
tab->formScopeName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
tab->nextFormVarIdx = 0;
tab->formScopeSymStart = tab->count;
}
void basSymTabEnterLocal(BasSymTabT *tab) { void basSymTabEnterLocal(BasSymTabT *tab) {
tab->inLocalScope = true; tab->inLocalScope = true;
@ -104,29 +96,25 @@ void basSymTabEnterLocal(BasSymTabT *tab) {
} }
// ============================================================
// basSymTabFind
// ============================================================
BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) { BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) {
// Search local scope first // Search local scope first
if (tab->inLocalScope) { if (tab->inLocalScope) {
for (int32_t i = tab->count - 1; i >= 0; i--) { for (int32_t i = tab->count - 1; i >= 0; i--) {
if (tab->symbols[i].scope == SCOPE_LOCAL && namesEqual(tab->symbols[i].name, name)) { if (tab->symbols[i]->scope == SCOPE_LOCAL && namesEqual(tab->symbols[i]->name, name)) {
return &tab->symbols[i]; return tab->symbols[i];
} }
} }
} }
// Search form scope and global scope // Search form scope and global scope
for (int32_t i = tab->count - 1; i >= 0; i--) { for (int32_t i = tab->count - 1; i >= 0; i--) {
if (tab->symbols[i].formScopeEnded) { if (tab->symbols[i]->formScopeEnded) {
continue; continue;
} }
if ((tab->symbols[i].scope == SCOPE_FORM || tab->symbols[i].scope == SCOPE_GLOBAL) && if ((tab->symbols[i]->scope == SCOPE_FORM || tab->symbols[i]->scope == SCOPE_GLOBAL) &&
namesEqual(tab->symbols[i].name, name)) { namesEqual(tab->symbols[i]->name, name)) {
return &tab->symbols[i]; return tab->symbols[i];
} }
} }
@ -134,18 +122,14 @@ BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) {
} }
// ============================================================
// basSymTabFindGlobal
// ============================================================
BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) { BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) {
for (int32_t i = 0; i < tab->count; i++) { for (int32_t i = 0; i < tab->count; i++) {
if (tab->symbols[i].formScopeEnded) { if (tab->symbols[i]->formScopeEnded) {
continue; continue;
} }
if (tab->symbols[i].scope == SCOPE_GLOBAL && namesEqual(tab->symbols[i].name, name)) { if (tab->symbols[i]->scope == SCOPE_GLOBAL && namesEqual(tab->symbols[i]->name, name)) {
return &tab->symbols[i]; return tab->symbols[i];
} }
} }
@ -153,27 +137,40 @@ BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) {
} }
// ============================================================
// basSymTabInit
// ============================================================
void basSymTabInit(BasSymTabT *tab) { void basSymTabInit(BasSymTabT *tab) {
memset(tab, 0, sizeof(*tab)); memset(tab, 0, sizeof(*tab));
} }
// ============================================================ int32_t basSymTabLeaveFormScope(BasSymTabT *tab) {
// basSymTabLeaveLocal int32_t varCount = tab->nextFormVarIdx;
// ============================================================
// Mark all form-scope symbols added since BEGINFORM as ended
for (int32_t i = tab->formScopeSymStart; i < tab->count; i++) {
if (tab->symbols[i]->scope == SCOPE_FORM) {
tab->symbols[i]->formScopeEnded = true;
}
}
tab->inFormScope = false;
tab->formScopeName[0] = '\0';
tab->nextFormVarIdx = 0;
tab->formScopeSymStart = 0;
return varCount;
}
void basSymTabLeaveLocal(BasSymTabT *tab) { void basSymTabLeaveLocal(BasSymTabT *tab) {
// Remove all local symbols, freeing their dynamic arrays // Remove all local symbols, freeing their dynamic arrays and the
// symbol struct itself.
int32_t newCount = 0; int32_t newCount = 0;
for (int32_t i = 0; i < tab->count; i++) { for (int32_t i = 0; i < tab->count; i++) {
if (tab->symbols[i].scope == SCOPE_LOCAL) { if (tab->symbols[i]->scope == SCOPE_LOCAL) {
arrfree(tab->symbols[i].patchAddrs); arrfree(tab->symbols[i]->patchAddrs);
arrfree(tab->symbols[i].fields); arrfree(tab->symbols[i]->fields);
free(tab->symbols[i]);
} else { } else {
if (i != newCount) { if (i != newCount) {
tab->symbols[newCount] = tab->symbols[i]; tab->symbols[newCount] = tab->symbols[i];
@ -191,36 +188,20 @@ void basSymTabLeaveLocal(BasSymTabT *tab) {
// ============================================================ // ============================================================
// basSymTabEnterFormScope // Case-insensitive name comparison
// ============================================================ // ============================================================
static bool namesEqual(const char *a, const char *b) {
while (*a && *b) {
char ca = *a >= 'a' && *a <= 'z' ? *a - 32 : *a;
char cb = *b >= 'a' && *b <= 'z' ? *b - 32 : *b;
void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName) { if (ca != cb) {
tab->inFormScope = true; return false;
strncpy(tab->formScopeName, formName, BAS_MAX_SYMBOL_NAME - 1);
tab->formScopeName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
tab->nextFormVarIdx = 0;
tab->formScopeSymStart = tab->count;
}
// ============================================================
// basSymTabLeaveFormScope
// ============================================================
int32_t basSymTabLeaveFormScope(BasSymTabT *tab) {
int32_t varCount = tab->nextFormVarIdx;
// Mark all form-scope symbols added since BEGINFORM as ended
for (int32_t i = tab->formScopeSymStart; i < tab->count; i++) {
if (tab->symbols[i].scope == SCOPE_FORM) {
tab->symbols[i].formScopeEnded = true;
} }
a++;
b++;
} }
tab->inFormScope = false; return *a == *b;
tab->formScopeName[0] = '\0';
tab->nextFormVarIdx = 0;
tab->formScopeSymStart = 0;
return varCount;
} }

View file

@ -98,7 +98,13 @@ typedef struct {
// ============================================================ // ============================================================
typedef struct { typedef struct {
BasSymbolT *symbols; // stb_ds dynamic array // Array of POINTERS to heap-allocated symbols (stb_ds array of pointers).
// Pointers-of-pointers rather than array-of-structs because callers
// routinely hold a BasSymbolT * across parsing operations that may
// trigger basSymTabAdd (which grows this array). An array of structs
// would be reallocated on growth, invalidating any held pointer.
// Indirection via a stable heap pointer per symbol avoids that.
BasSymbolT **symbols;
int32_t count; int32_t count;
int32_t nextGlobalIdx; // next global variable slot int32_t nextGlobalIdx; // next global variable slot
int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION) int32_t nextLocalIdx; // next local variable slot (reset per SUB/FUNCTION)

View file

@ -1,7 +1,9 @@
# dvxbasic.res -- Resource manifest for DVX BASIC # dvxbasic.res -- Resource manifest for DVX BASIC
icon32 icon icon32.bmp icon32 icon icon32.bmp
name text "DVX BASIC" name text "DVX BASIC"
author text "DVX Project" author text "Scott Duensing"
copyright text "Copyright 2026 Scott Duensing"
publisher text "Kangaroo Punch Studios"
description text "BASIC language IDE and runtime" description text "BASIC language IDE and runtime"
# Toolbar icons (16x16) # Toolbar icons (16x16)
tb_open icon tb_open.bmp tb_open icon tb_open.bmp

File diff suppressed because it is too large Load diff

View file

@ -27,6 +27,7 @@ typedef struct BasControlT BasControlT;
// ============================================================ // ============================================================
#define BAS_MAX_CTRL_NAME 32 #define BAS_MAX_CTRL_NAME 32
#define BAS_MAX_FRM_LINE_LEN 512
#define BAS_MAX_FRM_NESTING 16 #define BAS_MAX_FRM_NESTING 16
// ============================================================ // ============================================================

View file

@ -21,7 +21,6 @@
// Constants // Constants
// ============================================================ // ============================================================
#define FRM_LINE_MAX 512
#define DEFAULT_FORM_W 400 #define DEFAULT_FORM_W 400
#define DEFAULT_FORM_H 300 #define DEFAULT_FORM_H 300
#define DEFAULT_CTRL_W 100 #define DEFAULT_CTRL_W 100
@ -41,21 +40,102 @@ static const char *FORM_DEFAULT_EVENT = "Load";
// dsgnCreateDesignWidget is declared in ideDesigner.h (non-static) // dsgnCreateDesignWidget is declared in ideDesigner.h (non-static)
static const char *getPropValue(const DsgnControlT *ctrl, const char *name); static const char *getPropValue(const DsgnControlT *ctrl, const char *name);
static DsgnHandleE hitTestHandles(const DsgnControlT *ctrl, int32_t x, int32_t y);
static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y); static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y);
static const char *resolveTypeName(const char *typeName); static DsgnHandleE hitTestHandles(const DsgnControlT *ctrl, int32_t x, int32_t y);
static void setPropValue(DsgnControlT *ctrl, const char *name, const char *value);
static void rebuildWidgets(DsgnStateT *ds); static void rebuildWidgets(DsgnStateT *ds);
static const char *resolveTypeName(const char *typeName);
static int32_t saveControls(const DsgnFormT *form, char *buf, int32_t bufSize, int32_t pos, const char *parentName, int32_t indent);
static void setPropValue(DsgnControlT *ctrl, const char *name, const char *value);
static void syncWidgetGeom(DsgnControlT *ctrl); static void syncWidgetGeom(DsgnControlT *ctrl);
// ============================================================ void dsgnAutoName(const DsgnStateT *ds, const char *typeName, char *buf, int32_t bufSize) {
// dsgnCreateDesignWidget // Look up the name prefix from the widget interface descriptor.
// ============================================================ // Falls back to the type name itself if no prefix is registered.
// const char *prefix = typeName;
const char *wgtName = wgtFindByBasName(typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface && iface->namePrefix) {
prefix = iface->namePrefix;
}
}
int32_t highest = 0;
int32_t prefixLen = (int32_t)strlen(prefix);
int32_t count = ds->form ? (int32_t)arrlen(ds->form->controls) : 0;
for (int32_t i = 0; i < count; i++) {
if (strncasecmp(ds->form->controls[i]->name, prefix, prefixLen) == 0) {
int32_t num = atoi(ds->form->controls[i]->name + prefixLen);
if (num > highest) {
highest = num;
}
}
}
snprintf(buf, bufSize, "%s%d", prefix, (int)(highest + 1));
}
void dsgnBuildPreviewMenuBar(WindowT *win, const DsgnFormT *form) {
if (!win || !form) {
return;
}
int32_t menuCount = (int32_t)arrlen(form->menuItems);
if (menuCount <= 0) {
return;
}
MenuBarT *bar = wmAddMenuBar(win);
if (!bar) {
return;
}
MenuT *menuStack[8];
memset(menuStack, 0, sizeof(menuStack));
for (int32_t i = 0; i < menuCount; i++) {
const DsgnMenuItemT *mi = &form->menuItems[i];
bool isSep = (mi->caption[0] == '-');
bool isSubParent = (i + 1 < menuCount && form->menuItems[i + 1].level > mi->level);
if (mi->level == 0) {
menuStack[0] = wmAddMenu(bar, mi->caption);
} else if (isSep && mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
wmAddMenuSeparator(menuStack[mi->level - 1]);
} else if (isSubParent && mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
menuStack[mi->level] = wmAddSubMenu(menuStack[mi->level - 1], mi->caption);
} else if (mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
int32_t id = DSGN_MENU_ID_BASE + i;
if (mi->radioCheck) {
wmAddMenuRadioItem(menuStack[mi->level - 1], mi->caption, id, mi->checked);
} else if (mi->checked) {
wmAddMenuCheckItem(menuStack[mi->level - 1], mi->caption, id, true);
} else {
wmAddMenuItem(menuStack[mi->level - 1], mi->caption, id);
}
}
}
}
// dsgnCreateFormWindow / dsgnCreateContentBox
// Thin wrappers around formrt functions for backward compatibility.
WidgetT *dsgnCreateContentBox(WidgetT *root, const char *layout) {
return basFormRtCreateContentBox(root, layout);
}
// Create a real DVX widget for design-time display. Mirrors the // Create a real DVX widget for design-time display. Mirrors the
// logic in formrt.c createWidget(). // logic in formrt.c createWidget().
WidgetT *dsgnCreateDesignWidget(const char *vbTypeName, WidgetT *parent) { WidgetT *dsgnCreateDesignWidget(const char *vbTypeName, WidgetT *parent) {
const char *wgtName = resolveTypeName(vbTypeName); const char *wgtName = resolveTypeName(vbTypeName);
@ -116,111 +196,11 @@ WidgetT *dsgnCreateDesignWidget(const char *vbTypeName, WidgetT *parent) {
} }
// ============================================================
// dsgnCreateFormWindow / dsgnCreateContentBox
// Thin wrappers around formrt functions for backward compatibility.
// ============================================================
WidgetT *dsgnCreateContentBox(WidgetT *root, const char *layout) {
return basFormRtCreateContentBox(root, layout);
}
WindowT *dsgnCreateFormWindow(AppContextT *ctx, const char *title, const char *layout, bool resizable, bool centered, bool autoSize, int32_t width, int32_t height, int32_t left, int32_t top, WidgetT **outRoot, WidgetT **outContentBox) { WindowT *dsgnCreateFormWindow(AppContextT *ctx, const char *title, const char *layout, bool resizable, bool centered, bool autoSize, int32_t width, int32_t height, int32_t left, int32_t top, WidgetT **outRoot, WidgetT **outContentBox) {
return basFormRtCreateFormWindow(ctx, title, layout, resizable, centered, autoSize, width, height, left, top, outRoot, outContentBox); return basFormRtCreateFormWindow(ctx, title, layout, resizable, centered, autoSize, width, height, left, top, outRoot, outContentBox);
} }
// ============================================================
// dsgnBuildPreviewMenuBar
// ============================================================
void dsgnBuildPreviewMenuBar(WindowT *win, const DsgnFormT *form) {
if (!win || !form) {
return;
}
int32_t menuCount = (int32_t)arrlen(form->menuItems);
if (menuCount <= 0) {
return;
}
MenuBarT *bar = wmAddMenuBar(win);
if (!bar) {
return;
}
MenuT *menuStack[8];
memset(menuStack, 0, sizeof(menuStack));
for (int32_t i = 0; i < menuCount; i++) {
const DsgnMenuItemT *mi = &form->menuItems[i];
bool isSep = (mi->caption[0] == '-');
bool isSubParent = (i + 1 < menuCount && form->menuItems[i + 1].level > mi->level);
if (mi->level == 0) {
menuStack[0] = wmAddMenu(bar, mi->caption);
} else if (isSep && mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
wmAddMenuSeparator(menuStack[mi->level - 1]);
} else if (isSubParent && mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
menuStack[mi->level] = wmAddSubMenu(menuStack[mi->level - 1], mi->caption);
} else if (mi->level > 0 && mi->level < 8 && menuStack[mi->level - 1]) {
int32_t id = DSGN_MENU_ID_BASE + i;
if (mi->radioCheck) {
wmAddMenuRadioItem(menuStack[mi->level - 1], mi->caption, id, mi->checked);
} else if (mi->checked) {
wmAddMenuCheckItem(menuStack[mi->level - 1], mi->caption, id, true);
} else {
wmAddMenuItem(menuStack[mi->level - 1], mi->caption, id);
}
}
}
}
// ============================================================
// dsgnAutoName
// ============================================================
void dsgnAutoName(const DsgnStateT *ds, const char *typeName, char *buf, int32_t bufSize) {
// Look up the name prefix from the widget interface descriptor.
// Falls back to the type name itself if no prefix is registered.
const char *prefix = typeName;
const char *wgtName = wgtFindByBasName(typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface && iface->namePrefix) {
prefix = iface->namePrefix;
}
}
int32_t highest = 0;
int32_t prefixLen = (int32_t)strlen(prefix);
int32_t count = ds->form ? (int32_t)arrlen(ds->form->controls) : 0;
for (int32_t i = 0; i < count; i++) {
if (strncasecmp(ds->form->controls[i]->name, prefix, prefixLen) == 0) {
int32_t num = atoi(ds->form->controls[i]->name + prefixLen);
if (num > highest) {
highest = num;
}
}
}
snprintf(buf, bufSize, "%s%d", prefix, (int)(highest + 1));
}
// ============================================================
// dsgnCreateWidgets
// ============================================================
void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) { void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) {
if (!ds->form || !contentBox) { if (!ds->form || !contentBox) {
return; return;
@ -342,29 +322,6 @@ void dsgnCreateWidgets(DsgnStateT *ds, WidgetT *contentBox) {
} }
// ============================================================
// dsgnIsContainer
// ============================================================
bool dsgnIsContainer(const char *typeName) {
const char *wgtName = wgtFindByBasName(typeName);
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface) {
return iface->isContainer;
}
}
return false;
}
// ============================================================
// dsgnDefaultEvent
// ============================================================
const char *dsgnDefaultEvent(const char *typeName) { const char *dsgnDefaultEvent(const char *typeName) {
if (strcasecmp(typeName, "Form") == 0) { if (strcasecmp(typeName, "Form") == 0) {
return FORM_DEFAULT_EVENT; return FORM_DEFAULT_EVENT;
@ -384,10 +341,6 @@ const char *dsgnDefaultEvent(const char *typeName) {
} }
// ============================================================
// dsgnFree
// ============================================================
void dsgnFree(DsgnStateT *ds) { void dsgnFree(DsgnStateT *ds) {
if (ds->form) { if (ds->form) {
for (int32_t i = 0; i < arrlen(ds->form->controls); i++) { for (int32_t i = 0; i < arrlen(ds->form->controls); i++) {
@ -402,10 +355,6 @@ void dsgnFree(DsgnStateT *ds) {
} }
// ============================================================
// dsgnInit
// ============================================================
void dsgnInit(DsgnStateT *ds, AppContextT *ctx) { void dsgnInit(DsgnStateT *ds, AppContextT *ctx) {
memset(ds, 0, sizeof(*ds)); memset(ds, 0, sizeof(*ds));
ds->selectedIdx = -1; ds->selectedIdx = -1;
@ -416,9 +365,20 @@ void dsgnInit(DsgnStateT *ds, AppContextT *ctx) {
} }
// ============================================================ bool dsgnIsContainer(const char *typeName) {
// dsgnLoadFrm const char *wgtName = wgtFindByBasName(typeName);
// ============================================================
if (wgtName) {
const WgtIfaceT *iface = wgtGetIface(wgtName);
if (iface) {
return iface->isContainer;
}
}
return false;
}
bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) { bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
if (!source || sourceLen <= 0) { if (!source || sourceLen <= 0) {
@ -473,20 +433,16 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
if (pos < end && *pos == '\r') { pos++; } if (pos < end && *pos == '\r') { pos++; }
if (pos < end && *pos == '\n') { pos++; } if (pos < end && *pos == '\n') { pos++; }
char line[FRM_LINE_MAX]; char line[BAS_MAX_FRM_LINE_LEN];
if (lineLen >= FRM_LINE_MAX) { if (lineLen >= BAS_MAX_FRM_LINE_LEN) {
lineLen = FRM_LINE_MAX - 1; lineLen = BAS_MAX_FRM_LINE_LEN - 1;
} }
memcpy(line, lineStart, lineLen); memcpy(line, lineStart, lineLen);
line[lineLen] = '\0'; line[lineLen] = '\0';
char *trimmed = line; const char *trimmed = dvxSkipWs(line);
while (*trimmed == ' ' || *trimmed == '\t') {
trimmed++;
}
if (*trimmed == '\0' || *trimmed == '\'') { if (*trimmed == '\0' || *trimmed == '\'') {
continue; continue;
@ -499,9 +455,9 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
const char *ver = trimmed + 8; const char *ver = trimmed + 8;
if (strncasecmp(ver, "DVX ", 4) == 0) { if (strncasecmp(ver, "DVX ", 4) == 0) {
// Native DVX BASIC form always accepted // Native DVX BASIC form -- always accepted
} else { } else {
// VB form check version number // VB form -- check version number
double vbVer = atof(ver); double vbVer = atof(ver);
if (vbVer > 2.0) { if (vbVer > 2.0) {
@ -514,7 +470,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
// Begin TypeName CtrlName // Begin TypeName CtrlName
if (strncasecmp(trimmed, "Begin ", 6) == 0) { if (strncasecmp(trimmed, "Begin ", 6) == 0) {
char *rest = trimmed + 6; const char *rest = trimmed + 6;
char typeName[DSGN_MAX_NAME]; char typeName[DSGN_MAX_NAME];
char ctrlName[DSGN_MAX_NAME]; char ctrlName[DSGN_MAX_NAME];
int32_t ti = 0; int32_t ti = 0;
@ -525,7 +481,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
typeName[ti] = '\0'; typeName[ti] = '\0';
while (*rest == ' ' || *rest == '\t') { rest++; } rest = dvxSkipWs(rest);
int32_t ci = 0; int32_t ci = 0;
@ -634,11 +590,11 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
} }
// Property = Value // Property = Value
char *eq = strchr(trimmed, '='); const char *eq = strchr(trimmed, '=');
if (eq && inForm) { if (eq && inForm) {
char key[DSGN_MAX_NAME]; char key[DSGN_MAX_NAME];
char *kend = eq - 1; const char *kend = eq - 1;
while (kend > trimmed && (*kend == ' ' || *kend == '\t')) { kend--; } while (kend > trimmed && (*kend == ' ' || *kend == '\t')) { kend--; }
@ -649,9 +605,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
memcpy(key, trimmed, klen); memcpy(key, trimmed, klen);
key[klen] = '\0'; key[klen] = '\0';
char *vstart = eq + 1; const char *vstart = dvxSkipWs(eq + 1);
while (*vstart == ' ' || *vstart == '\t') { vstart++; }
char val[DSGN_MAX_TEXT]; char val[DSGN_MAX_TEXT];
int32_t vi = 0; int32_t vi = 0;
@ -712,10 +666,6 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) {
} }
// ============================================================
// dsgnNewForm
// ============================================================
void dsgnNewForm(DsgnStateT *ds, const char *name) { void dsgnNewForm(DsgnStateT *ds, const char *name) {
dsgnFree(ds); dsgnFree(ds);
@ -737,10 +687,6 @@ void dsgnNewForm(DsgnStateT *ds, const char *name) {
} }
// ============================================================
// dsgnOnKey
// ============================================================
void dsgnOnKey(DsgnStateT *ds, int32_t key) { void dsgnOnKey(DsgnStateT *ds, int32_t key) {
if (!ds->form) { if (!ds->form) {
return; return;
@ -774,10 +720,6 @@ void dsgnOnKey(DsgnStateT *ds, int32_t key) {
} }
// ============================================================
// dsgnOnMouse
// ============================================================
void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) { void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
if (!ds->form) { if (!ds->form) {
return; return;
@ -947,13 +889,8 @@ void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) {
} }
// ============================================================
// dsgnPaint
// ============================================================
//
// Draw grid dots and selection handles over the live widgets. // Draw grid dots and selection handles over the live widgets.
// Called from the form window's onPaint callback. // Called from the form window's onPaint callback.
void dsgnPaint(DsgnStateT *ds) { void dsgnPaint(DsgnStateT *ds) {
if (!ds->form || !ds->ctx) { if (!ds->form || !ds->ctx) {
return; return;
@ -968,13 +905,8 @@ void dsgnPaint(DsgnStateT *ds) {
} }
// ============================================================
// dsgnPaintOverlay
// ============================================================
//
// Draw selection handles on the window's painted surface. // Draw selection handles on the window's painted surface.
// Called after widgets have painted, using direct display drawing. // Called after widgets have painted, using direct display drawing.
void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY) { void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY) {
(void)winX; (void)winX;
(void)winY; (void)winY;
@ -1028,133 +960,6 @@ void dsgnPaintOverlay(DsgnStateT *ds, int32_t winX, int32_t winY) {
} }
// ============================================================
// dsgnSaveFrm
// ============================================================
// Write controls at a given nesting level with the specified parent name.
static int32_t saveControls(const DsgnFormT *form, char *buf, int32_t bufSize, int32_t pos, const char *parentName, int32_t indent) {
int32_t count = (int32_t)arrlen(form->controls);
for (int32_t i = 0; i < count; i++) {
const DsgnControlT *ctrl = form->controls[i];
// Only output controls whose parent matches
if (parentName[0] == '\0' && ctrl->parentName[0] != '\0') { continue; }
if (parentName[0] != '\0' && strcasecmp(ctrl->parentName, parentName) != 0) { continue; }
// Indent
char pad[32];
int32_t padLen = indent * 4;
if (padLen > 31) { padLen = 31; }
memset(pad, ' ', padLen);
pad[padLen] = '\0';
pos += snprintf(buf + pos, bufSize - pos, "%sBegin %s %s\n", pad, ctrl->typeName, ctrl->name);
if (ctrl->index >= 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s Index = %d\n", pad, (int)ctrl->index);
}
const char *caption = getPropValue(ctrl, "Caption");
const char *text = getPropValue(ctrl, "Text");
if (caption) { pos += snprintf(buf + pos, bufSize - pos, "%s Caption = \"%s\"\n", pad, caption); }
if (text) { pos += snprintf(buf + pos, bufSize - pos, "%s Text = \"%s\"\n", pad, text); }
pos += snprintf(buf + pos, bufSize - pos, "%s Left = %d\n", pad, (int)ctrl->left);
pos += snprintf(buf + pos, bufSize - pos, "%s Top = %d\n", pad, (int)ctrl->top);
pos += snprintf(buf + pos, bufSize - pos, "%s MinWidth = %d\n", pad, (int)ctrl->width);
pos += snprintf(buf + pos, bufSize - pos, "%s MinHeight = %d\n", pad, (int)ctrl->height);
if (ctrl->maxWidth > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s MaxWidth = %d\n", pad, (int)ctrl->maxWidth);
}
if (ctrl->maxHeight > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s MaxHeight = %d\n", pad, (int)ctrl->maxHeight);
}
if (ctrl->weight > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s Weight = %d\n", pad, (int)ctrl->weight);
}
if (ctrl->helpTopic[0]) {
pos += snprintf(buf + pos, bufSize - pos, "%s HelpTopic = \"%s\"\n", pad, ctrl->helpTopic);
}
for (int32_t j = 0; j < ctrl->propCount; j++) {
if (strcasecmp(ctrl->props[j].name, "Caption") == 0) { continue; }
if (strcasecmp(ctrl->props[j].name, "Text") == 0) { continue; }
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 || !p->setFn) {
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);
}
pos += snprintf(buf + pos, bufSize - pos, "%sEnd\n", pad);
}
return pos;
}
int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) { int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
if (!ds->form || !buf || bufSize <= 0) { if (!ds->form || !buf || bufSize <= 0) {
return -1; return -1;
@ -1290,10 +1095,6 @@ int32_t dsgnSaveFrm(const DsgnStateT *ds, char *buf, int32_t bufSize) {
} }
// ============================================================
// dsgnSelectedName
// ============================================================
const char *dsgnSelectedName(const DsgnStateT *ds) { const char *dsgnSelectedName(const DsgnStateT *ds) {
if (!ds->form) { if (!ds->form) {
return ""; return "";
@ -1307,14 +1108,6 @@ const char *dsgnSelectedName(const DsgnStateT *ds) {
} }
// ============================================================
// getPropValue
// ============================================================
static const char *getPropValue(const DsgnControlT *ctrl, const char *name) { static const char *getPropValue(const DsgnControlT *ctrl, const char *name) {
for (int32_t i = 0; i < ctrl->propCount; i++) { for (int32_t i = 0; i < ctrl->propCount; i++) {
if (strcasecmp(ctrl->props[i].name, name) == 0) { if (strcasecmp(ctrl->props[i].name, name) == 0) {
@ -1326,10 +1119,6 @@ static const char *getPropValue(const DsgnControlT *ctrl, const char *name) {
} }
// ============================================================
// hitTestControl
// ============================================================
static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y) { static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y) {
int32_t count = (int32_t)arrlen(ds->form->controls); int32_t count = (int32_t)arrlen(ds->form->controls);
@ -1355,10 +1144,6 @@ static int32_t hitTestControl(const DsgnStateT *ds, int32_t x, int32_t y) {
} }
// ============================================================
// hitTestHandles
// ============================================================
static DsgnHandleE hitTestHandles(const DsgnControlT *ctrl, int32_t x, int32_t y) { static DsgnHandleE hitTestHandles(const DsgnControlT *ctrl, int32_t x, int32_t y) {
if (!ctrl->widget) { if (!ctrl->widget) {
return HANDLE_NONE; return HANDLE_NONE;
@ -1384,55 +1169,9 @@ static DsgnHandleE hitTestHandles(const DsgnControlT *ctrl, int32_t x, int32_t y
} }
// ============================================================
// resolveTypeName
// ============================================================
static const char *resolveTypeName(const char *typeName) {
const char *wgtName = wgtFindByBasName(typeName);
if (wgtName) {
return wgtName;
}
if (wgtGetApi(typeName)) {
return typeName;
}
return NULL;
}
// ============================================================
// setPropValue
// ============================================================
static void setPropValue(DsgnControlT *ctrl, const char *name, const char *value) {
for (int32_t i = 0; i < ctrl->propCount; i++) {
if (strcasecmp(ctrl->props[i].name, name) == 0) {
snprintf(ctrl->props[i].value, DSGN_MAX_TEXT, "%s", value);
return;
}
}
if (ctrl->propCount < DSGN_MAX_PROPS) {
snprintf(ctrl->props[ctrl->propCount].name, DSGN_MAX_NAME, "%s", name);
snprintf(ctrl->props[ctrl->propCount].value, DSGN_MAX_TEXT, "%s", value);
ctrl->propCount++;
}
}
// ============================================================
// rebuildWidgets
// ============================================================
//
// Destroy all live widgets and recreate them in the current // Destroy all live widgets and recreate them in the current
// array order. This is the safest way to reorder since the // array order. This is the safest way to reorder since the
// widget tree child list matches the creation order. // widget tree child list matches the creation order.
static void rebuildWidgets(DsgnStateT *ds) { static void rebuildWidgets(DsgnStateT *ds) {
WidgetT *parent = ds->form ? ds->form->contentBox : NULL; WidgetT *parent = ds->form ? ds->form->contentBox : NULL;
@ -1457,12 +1196,160 @@ static void rebuildWidgets(DsgnStateT *ds) {
} }
// ============================================================ static const char *resolveTypeName(const char *typeName) {
// syncWidgetGeom const char *wgtName = wgtFindByBasName(typeName);
// ============================================================
//
// Update the live widget's position and size from the design data.
if (wgtName) {
return wgtName;
}
if (wgtGetApi(typeName)) {
return typeName;
}
return NULL;
}
// Write controls at a given nesting level with the specified parent name.
static int32_t saveControls(const DsgnFormT *form, char *buf, int32_t bufSize, int32_t pos, const char *parentName, int32_t indent) {
int32_t count = (int32_t)arrlen(form->controls);
for (int32_t i = 0; i < count; i++) {
const DsgnControlT *ctrl = form->controls[i];
// Only output controls whose parent matches
if (parentName[0] == '\0' && ctrl->parentName[0] != '\0') { continue; }
if (parentName[0] != '\0' && strcasecmp(ctrl->parentName, parentName) != 0) { continue; }
// Indent
char pad[32];
int32_t padLen = indent * 4;
if (padLen > 31) { padLen = 31; }
memset(pad, ' ', padLen);
pad[padLen] = '\0';
pos += snprintf(buf + pos, bufSize - pos, "%sBegin %s %s\n", pad, ctrl->typeName, ctrl->name);
if (ctrl->index >= 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s Index = %d\n", pad, (int)ctrl->index);
}
const char *caption = getPropValue(ctrl, "Caption");
const char *text = getPropValue(ctrl, "Text");
if (caption) { pos += snprintf(buf + pos, bufSize - pos, "%s Caption = \"%s\"\n", pad, caption); }
if (text) { pos += snprintf(buf + pos, bufSize - pos, "%s Text = \"%s\"\n", pad, text); }
pos += snprintf(buf + pos, bufSize - pos, "%s Left = %d\n", pad, (int)ctrl->left);
pos += snprintf(buf + pos, bufSize - pos, "%s Top = %d\n", pad, (int)ctrl->top);
pos += snprintf(buf + pos, bufSize - pos, "%s MinWidth = %d\n", pad, (int)ctrl->width);
pos += snprintf(buf + pos, bufSize - pos, "%s MinHeight = %d\n", pad, (int)ctrl->height);
if (ctrl->maxWidth > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s MaxWidth = %d\n", pad, (int)ctrl->maxWidth);
}
if (ctrl->maxHeight > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s MaxHeight = %d\n", pad, (int)ctrl->maxHeight);
}
if (ctrl->weight > 0) {
pos += snprintf(buf + pos, bufSize - pos, "%s Weight = %d\n", pad, (int)ctrl->weight);
}
if (ctrl->helpTopic[0]) {
pos += snprintf(buf + pos, bufSize - pos, "%s HelpTopic = \"%s\"\n", pad, ctrl->helpTopic);
}
for (int32_t j = 0; j < ctrl->propCount; j++) {
if (strcasecmp(ctrl->props[j].name, "Caption") == 0) { continue; }
if (strcasecmp(ctrl->props[j].name, "Text") == 0) { continue; }
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 || !p->setFn) {
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);
}
pos += snprintf(buf + pos, bufSize - pos, "%sEnd\n", pad);
}
return pos;
}
static void setPropValue(DsgnControlT *ctrl, const char *name, const char *value) {
for (int32_t i = 0; i < ctrl->propCount; i++) {
if (strcasecmp(ctrl->props[i].name, name) == 0) {
snprintf(ctrl->props[i].value, DSGN_MAX_TEXT, "%s", value);
return;
}
}
if (ctrl->propCount < DSGN_MAX_PROPS) {
snprintf(ctrl->props[ctrl->propCount].name, DSGN_MAX_NAME, "%s", name);
snprintf(ctrl->props[ctrl->propCount].value, DSGN_MAX_TEXT, "%s", value);
ctrl->propCount++;
}
}
// Update the live widget's position and size from the design data.
static void syncWidgetGeom(DsgnControlT *ctrl) { static void syncWidgetGeom(DsgnControlT *ctrl) {
if (!ctrl->widget) { if (!ctrl->widget) {
return; return;

File diff suppressed because it is too large Load diff

View file

@ -23,7 +23,6 @@
// Constants // Constants
// ============================================================ // ============================================================
#define MAX_MENU_ITEMS 128
#define MAX_MENU_LEVEL 5 #define MAX_MENU_LEVEL 5
#define ARROW_STR "-> " #define ARROW_STR "-> "
@ -54,32 +53,33 @@ typedef struct {
static MnuEdStateT sMed; static MnuEdStateT sMed;
// Dynamic per-refresh display buffers (stb_ds). The listBox stores the pointer
// array, so these must outlive each SetItems call. Freed on dialog teardown.
static char **sLabelBufs = NULL;
static const char **sLabels = NULL;
// ============================================================ // ============================================================
// Prototypes // Prototypes
// ============================================================ // ============================================================
static void applyFields(void); static void applyFields(void);
static int32_t findSubtreeEnd(int32_t idx); static int32_t findSubtreeEnd(int32_t idx);
static void loadFields(void); static void loadFields(void);
static void onCancel(WidgetT *w); static void onCancel(WidgetT *w);
static void onCaptionChange(WidgetT *w); static void onCaptionChange(WidgetT *w);
static void onDelete(WidgetT *w); static void onDelete(WidgetT *w);
static void onNameChange(WidgetT *w); static void onIndent(WidgetT *w);
static void onIndent(WidgetT *w); static void onInsert(WidgetT *w);
static void onInsert(WidgetT *w); static void onListClick(WidgetT *w);
static void onListClick(WidgetT *w); static void onMoveDown(WidgetT *w);
static void onMoveDown(WidgetT *w); static void onMoveUp(WidgetT *w);
static void onMoveUp(WidgetT *w); static void onNameChange(WidgetT *w);
static void onNext(WidgetT *w); static void onNext(WidgetT *w);
static void onOk(WidgetT *w); static void onOk(WidgetT *w);
static void onOutdent(WidgetT *w); static void onOutdent(WidgetT *w);
static void rebuildList(void); static void rebuildList(void);
// ============================================================
// applyFields -- save current widget values to selected item
// ============================================================
static void applyFields(void) { static void applyFields(void) {
int32_t count = (int32_t)arrlen(sMed.items); int32_t count = (int32_t)arrlen(sMed.items);
@ -150,10 +150,6 @@ static void applyFields(void) {
} }
// ============================================================
// findSubtreeEnd -- index past the last child of items[idx]
// ============================================================
static int32_t findSubtreeEnd(int32_t idx) { static int32_t findSubtreeEnd(int32_t idx) {
int32_t count = (int32_t)arrlen(sMed.items); int32_t count = (int32_t)arrlen(sMed.items);
int32_t level = sMed.items[idx].level; int32_t level = sMed.items[idx].level;
@ -167,14 +163,10 @@ static int32_t findSubtreeEnd(int32_t idx) {
} }
// ============================================================
// loadFields -- populate widgets from selected item
// ============================================================
static void loadFields(void) { static void loadFields(void) {
int32_t count = (int32_t)arrlen(sMed.items); int32_t count = (int32_t)arrlen(sMed.items);
// Reset auto-gen flag if the loaded item has an empty name, it's // Reset auto-gen flag -- if the loaded item has an empty name, it's
// eligible for auto-generation; if it has a name, the user set it. // eligible for auto-generation; if it has a name, the user set it.
sMed.nameAutoGen = false; sMed.nameAutoGen = false;
@ -198,417 +190,6 @@ static void loadFields(void) {
} }
// ============================================================
// onCancel
// ============================================================
static void onCancel(WidgetT *w) {
(void)w;
sMed.accepted = false;
sMed.done = true;
}
// ============================================================
// onCaptionChange -- caption field changed, auto-update name
// ============================================================
static void onCaptionChange(WidgetT *w) {
(void)w;
applyFields();
rebuildList();
}
// ============================================================
// onNameChange -- user manually edited the name field
// ============================================================
static void onNameChange(WidgetT *w) {
(void)w;
sMed.nameAutoGen = false; // user took over
applyFields();
rebuildList();
}
// ============================================================
// onDelete
// ============================================================
static void onDelete(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
if (sMed.selectedIdx < 0 || sMed.selectedIdx >= count) {
return;
}
applyFields();
// Delete selected item and its children
int32_t subEnd = findSubtreeEnd(sMed.selectedIdx);
for (int32_t i = subEnd - 1; i >= sMed.selectedIdx; i--) {
arrdel(sMed.items, i);
}
count = (int32_t)arrlen(sMed.items);
if (sMed.selectedIdx >= count) {
sMed.selectedIdx = count - 1;
}
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
}
// ============================================================
// onIndent -- increase level (make child of previous item)
// ============================================================
static void onIndent(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
int32_t idx = sMed.selectedIdx;
if (idx <= 0 || idx >= count) {
return;
}
applyFields();
// Can only indent if previous item is at same or higher level
int32_t prevLevel = sMed.items[idx - 1].level;
if (sMed.items[idx].level > prevLevel) {
return; // already deeper than prev
}
if (sMed.items[idx].level >= MAX_MENU_LEVEL) {
return;
}
// Indent this item and all its children
int32_t subEnd = findSubtreeEnd(idx);
for (int32_t i = idx; i < subEnd; i++) {
sMed.items[i].level++;
}
rebuildList();
}
// ============================================================
// onInsert
// ============================================================
static void onInsert(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
if (count >= MAX_MENU_ITEMS) {
return;
}
applyFields();
DsgnMenuItemT mi;
memset(&mi, 0, sizeof(mi));
mi.enabled = true;
// Insert after the current item's subtree, at the same level
int32_t insertAt;
if (sMed.selectedIdx >= 0 && sMed.selectedIdx < count) {
mi.level = sMed.items[sMed.selectedIdx].level;
insertAt = findSubtreeEnd(sMed.selectedIdx);
} else {
insertAt = count;
}
arrins(sMed.items, insertAt, mi);
sMed.selectedIdx = insertAt;
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
}
// ============================================================
// onListClick
// ============================================================
static void onListClick(WidgetT *w) {
int32_t prev = sMed.selectedIdx;
sMed.selectedIdx = wgtListBoxGetSelected(w);
if (prev != sMed.selectedIdx && prev >= 0 && prev < (int32_t)arrlen(sMed.items)) {
// Save fields from previously selected item
int32_t saveSel = sMed.selectedIdx;
sMed.selectedIdx = prev;
applyFields();
sMed.selectedIdx = saveSel;
}
loadFields();
}
// ============================================================
// onMoveDown
// ============================================================
static void onMoveDown(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
int32_t idx = sMed.selectedIdx;
if (idx < 0 || idx >= count) {
return;
}
applyFields();
int32_t subEnd = findSubtreeEnd(idx);
if (subEnd >= count) {
return; // already at bottom
}
// The item/subtree after us
int32_t nextEnd = findSubtreeEnd(subEnd);
// Rotate: move the next subtree before our subtree
// Simple approach: extract our subtree, delete it, insert after next subtree
int32_t subSize = subEnd - idx;
DsgnMenuItemT *tmp = (DsgnMenuItemT *)malloc(subSize * sizeof(DsgnMenuItemT));
memcpy(tmp, &sMed.items[idx], subSize * sizeof(DsgnMenuItemT));
// Delete our subtree
for (int32_t i = subEnd - 1; i >= idx; i--) {
arrdel(sMed.items, i);
}
// Insert after what was the next subtree (now shifted)
int32_t insertAt = nextEnd - subSize;
for (int32_t i = 0; i < subSize; i++) {
arrins(sMed.items, insertAt + i, tmp[i]);
}
free(tmp);
sMed.selectedIdx = insertAt;
rebuildList();
}
// ============================================================
// onMoveUp
// ============================================================
static void onMoveUp(WidgetT *w) {
(void)w;
int32_t idx = sMed.selectedIdx;
if (idx <= 0) {
return;
}
applyFields();
// Find the start of the previous subtree at the same or lower level
int32_t prevIdx = idx - 1;
while (prevIdx > 0 && sMed.items[prevIdx].level > sMed.items[idx].level) {
prevIdx--;
}
// Move our subtree before the previous item's subtree
int32_t subEnd = findSubtreeEnd(idx);
int32_t subSize = subEnd - idx;
DsgnMenuItemT *tmp = (DsgnMenuItemT *)malloc(subSize * sizeof(DsgnMenuItemT));
memcpy(tmp, &sMed.items[idx], subSize * sizeof(DsgnMenuItemT));
// Delete our subtree
for (int32_t i = subEnd - 1; i >= idx; i--) {
arrdel(sMed.items, i);
}
// Insert at prevIdx
for (int32_t i = 0; i < subSize; i++) {
arrins(sMed.items, prevIdx + i, tmp[i]);
}
free(tmp);
sMed.selectedIdx = prevIdx;
rebuildList();
}
// ============================================================
// onNext
// ============================================================
static void onNext(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
applyFields();
if (sMed.selectedIdx < count - 1) {
sMed.selectedIdx++;
} else {
// Append new item
if (count >= MAX_MENU_ITEMS) {
return;
}
DsgnMenuItemT mi;
memset(&mi, 0, sizeof(mi));
mi.enabled = true;
if (count > 0) {
mi.level = sMed.items[count - 1].level;
}
arrput(sMed.items, mi);
sMed.selectedIdx = (int32_t)arrlen(sMed.items) - 1;
}
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
}
// ============================================================
// onOk
// ============================================================
static void onOk(WidgetT *w) {
(void)w;
applyFields();
// Strip blank items (no caption and no name)
for (int32_t i = (int32_t)arrlen(sMed.items) - 1; i >= 0; i--) {
if (sMed.items[i].caption[0] == '\0' && sMed.items[i].name[0] == '\0') {
arrdel(sMed.items, i);
}
}
// Validate: check names are non-empty and unique
int32_t count = (int32_t)arrlen(sMed.items);
for (int32_t i = 0; i < count; i++) {
if (sMed.items[i].name[0] == '\0') {
dvxMessageBox(sMed.ctx, "Menu Editor", "All menu items must have a Name.", MB_OK | MB_ICONERROR);
sMed.selectedIdx = i;
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
return;
}
for (int32_t j = i + 1; j < count; j++) {
if (strcasecmp(sMed.items[i].name, sMed.items[j].name) == 0) {
char msg[128];
snprintf(msg, sizeof(msg), "Duplicate menu name: %s", sMed.items[i].name);
dvxMessageBox(sMed.ctx, "Menu Editor", msg, MB_OK | MB_ICONERROR);
sMed.selectedIdx = j;
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
return;
}
}
}
// Copy working items back to form
arrfree(sMed.form->menuItems);
sMed.form->menuItems = NULL;
for (int32_t i = 0; i < count; i++) {
arrput(sMed.form->menuItems, sMed.items[i]);
}
sMed.accepted = true;
sMed.done = true;
}
// ============================================================
// onOutdent -- decrease level
// ============================================================
static void onOutdent(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
int32_t idx = sMed.selectedIdx;
if (idx < 0 || idx >= count) {
return;
}
if (sMed.items[idx].level <= 0) {
return;
}
applyFields();
int32_t subEnd = findSubtreeEnd(idx);
for (int32_t i = idx; i < subEnd; i++) {
sMed.items[i].level--;
}
rebuildList();
}
// ============================================================
// rebuildList -- refresh the listbox display
// ============================================================
static void rebuildList(void) {
int32_t count = (int32_t)arrlen(sMed.items);
// Build display strings
static const char *sLabels[MAX_MENU_ITEMS];
static char sLabelBufs[MAX_MENU_ITEMS][DSGN_MAX_TEXT + 32];
for (int32_t i = 0; i < count && i < MAX_MENU_ITEMS; i++) {
int32_t pos = 0;
for (int32_t lv = 0; lv < sMed.items[i].level; lv++) {
pos += snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "%s", ARROW_STR);
}
const char *cap = sMed.items[i].caption;
if (cap[0] == '-') {
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "--------");
} else if (cap[0]) {
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "%s", cap);
} else {
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "(%s)", sMed.items[i].name[0] ? sMed.items[i].name : "untitled");
}
sLabels[i] = sLabelBufs[i];
}
wgtListBoxSetItems(sMed.listBox, sLabels, count < MAX_MENU_ITEMS ? count : MAX_MENU_ITEMS);
wgtListBoxSetSelected(sMed.listBox, sMed.selectedIdx);
}
// ============================================================
// mnuEditorDialog
// ============================================================
bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) { bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) {
memset(&sMed, 0, sizeof(sMed)); memset(&sMed, 0, sizeof(sMed));
sMed.ctx = ctx; sMed.ctx = ctx;
@ -742,5 +323,374 @@ bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) {
arrfree(sMed.items); arrfree(sMed.items);
sMed.items = NULL; sMed.items = NULL;
// Cleanup display buffers
for (int32_t i = 0; i < (int32_t)arrlen(sLabelBufs); i++) {
free(sLabelBufs[i]);
}
arrfree(sLabelBufs);
arrfree(sLabels);
sLabelBufs = NULL;
sLabels = NULL;
return sMed.accepted; return sMed.accepted;
} }
static void onCancel(WidgetT *w) {
(void)w;
sMed.accepted = false;
sMed.done = true;
}
static void onCaptionChange(WidgetT *w) {
(void)w;
applyFields();
rebuildList();
}
static void onDelete(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
if (sMed.selectedIdx < 0 || sMed.selectedIdx >= count) {
return;
}
applyFields();
// Delete selected item and its children
int32_t subEnd = findSubtreeEnd(sMed.selectedIdx);
for (int32_t i = subEnd - 1; i >= sMed.selectedIdx; i--) {
arrdel(sMed.items, i);
}
count = (int32_t)arrlen(sMed.items);
if (sMed.selectedIdx >= count) {
sMed.selectedIdx = count - 1;
}
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
}
static void onIndent(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
int32_t idx = sMed.selectedIdx;
if (idx <= 0 || idx >= count) {
return;
}
applyFields();
// Can only indent if previous item is at same or higher level
int32_t prevLevel = sMed.items[idx - 1].level;
if (sMed.items[idx].level > prevLevel) {
return; // already deeper than prev
}
if (sMed.items[idx].level >= MAX_MENU_LEVEL) {
return;
}
// Indent this item and all its children
int32_t subEnd = findSubtreeEnd(idx);
for (int32_t i = idx; i < subEnd; i++) {
sMed.items[i].level++;
}
rebuildList();
}
static void onInsert(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
applyFields();
DsgnMenuItemT mi;
memset(&mi, 0, sizeof(mi));
mi.enabled = true;
// Insert after the current item's subtree, at the same level
int32_t insertAt;
if (sMed.selectedIdx >= 0 && sMed.selectedIdx < count) {
mi.level = sMed.items[sMed.selectedIdx].level;
insertAt = findSubtreeEnd(sMed.selectedIdx);
} else {
insertAt = count;
}
arrins(sMed.items, insertAt, mi);
sMed.selectedIdx = insertAt;
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
}
static void onListClick(WidgetT *w) {
int32_t prev = sMed.selectedIdx;
sMed.selectedIdx = wgtListBoxGetSelected(w);
if (prev != sMed.selectedIdx && prev >= 0 && prev < (int32_t)arrlen(sMed.items)) {
// Save fields from previously selected item
int32_t saveSel = sMed.selectedIdx;
sMed.selectedIdx = prev;
applyFields();
sMed.selectedIdx = saveSel;
}
loadFields();
}
static void onMoveDown(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
int32_t idx = sMed.selectedIdx;
if (idx < 0 || idx >= count) {
return;
}
applyFields();
int32_t subEnd = findSubtreeEnd(idx);
if (subEnd >= count) {
return; // already at bottom
}
// The item/subtree after us
int32_t nextEnd = findSubtreeEnd(subEnd);
// Rotate: move the next subtree before our subtree
// Simple approach: extract our subtree, delete it, insert after next subtree
int32_t subSize = subEnd - idx;
DsgnMenuItemT *tmp = (DsgnMenuItemT *)malloc(subSize * sizeof(DsgnMenuItemT));
memcpy(tmp, &sMed.items[idx], subSize * sizeof(DsgnMenuItemT));
// Delete our subtree
for (int32_t i = subEnd - 1; i >= idx; i--) {
arrdel(sMed.items, i);
}
// Insert after what was the next subtree (now shifted)
int32_t insertAt = nextEnd - subSize;
for (int32_t i = 0; i < subSize; i++) {
arrins(sMed.items, insertAt + i, tmp[i]);
}
free(tmp);
sMed.selectedIdx = insertAt;
rebuildList();
}
static void onMoveUp(WidgetT *w) {
(void)w;
int32_t idx = sMed.selectedIdx;
if (idx <= 0) {
return;
}
applyFields();
// Find the start of the previous subtree at the same or lower level
int32_t prevIdx = idx - 1;
while (prevIdx > 0 && sMed.items[prevIdx].level > sMed.items[idx].level) {
prevIdx--;
}
// Move our subtree before the previous item's subtree
int32_t subEnd = findSubtreeEnd(idx);
int32_t subSize = subEnd - idx;
DsgnMenuItemT *tmp = (DsgnMenuItemT *)malloc(subSize * sizeof(DsgnMenuItemT));
memcpy(tmp, &sMed.items[idx], subSize * sizeof(DsgnMenuItemT));
// Delete our subtree
for (int32_t i = subEnd - 1; i >= idx; i--) {
arrdel(sMed.items, i);
}
// Insert at prevIdx
for (int32_t i = 0; i < subSize; i++) {
arrins(sMed.items, prevIdx + i, tmp[i]);
}
free(tmp);
sMed.selectedIdx = prevIdx;
rebuildList();
}
static void onNameChange(WidgetT *w) {
(void)w;
sMed.nameAutoGen = false; // user took over
applyFields();
rebuildList();
}
static void onNext(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
applyFields();
if (sMed.selectedIdx < count - 1) {
sMed.selectedIdx++;
} else {
// Append new item
DsgnMenuItemT mi;
memset(&mi, 0, sizeof(mi));
mi.enabled = true;
if (count > 0) {
mi.level = sMed.items[count - 1].level;
}
arrput(sMed.items, mi);
sMed.selectedIdx = (int32_t)arrlen(sMed.items) - 1;
}
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
}
static void onOk(WidgetT *w) {
(void)w;
applyFields();
// Strip blank items (no caption and no name)
for (int32_t i = (int32_t)arrlen(sMed.items) - 1; i >= 0; i--) {
if (sMed.items[i].caption[0] == '\0' && sMed.items[i].name[0] == '\0') {
arrdel(sMed.items, i);
}
}
// Validate: check names are non-empty and unique
int32_t count = (int32_t)arrlen(sMed.items);
for (int32_t i = 0; i < count; i++) {
if (sMed.items[i].name[0] == '\0') {
dvxErrorBox(sMed.ctx, "Menu Editor", "All menu items must have a Name.");
sMed.selectedIdx = i;
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
return;
}
for (int32_t j = i + 1; j < count; j++) {
if (strcasecmp(sMed.items[i].name, sMed.items[j].name) == 0) {
char msg[128];
snprintf(msg, sizeof(msg), "Duplicate menu name: %s", sMed.items[i].name);
dvxErrorBox(sMed.ctx, "Menu Editor", msg);
sMed.selectedIdx = j;
rebuildList();
loadFields();
wgtSetFocused(sMed.captionInput);
return;
}
}
}
// Copy working items back to form
arrfree(sMed.form->menuItems);
sMed.form->menuItems = NULL;
for (int32_t i = 0; i < count; i++) {
arrput(sMed.form->menuItems, sMed.items[i]);
}
sMed.accepted = true;
sMed.done = true;
}
static void onOutdent(WidgetT *w) {
(void)w;
int32_t count = (int32_t)arrlen(sMed.items);
int32_t idx = sMed.selectedIdx;
if (idx < 0 || idx >= count) {
return;
}
if (sMed.items[idx].level <= 0) {
return;
}
applyFields();
int32_t subEnd = findSubtreeEnd(idx);
for (int32_t i = idx; i < subEnd; i++) {
sMed.items[i].level--;
}
rebuildList();
}
static void rebuildList(void) {
int32_t count = (int32_t)arrlen(sMed.items);
// The listBox stores pointers, not copies -- so we must keep these buffers
// alive until the next rebuild or dialog destroy. Free prior pass first.
for (int32_t i = 0; i < (int32_t)arrlen(sLabelBufs); i++) {
free(sLabelBufs[i]);
}
arrsetlen(sLabelBufs, 0);
arrsetlen(sLabels, 0);
for (int32_t i = 0; i < count; i++) {
char buf[DSGN_MAX_TEXT + 32];
int32_t pos = 0;
buf[0] = '\0';
for (int32_t lv = 0; lv < sMed.items[i].level; lv++) {
pos += snprintf(buf + pos, sizeof(buf) - pos, "%s", ARROW_STR);
}
const char *cap = sMed.items[i].caption;
if (cap[0] == '-') {
snprintf(buf + pos, sizeof(buf) - pos, "--------");
} else if (cap[0]) {
snprintf(buf + pos, sizeof(buf) - pos, "%s", cap);
} else {
snprintf(buf + pos, sizeof(buf) - pos, "(%s)", sMed.items[i].name[0] ? sMed.items[i].name : "untitled");
}
arrput(sLabelBufs, strdup(buf));
}
for (int32_t i = 0; i < count; i++) {
arrput(sLabels, (const char *)sLabelBufs[i]);
}
wgtListBoxSetItems(sMed.listBox, sLabels, count);
wgtListBoxSetSelected(sMed.listBox, sMed.selectedIdx);
}

File diff suppressed because it is too large Load diff

View file

@ -51,7 +51,7 @@ typedef struct {
char startupForm[PRJ_MAX_NAME]; char startupForm[PRJ_MAX_NAME];
// Project metadata (for binary generation) // Project metadata (for binary generation)
char author[PRJ_MAX_STRING]; char author[PRJ_MAX_STRING];
char company[PRJ_MAX_STRING]; char publisher[PRJ_MAX_STRING];
char version[PRJ_MAX_NAME]; char version[PRJ_MAX_NAME];
char copyright[PRJ_MAX_STRING]; char copyright[PRJ_MAX_STRING];
char description[PRJ_MAX_DESC]; char description[PRJ_MAX_DESC];

View file

@ -26,6 +26,19 @@
#define PRP_WIN_W 220 #define PRP_WIN_W 220
#define PRP_WIN_H 400 #define PRP_WIN_H 400
#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
#define PROP_TYPE_LAYOUT 251
#define PROP_TYPE_DATASOURCE 254
#define PROP_TYPE_DATAFIELD 253
#define PROP_TYPE_RECORDSRC 252
// No fixed caps: field and table arrays are heap-allocated and sized to the
// actual count reported by SQL.
// ============================================================ // ============================================================
// Module state // Module state
// ============================================================ // ============================================================
@ -41,17 +54,149 @@ static char **sCellData = NULL; // stb_ds array of strdup'd strings
static int32_t sCellRows = 0; static int32_t sCellRows = 0;
// ============================================================ // ============================================================
// Helpers // Prototypes
// ============================================================ // ============================================================
static void freeTreeLabels(void) { static void addPropRow(const char *name, const char *value);
int32_t count = (int32_t)arrlen(sTreeLabels); static void cascadeToChildren(DsgnStateT *ds, const char *parentName, bool visible, bool enabled);
static void collectTreeOrder(WidgetT *parent, DsgnControlT **srcArr, int32_t srcCount, DsgnControlT ***outArr, const char *parentName);
static const WgtPropDescT *findIfaceProp(const char *typeName, const char *propName);
static WidgetT *findTreeItemByName(WidgetT *parent, const char *name);
static void freeCellData(void);
static void freeTreeLabels(void);
static int32_t getDataFieldNames(const DsgnStateT *ds, const char *dataSourceName, char (**outNames)[DSGN_MAX_NAME]);
static uint8_t getPropType(const char *propName, const char *typeName);
static int32_t getTableNames(const char *dbName, char (**outNames)[DSGN_MAX_NAME]);
static void onPropDblClick(WidgetT *w);
static void onPrpClose(WindowT *win);
static void onTreeChange(WidgetT *w);
static void onTreeItemClick(WidgetT *w);
static void resolveDbPath(const char *dbName, char *out, int32_t outSize);
static bool treeOrderMatches(void);
static void addPropRow(const char *name, const char *value) {
arrput(sCellData, strdup(name));
arrput(sCellData, strdup(value ? value : ""));
sCellRows++;
}
// Recursively apply Visible or Enabled to all descendants of a
// container control.
static void cascadeToChildren(DsgnStateT *ds, const char *parentName, bool visible, bool enabled) {
int32_t count = (int32_t)arrlen(ds->form->controls);
for (int32_t i = 0; i < count; i++) { for (int32_t i = 0; i < count; i++) {
free(sTreeLabels[i]); DsgnControlT *child = ds->form->controls[i];
if (strcasecmp(child->parentName, parentName) != 0) {
continue;
}
if (child->widget) {
wgtSetVisible(child->widget, visible);
wgtSetEnabled(child->widget, enabled);
}
// Recurse into nested containers
if (dsgnIsContainer(child->typeName)) {
cascadeToChildren(ds, child->name, visible, enabled);
}
}
}
// Walk tree items recursively, collecting control names in order.
static void collectTreeOrder(WidgetT *parent, DsgnControlT **srcArr, int32_t srcCount, DsgnControlT ***outArr, const char *parentName) {
for (WidgetT *item = parent->firstChild; item; item = item->nextSibling) {
const char *label = (const char *)item->userData;
if (!label) {
continue;
}
char itemName[DSGN_MAX_NAME];
int32_t ni = 0;
while (label[ni] && label[ni] != ' ' && ni < DSGN_MAX_NAME - 1) {
itemName[ni] = label[ni];
ni++;
}
itemName[ni] = '\0';
for (int32_t i = 0; i < srcCount; i++) {
if (strcmp(srcArr[i]->name, itemName) == 0) {
snprintf(srcArr[i]->parentName, DSGN_MAX_NAME, "%s", parentName);
arrput(*outArr, srcArr[i]);
// Recurse into children (for containers)
if (item->firstChild) {
collectTreeOrder(item, srcArr, srcCount, outArr, itemName);
}
break;
}
}
}
}
static const WgtPropDescT *findIfaceProp(const char *typeName, const char *propName) {
if (!typeName || !typeName[0]) {
return NULL;
} }
arrsetlen(sTreeLabels, 0); 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;
}
// Walk tree items recursively to find the one matching a control name.
static WidgetT *findTreeItemByName(WidgetT *parent, const char *name) {
for (WidgetT *item = parent->firstChild; item; item = item->nextSibling) {
const char *label = (const char *)item->userData;
if (label) {
// Labels are "Name (Type)" -- match the name portion
int32_t len = 0;
while (label[len] && label[len] != ' ') {
len++;
}
if ((int32_t)strlen(name) == len && strncmp(label, name, len) == 0) {
return item;
}
}
// Recurse into children (containers)
WidgetT *found = findTreeItemByName(item, name);
if (found) {
return found;
}
}
return NULL;
} }
@ -67,63 +212,27 @@ static void freeCellData(void) {
} }
static void addPropRow(const char *name, const char *value) { static void freeTreeLabels(void) {
arrput(sCellData, strdup(name)); int32_t count = (int32_t)arrlen(sTreeLabels);
arrput(sCellData, strdup(value ? value : ""));
sCellRows++;
}
// ============================================================ for (int32_t i = 0; i < count; i++) {
// Prototypes free(sTreeLabels[i]);
// ============================================================
static int32_t getDataFieldNames(const DsgnStateT *ds, const char *dataSourceName, char names[][DSGN_MAX_NAME], int32_t maxNames);
static int32_t getTableNames(const char *dbName, char names[][DSGN_MAX_NAME], int32_t maxNames);
static void onPrpClose(WindowT *win);
static void onPropDblClick(WidgetT *w);
static void onTreeItemClick(WidgetT *w);
static void onTreeChange(WidgetT *w);
// ============================================================
// onPrpClose
// ============================================================
static void onPrpClose(WindowT *win) {
dvxHideWindow(sPrpCtx, win);
}
// ============================================================
// resolveDbPath -- resolve a DatabaseName against the project directory
// ============================================================
static void resolveDbPath(const char *dbName, char *out, int32_t outSize) {
// If it's already an absolute path (starts with drive letter or /), use as-is
if ((dbName[0] && dbName[1] == ':') || dbName[0] == '/' || dbName[0] == '\\') {
snprintf(out, outSize, "%s", dbName);
return;
} }
// Resolve relative to project directory arrsetlen(sTreeLabels, 0);
if (sDs && sDs->projectDir && sDs->projectDir[0]) {
snprintf(out, outSize, "%s%c%s", sDs->projectDir, DVX_PATH_SEP, dbName);
} else {
snprintf(out, outSize, "%s", dbName);
}
} }
// ============================================================
// getDataFieldNames -- query column names from a Data control's database // getDataFieldNames -- query column names from a Data control's database
// ============================================================
// //
// Finds the named Data control in the designer, reads its DatabaseName // Finds the named Data control in the designer, reads its DatabaseName
// and RecordSource properties, opens the database via dvxSql* (resolved // and RecordSource properties, opens the database via dvxSql* (resolved
// through dlsym), and returns up to maxNames column names. Returns the // through dlsym), and heap-allocates *outNames sized to the actual column
// count of names found (0 if anything fails). // count. Caller must free *outNames. Returns the count of names (0 if
// anything fails).
static int32_t getDataFieldNames(const DsgnStateT *ds, const char *dataSourceName, char (**outNames)[DSGN_MAX_NAME]) {
*outNames = NULL;
static int32_t getDataFieldNames(const DsgnStateT *ds, const char *dataSourceName, char names[][DSGN_MAX_NAME], int32_t maxNames) {
if (!ds || !ds->form || !dataSourceName || !dataSourceName[0]) { if (!ds || !ds->form || !dataSourceName || !dataSourceName[0]) {
return 0; return 0;
} }
@ -200,9 +309,24 @@ static int32_t getDataFieldNames(const DsgnStateT *ds, const char *dataSourceNam
} }
int32_t colCount = sqlFieldCount(rs); int32_t colCount = sqlFieldCount(rs);
int32_t count = 0;
for (int32_t i = 0; i < colCount && count < maxNames; i++) { if (colCount <= 0) {
sqlFreeResult(rs);
sqlClose(db);
return 0;
}
char (*names)[DSGN_MAX_NAME] = (char (*)[DSGN_MAX_NAME])calloc(colCount, DSGN_MAX_NAME);
if (!names) {
sqlFreeResult(rs);
sqlClose(db);
return 0;
}
int32_t count = 0;
for (int32_t i = 0; i < colCount; i++) {
const char *name = sqlFieldName(rs, i); const char *name = sqlFieldName(rs, i);
if (name) { if (name) {
@ -212,90 +336,14 @@ static int32_t getDataFieldNames(const DsgnStateT *ds, const char *dataSourceNam
sqlFreeResult(rs); sqlFreeResult(rs);
sqlClose(db); sqlClose(db);
*outNames = names;
return count; return count;
} }
// ============================================================
// getTableNames -- query table names from a SQLite database
// ============================================================
static int32_t getTableNames(const char *dbName, char names[][DSGN_MAX_NAME], int32_t maxNames) {
if (!dbName || !dbName[0]) {
return 0;
}
typedef int32_t (*SqlOpenFnT)(const char *);
typedef void (*SqlCloseFnT)(int32_t);
typedef int32_t (*SqlQueryFnT)(int32_t, const char *);
typedef bool (*SqlNextFnT)(int32_t);
typedef const char *(*SqlFieldTextFnT)(int32_t, int32_t);
typedef void (*SqlFreeResultFnT)(int32_t);
SqlOpenFnT sqlOpen = (SqlOpenFnT)dlsym(NULL, "_dvxSqlOpen");
SqlCloseFnT sqlClose = (SqlCloseFnT)dlsym(NULL, "_dvxSqlClose");
SqlQueryFnT sqlQuery = (SqlQueryFnT)dlsym(NULL, "_dvxSqlQuery");
SqlNextFnT sqlNext = (SqlNextFnT)dlsym(NULL, "_dvxSqlNext");
SqlFieldTextFnT sqlFieldText = (SqlFieldTextFnT)dlsym(NULL, "_dvxSqlFieldText");
SqlFreeResultFnT sqlFreeResult = (SqlFreeResultFnT)dlsym(NULL, "_dvxSqlFreeResult");
if (!sqlOpen || !sqlClose || !sqlQuery || !sqlNext || !sqlFieldText || !sqlFreeResult) {
return 0;
}
char fullPath[DVX_MAX_PATH];
resolveDbPath(dbName, fullPath, sizeof(fullPath));
int32_t db = sqlOpen(fullPath);
if (db <= 0) {
return 0;
}
int32_t rs = sqlQuery(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
if (rs <= 0) {
sqlClose(db);
return 0;
}
int32_t count = 0;
while (sqlNext(rs) && count < maxNames) {
const char *name = sqlFieldText(rs, 0);
if (name && name[0]) {
snprintf(names[count++], DSGN_MAX_NAME, "%s", name);
}
}
sqlFreeResult(rs);
sqlClose(db);
return count;
}
// ============================================================
// getPropType
// ============================================================
//
// Determine the data type of a property by name. Checks built-in // Determine the data type of a property by name. Checks built-in
// properties first, then looks up the widget's interface descriptor. // properties first, then looks up the widget's interface descriptor.
// Returns WGT_IFACE_STRING, WGT_IFACE_INT, or WGT_IFACE_BOOL. // Returns WGT_IFACE_STRING, WGT_IFACE_INT, or WGT_IFACE_BOOL.
#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
#define PROP_TYPE_LAYOUT 251
#define PROP_TYPE_DATASOURCE 254
#define PROP_TYPE_DATAFIELD 253
#define PROP_TYPE_RECORDSRC 252
#define MAX_DATAFIELD_COLS 64
#define MAX_TABLES 64
static uint8_t getPropType(const char *propName, const char *typeName) { static uint8_t getPropType(const char *propName, const char *typeName) {
// Read-only properties // Read-only properties
if (strcasecmp(propName, "Type") == 0) { return PROP_TYPE_READONLY; } if (strcasecmp(propName, "Type") == 0) { return PROP_TYPE_READONLY; }
@ -349,67 +397,86 @@ static uint8_t getPropType(const char *propName, const char *typeName) {
} }
static const WgtPropDescT *findIfaceProp(const char *typeName, const char *propName) { // getTableNames -- query table names from a SQLite database
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
// ============================================================
// //
// Recursively apply Visible or Enabled to all descendants of a // Heap-allocates *outNames and grows it as tables are enumerated. Caller
// container control. // must free *outNames. Returns the number of tables found (0 if anything
// fails).
static int32_t getTableNames(const char *dbName, char (**outNames)[DSGN_MAX_NAME]) {
*outNames = NULL;
static void cascadeToChildren(DsgnStateT *ds, const char *parentName, bool visible, bool enabled) { if (!dbName || !dbName[0]) {
int32_t count = (int32_t)arrlen(ds->form->controls); return 0;
}
for (int32_t i = 0; i < count; i++) { typedef int32_t (*SqlOpenFnT)(const char *);
DsgnControlT *child = ds->form->controls[i]; typedef void (*SqlCloseFnT)(int32_t);
typedef int32_t (*SqlQueryFnT)(int32_t, const char *);
typedef bool (*SqlNextFnT)(int32_t);
typedef const char *(*SqlFieldTextFnT)(int32_t, int32_t);
typedef void (*SqlFreeResultFnT)(int32_t);
if (strcasecmp(child->parentName, parentName) != 0) { SqlOpenFnT sqlOpen = (SqlOpenFnT)dlsym(NULL, "_dvxSqlOpen");
SqlCloseFnT sqlClose = (SqlCloseFnT)dlsym(NULL, "_dvxSqlClose");
SqlQueryFnT sqlQuery = (SqlQueryFnT)dlsym(NULL, "_dvxSqlQuery");
SqlNextFnT sqlNext = (SqlNextFnT)dlsym(NULL, "_dvxSqlNext");
SqlFieldTextFnT sqlFieldText = (SqlFieldTextFnT)dlsym(NULL, "_dvxSqlFieldText");
SqlFreeResultFnT sqlFreeResult = (SqlFreeResultFnT)dlsym(NULL, "_dvxSqlFreeResult");
if (!sqlOpen || !sqlClose || !sqlQuery || !sqlNext || !sqlFieldText || !sqlFreeResult) {
return 0;
}
char fullPath[DVX_MAX_PATH];
resolveDbPath(dbName, fullPath, sizeof(fullPath));
int32_t db = sqlOpen(fullPath);
if (db <= 0) {
return 0;
}
int32_t rs = sqlQuery(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
if (rs <= 0) {
sqlClose(db);
return 0;
}
// Grow with realloc doubling
char (*names)[DSGN_MAX_NAME] = NULL;
int32_t count = 0;
int32_t cap = 0;
while (sqlNext(rs)) {
const char *name = sqlFieldText(rs, 0);
if (!name || !name[0]) {
continue; continue;
} }
if (child->widget) { if (count >= cap) {
wgtSetVisible(child->widget, visible); int32_t newCap = cap == 0 ? 16 : cap * 2;
wgtSetEnabled(child->widget, enabled); char (*grown)[DSGN_MAX_NAME] = (char (*)[DSGN_MAX_NAME])realloc(names, (size_t)newCap * DSGN_MAX_NAME);
if (!grown) {
break;
}
names = grown;
cap = newCap;
} }
// Recurse into nested containers snprintf(names[count++], DSGN_MAX_NAME, "%s", name);
if (dsgnIsContainer(child->typeName)) {
cascadeToChildren(ds, child->name, visible, enabled);
}
} }
sqlFreeResult(rs);
sqlClose(db);
*outNames = names;
return count;
} }
// ============================================================
// onPropDblClick
// ============================================================
static void onPropDblClick(WidgetT *w) { static void onPropDblClick(WidgetT *w) {
if (!sDs || !sDs->form || !sPropList || !sPrpCtx) { if (!sDs || !sDs->form || !sPropList || !sPrpCtx) {
return; return;
@ -424,7 +491,7 @@ static void onPropDblClick(WidgetT *w) {
const char *propName = sCellData[row * 2]; const char *propName = sCellData[row * 2];
const char *curValue = sCellData[row * 2 + 1]; const char *curValue = sCellData[row * 2 + 1];
// Layout select from discovered layout containers // Layout -- select from discovered layout containers
if (strcasecmp(propName, "Layout") == 0) { if (strcasecmp(propName, "Layout") == 0) {
// Discover available layout types from loaded widget interfaces. // Discover available layout types from loaded widget interfaces.
// A layout container is isContainer with WGT_CREATE_PARENT (no extra args). // A layout container is isContainer with WGT_CREATE_PARENT (no extra args).
@ -616,20 +683,27 @@ static void onPropDblClick(WidgetT *w) {
} }
} }
char fieldNames[MAX_DATAFIELD_COLS][DSGN_MAX_NAME]; char (*fieldNames)[DSGN_MAX_NAME] = NULL;
int32_t fieldCount = getDataFieldNames(sDs, dataSrc, fieldNames, MAX_DATAFIELD_COLS); int32_t fieldCount = getDataFieldNames(sDs, dataSrc, &fieldNames);
if (fieldCount <= 0) { if (fieldCount <= 0) {
// No columns found fall back to text input // No columns found -- fall back to text input
char prompt[128]; char prompt[128];
snprintf(prompt, sizeof(prompt), "%s:", propName); snprintf(prompt, sizeof(prompt), "%s:", propName);
snprintf(newValue, sizeof(newValue), "%s", curValue); snprintf(newValue, sizeof(newValue), "%s", curValue);
if (!dvxInputBox(sPrpCtx, "Edit Property", prompt, curValue, newValue, sizeof(newValue))) { if (!dvxInputBox(sPrpCtx, "Edit Property", prompt, curValue, newValue, sizeof(newValue))) {
free(fieldNames);
return; return;
} }
} else { } else {
const char *fieldPtrs[MAX_DATAFIELD_COLS + 1]; const char **fieldPtrs = (const char **)calloc((size_t)fieldCount + 1, sizeof(const char *));
if (!fieldPtrs) {
free(fieldNames);
return;
}
fieldPtrs[0] = "(none)"; fieldPtrs[0] = "(none)";
for (int32_t i = 0; i < fieldCount; i++) { for (int32_t i = 0; i < fieldCount; i++) {
@ -648,6 +722,8 @@ static void onPropDblClick(WidgetT *w) {
int32_t chosenIdx = 0; int32_t chosenIdx = 0;
if (!dvxChoiceDialog(sPrpCtx, propName, "Select column:", fieldPtrs, fieldCount + 1, defIdx, &chosenIdx)) { if (!dvxChoiceDialog(sPrpCtx, propName, "Select column:", fieldPtrs, fieldCount + 1, defIdx, &chosenIdx)) {
free(fieldPtrs);
free(fieldNames);
return; return;
} }
@ -656,7 +732,11 @@ static void onPropDblClick(WidgetT *w) {
} else { } else {
snprintf(newValue, sizeof(newValue), "%s", fieldNames[chosenIdx - 1]); snprintf(newValue, sizeof(newValue), "%s", fieldNames[chosenIdx - 1]);
} }
free(fieldPtrs);
} }
free(fieldNames);
} else if (propType == PROP_TYPE_RECORDSRC) { } else if (propType == PROP_TYPE_RECORDSRC) {
// Show dropdown of table names from the Data control's database // Show dropdown of table names from the Data control's database
// Find DatabaseName on this control (which is a Data control) // Find DatabaseName on this control (which is a Data control)
@ -674,20 +754,27 @@ static void onPropDblClick(WidgetT *w) {
} }
} }
char tableNames[MAX_TABLES][DSGN_MAX_NAME]; char (*tableNames)[DSGN_MAX_NAME] = NULL;
int32_t tableCount = getTableNames(dbName, tableNames, MAX_TABLES); int32_t tableCount = getTableNames(dbName, &tableNames);
if (tableCount <= 0) { if (tableCount <= 0) {
// No tables or can't open DB fall back to text input // No tables or can't open DB -- fall back to text input
char prompt[128]; char prompt[128];
snprintf(prompt, sizeof(prompt), "%s:", propName); snprintf(prompt, sizeof(prompt), "%s:", propName);
snprintf(newValue, sizeof(newValue), "%s", curValue); snprintf(newValue, sizeof(newValue), "%s", curValue);
if (!dvxInputBox(sPrpCtx, "Edit Property", prompt, curValue, newValue, sizeof(newValue))) { if (!dvxInputBox(sPrpCtx, "Edit Property", prompt, curValue, newValue, sizeof(newValue))) {
free(tableNames);
return; return;
} }
} else { } else {
const char *tablePtrs[MAX_TABLES + 1]; const char **tablePtrs = (const char **)calloc((size_t)tableCount + 1, sizeof(const char *));
if (!tablePtrs) {
free(tableNames);
return;
}
tablePtrs[0] = "(none)"; tablePtrs[0] = "(none)";
for (int32_t i = 0; i < tableCount; i++) { for (int32_t i = 0; i < tableCount; i++) {
@ -706,6 +793,8 @@ static void onPropDblClick(WidgetT *w) {
int32_t chosenIdx = 0; int32_t chosenIdx = 0;
if (!dvxChoiceDialog(sPrpCtx, "RecordSource", "Select table:", tablePtrs, tableCount + 1, defIdx, &chosenIdx)) { if (!dvxChoiceDialog(sPrpCtx, "RecordSource", "Select table:", tablePtrs, tableCount + 1, defIdx, &chosenIdx)) {
free(tablePtrs);
free(tableNames);
return; return;
} }
@ -714,7 +803,11 @@ static void onPropDblClick(WidgetT *w) {
} else { } else {
snprintf(newValue, sizeof(newValue), "%s", tableNames[chosenIdx - 1]); snprintf(newValue, sizeof(newValue), "%s", tableNames[chosenIdx - 1]);
} }
free(tablePtrs);
} }
free(tableNames);
} else if (propType == PROP_TYPE_INT) { } else if (propType == PROP_TYPE_INT) {
// Spinner dialog for integers // Spinner dialog for integers
char prompt[128]; char prompt[128];
@ -1001,9 +1094,68 @@ static void onPropDblClick(WidgetT *w) {
} }
// ============================================================ static void onPrpClose(WindowT *win) {
// onTreeItemClick dvxHideWindow(sPrpCtx, win);
// ============================================================ }
static void onTreeChange(WidgetT *w) {
(void)w;
if (!sDs || !sDs->form || !sTree || sUpdating) {
return;
}
// If the order hasn't changed, this is just a selection or expand/collapse.
// The onClick handler on individual items handles selection updates.
if (treeOrderMatches()) {
return;
}
// Actual reorder happened -- rebuild the controls array from tree order.
int32_t count = (int32_t)arrlen(sDs->form->controls);
DsgnControlT **newArr = NULL;
WidgetT *formItem = sTree->firstChild;
if (!formItem) {
return;
}
collectTreeOrder(formItem, sDs->form->controls, count, &newArr, "");
// If we lost items (dragged above form), revert
if ((int32_t)arrlen(newArr) != count) {
arrfree(newArr);
prpRebuildTree(sDs);
return;
}
arrfree(sDs->form->controls);
sDs->form->controls = newArr;
sDs->form->dirty = true;
if (sDs->form->contentBox) {
sDs->form->contentBox->firstChild = NULL;
sDs->form->contentBox->lastChild = NULL;
int32_t newCount = (int32_t)arrlen(sDs->form->controls);
for (int32_t i = 0; i < newCount; i++) {
sDs->form->controls[i]->widget = NULL;
}
dsgnCreateWidgets(sDs, sDs->form->contentBox);
}
prpRebuildTree(sDs);
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
prpRefresh(sDs);
}
static void onTreeItemClick(WidgetT *w) { static void onTreeItemClick(WidgetT *w) {
(void)w; (void)w;
@ -1061,141 +1213,6 @@ static void onTreeItemClick(WidgetT *w) {
} }
// ============================================================
// onTreeReorder
// ============================================================
// Walk tree items recursively, collecting control names in order.
static void collectTreeOrder(WidgetT *parent, DsgnControlT **srcArr, int32_t srcCount, DsgnControlT ***outArr, const char *parentName) {
for (WidgetT *item = parent->firstChild; item; item = item->nextSibling) {
const char *label = (const char *)item->userData;
if (!label) {
continue;
}
char itemName[DSGN_MAX_NAME];
int32_t ni = 0;
while (label[ni] && label[ni] != ' ' && ni < DSGN_MAX_NAME - 1) {
itemName[ni] = label[ni];
ni++;
}
itemName[ni] = '\0';
for (int32_t i = 0; i < srcCount; i++) {
if (strcmp(srcArr[i]->name, itemName) == 0) {
snprintf(srcArr[i]->parentName, DSGN_MAX_NAME, "%s", parentName);
arrput(*outArr, srcArr[i]);
// Recurse into children (for containers)
if (item->firstChild) {
collectTreeOrder(item, srcArr, srcCount, outArr, itemName);
}
break;
}
}
}
}
// Check whether the tree order matches the controls array.
// Returns true if they match (no reorder happened).
static bool treeOrderMatches(void) {
WidgetT *formItem = sTree->firstChild;
if (!formItem) {
return true;
}
DsgnControlT **newArr = NULL;
int32_t count = (int32_t)arrlen(sDs->form->controls);
collectTreeOrder(formItem, sDs->form->controls, count, &newArr, "");
bool match = ((int32_t)arrlen(newArr) == count);
if (match) {
for (int32_t i = 0; i < count; i++) {
if (strcmp(newArr[i]->name, sDs->form->controls[i]->name) != 0 ||
strcmp(newArr[i]->parentName, sDs->form->controls[i]->parentName) != 0) {
match = false;
break;
}
}
}
arrfree(newArr);
return match;
}
static void onTreeChange(WidgetT *w) {
(void)w;
if (!sDs || !sDs->form || !sTree || sUpdating) {
return;
}
// If the order hasn't changed, this is just a selection or expand/collapse.
// The onClick handler on individual items handles selection updates.
if (treeOrderMatches()) {
return;
}
// Actual reorder happened — rebuild the controls array from tree order.
int32_t count = (int32_t)arrlen(sDs->form->controls);
DsgnControlT **newArr = NULL;
WidgetT *formItem = sTree->firstChild;
if (!formItem) {
return;
}
collectTreeOrder(formItem, sDs->form->controls, count, &newArr, "");
// If we lost items (dragged above form), revert
if ((int32_t)arrlen(newArr) != count) {
arrfree(newArr);
prpRebuildTree(sDs);
return;
}
arrfree(sDs->form->controls);
sDs->form->controls = newArr;
sDs->form->dirty = true;
if (sDs->form->contentBox) {
sDs->form->contentBox->firstChild = NULL;
sDs->form->contentBox->lastChild = NULL;
int32_t newCount = (int32_t)arrlen(sDs->form->controls);
for (int32_t i = 0; i < newCount; i++) {
sDs->form->controls[i]->widget = NULL;
}
dsgnCreateWidgets(sDs, sDs->form->contentBox);
}
prpRebuildTree(sDs);
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
prpRefresh(sDs);
}
// ============================================================
// prpCreate
// ============================================================
WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) { WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) {
sDs = ds; sDs = ds;
sPrpCtx = ctx; sPrpCtx = ctx;
@ -1239,10 +1256,6 @@ WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) {
} }
// ============================================================
// prpDestroy
// ============================================================
void prpDestroy(AppContextT *ctx, WindowT *win) { void prpDestroy(AppContextT *ctx, WindowT *win) {
freeTreeLabels(); freeTreeLabels();
arrfree(sTreeLabels); arrfree(sTreeLabels);
@ -1263,10 +1276,6 @@ void prpDestroy(AppContextT *ctx, WindowT *win) {
} }
// ============================================================
// prpRebuildTree
// ============================================================
void prpRebuildTree(DsgnStateT *ds) { void prpRebuildTree(DsgnStateT *ds) {
if (!sTree || !ds || !ds->form) { if (!sTree || !ds || !ds->form) {
return; return;
@ -1342,41 +1351,6 @@ void prpRebuildTree(DsgnStateT *ds) {
} }
// ============================================================
// prpRefresh
// ============================================================
// Walk tree items recursively to find the one matching a control name.
static WidgetT *findTreeItemByName(WidgetT *parent, const char *name) {
for (WidgetT *item = parent->firstChild; item; item = item->nextSibling) {
const char *label = (const char *)item->userData;
if (label) {
// Labels are "Name (Type)" — match the name portion
int32_t len = 0;
while (label[len] && label[len] != ' ') {
len++;
}
if ((int32_t)strlen(name) == len && strncmp(label, name, len) == 0) {
return item;
}
}
// Recurse into children (containers)
WidgetT *found = findTreeItemByName(item, name);
if (found) {
return found;
}
}
return NULL;
}
void prpRefresh(DsgnStateT *ds) { void prpRefresh(DsgnStateT *ds) {
if (!ds || !ds->form) { if (!ds || !ds->form) {
return; return;
@ -1533,3 +1507,51 @@ void prpRefresh(DsgnStateT *ds) {
wgtListViewSetData(sPropList, (const char **)sCellData, sCellRows); wgtListViewSetData(sPropList, (const char **)sCellData, sCellRows);
} }
// resolveDbPath -- resolve a DatabaseName against the project directory
static void resolveDbPath(const char *dbName, char *out, int32_t outSize) {
// If it's already an absolute path (starts with drive letter or /), use as-is
if ((dbName[0] && dbName[1] == ':') || dbName[0] == '/' || dbName[0] == '\\') {
snprintf(out, outSize, "%s", dbName);
return;
}
// Resolve relative to project directory
if (sDs && sDs->projectDir && sDs->projectDir[0]) {
snprintf(out, outSize, "%s" DVX_PATH_SEP "%s", sDs->projectDir, dbName);
} else {
snprintf(out, outSize, "%s", dbName);
}
}
// Check whether the tree order matches the controls array.
// Returns true if they match (no reorder happened).
static bool treeOrderMatches(void) {
WidgetT *formItem = sTree->firstChild;
if (!formItem) {
return true;
}
DsgnControlT **newArr = NULL;
int32_t count = (int32_t)arrlen(sDs->form->controls);
collectTreeOrder(formItem, sDs->form->controls, count, &newArr, "");
bool match = ((int32_t)arrlen(newArr) == count);
if (match) {
for (int32_t i = 0; i < count; i++) {
if (strcmp(newArr[i]->name, sDs->form->controls[i]->name) != 0 ||
strcmp(newArr[i]->parentName, sDs->form->controls[i]->parentName) != 0) {
match = false;
break;
}
}
}
arrfree(newArr);
return match;
}

View file

@ -41,9 +41,15 @@ typedef struct {
static DsgnStateT *sDs = NULL; static DsgnStateT *sDs = NULL;
static TbxToolEntryT *sTbxTools = NULL; // stb_ds dynamic array static TbxToolEntryT *sTbxTools = NULL; // stb_ds dynamic array
// ============================================================ // Function prototypes
// Callbacks static void onTbxClose(WindowT *win);
// ============================================================ static void onToolClick(WidgetT *w);
static void onTbxClose(WindowT *win) {
dvxHideWindow(sDs->ctx, win);
}
static void onToolClick(WidgetT *w) { static void onToolClick(WidgetT *w) {
if (!sDs) { if (!sDs) {
@ -67,15 +73,6 @@ static void onToolClick(WidgetT *w) {
} }
static void onTbxClose(WindowT *win) {
dvxHideWindow(sDs->ctx, win);
}
// ============================================================
// tbxCreate
// ============================================================
WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) { WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
sDs = ds; sDs = ds;
arrsetlen(sTbxTools, 0); arrsetlen(sTbxTools, 0);
@ -181,10 +178,6 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) {
} }
// ============================================================
// tbxDestroy
// ============================================================
void tbxDestroy(AppContextT *ctx, WindowT *win) { void tbxDestroy(AppContextT *ctx, WindowT *win) {
if (win) { if (win) {
dvxDestroyWindow(ctx, win); dvxDestroyWindow(ctx, win);

View file

@ -35,6 +35,13 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
// Module file header: 4-byte magic + uint16 version. A matched pair
// -- read by basModuleDeserialize, written by basModuleSerialize --
// so both sides pull from the same definition.
#define BAS_MOD_MAGIC "DBXM"
#define BAS_MOD_MAGIC_LEN 4
#define BAS_MOD_VERSION 1
// ============================================================ // ============================================================
// Write helpers // Write helpers
// ============================================================ // ============================================================
@ -45,59 +52,6 @@ typedef struct {
int32_t cap; int32_t cap;
} BufT; } BufT;
static void bufInit(BufT *b) {
b->cap = 4096;
b->buf = (uint8_t *)malloc(b->cap);
b->len = 0;
}
static void bufGrow(BufT *b, int32_t need) {
while (b->len + need > b->cap) {
b->cap *= 2;
}
b->buf = (uint8_t *)realloc(b->buf, b->cap);
}
static void bufWrite(BufT *b, const void *data, int32_t len) {
bufGrow(b, len);
memcpy(b->buf + b->len, data, len);
b->len += len;
}
static void bufWriteU8(BufT *b, uint8_t v) {
bufWrite(b, &v, 1);
}
static void bufWriteU16(BufT *b, uint16_t v) {
bufWrite(b, &v, 2);
}
static void bufWriteI32(BufT *b, int32_t v) {
bufWrite(b, &v, 4);
}
static void bufWriteStr(BufT *b, const char *s) {
uint16_t len = s ? (uint16_t)strlen(s) : 0;
bufWriteU16(b, len);
if (len > 0) {
bufWrite(b, s, len);
}
}
// ============================================================
// Read helpers
// ============================================================
typedef struct { typedef struct {
const uint8_t *data; const uint8_t *data;
int32_t len; int32_t len;
@ -105,79 +59,58 @@ typedef struct {
} ReaderT; } ReaderT;
static bool rOk(const ReaderT *r, int32_t need) { // Function prototypes (alphabetical)
return r->pos + need <= r->len; void basDebugDeserialize(BasModuleT *mod, const uint8_t *data, int32_t dataLen);
} uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen);
BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen);
void basModuleFree(BasModuleT *mod);
uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen);
static void bufGrow(BufT *b, int32_t need);
static void bufInit(BufT *b);
static void bufWrite(BufT *b, const void *data, int32_t len);
static void bufWriteI32(BufT *b, int32_t v);
static void bufWriteStr(BufT *b, const char *s);
static void bufWriteU16(BufT *b, uint16_t v);
static void bufWriteU8(BufT *b, uint8_t v);
static double rF64(ReaderT *r);
static int32_t rI32(ReaderT *r);
static bool rOk(const ReaderT *r, int32_t need);
static char *rStr(ReaderT *r);
static uint16_t rU16(ReaderT *r);
static uint8_t rU8(ReaderT *r);
void basDebugDeserialize(BasModuleT *mod, const uint8_t *data, int32_t dataLen) {
static uint8_t rU8(ReaderT *r) { if (!mod || !data || dataLen < 4) {
if (!rOk(r, 1)) { return;
return 0;
} }
return r->data[r->pos++]; ReaderT r = { data, dataLen, 0 };
}
// Debug variables
mod->debugVarCount = rI32(&r);
static uint16_t rU16(ReaderT *r) { if (mod->debugVarCount > 0) {
if (!rOk(r, 2)) { mod->debugVars = (BasDebugVarT *)calloc(mod->debugVarCount, sizeof(BasDebugVarT));
return 0;
for (int32_t i = 0; i < mod->debugVarCount; i++) {
BasDebugVarT *v = &mod->debugVars[i];
char *name = rStr(&r);
char *formName = rStr(&r);
snprintf(v->name, BAS_MAX_PROC_NAME, "%s", name);
snprintf(v->formName, BAS_MAX_PROC_NAME, "%s", formName);
free(name);
free(formName);
v->scope = rU8(&r);
v->dataType = rU8(&r);
v->index = rI32(&r);
v->procIndex = rI32(&r);
}
} }
uint16_t v;
memcpy(&v, r->data + r->pos, 2);
r->pos += 2;
return v;
} }
static int32_t rI32(ReaderT *r) { uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen) {
if (!rOk(r, 4)) {
return 0;
}
int32_t v;
memcpy(&v, r->data + r->pos, 4);
r->pos += 4;
return v;
}
static double rF64(ReaderT *r) {
if (!rOk(r, 8)) {
return 0.0;
}
double v;
memcpy(&v, r->data + r->pos, 8);
r->pos += 8;
return v;
}
// Read a length-prefixed string into a malloc'd buffer.
static char *rStr(ReaderT *r) {
uint16_t len = rU16(r);
if (len == 0 || !rOk(r, len)) {
char *s = (char *)malloc(1);
s[0] = '\0';
return s;
}
char *s = (char *)malloc(len + 1);
memcpy(s, r->data + r->pos, len);
s[len] = '\0';
r->pos += len;
return s;
}
// ============================================================
// basModuleSerialize
// ============================================================
uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen) {
if (!mod || !outLen) { if (!mod || !outLen) {
return NULL; return NULL;
} }
@ -185,83 +118,17 @@ uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen) {
BufT b; BufT b;
bufInit(&b); bufInit(&b);
// Header // Debug variables
bufWrite(&b, "DBXM", 4); bufWriteI32(&b, mod->debugVarCount);
bufWriteU16(&b, 1); // version
bufWriteI32(&b, mod->entryPoint);
bufWriteI32(&b, mod->globalCount);
bufWriteI32(&b, mod->codeLen);
bufWriteI32(&b, mod->constCount);
bufWriteI32(&b, mod->dataCount);
bufWriteI32(&b, mod->procCount);
// Code for (int32_t i = 0; i < mod->debugVarCount; i++) {
bufWrite(&b, mod->code, mod->codeLen); const BasDebugVarT *v = &mod->debugVars[i];
bufWriteStr(&b, v->name);
// String constant pool bufWriteStr(&b, v->formName);
for (int32_t i = 0; i < mod->constCount; i++) { bufWriteU8(&b, v->scope);
const char *s = mod->constants[i] ? mod->constants[i]->data : ""; bufWriteU8(&b, v->dataType);
bufWriteStr(&b, s); bufWriteI32(&b, v->index);
} bufWriteI32(&b, v->procIndex);
// Data pool
for (int32_t i = 0; i < mod->dataCount; i++) {
const BasValueT *v = &mod->dataPool[i];
bufWriteU8(&b, v->type);
switch (v->type) {
case BAS_TYPE_INTEGER:
bufWriteI32(&b, v->intVal);
break;
case BAS_TYPE_LONG:
bufWriteI32(&b, v->longVal);
break;
case BAS_TYPE_SINGLE: {
float f = (float)v->dblVal;
bufWrite(&b, &f, 4);
break;
}
case BAS_TYPE_DOUBLE:
bufWrite(&b, &v->dblVal, 8);
break;
case BAS_TYPE_STRING:
bufWriteStr(&b, v->strVal ? v->strVal->data : "");
break;
case BAS_TYPE_BOOLEAN:
bufWriteU8(&b, v->intVal ? 1 : 0);
break;
default:
bufWriteI32(&b, 0);
break;
}
}
// Procedure table
for (int32_t i = 0; i < mod->procCount; i++) {
const BasProcEntryT *p = &mod->procs[i];
bufWriteI32(&b, p->codeAddr);
bufWriteI32(&b, p->paramCount);
bufWriteI32(&b, p->localCount);
bufWriteU8(&b, p->returnType);
bufWriteU8(&b, p->isFunction ? 1 : 0);
bufWriteStr(&b, p->name);
}
// Form variable info (runtime-essential for per-form variable allocation)
bufWriteI32(&b, mod->formVarInfoCount);
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
const BasFormVarInfoT *fv = &mod->formVarInfo[i];
bufWriteStr(&b, fv->formName);
bufWriteI32(&b, fv->varCount);
bufWriteI32(&b, fv->initCodeAddr);
bufWriteI32(&b, fv->initCodeLen);
} }
*outLen = b.len; *outLen = b.len;
@ -269,10 +136,6 @@ uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen) {
} }
// ============================================================
// basModuleDeserialize
// ============================================================
BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen) { BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen) {
if (!data || dataLen < 30) { if (!data || dataLen < 30) {
return NULL; return NULL;
@ -281,15 +144,15 @@ BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen) {
ReaderT r = { data, dataLen, 0 }; ReaderT r = { data, dataLen, 0 };
// Check magic // Check magic
if (data[0] != 'D' || data[1] != 'B' || data[2] != 'X' || data[3] != 'M') { if (memcmp(data, BAS_MOD_MAGIC, BAS_MOD_MAGIC_LEN) != 0) {
return NULL; return NULL;
} }
r.pos = 4; r.pos = BAS_MOD_MAGIC_LEN;
uint16_t version = rU16(&r); uint16_t version = rU16(&r);
if (version != 1) { if (version != BAS_MOD_VERSION) {
return NULL; return NULL;
} }
@ -413,10 +276,6 @@ BasModuleT *basModuleDeserialize(const uint8_t *data, int32_t dataLen) {
} }
// ============================================================
// basModuleFree
// ============================================================
void basModuleFree(BasModuleT *mod) { void basModuleFree(BasModuleT *mod) {
if (!mod) { if (!mod) {
return; return;
@ -456,11 +315,7 @@ void basModuleFree(BasModuleT *mod) {
} }
// ============================================================ uint8_t *basModuleSerialize(const BasModuleT *mod, int32_t *outLen) {
// basDebugSerialize
// ============================================================
uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen) {
if (!mod || !outLen) { if (!mod || !outLen) {
return NULL; return NULL;
} }
@ -468,17 +323,83 @@ uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen) {
BufT b; BufT b;
bufInit(&b); bufInit(&b);
// Debug variables // Header
bufWriteI32(&b, mod->debugVarCount); bufWrite(&b, BAS_MOD_MAGIC, BAS_MOD_MAGIC_LEN);
bufWriteU16(&b, BAS_MOD_VERSION);
bufWriteI32(&b, mod->entryPoint);
bufWriteI32(&b, mod->globalCount);
bufWriteI32(&b, mod->codeLen);
bufWriteI32(&b, mod->constCount);
bufWriteI32(&b, mod->dataCount);
bufWriteI32(&b, mod->procCount);
for (int32_t i = 0; i < mod->debugVarCount; i++) { // Code
const BasDebugVarT *v = &mod->debugVars[i]; bufWrite(&b, mod->code, mod->codeLen);
bufWriteStr(&b, v->name);
bufWriteStr(&b, v->formName); // String constant pool
bufWriteU8(&b, v->scope); for (int32_t i = 0; i < mod->constCount; i++) {
bufWriteU8(&b, v->dataType); const char *s = mod->constants[i] ? mod->constants[i]->data : "";
bufWriteI32(&b, v->index); bufWriteStr(&b, s);
bufWriteI32(&b, v->procIndex); }
// Data pool
for (int32_t i = 0; i < mod->dataCount; i++) {
const BasValueT *v = &mod->dataPool[i];
bufWriteU8(&b, v->type);
switch (v->type) {
case BAS_TYPE_INTEGER:
bufWriteI32(&b, v->intVal);
break;
case BAS_TYPE_LONG:
bufWriteI32(&b, v->longVal);
break;
case BAS_TYPE_SINGLE: {
float f = (float)v->dblVal;
bufWrite(&b, &f, 4);
break;
}
case BAS_TYPE_DOUBLE:
bufWrite(&b, &v->dblVal, 8);
break;
case BAS_TYPE_STRING:
bufWriteStr(&b, v->strVal ? v->strVal->data : "");
break;
case BAS_TYPE_BOOLEAN:
bufWriteU8(&b, v->intVal ? 1 : 0);
break;
default:
bufWriteI32(&b, 0);
break;
}
}
// Procedure table
for (int32_t i = 0; i < mod->procCount; i++) {
const BasProcEntryT *p = &mod->procs[i];
bufWriteI32(&b, p->codeAddr);
bufWriteI32(&b, p->paramCount);
bufWriteI32(&b, p->localCount);
bufWriteU8(&b, p->returnType);
bufWriteU8(&b, p->isFunction ? 1 : 0);
bufWriteStr(&b, p->name);
}
// Form variable info (runtime-essential for per-form variable allocation)
bufWriteI32(&b, mod->formVarInfoCount);
for (int32_t i = 0; i < mod->formVarInfoCount; i++) {
const BasFormVarInfoT *fv = &mod->formVarInfo[i];
bufWriteStr(&b, fv->formName);
bufWriteI32(&b, fv->varCount);
bufWriteI32(&b, fv->initCodeAddr);
bufWriteI32(&b, fv->initCodeLen);
} }
*outLen = b.len; *outLen = b.len;
@ -486,36 +407,117 @@ uint8_t *basDebugSerialize(const BasModuleT *mod, int32_t *outLen) {
} }
// ============================================================ static void bufGrow(BufT *b, int32_t need) {
// basDebugDeserialize while (b->len + need > b->cap) {
// ============================================================ b->cap *= 2;
void basDebugDeserialize(BasModuleT *mod, const uint8_t *data, int32_t dataLen) {
if (!mod || !data || dataLen < 4) {
return;
}
ReaderT r = { data, dataLen, 0 };
// Debug variables
mod->debugVarCount = rI32(&r);
if (mod->debugVarCount > 0) {
mod->debugVars = (BasDebugVarT *)calloc(mod->debugVarCount, sizeof(BasDebugVarT));
for (int32_t i = 0; i < mod->debugVarCount; i++) {
BasDebugVarT *v = &mod->debugVars[i];
char *name = rStr(&r);
char *formName = rStr(&r);
snprintf(v->name, BAS_MAX_PROC_NAME, "%s", name);
snprintf(v->formName, BAS_MAX_PROC_NAME, "%s", formName);
free(name);
free(formName);
v->scope = rU8(&r);
v->dataType = rU8(&r);
v->index = rI32(&r);
v->procIndex = rI32(&r);
}
} }
b->buf = (uint8_t *)realloc(b->buf, b->cap);
}
static void bufInit(BufT *b) {
b->cap = 4096;
b->buf = (uint8_t *)malloc(b->cap);
b->len = 0;
}
static void bufWrite(BufT *b, const void *data, int32_t len) {
bufGrow(b, len);
memcpy(b->buf + b->len, data, len);
b->len += len;
}
static void bufWriteI32(BufT *b, int32_t v) {
bufWrite(b, &v, 4);
}
static void bufWriteStr(BufT *b, const char *s) {
uint16_t len = s ? (uint16_t)strlen(s) : 0;
bufWriteU16(b, len);
if (len > 0) {
bufWrite(b, s, len);
}
}
static void bufWriteU16(BufT *b, uint16_t v) {
bufWrite(b, &v, 2);
}
static void bufWriteU8(BufT *b, uint8_t v) {
bufWrite(b, &v, 1);
}
static double rF64(ReaderT *r) {
if (!rOk(r, 8)) {
return 0.0;
}
double v;
memcpy(&v, r->data + r->pos, 8);
r->pos += 8;
return v;
}
static int32_t rI32(ReaderT *r) {
if (!rOk(r, 4)) {
return 0;
}
int32_t v;
memcpy(&v, r->data + r->pos, 4);
r->pos += 4;
return v;
}
static bool rOk(const ReaderT *r, int32_t need) {
return r->pos + need <= r->len;
}
// Read a length-prefixed string into a malloc'd buffer.
static char *rStr(ReaderT *r) {
uint16_t len = rU16(r);
if (len == 0 || !rOk(r, len)) {
char *s = (char *)malloc(1);
s[0] = '\0';
return s;
}
char *s = (char *)malloc(len + 1);
memcpy(s, r->data + r->pos, len);
s[len] = '\0';
r->pos += len;
return s;
}
static uint16_t rU16(ReaderT *r) {
if (!rOk(r, 2)) {
return 0;
}
uint16_t v;
memcpy(&v, r->data + r->pos, 2);
r->pos += 2;
return v;
}
static uint8_t rU8(ReaderT *r) {
if (!rOk(r, 1)) {
return 0;
}
return r->data[r->pos++];
} }

View file

@ -26,148 +26,52 @@ static struct {
BasStringT *basEmptyString = &sEmptyStringStorage.hdr; BasStringT *basEmptyString = &sEmptyStringStorage.hdr;
BasStringT *basStringAlloc(int32_t cap) { // Function prototypes (alphabetical)
if (cap < 1) { void basArrayFree(BasArrayT *arr);
cap = 1; int32_t basArrayIndex(BasArrayT *arr, int32_t *indices, int32_t ndims);
} BasArrayT *basArrayNew(int32_t dims, int32_t *lbounds, int32_t *ubounds, uint8_t elementType);
BasArrayT *basArrayRef(BasArrayT *arr);
BasStringT *s = (BasStringT *)malloc(sizeof(BasStringT) + cap); void basArrayUnref(BasArrayT *arr);
BasStringT *basStringAlloc(int32_t cap);
if (!s) { int32_t basStringCompare(const BasStringT *a, const BasStringT *b);
return basStringRef(basEmptyString); int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b);
} BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b);
BasStringT *basStringNew(const char *text, int32_t len);
s->refCount = 1; BasStringT *basStringRef(BasStringT *s);
s->len = 0; BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len);
s->cap = cap; void basStringSystemInit(void);
s->data[0] = '\0'; void basStringSystemShutdown(void);
return s; void basStringUnref(BasStringT *s);
} void basUdtFree(BasUdtT *udt);
BasUdtT *basUdtNew(int32_t typeId, int32_t fieldCount);
BasUdtT *basUdtRef(BasUdtT *udt);
BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b) { void basUdtUnref(BasUdtT *udt);
int32_t newLen = a->len + b->len; BasValueT basValBool(bool v);
BasStringT *s = basStringAlloc(newLen + 1); int32_t basValCompare(BasValueT a, BasValueT b);
memcpy(s->data, a->data, a->len); int32_t basValCompareCI(BasValueT a, BasValueT b);
memcpy(s->data + a->len, b->data, b->len); BasValueT basValCopy(BasValueT v);
s->data[newLen] = '\0'; BasValueT basValDouble(double v);
s->len = newLen; BasStringT *basValFormatString(BasValueT v);
return s; BasValueT basValInteger(int16_t v);
} bool basValIsTruthy(BasValueT v);
BasValueT basValLong(int32_t v);
BasValueT basValObject(void *obj);
int32_t basStringCompare(const BasStringT *a, const BasStringT *b) { uint8_t basValPromoteType(uint8_t a, uint8_t b);
int32_t minLen = a->len < b->len ? a->len : b->len; void basValRelease(BasValueT *v);
int32_t cmp = memcmp(a->data, b->data, minLen); BasValueT basValSingle(float v);
BasValueT basValString(BasStringT *s);
if (cmp != 0) { BasValueT basValStringFromC(const char *text);
return cmp; BasValueT basValToBool(BasValueT v);
} BasValueT basValToDouble(BasValueT v);
BasValueT basValToInteger(BasValueT v);
if (a->len < b->len) { BasValueT basValToLong(BasValueT v);
return -1; double basValToNumber(BasValueT v);
} BasValueT basValToSingle(BasValueT v);
BasValueT basValToString(BasValueT v);
if (a->len > b->len) {
return 1;
}
return 0;
}
int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b) {
int32_t minLen = a->len < b->len ? a->len : b->len;
for (int32_t i = 0; i < minLen; i++) {
int32_t ca = toupper((unsigned char)a->data[i]);
int32_t cb = toupper((unsigned char)b->data[i]);
if (ca != cb) {
return ca - cb;
}
}
if (a->len < b->len) {
return -1;
}
if (a->len > b->len) {
return 1;
}
return 0;
}
BasStringT *basStringNew(const char *text, int32_t len) {
if (!text || len <= 0) {
return basStringRef(basEmptyString);
}
BasStringT *s = basStringAlloc(len + 1);
if (s->cap >= len + 1) {
memcpy(s->data, text, len);
s->data[len] = '\0';
s->len = len;
}
return s;
}
BasStringT *basStringRef(BasStringT *s) {
if (s) {
s->refCount++;
}
return s;
}
BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len) {
if (start < 0) {
start = 0;
}
if (start >= s->len) {
return basStringRef(basEmptyString);
}
if (len < 0 || start + len > s->len) {
len = s->len - start;
}
return basStringNew(s->data + start, len);
}
void basStringSystemInit(void) {
sEmptyStringStorage.nul = '\0';
}
void basStringSystemShutdown(void) {
// Nothing to do -- empty string is static
}
void basStringUnref(BasStringT *s) {
if (!s || s == basEmptyString) {
return;
}
s->refCount--;
if (s->refCount <= 0) {
free(s);
}
}
// ============================================================ // ============================================================
// Array system // Array system
// ============================================================ // ============================================================
void basArrayFree(BasArrayT *arr) { void basArrayFree(BasArrayT *arr) {
if (!arr) { if (!arr) {
return; return;
@ -279,10 +183,147 @@ void basArrayUnref(BasArrayT *arr) {
} }
BasStringT *basStringAlloc(int32_t cap) {
if (cap < 1) {
cap = 1;
}
BasStringT *s = (BasStringT *)malloc(sizeof(BasStringT) + cap);
if (!s) {
return basStringRef(basEmptyString);
}
s->refCount = 1;
s->len = 0;
s->cap = cap;
s->data[0] = '\0';
return s;
}
int32_t basStringCompare(const BasStringT *a, const BasStringT *b) {
int32_t minLen = a->len < b->len ? a->len : b->len;
int32_t cmp = memcmp(a->data, b->data, minLen);
if (cmp != 0) {
return cmp;
}
if (a->len < b->len) {
return -1;
}
if (a->len > b->len) {
return 1;
}
return 0;
}
int32_t basStringCompareCI(const BasStringT *a, const BasStringT *b) {
int32_t minLen = a->len < b->len ? a->len : b->len;
for (int32_t i = 0; i < minLen; i++) {
int32_t ca = toupper((unsigned char)a->data[i]);
int32_t cb = toupper((unsigned char)b->data[i]);
if (ca != cb) {
return ca - cb;
}
}
if (a->len < b->len) {
return -1;
}
if (a->len > b->len) {
return 1;
}
return 0;
}
BasStringT *basStringConcat(const BasStringT *a, const BasStringT *b) {
int32_t newLen = a->len + b->len;
BasStringT *s = basStringAlloc(newLen + 1);
memcpy(s->data, a->data, a->len);
memcpy(s->data + a->len, b->data, b->len);
s->data[newLen] = '\0';
s->len = newLen;
return s;
}
BasStringT *basStringNew(const char *text, int32_t len) {
if (!text || len <= 0) {
return basStringRef(basEmptyString);
}
BasStringT *s = basStringAlloc(len + 1);
if (s->cap >= len + 1) {
memcpy(s->data, text, len);
s->data[len] = '\0';
s->len = len;
}
return s;
}
BasStringT *basStringRef(BasStringT *s) {
if (s) {
s->refCount++;
}
return s;
}
BasStringT *basStringSub(const BasStringT *s, int32_t start, int32_t len) {
if (start < 0) {
start = 0;
}
if (start >= s->len) {
return basStringRef(basEmptyString);
}
if (len < 0 || start + len > s->len) {
len = s->len - start;
}
return basStringNew(s->data + start, len);
}
void basStringSystemInit(void) {
sEmptyStringStorage.nul = '\0';
}
void basStringSystemShutdown(void) {
// Nothing to do -- empty string is static
}
void basStringUnref(BasStringT *s) {
if (!s || s == basEmptyString) {
return;
}
s->refCount--;
if (s->refCount <= 0) {
free(s);
}
}
// ============================================================ // ============================================================
// UDT system // UDT system
// ============================================================ // ============================================================
void basUdtFree(BasUdtT *udt) { void basUdtFree(BasUdtT *udt) {
if (!udt) { if (!udt) {
return; return;
@ -346,7 +387,6 @@ void basUdtUnref(BasUdtT *udt) {
// ============================================================ // ============================================================
// Value constructors // Value constructors
// ============================================================ // ============================================================
BasValueT basValBool(bool v) { BasValueT basValBool(bool v) {
BasValueT val; BasValueT val;
val.type = BAS_TYPE_BOOLEAN; val.type = BAS_TYPE_BOOLEAN;
@ -355,11 +395,47 @@ BasValueT basValBool(bool v) {
} }
BasValueT basValObject(void *obj) { int32_t basValCompare(BasValueT a, BasValueT b) {
BasValueT val; // String comparison
val.type = BAS_TYPE_OBJECT; if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
val.objVal = obj; return basStringCompare(a.strVal ? a.strVal : basEmptyString, b.strVal ? b.strVal : basEmptyString);
return val; }
// Numeric comparison
double na = basValToNumber(a);
double nb = basValToNumber(b);
if (na < nb) {
return -1;
}
if (na > nb) {
return 1;
}
return 0;
}
int32_t basValCompareCI(BasValueT a, BasValueT b) {
// String comparison (case-insensitive)
if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
return basStringCompareCI(a.strVal ? a.strVal : basEmptyString, b.strVal ? b.strVal : basEmptyString);
}
// Numeric comparison (same as basValCompare)
double na = basValToNumber(a);
double nb = basValToNumber(b);
if (na < nb) {
return -1;
}
if (na > nb) {
return 1;
}
return 0;
} }
@ -384,6 +460,39 @@ BasValueT basValDouble(double v) {
} }
BasStringT *basValFormatString(BasValueT v) {
char buf[64];
switch (v.type) {
case BAS_TYPE_INTEGER:
snprintf(buf, sizeof(buf), "%d", (int)v.intVal);
return basStringNew(buf, (int32_t)strlen(buf));
case BAS_TYPE_LONG:
snprintf(buf, sizeof(buf), "%ld", (long)v.longVal);
return basStringNew(buf, (int32_t)strlen(buf));
case BAS_TYPE_SINGLE: {
snprintf(buf, sizeof(buf), "%g", (double)v.sngVal);
return basStringNew(buf, (int32_t)strlen(buf));
}
case BAS_TYPE_DOUBLE:
snprintf(buf, sizeof(buf), "%g", v.dblVal);
return basStringNew(buf, (int32_t)strlen(buf));
case BAS_TYPE_BOOLEAN:
return basStringNew(v.boolVal ? "True" : "False", v.boolVal ? 4 : 5);
case BAS_TYPE_STRING:
return v.strVal ? basStringRef(v.strVal) : basStringRef(basEmptyString);
default:
return basStringRef(basEmptyString);
}
}
BasValueT basValInteger(int16_t v) { BasValueT basValInteger(int16_t v) {
BasValueT val; BasValueT val;
val.type = BAS_TYPE_INTEGER; val.type = BAS_TYPE_INTEGER;
@ -392,6 +501,32 @@ BasValueT basValInteger(int16_t v) {
} }
bool basValIsTruthy(BasValueT v) {
switch (v.type) {
case BAS_TYPE_INTEGER:
return v.intVal != 0;
case BAS_TYPE_LONG:
return v.longVal != 0;
case BAS_TYPE_SINGLE:
return v.sngVal != 0.0f;
case BAS_TYPE_DOUBLE:
return v.dblVal != 0.0;
case BAS_TYPE_BOOLEAN:
return v.boolVal != 0;
case BAS_TYPE_STRING:
return v.strVal && v.strVal->len > 0;
default:
return false;
}
}
BasValueT basValLong(int32_t v) { BasValueT basValLong(int32_t v) {
BasValueT val; BasValueT val;
val.type = BAS_TYPE_LONG; val.type = BAS_TYPE_LONG;
@ -400,6 +535,53 @@ BasValueT basValLong(int32_t v) {
} }
BasValueT basValObject(void *obj) {
BasValueT val;
val.type = BAS_TYPE_OBJECT;
val.objVal = obj;
return val;
}
uint8_t basValPromoteType(uint8_t a, uint8_t b) {
// String stays string (concat, not arithmetic)
if (a == BAS_TYPE_STRING || b == BAS_TYPE_STRING) {
return BAS_TYPE_STRING;
}
// Double wins over everything
if (a == BAS_TYPE_DOUBLE || b == BAS_TYPE_DOUBLE) {
return BAS_TYPE_DOUBLE;
}
// Single wins over integer/long
if (a == BAS_TYPE_SINGLE || b == BAS_TYPE_SINGLE) {
return BAS_TYPE_SINGLE;
}
// Long wins over integer
if (a == BAS_TYPE_LONG || b == BAS_TYPE_LONG) {
return BAS_TYPE_LONG;
}
return BAS_TYPE_INTEGER;
}
void basValRelease(BasValueT *v) {
if (v->type == BAS_TYPE_STRING) {
basStringUnref(v->strVal);
v->strVal = NULL;
} else if (v->type == BAS_TYPE_ARRAY) {
basArrayUnref(v->arrVal);
v->arrVal = NULL;
} else if (v->type == BAS_TYPE_UDT) {
basUdtUnref(v->udtVal);
v->udtVal = NULL;
}
}
BasValueT basValSingle(float v) { BasValueT basValSingle(float v) {
BasValueT val; BasValueT val;
val.type = BAS_TYPE_SINGLE; val.type = BAS_TYPE_SINGLE;
@ -424,24 +606,9 @@ BasValueT basValStringFromC(const char *text) {
} }
void basValRelease(BasValueT *v) {
if (v->type == BAS_TYPE_STRING) {
basStringUnref(v->strVal);
v->strVal = NULL;
} else if (v->type == BAS_TYPE_ARRAY) {
basArrayUnref(v->arrVal);
v->arrVal = NULL;
} else if (v->type == BAS_TYPE_UDT) {
basUdtUnref(v->udtVal);
v->udtVal = NULL;
}
}
// ============================================================ // ============================================================
// Type conversion // Type conversion
// ============================================================ // ============================================================
BasValueT basValToBool(BasValueT v) { BasValueT basValToBool(BasValueT v) {
return basValBool(basValIsTruthy(v)); return basValBool(basValIsTruthy(v));
} }
@ -513,131 +680,3 @@ BasValueT basValToString(BasValueT v) {
result.strVal = s; result.strVal = s;
return result; return result;
} }
BasStringT *basValFormatString(BasValueT v) {
char buf[64];
switch (v.type) {
case BAS_TYPE_INTEGER:
snprintf(buf, sizeof(buf), "%d", (int)v.intVal);
return basStringNew(buf, (int32_t)strlen(buf));
case BAS_TYPE_LONG:
snprintf(buf, sizeof(buf), "%ld", (long)v.longVal);
return basStringNew(buf, (int32_t)strlen(buf));
case BAS_TYPE_SINGLE: {
snprintf(buf, sizeof(buf), "%g", (double)v.sngVal);
return basStringNew(buf, (int32_t)strlen(buf));
}
case BAS_TYPE_DOUBLE:
snprintf(buf, sizeof(buf), "%g", v.dblVal);
return basStringNew(buf, (int32_t)strlen(buf));
case BAS_TYPE_BOOLEAN:
return basStringNew(v.boolVal ? "True" : "False", v.boolVal ? 4 : 5);
case BAS_TYPE_STRING:
return v.strVal ? basStringRef(v.strVal) : basStringRef(basEmptyString);
default:
return basStringRef(basEmptyString);
}
}
bool basValIsTruthy(BasValueT v) {
switch (v.type) {
case BAS_TYPE_INTEGER:
return v.intVal != 0;
case BAS_TYPE_LONG:
return v.longVal != 0;
case BAS_TYPE_SINGLE:
return v.sngVal != 0.0f;
case BAS_TYPE_DOUBLE:
return v.dblVal != 0.0;
case BAS_TYPE_BOOLEAN:
return v.boolVal != 0;
case BAS_TYPE_STRING:
return v.strVal && v.strVal->len > 0;
default:
return false;
}
}
int32_t basValCompare(BasValueT a, BasValueT b) {
// String comparison
if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
return basStringCompare(a.strVal ? a.strVal : basEmptyString, b.strVal ? b.strVal : basEmptyString);
}
// Numeric comparison
double na = basValToNumber(a);
double nb = basValToNumber(b);
if (na < nb) {
return -1;
}
if (na > nb) {
return 1;
}
return 0;
}
int32_t basValCompareCI(BasValueT a, BasValueT b) {
// String comparison (case-insensitive)
if (a.type == BAS_TYPE_STRING && b.type == BAS_TYPE_STRING) {
return basStringCompareCI(a.strVal ? a.strVal : basEmptyString, b.strVal ? b.strVal : basEmptyString);
}
// Numeric comparison (same as basValCompare)
double na = basValToNumber(a);
double nb = basValToNumber(b);
if (na < nb) {
return -1;
}
if (na > nb) {
return 1;
}
return 0;
}
uint8_t basValPromoteType(uint8_t a, uint8_t b) {
// String stays string (concat, not arithmetic)
if (a == BAS_TYPE_STRING || b == BAS_TYPE_STRING) {
return BAS_TYPE_STRING;
}
// Double wins over everything
if (a == BAS_TYPE_DOUBLE || b == BAS_TYPE_DOUBLE) {
return BAS_TYPE_DOUBLE;
}
// Single wins over integer/long
if (a == BAS_TYPE_SINGLE || b == BAS_TYPE_SINGLE) {
return BAS_TYPE_SINGLE;
}
// Long wins over integer
if (a == BAS_TYPE_LONG || b == BAS_TYPE_LONG) {
return BAS_TYPE_LONG;
}
return BAS_TYPE_INTEGER;
}

View file

@ -30,9 +30,10 @@
static BasCallFrameT *currentFrame(BasVmT *vm); static BasCallFrameT *currentFrame(BasVmT *vm);
static void defaultPrint(void *ctx, const char *text, bool newline); static void defaultPrint(void *ctx, const char *text, bool newline);
static void dirClose(void);
static const char *dirNext(void);
static BasVmResultE execArith(BasVmT *vm, uint8_t op); static BasVmResultE execArith(BasVmT *vm, uint8_t op);
static BasVmResultE execCompare(BasVmT *vm, uint8_t op); static BasVmResultE execCompare(BasVmT *vm, uint8_t op);
static void dirClose(void);
static BasVmResultE execFileOp(BasVmT *vm, uint8_t op); static BasVmResultE execFileOp(BasVmT *vm, uint8_t op);
static BasVmResultE execFsOp(BasVmT *vm, uint8_t op); static BasVmResultE execFsOp(BasVmT *vm, uint8_t op);
static BasVmResultE execLogical(BasVmT *vm, uint8_t op); static BasVmResultE execLogical(BasVmT *vm, uint8_t op);
@ -42,94 +43,12 @@ static BasVmResultE execStringOp(BasVmT *vm, uint8_t op);
static bool pop(BasVmT *vm, BasValueT *val); static bool pop(BasVmT *vm, BasValueT *val);
static bool push(BasVmT *vm, BasValueT val); static bool push(BasVmT *vm, BasValueT val);
static int16_t readInt16(BasVmT *vm); static int16_t readInt16(BasVmT *vm);
static uint8_t readUint8(BasVmT *vm);
static uint16_t readUint16(BasVmT *vm); static uint16_t readUint16(BasVmT *vm);
static uint8_t readUint8(BasVmT *vm);
static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool savedRunning);
static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg); static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg);
// ============================================================
// runSubLoop -- shared inner loop for basVmCallSub variants
// ============================================================
//
// Executes instructions until callDepth drops back to savedCallDepth
// (meaning the subroutine returned). Periodically yields via the
// doEvents callback to keep the GUI responsive during long-running
// event handlers.
static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool savedRunning) {
int32_t stepsSinceYield = 0;
bool hadBreakpoint = false;
while (vm->running && vm->callDepth > savedCallDepth) {
BasVmResultE result = basVmStep(vm);
if (result == BAS_VM_HALTED) {
break;
}
if (result == BAS_VM_BREAKPOINT) {
// Pause in place: yield to the GUI until the host resumes.
// This ensures the sub runs to completion before returning
// to the caller (critical for form init code, event handlers).
vm->running = false;
hadBreakpoint = true;
// Notify host to update debug UI (highlight line, locals, etc.)
if (vm->breakpointFn) {
vm->breakpointFn(vm->breakpointCtx, vm->currentLine);
}
while (!vm->running && vm->doEventsFn) {
if (!vm->doEventsFn(vm->doEventsCtx)) {
// Host is shutting down
vm->pc = savedPc;
vm->running = savedRunning;
return false;
}
}
// User resumed — continue executing the sub
stepsSinceYield = 0;
continue;
}
if (result != BAS_VM_OK) {
vm->pc = savedPc;
vm->callDepth = savedCallDepth;
vm->running = savedRunning;
return false;
}
// Yield periodically to keep the GUI responsive
if (++stepsSinceYield >= SUB_YIELD_INTERVAL) {
stepsSinceYield = 0;
if (vm->doEventsFn) {
if (!vm->doEventsFn(vm->doEventsCtx)) {
vm->running = false;
break;
}
}
}
}
vm->pc = savedPc;
vm->running = savedRunning;
// If we paused at a breakpoint during this sub, notify the host
// so it can pause the event loop before any new event fires.
if (hadBreakpoint && vm->breakpointFn) {
vm->breakpointFn(vm->breakpointCtx, -1);
}
return true;
}
// ============================================================
// basVmCallSub
// ============================================================
bool basVmCallSub(BasVmT *vm, int32_t codeAddr) { bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
if (!vm || !vm->module) { if (!vm || !vm->module) {
return false; return false;
@ -168,10 +87,6 @@ bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
} }
// ============================================================
// basVmCallSubWithArgs
// ============================================================
bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount) { bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount) {
if (!vm || !vm->module) { if (!vm || !vm->module) {
return false; return false;
@ -210,10 +125,6 @@ bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, i
} }
// ============================================================
// basVmCallSubWithArgsOut
// ============================================================
bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount) { bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount) {
if (!vm || !vm->module) { if (!vm || !vm->module) {
return false; return false;
@ -264,10 +175,6 @@ bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args
} }
// ============================================================
// basVmCreate
// ============================================================
BasVmT *basVmCreate(void) { BasVmT *basVmCreate(void) {
BasVmT *vm = (BasVmT *)calloc(1, sizeof(BasVmT)); BasVmT *vm = (BasVmT *)calloc(1, sizeof(BasVmT));
@ -284,10 +191,6 @@ BasVmT *basVmCreate(void) {
} }
// ============================================================
// basVmDestroy
// ============================================================
void basVmDestroy(BasVmT *vm) { void basVmDestroy(BasVmT *vm) {
if (!vm) { if (!vm) {
return; return;
@ -331,65 +234,15 @@ void basVmDestroy(BasVmT *vm) {
} }
// ============================================================
// basVmGetError
// ============================================================
const char *basVmGetError(const BasVmT *vm) {
return vm->errorMsg;
}
// ============================================================
// Debugger API
// ============================================================
void basVmSetBreakpoints(BasVmT *vm, int32_t *lines, int32_t count) {
if (!vm) {
return;
}
vm->breakpoints = lines;
vm->breakpointCount = count;
}
void basVmStepInto(BasVmT *vm) {
if (vm) {
vm->debugBreak = true;
}
}
void basVmStepOver(BasVmT *vm) {
if (vm) {
vm->stepOverDepth = vm->callDepth;
}
}
void basVmStepOut(BasVmT *vm) {
if (vm) {
vm->stepOutDepth = vm->callDepth;
}
}
void basVmRunToCursor(BasVmT *vm, int32_t line) {
if (vm) {
vm->runToCursorLine = line;
}
}
int32_t basVmGetCurrentLine(const BasVmT *vm) { int32_t basVmGetCurrentLine(const BasVmT *vm) {
return vm ? vm->currentLine : 0; return vm ? vm->currentLine : 0;
} }
// ============================================================ const char *basVmGetError(const BasVmT *vm) {
// basVmLoadModule return vm->errorMsg;
// ============================================================ }
void basVmLoadModule(BasVmT *vm, BasModuleT *module) { void basVmLoadModule(BasVmT *vm, BasModuleT *module) {
vm->module = module; vm->module = module;
@ -397,28 +250,16 @@ void basVmLoadModule(BasVmT *vm, BasModuleT *module) {
} }
// ============================================================
// basVmPop
// ============================================================
bool basVmPop(BasVmT *vm, BasValueT *val) { bool basVmPop(BasVmT *vm, BasValueT *val) {
return pop(vm, val); return pop(vm, val);
} }
// ============================================================
// basVmPush
// ============================================================
bool basVmPush(BasVmT *vm, BasValueT val) { bool basVmPush(BasVmT *vm, BasValueT val) {
return push(vm, val); return push(vm, val);
} }
// ============================================================
// basVmReset
// ============================================================
void basVmReset(BasVmT *vm) { void basVmReset(BasVmT *vm) {
for (int32_t i = 0; i < vm->sp; i++) { for (int32_t i = 0; i < vm->sp; i++) {
basValRelease(&vm->stack[i]); basValRelease(&vm->stack[i]);
@ -444,10 +285,6 @@ void basVmReset(BasVmT *vm) {
} }
// ============================================================
// basVmRun
// ============================================================
BasVmResultE basVmRun(BasVmT *vm) { BasVmResultE basVmRun(BasVmT *vm) {
vm->running = true; vm->running = true;
vm->yielded = false; vm->yielded = false;
@ -484,47 +321,44 @@ BasVmResultE basVmRun(BasVmT *vm) {
} }
// ============================================================ void basVmRunToCursor(BasVmT *vm, int32_t line) {
// basVmSetDoEventsCallback if (vm) {
// ============================================================ vm->runToCursorLine = line;
}
void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx) {
vm->doEventsFn = fn;
vm->doEventsCtx = ctx;
} }
// ============================================================ void basVmSetBreakpoints(BasVmT *vm, int32_t *lines, int32_t count) {
// basVmSetCurrentForm if (!vm) {
// ============================================================ return;
}
vm->breakpoints = lines;
vm->breakpointCount = count;
}
void basVmSetCurrentForm(BasVmT *vm, void *formRef) { void basVmSetCurrentForm(BasVmT *vm, void *formRef) {
vm->currentForm = formRef; vm->currentForm = formRef;
} }
// ============================================================
// basVmSetCurrentFormVars
// ============================================================
void basVmSetCurrentFormVars(BasVmT *vm, BasValueT *vars, int32_t count) { void basVmSetCurrentFormVars(BasVmT *vm, BasValueT *vars, int32_t count) {
vm->currentFormVars = vars; vm->currentFormVars = vars;
vm->currentFormVarCount = count; vm->currentFormVarCount = count;
} }
// ============================================================ void basVmSetDoEventsCallback(BasVmT *vm, BasDoEventsFnT fn, void *ctx) {
// basVmSetStepLimit vm->doEventsFn = fn;
// ============================================================ vm->doEventsCtx = ctx;
void basVmSetStepLimit(BasVmT *vm, int32_t limit) {
vm->stepLimit = limit;
} }
// ============================================================ void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext) {
// basVmSetInputCallback vm->ext = *ext;
// ============================================================ }
void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx) { void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx) {
vm->inputFn = fn; vm->inputFn = fn;
@ -532,38 +366,26 @@ void basVmSetInputCallback(BasVmT *vm, BasInputFnT fn, void *ctx) {
} }
// ============================================================
// basVmSetPrintCallback
// ============================================================
void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx) { void basVmSetPrintCallback(BasVmT *vm, BasPrintFnT fn, void *ctx) {
vm->printFn = fn; vm->printFn = fn;
vm->printCtx = ctx; vm->printCtx = ctx;
} }
// ============================================================
// basVmSetUiCallbacks
// ============================================================
void basVmSetExternCallbacks(BasVmT *vm, const BasExternCallbacksT *ext) {
vm->ext = *ext;
}
void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui) {
vm->ui = *ui;
}
void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql) { void basVmSetSqlCallbacks(BasVmT *vm, const BasSqlCallbacksT *sql) {
vm->sql = *sql; vm->sql = *sql;
} }
// ============================================================ void basVmSetStepLimit(BasVmT *vm, int32_t limit) {
// basVmStep -- execute one instruction vm->stepLimit = limit;
// ============================================================ }
void basVmSetUiCallbacks(BasVmT *vm, const BasUiCallbacksT *ui) {
vm->ui = *ui;
}
BasVmResultE basVmStep(BasVmT *vm) { BasVmResultE basVmStep(BasVmT *vm) {
if (!vm->module || vm->pc < 0 || vm->pc >= vm->module->codeLen) { if (!vm->module || vm->pc < 0 || vm->pc >= vm->module->codeLen) {
@ -3448,8 +3270,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ')) { while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ')) {
*end-- = '\0'; *end-- = '\0';
} }
char *p = line; char *p = (char *)dvxSkipWs(line);
while (*p == ' ' || *p == '\t') { p++; }
if (*p == '\0' || *p == ';' || *p == '#') { continue; } if (*p == '\0' || *p == ';' || *p == '#') { continue; }
if (*p == '[') { if (*p == '[') {
char *rb = strchr(p, ']'); char *rb = strchr(p, ']');
@ -3467,9 +3288,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
char *ke = eq - 1; char *ke = eq - 1;
while (ke >= k && (*ke == ' ' || *ke == '\t')) { *ke-- = '\0'; } while (ke >= k && (*ke == ' ' || *ke == '\t')) { *ke-- = '\0'; }
if (strcasecmp(k, keyStr->data) == 0) { if (strcasecmp(k, keyStr->data) == 0) {
char *v = eq + 1; result = dvxSkipWs(eq + 1);
while (*v == ' ' || *v == '\t') { v++; }
result = v;
break; break;
} }
} }
@ -3502,23 +3321,10 @@ BasVmResultE basVmStep(BasVmT *vm) {
BasStringT *valStr = basValFormatString(valVal); BasStringT *valStr = basValFormatString(valVal);
// Read existing file into memory // Read existing file into memory
char *content = NULL; char *content = platformReadFile(fileStr->data, NULL);
int32_t contentLen = 0;
FILE *fp = fopen(fileStr->data, "r");
if (fp) {
fseek(fp, 0, SEEK_END);
contentLen = (int32_t)ftell(fp);
fseek(fp, 0, SEEK_SET);
content = (char *)malloc(contentLen + 1);
if (content) {
contentLen = (int32_t)fread(content, 1, contentLen, fp);
content[contentLen] = '\0';
}
fclose(fp);
}
// Write updated file // Write updated file
fp = fopen(fileStr->data, "w"); FILE *fp = fopen(fileStr->data, "w");
if (fp) { if (fp) {
bool keyWritten = false; bool keyWritten = false;
bool inSection = false; bool inSection = false;
@ -3543,8 +3349,7 @@ BasVmResultE basVmStep(BasVmT *vm) {
line = NULL; line = NULL;
} }
char *p = lineBuf; char *p = (char *)dvxSkipWs(lineBuf);
while (*p == ' ' || *p == '\t') { p++; }
if (*p == '[') { if (*p == '[') {
// Write pending key before leaving section // Write pending key before leaving section
@ -3621,9 +3426,26 @@ BasVmResultE basVmStep(BasVmT *vm) {
} }
// ============================================================ void basVmStepInto(BasVmT *vm) {
// currentFrame if (vm) {
// ============================================================ vm->debugBreak = true;
}
}
void basVmStepOut(BasVmT *vm) {
if (vm) {
vm->stepOutDepth = vm->callDepth;
}
}
void basVmStepOver(BasVmT *vm) {
if (vm) {
vm->stepOverDepth = vm->callDepth;
}
}
static BasCallFrameT *currentFrame(BasVmT *vm) { static BasCallFrameT *currentFrame(BasVmT *vm) {
if (vm->callDepth <= 0) { if (vm->callDepth <= 0) {
@ -3635,10 +3457,6 @@ static BasCallFrameT *currentFrame(BasVmT *vm) {
} }
// ============================================================
// defaultPrint
// ============================================================
static void defaultPrint(void *ctx, const char *text, bool newline) { static void defaultPrint(void *ctx, const char *text, bool newline) {
(void)ctx; (void)ctx;
fputs(text, stdout); fputs(text, stdout);
@ -3649,9 +3467,36 @@ static void defaultPrint(void *ctx, const char *text, bool newline) {
} }
// ============================================================ // Dir$ state -- persists across Dir$() calls within a VM session
// execArith static DIR *sDirHandle = NULL;
// ============================================================ static char sDirPattern[DVX_MAX_PATH];
static char sDirPath[DVX_MAX_PATH];
static void dirClose(void) {
if (sDirHandle) {
closedir(sDirHandle);
sDirHandle = NULL;
}
}
static const char *dirNext(void) {
if (!sDirHandle) {
return NULL;
}
struct dirent *ent;
while ((ent = readdir(sDirHandle)) != NULL) {
if (fnmatch(sDirPattern, ent->d_name, FNM_CASEFOLD) == 0) {
return ent->d_name;
}
}
dirClose();
return NULL;
}
static BasVmResultE execArith(BasVmT *vm, uint8_t op) { static BasVmResultE execArith(BasVmT *vm, uint8_t op) {
BasValueT b; BasValueT b;
@ -3765,10 +3610,6 @@ static BasVmResultE execArith(BasVmT *vm, uint8_t op) {
} }
// ============================================================
// execCompare
// ============================================================
static BasVmResultE execCompare(BasVmT *vm, uint8_t op) { static BasVmResultE execCompare(BasVmT *vm, uint8_t op) {
BasValueT b; BasValueT b;
BasValueT a; BasValueT a;
@ -3798,10 +3639,6 @@ static BasVmResultE execCompare(BasVmT *vm, uint8_t op) {
} }
// ============================================================
// execFileOp
// ============================================================
// File mode constants (matches compiler/parser.c emission) // File mode constants (matches compiler/parser.c emission)
#define FILE_MODE_INPUT 1 #define FILE_MODE_INPUT 1
#define FILE_MODE_OUTPUT 2 #define FILE_MODE_OUTPUT 2
@ -4446,41 +4283,6 @@ static BasVmResultE execFileOp(BasVmT *vm, uint8_t op) {
} }
// ============================================================
// execFsOp -- filesystem operations (Kill, Name, MkDir, etc.)
// ============================================================
// Dir$ state -- persists across Dir$() calls within a VM session
static DIR *sDirHandle = NULL;
static char sDirPattern[260];
static char sDirPath[260];
static void dirClose(void) {
if (sDirHandle) {
closedir(sDirHandle);
sDirHandle = NULL;
}
}
static const char *dirNext(void) {
if (!sDirHandle) {
return NULL;
}
struct dirent *ent;
while ((ent = readdir(sDirHandle)) != NULL) {
if (fnmatch(sDirPattern, ent->d_name, FNM_CASEFOLD) == 0) {
return ent->d_name;
}
}
dirClose();
return NULL;
}
static BasVmResultE execFsOp(BasVmT *vm, uint8_t op) { static BasVmResultE execFsOp(BasVmT *vm, uint8_t op) {
switch (op) { switch (op) {
case OP_FS_KILL: { case OP_FS_KILL: {
@ -4667,7 +4469,7 @@ static BasVmResultE execFsOp(BasVmT *vm, uint8_t op) {
} }
case OP_FS_CURDIR: { case OP_FS_CURDIR: {
char cwd[260]; char cwd[DVX_MAX_PATH];
if (!getcwd(cwd, sizeof(cwd))) { if (!getcwd(cwd, sizeof(cwd))) {
cwd[0] = '\0'; cwd[0] = '\0';
@ -4867,10 +4669,6 @@ static BasVmResultE execFsOp(BasVmT *vm, uint8_t op) {
} }
// ============================================================
// execLogical
// ============================================================
static BasVmResultE execLogical(BasVmT *vm, uint8_t op) { static BasVmResultE execLogical(BasVmT *vm, uint8_t op) {
if (op == OP_NOT) { if (op == OP_NOT) {
if (vm->sp < 1) { if (vm->sp < 1) {
@ -4912,10 +4710,6 @@ static BasVmResultE execLogical(BasVmT *vm, uint8_t op) {
} }
// ============================================================
// execMath
// ============================================================
static BasVmResultE execMath(BasVmT *vm, uint8_t op) { static BasVmResultE execMath(BasVmT *vm, uint8_t op) {
if (op == OP_MATH_RND) { if (op == OP_MATH_RND) {
// Pop the dummy arg (parser pushes -1 for RND()) // Pop the dummy arg (parser pushes -1 for RND())
@ -4985,10 +4779,6 @@ static BasVmResultE execMath(BasVmT *vm, uint8_t op) {
} }
// ============================================================
// execPrint
// ============================================================
static BasVmResultE execPrint(BasVmT *vm) { static BasVmResultE execPrint(BasVmT *vm) {
BasValueT val; BasValueT val;
@ -5015,10 +4805,6 @@ static BasVmResultE execPrint(BasVmT *vm) {
} }
// ============================================================
// execStringOp
// ============================================================
static BasVmResultE execStringOp(BasVmT *vm, uint8_t op) { static BasVmResultE execStringOp(BasVmT *vm, uint8_t op) {
switch (op) { switch (op) {
case OP_STR_CONCAT: { case OP_STR_CONCAT: {
@ -5435,10 +5221,6 @@ static BasVmResultE execStringOp(BasVmT *vm, uint8_t op) {
} }
// ============================================================
// pop
// ============================================================
static bool pop(BasVmT *vm, BasValueT *val) { static bool pop(BasVmT *vm, BasValueT *val) {
if (vm->sp <= 0) { if (vm->sp <= 0) {
return false; return false;
@ -5449,10 +5231,6 @@ static bool pop(BasVmT *vm, BasValueT *val) {
} }
// ============================================================
// push
// ============================================================
static bool push(BasVmT *vm, BasValueT val) { static bool push(BasVmT *vm, BasValueT val) {
if (vm->sp >= BAS_VM_STACK_SIZE) { if (vm->sp >= BAS_VM_STACK_SIZE) {
return false; return false;
@ -5463,10 +5241,6 @@ static bool push(BasVmT *vm, BasValueT val) {
} }
// ============================================================
// readInt16
// ============================================================
static int16_t readInt16(BasVmT *vm) { static int16_t readInt16(BasVmT *vm) {
int16_t val; int16_t val;
memcpy(&val, &vm->module->code[vm->pc], sizeof(int16_t)); memcpy(&val, &vm->module->code[vm->pc], sizeof(int16_t));
@ -5475,19 +5249,6 @@ static int16_t readInt16(BasVmT *vm) {
} }
// ============================================================
// readUint8
// ============================================================
static uint8_t readUint8(BasVmT *vm) {
return vm->module->code[vm->pc++];
}
// ============================================================
// readUint16
// ============================================================
static uint16_t readUint16(BasVmT *vm) { static uint16_t readUint16(BasVmT *vm) {
uint16_t val; uint16_t val;
memcpy(&val, &vm->module->code[vm->pc], sizeof(uint16_t)); memcpy(&val, &vm->module->code[vm->pc], sizeof(uint16_t));
@ -5496,9 +5257,85 @@ static uint16_t readUint16(BasVmT *vm) {
} }
// ============================================================ static uint8_t readUint8(BasVmT *vm) {
// runtimeError return vm->module->code[vm->pc++];
// ============================================================ }
// Executes instructions until callDepth drops back to savedCallDepth
// (meaning the subroutine returned). Periodically yields via the
// doEvents callback to keep the GUI responsive during long-running
// event handlers.
static bool runSubLoop(BasVmT *vm, int32_t savedPc, int32_t savedCallDepth, bool savedRunning) {
int32_t stepsSinceYield = 0;
bool hadBreakpoint = false;
while (vm->running && vm->callDepth > savedCallDepth) {
BasVmResultE result = basVmStep(vm);
if (result == BAS_VM_HALTED) {
break;
}
if (result == BAS_VM_BREAKPOINT) {
// Pause in place: yield to the GUI until the host resumes.
// This ensures the sub runs to completion before returning
// to the caller (critical for form init code, event handlers).
vm->running = false;
hadBreakpoint = true;
// Notify host to update debug UI (highlight line, locals, etc.)
if (vm->breakpointFn) {
vm->breakpointFn(vm->breakpointCtx, vm->currentLine);
}
while (!vm->running && vm->doEventsFn) {
if (!vm->doEventsFn(vm->doEventsCtx)) {
// Host is shutting down
vm->pc = savedPc;
vm->running = savedRunning;
return false;
}
}
// User resumed — continue executing the sub
stepsSinceYield = 0;
continue;
}
if (result != BAS_VM_OK) {
vm->pc = savedPc;
vm->callDepth = savedCallDepth;
vm->running = savedRunning;
return false;
}
// Yield periodically to keep the GUI responsive
if (++stepsSinceYield >= SUB_YIELD_INTERVAL) {
stepsSinceYield = 0;
if (vm->doEventsFn) {
if (!vm->doEventsFn(vm->doEventsCtx)) {
vm->running = false;
break;
}
}
}
}
vm->pc = savedPc;
vm->running = savedRunning;
// If we paused at a breakpoint during this sub, notify the host
// so it can pause the event loop before any new event fires.
if (hadBreakpoint && vm->breakpointFn) {
vm->breakpointFn(vm->breakpointCtx, -1);
}
return true;
}
static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg) { static void runtimeError(BasVmT *vm, int32_t errNum, const char *msg) {
vm->errorNumber = errNum; vm->errorNumber = errNum;

View file

@ -25,12 +25,13 @@
// Limits // Limits
// ============================================================ // ============================================================
#define BAS_VM_STACK_SIZE 256 // evaluation stack depth #define BAS_VM_STACK_SIZE 256 // evaluation stack depth
#define BAS_VM_CALL_STACK_SIZE 64 // max call nesting #define BAS_VM_CALL_STACK_SIZE 64 // max call nesting
#define BAS_VM_MAX_GLOBALS 512 // global variable slots #define BAS_VM_MAX_GLOBALS 512 // global variable slots
#define BAS_VM_MAX_LOCALS 64 // locals per stack frame #define BAS_VM_MAX_LOCALS 64 // locals per stack frame
#define BAS_VM_MAX_FOR_DEPTH 32 // nested FOR loops #define BAS_VM_MAX_FOR_DEPTH 32 // nested FOR loops
#define BAS_VM_MAX_FILES 16 // open file channels #define BAS_VM_MAX_FILES 16 // open file channels
#define BAS_VM_DEFAULT_STEP_SLICE 10000 // bytecode steps per yield
// ============================================================ // ============================================================
// Result codes // Result codes

View file

@ -14,6 +14,7 @@
#include "../compiler/obfuscate.h" #include "../compiler/obfuscate.h"
#include "../compiler/parser.h" #include "../compiler/parser.h"
#include "../compiler/strip.h" #include "../compiler/strip.h"
#include "../compiler/symtab.h"
#include "../compiler/opcodes.h" #include "../compiler/opcodes.h"
#include "../runtime/vm.h" #include "../runtime/vm.h"
#include "../runtime/values.h" #include "../runtime/values.h"
@ -21,35 +22,24 @@
#include "../../../../libs/kpunch/libdvx/dvxRes.h" #include "../../../../libs/kpunch/libdvx/dvxRes.h"
#include "../../../../libs/kpunch/libdvx/dvxPrefs.h" #include "../../../../libs/kpunch/libdvx/dvxPrefs.h"
#include "../../../../libs/kpunch/libdvx/dvxTypes.h" #include "../../../../libs/kpunch/libdvx/dvxTypes.h"
#include "../../../../tools/dvxResWrite.h" #include "../../../../libs/kpunch/libdvx/platform/dvxPlat.h"
#include "../basBuild.h"
#include "../basRes.h"
#include "stb_ds_wrap.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h> #include <strings.h>
// ============================================================ // Function prototypes (alphabetical)
// Limits static void concatGrow(char **buf, int32_t *cap, int32_t need);
// ============================================================
#define MAX_FILES 64
#define MAX_PATH_LEN 260
#define MAX_NAME 64
// ============================================================
// Prototypes
// ============================================================
static void concatGrow(char **buf, int32_t *cap, int32_t need);
static char *readFile(const char *path, int32_t *outLen);
static const char *extractFormCode(const char *frmText); static const char *extractFormCode(const char *frmText);
int main(int argc, char **argv);
static void usage(void); static void usage(void);
// ============================================================
// extractFormCode -- skip past the form layout to the code section
// ============================================================
static void concatGrow(char **buf, int32_t *cap, int32_t need) { static void concatGrow(char **buf, int32_t *cap, int32_t need) {
while (*cap < need) { while (*cap < need) {
*cap *= 2; *cap *= 2;
@ -70,7 +60,7 @@ static const char *extractFormCode(const char *frmText) {
while (*p) { while (*p) {
// Skip leading whitespace // Skip leading whitespace
while (*p == ' ' || *p == '\t') { p++; } p = dvxSkipWs(p);
if (strncasecmp(p, "Begin ", 6) == 0) { if (strncasecmp(p, "Begin ", 6) == 0) {
if (!inForm && strncasecmp(p + 6, "Form ", 5) == 0) { if (!inForm && strncasecmp(p + 6, "Form ", 5) == 0) {
@ -101,44 +91,6 @@ static const char *extractFormCode(const char *frmText) {
} }
// ============================================================
// readFile
// ============================================================
static char *readFile(const char *path, int32_t *outLen) {
FILE *f = fopen(path, "rb");
if (!f) {
return NULL;
}
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = (char *)malloc(size + 1);
if (!buf) {
fclose(f);
return NULL;
}
int32_t n = (int32_t)fread(buf, 1, size, f);
fclose(f);
buf[n] = '\0';
if (outLen) {
*outLen = n;
}
return buf;
}
// ============================================================
// usage
// ============================================================
static void usage(void) { static void usage(void) {
fprintf(stderr, "DVX BASIC Compiler\n\n"); fprintf(stderr, "DVX BASIC Compiler\n\n");
fprintf(stderr, "Usage: BASCOMP project.dbp [-o output.app] [-release]\n\n"); fprintf(stderr, "Usage: BASCOMP project.dbp [-o output.app] [-release]\n\n");
@ -148,10 +100,6 @@ static void usage(void) {
} }
// ============================================================
// main
// ============================================================
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (argc < 2) { if (argc < 2) {
usage(); usage();
@ -191,14 +139,9 @@ int main(int argc, char **argv) {
} }
// Derive project directory // Derive project directory
char projectDir[MAX_PATH_LEN]; char projectDir[DVX_MAX_PATH];
snprintf(projectDir, sizeof(projectDir), "%s", dbpPath); snprintf(projectDir, sizeof(projectDir), "%s", dbpPath);
char *sep = strrchr(projectDir, '/'); char *sep = platformPathDirEnd(projectDir);
char *sep2 = strrchr(projectDir, '\\');
if (sep2 > sep) {
sep = sep2;
}
if (sep) { if (sep) {
*sep = '\0'; *sep = '\0';
@ -208,19 +151,19 @@ int main(int argc, char **argv) {
} }
// Read project metadata // Read project metadata
const char *projName = prefsGetString(prefs, "Project", "Name", "App"); const char *projName = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_NAME, "App");
const char *author = prefsGetString(prefs, "Project", "Author", ""); const char *author = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_AUTHOR, "");
const char *company = prefsGetString(prefs, "Project", "Company", ""); const char *publisher = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_PUBLISHER, "");
const char *version = prefsGetString(prefs, "Project", "Version", ""); const char *version = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_VERSION, "");
const char *copyright = prefsGetString(prefs, "Project", "Copyright", ""); const char *copyright = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_COPYRIGHT, "");
const char *description = prefsGetString(prefs, "Project", "Description", ""); const char *description = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_DESCRIPTION, "");
const char *iconPath = prefsGetString(prefs, "Project", "Icon", ""); const char *iconPath = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_ICON, "");
const char *helpFile = prefsGetString(prefs, "Project", "HelpFile", ""); const char *helpFile = prefsGetString(prefs, BAS_INI_SECTION_PROJECT, BAS_INI_KEY_HELPFILE, "");
const char *startupForm = prefsGetString(prefs, "Settings", "StartupForm", ""); const char *startupForm = prefsGetString(prefs, BAS_INI_SECTION_SETTINGS, BAS_INI_KEY_STARTUPFORM, "");
(void)startupForm; // used implicitly by stub's basFormRtLoadAllForms (void)startupForm; // used implicitly by stub's basFormRtLoadAllForms
// Derive output path // Derive output path
char outBuf[MAX_PATH_LEN]; char outBuf[DVX_MAX_PATH];
if (!outputPath) { if (!outputPath) {
snprintf(outBuf, sizeof(outBuf), "%s/%s.app", projectDir, projName); snprintf(outBuf, sizeof(outBuf), "%s/%s.app", projectDir, projName);
@ -230,45 +173,48 @@ int main(int argc, char **argv) {
printf("Project: %s\n", projName); printf("Project: %s\n", projName);
printf("Output: %s (%s)\n", outputPath, release ? "release" : "debug"); printf("Output: %s (%s)\n", outputPath, release ? "release" : "debug");
// Collect source files // Collect source files (dynamic array; grows as needed)
typedef struct { typedef struct {
char path[MAX_PATH_LEN]; char path[DVX_MAX_PATH];
bool isForm; bool isForm;
} SrcFileT; } SrcFileT;
SrcFileT files[MAX_FILES]; SrcFileT *files = NULL;
int32_t fileCount = 0;
// Modules // Modules
for (int32_t i = 0; i < MAX_FILES; i++) { for (int32_t i = 0; ; i++) {
char key[16]; char key[16];
snprintf(key, sizeof(key), "File%d", (int)i); snprintf(key, sizeof(key), "File%d", (int)i);
const char *val = prefsGetString(prefs, "Modules", key, NULL); const char *val = prefsGetString(prefs, BAS_INI_SECTION_MODULES, key, NULL);
if (!val) { if (!val) {
break; break;
} }
snprintf(files[fileCount].path, MAX_PATH_LEN, "%s/%s", projectDir, val); SrcFileT entry;
files[fileCount].isForm = false; snprintf(entry.path, DVX_MAX_PATH, "%s/%s", projectDir, val);
fileCount++; entry.isForm = false;
arrput(files, entry);
} }
// Forms // Forms
for (int32_t i = 0; i < MAX_FILES; i++) { for (int32_t i = 0; ; i++) {
char key[16]; char key[16];
snprintf(key, sizeof(key), "File%d", (int)i); snprintf(key, sizeof(key), "File%d", (int)i);
const char *val = prefsGetString(prefs, "Forms", key, NULL); const char *val = prefsGetString(prefs, BAS_INI_SECTION_FORMS, key, NULL);
if (!val) { if (!val) {
break; break;
} }
snprintf(files[fileCount].path, MAX_PATH_LEN, "%s/%s", projectDir, val); SrcFileT entry;
files[fileCount].isForm = true; snprintf(entry.path, DVX_MAX_PATH, "%s/%s", projectDir, val);
fileCount++; entry.isForm = true;
arrput(files, entry);
} }
int32_t fileCount = (int32_t)arrlen(files);
if (fileCount == 0) { if (fileCount == 0) {
fprintf(stderr, "Error: project has no source files.\n"); fprintf(stderr, "Error: project has no source files.\n");
prefsClose(prefs); prefsClose(prefs);
@ -294,7 +240,7 @@ int main(int argc, char **argv) {
if (pass == 1 && !files[i].isForm) { continue; } if (pass == 1 && !files[i].isForm) { continue; }
int32_t srcLen = 0; int32_t srcLen = 0;
char *srcBuf = readFile(files[i].path, &srcLen); char *srcBuf = platformReadFile(files[i].path, &srcLen);
if (!srcBuf) { if (!srcBuf) {
fprintf(stderr, "Error: cannot read %s\n", files[i].path); fprintf(stderr, "Error: cannot read %s\n", files[i].path);
@ -307,20 +253,18 @@ int main(int argc, char **argv) {
if (files[i].isForm) { if (files[i].isForm) {
// Extract form name from "Begin Form <name>" // Extract form name from "Begin Form <name>"
char formName[MAX_NAME] = ""; char formName[BAS_MAX_SYMBOL_NAME] = "";
const char *bp = srcBuf; const char *bp = srcBuf;
while (*bp) { while (*bp) {
while (*bp == ' ' || *bp == '\t') { bp++; } bp = dvxSkipWs(bp);
if (strncasecmp(bp, "Begin Form ", 11) == 0) { if (strncasecmp(bp, "Begin Form ", 11) == 0) {
bp += 11; bp = dvxSkipWs(bp + 11);
while (*bp == ' ' || *bp == '\t') { bp++; }
int32_t ni = 0; int32_t ni = 0;
while (*bp && *bp != ' ' && *bp != '\t' && *bp != '\r' && *bp != '\n' && ni < MAX_NAME - 1) { while (*bp && *bp != ' ' && *bp != '\t' && *bp != '\r' && *bp != '\n' && ni < BAS_MAX_SYMBOL_NAME - 1) {
formName[ni++] = *bp++; formName[ni++] = *bp++;
} }
@ -420,11 +364,10 @@ int main(int argc, char **argv) {
} }
// Read all .frm texts up front; they're used for obfuscation and // Read all .frm texts up front; they're used for obfuscation and
// then embedded as FORM0, FORM1, ... resources below. // then embedded as FORM0, FORM1, ... resources below. Parallel
int32_t frmCount = 0; // dynamic arrays, one entry per form file.
char *frmData[MAX_FILES]; char **frmData = NULL; // stb_ds: strdup'd stripped form text
int32_t frmLens[MAX_FILES]; int32_t *frmLens = NULL; // stb_ds: length of stripped form text
int32_t frmFileIdx[MAX_FILES]; // index into files[] for this frm
for (int32_t i = 0; i < fileCount; i++) { for (int32_t i = 0; i < fileCount; i++) {
if (!files[i].isForm) { if (!files[i].isForm) {
@ -432,7 +375,7 @@ int main(int argc, char **argv) {
} }
int32_t flen = 0; int32_t flen = 0;
char *fdata = readFile(files[i].path, &flen); char *fdata = platformReadFile(files[i].path, &flen);
if (!fdata) { if (!fdata) {
continue; continue;
@ -452,28 +395,29 @@ int main(int argc, char **argv) {
int32_t strippedLen = basStripFrmComments(fdata, flen, stripped, stripCap); int32_t strippedLen = basStripFrmComments(fdata, flen, stripped, stripCap);
free(fdata); free(fdata);
frmData[frmCount] = (char *)stripped; arrput(frmData, (char *)stripped);
frmLens[frmCount] = strippedLen; arrput(frmLens, strippedLen);
frmFileIdx[frmCount] = i;
frmCount++;
} }
// Obfuscate form/control names in release mode int32_t frmCount = (int32_t)arrlen(frmData);
BasObfFrmT obfFrms[MAX_FILES];
for (int32_t i = 0; i < MAX_FILES; i++) { // Obfuscate form/control names in release mode
obfFrms[i].data = NULL; BasObfFrmT *obfFrms = NULL;
obfFrms[i].len = 0;
for (int32_t i = 0; i < frmCount; i++) {
BasObfFrmT empty = { NULL, 0 };
arrput(obfFrms, empty);
} }
if (release && frmCount > 0) { if (release && frmCount > 0) {
const char *frmTexts[MAX_FILES]; const char **frmTexts = NULL;
for (int32_t i = 0; i < frmCount; i++) { for (int32_t i = 0; i < frmCount; i++) {
frmTexts[i] = frmData[i]; arrput(frmTexts, frmData[i]);
} }
basObfuscateNames(mod, frmTexts, frmLens, frmCount, obfFrms); basObfuscateNames(mod, frmTexts, frmLens, frmCount, obfFrms);
arrfree(frmTexts);
printf(" obfuscated %d form(s)\n", (int)frmCount); printf(" obfuscated %d form(s)\n", (int)frmCount);
} }
@ -507,31 +451,26 @@ int main(int argc, char **argv) {
basModuleFree(mod); basModuleFree(mod);
// Find the stub -- look in the compiler's own directory // Read the stub DXE embedded in our own executable as a resource.
char compilerDir[MAX_PATH_LEN]; // The bascomp Makefile appends basstub.app to bascomp post-link
snprintf(compilerDir, sizeof(compilerDir), "%s", argv[0]); // via `dvxres add ... STUB binary @basstub.app`, so BASSTUB.APP no
sep = strrchr(compilerDir, '/'); // longer has to sit alongside the compiler.
sep2 = strrchr(compilerDir, '\\'); DvxResHandleT *selfRes = dvxResOpen(argv[0]);
if (sep2 > sep) { if (!selfRes) {
sep = sep2; fprintf(stderr, "Error: cannot open %s to read embedded stub.\n", argv[0]);
free(modData);
free(dbgData);
prefsClose(prefs);
return 1;
} }
if (sep) { uint32_t stubLen = 0;
*sep = '\0'; void *stubData = dvxResRead(selfRes, BAS_RES_STUB, &stubLen);
} else { dvxResClose(selfRes);
compilerDir[0] = '.';
compilerDir[1] = '\0';
}
char stubPath[MAX_PATH_LEN];
snprintf(stubPath, sizeof(stubPath), "%s/BASSTUB.APP", compilerDir);
int32_t stubLen = 0;
char *stubData = readFile(stubPath, &stubLen);
if (!stubData) { if (!stubData) {
fprintf(stderr, "Error: cannot find stub at %s\n", stubPath); fprintf(stderr, "Error: STUB resource not found in %s.\n", argv[0]);
free(modData); free(modData);
free(dbgData); free(dbgData);
prefsClose(prefs); prefsClose(prefs);
@ -554,56 +493,66 @@ int main(int argc, char **argv) {
fclose(out); fclose(out);
free(stubData); free(stubData);
// Attach resources // Pick the right form bytes per build mode: release builds use the
dvxResAppendEntry(outputPath, "name", DVX_RES_TEXT, projName, (uint32_t)strlen(projName) + 1); // obfuscated variant when available, debug builds always use the raw
// stripped text.
const uint8_t **emitFormData = NULL;
int32_t *emitFormLens = NULL;
if (author[0]) { dvxResAppendEntry(outputPath, "author", DVX_RES_TEXT, author, (uint32_t)strlen(author) + 1); } for (int32_t fi = 0; fi < frmCount; fi++) {
if (company[0]) { dvxResAppendEntry(outputPath, "company", DVX_RES_TEXT, company, (uint32_t)strlen(company) + 1); } if (release && obfFrms[fi].data) {
if (version[0]) { dvxResAppendEntry(outputPath, "version", DVX_RES_TEXT, version, (uint32_t)strlen(version) + 1); } arrput(emitFormData, (const uint8_t *)obfFrms[fi].data);
if (copyright[0]) { dvxResAppendEntry(outputPath, "copyright", DVX_RES_TEXT, copyright, (uint32_t)strlen(copyright) + 1); } arrput(emitFormLens, obfFrms[fi].len);
if (description[0]) { dvxResAppendEntry(outputPath, "description", DVX_RES_TEXT, description, (uint32_t)strlen(description) + 1); } } else {
arrput(emitFormData, (const uint8_t *)frmData[fi]);
// Icon arrput(emitFormLens, frmLens[fi]);
if (iconPath[0]) {
char iconFullPath[MAX_PATH_LEN];
snprintf(iconFullPath, sizeof(iconFullPath), "%s/%s", projectDir, iconPath);
int32_t iconLen = 0;
char *iconData = readFile(iconFullPath, &iconLen);
if (iconData) {
dvxResAppendEntry(outputPath, "icon32", DVX_RES_ICON, iconData, (uint32_t)iconLen);
free(iconData);
} }
} }
// Help file name (file is expected alongside the .app) // Resolve icon disk path (if any) against the project directory.
char iconFullPath[DVX_MAX_PATH];
const char *iconDiskPath = NULL;
if (iconPath[0]) {
snprintf(iconFullPath, sizeof(iconFullPath), "%s/%s", projectDir, iconPath);
iconDiskPath = iconFullPath;
}
// Emit all resources via the shared helper.
BasBuildSpecT spec;
memset(&spec, 0, sizeof(spec));
spec.projName = projName;
spec.author = author;
spec.publisher = publisher;
spec.version = version;
spec.copyright = copyright;
spec.description = description;
spec.helpFile = helpFile;
spec.iconPath = iconDiskPath;
spec.moduleData = modData;
spec.moduleLen = modLen;
spec.debugData = dbgData;
spec.debugLen = dbgLen;
spec.formCount = frmCount;
spec.formData = emitFormData;
spec.formLens = emitFormLens;
basBuildEmitResources(outputPath, &spec);
free(modData);
free(dbgData);
// Copy help file to output directory (the HELPFILE resource itself was
// written by basBuildEmitResources).
if (helpFile[0]) { if (helpFile[0]) {
const char *helpBase = helpFile; const char *helpBase = platformPathBaseName(helpFile);
const char *hs = strrchr(helpBase, '/');
const char *hs2 = strrchr(helpBase, '\\');
if (hs2 > hs) { char helpSrc[DVX_MAX_PATH];
hs = hs2;
}
if (hs) {
helpBase = hs + 1;
}
dvxResAppendEntry(outputPath, "helpfile", DVX_RES_TEXT, helpBase, (uint32_t)strlen(helpBase) + 1);
// Copy help file to output directory
char helpSrc[MAX_PATH_LEN];
snprintf(helpSrc, sizeof(helpSrc), "%s/%s", projectDir, helpFile); snprintf(helpSrc, sizeof(helpSrc), "%s/%s", projectDir, helpFile);
char outDir[MAX_PATH_LEN]; char outDir[DVX_MAX_PATH];
snprintf(outDir, sizeof(outDir), "%s", outputPath); snprintf(outDir, sizeof(outDir), "%s", outputPath);
char *outSep = strrchr(outDir, '/'); char *outSep = platformPathDirEnd(outDir);
char *outSep2 = strrchr(outDir, '\\');
if (outSep2 > outSep) {
outSep = outSep2;
}
if (outSep) { if (outSep) {
*outSep = '\0'; *outSep = '\0';
@ -612,11 +561,11 @@ int main(int argc, char **argv) {
outDir[1] = '\0'; outDir[1] = '\0';
} }
char helpDst[MAX_PATH_LEN]; char helpDst[DVX_MAX_PATH];
snprintf(helpDst, sizeof(helpDst), "%s/%s", outDir, helpBase); snprintf(helpDst, sizeof(helpDst), "%s/%s", outDir, helpBase);
int32_t hLen = 0; int32_t hLen = 0;
char *hData = readFile(helpSrc, &hLen); char *hData = platformReadFile(helpSrc, &hLen);
if (hData) { if (hData) {
FILE *hf = fopen(helpDst, "wb"); FILE *hf = fopen(helpDst, "wb");
@ -630,35 +579,18 @@ int main(int argc, char **argv) {
} }
} }
// MODULE resource
dvxResAppendEntry(outputPath, "MODULE", DVX_RES_BINARY, modData, (uint32_t)modLen);
free(modData);
// DEBUG resource
if (dbgData) {
dvxResAppendEntry(outputPath, "DEBUG", DVX_RES_BINARY, dbgData, (uint32_t)dbgLen);
free(dbgData);
}
// Form resources -- use obfuscated bytes in release mode, raw otherwise
for (int32_t fi = 0; fi < frmCount; fi++) {
char resName[16];
snprintf(resName, sizeof(resName), "FORM%d", (int)fi);
if (release && obfFrms[fi].data) {
dvxResAppendEntry(outputPath, resName, DVX_RES_BINARY, obfFrms[fi].data, (uint32_t)obfFrms[fi].len);
} else {
dvxResAppendEntry(outputPath, resName, DVX_RES_BINARY, frmData[fi], (uint32_t)frmLens[fi]);
}
}
// Free .frm buffers // Free .frm buffers
for (int32_t i = 0; i < frmCount; i++) { for (int32_t i = 0; i < frmCount; i++) {
free(frmData[i]); free(frmData[i]);
free(obfFrms[i].data); free(obfFrms[i].data);
} }
(void)frmFileIdx; // unused (kept in case of future per-file metadata) arrfree(files);
arrfree(frmData);
arrfree(frmLens);
arrfree(obfFrms);
arrfree(emitFormData);
arrfree(emitFormLens);
prefsClose(prefs); prefsClose(prefs);

View file

@ -14,6 +14,7 @@
#include "dvxRes.h" #include "dvxRes.h"
#include "dvxWgt.h" #include "dvxWgt.h"
#include "shellApp.h" #include "shellApp.h"
#include "../basRes.h"
#include "../runtime/vm.h" #include "../runtime/vm.h"
#include "../runtime/serialize.h" #include "../runtime/serialize.h"
#include "../formrt/formrt.h" #include "../formrt/formrt.h"
@ -45,10 +46,19 @@ AppDescriptorT appDescriptor = {
static AppContextT *sAc = NULL; static AppContextT *sAc = NULL;
static void stubPrint(void *ctx, const char *text, bool newline) { // Function prototypes (alphabetical; main/appMain last)
static bool stubDoEvents(void *ctx);
static bool stubInput(void *ctx, const char *prompt, char *buf, int32_t bufSize);
static void stubPrint(void *ctx, const char *text, bool newline);
int32_t appMain(DxeAppContextT *ctx);
static bool stubDoEvents(void *ctx) {
(void)ctx; (void)ctx;
(void)text;
(void)newline; if (!sAc) {
return false;
}
return dvxUpdate(sAc);
} }
@ -64,21 +74,11 @@ static bool stubInput(void *ctx, const char *prompt, char *buf, int32_t bufSize)
} }
static bool stubDoEvents(void *ctx) { static void stubPrint(void *ctx, const char *text, bool newline) {
(void)ctx; (void)ctx;
(void)text;
if (!sAc) { (void)newline;
return false;
}
return dvxUpdate(sAc);
} }
// ============================================================
// appMain
// ============================================================
int32_t appMain(DxeAppContextT *ctx) { int32_t appMain(DxeAppContextT *ctx) {
sAc = ctx->shellCtx; sAc = ctx->shellCtx;
@ -92,7 +92,7 @@ int32_t appMain(DxeAppContextT *ctx) {
// Read app name and update the shell's app record // Read app name and update the shell's app record
uint32_t nameSize = 0; uint32_t nameSize = 0;
char *appName = (char *)dvxResRead(res, "name", &nameSize); char *appName = (char *)dvxResRead(res, BAS_RES_NAME, &nameSize);
if (appName) { if (appName) {
ShellAppT *app = shellGetApp(ctx->appId); ShellAppT *app = shellGetApp(ctx->appId);
@ -106,16 +106,16 @@ int32_t appMain(DxeAppContextT *ctx) {
// Set help file path if present // Set help file path if present
uint32_t helpNameSize = 0; uint32_t helpNameSize = 0;
char *helpName = (char *)dvxResRead(res, "helpfile", &helpNameSize); char *helpName = (char *)dvxResRead(res, BAS_RES_HELPFILE, &helpNameSize);
if (helpName) { if (helpName) {
snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s%c%s", ctx->appDir, DVX_PATH_SEP, helpName); snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s" DVX_PATH_SEP "%s", ctx->appDir, helpName);
free(helpName); free(helpName);
} }
// Load MODULE resource // Load MODULE resource
uint32_t modSize = 0; uint32_t modSize = 0;
uint8_t *modData = (uint8_t *)dvxResRead(res, "MODULE", &modSize); uint8_t *modData = (uint8_t *)dvxResRead(res, BAS_RES_MODULE, &modSize);
if (!modData) { if (!modData) {
dvxMessageBox(sAc, "Error", "MODULE resource not found.", 0); dvxMessageBox(sAc, "Error", "MODULE resource not found.", 0);
@ -134,7 +134,7 @@ int32_t appMain(DxeAppContextT *ctx) {
// Load optional DEBUG resource // Load optional DEBUG resource
uint32_t dbgSize = 0; uint32_t dbgSize = 0;
uint8_t *dbgData = (uint8_t *)dvxResRead(res, "DEBUG", &dbgSize); uint8_t *dbgData = (uint8_t *)dvxResRead(res, BAS_RES_DEBUG, &dbgSize);
if (dbgData) { if (dbgData) {
basDebugDeserialize(mod, dbgData, (int32_t)dbgSize); basDebugDeserialize(mod, dbgData, (int32_t)dbgSize);
@ -180,12 +180,10 @@ int32_t appMain(DxeAppContextT *ctx) {
const char *p = frmText; const char *p = frmText;
while (*p) { while (*p) {
while (*p == ' ' || *p == '\t') { p++; } p = dvxSkipWs(p);
if (strncasecmp(p, "Begin Form ", 11) == 0) { if (strncasecmp(p, "Begin Form ", 11) == 0) {
p += 11; p = dvxSkipWs(p + 11);
while (*p == ' ' || *p == '\t') { p++; }
int32_t ni = 0; int32_t ni = 0;

View file

@ -26,6 +26,12 @@ typedef struct {
} OutBufT; } OutBufT;
// Function prototypes
static void captureCallback(void *ctx, const char *text, bool newline);
static int32_t runAndCapture(const char *source, bool compact, char *outBuf, int32_t outCap);
static void testCompact(const char *name, const char *source);
static void captureCallback(void *ctx, const char *text, bool newline) { static void captureCallback(void *ctx, const char *text, bool newline) {
OutBufT *ob = (OutBufT *)ctx; OutBufT *ob = (OutBufT *)ctx;
int32_t tlen = (int32_t)strlen(text); int32_t tlen = (int32_t)strlen(text);

View file

@ -9,6 +9,10 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
// Function prototypes
static void runProgram(const char *name, const char *source);
static void runProgram(const char *name, const char *source) { static void runProgram(const char *name, const char *source) {
printf("=== %s ===\n", name); printf("=== %s ===\n", name);

View file

@ -19,9 +19,14 @@
static uint8_t sCode[4096]; static uint8_t sCode[4096];
static int32_t sCodeLen = 0; static int32_t sCodeLen = 0;
static void emit8(uint8_t b) { // Function prototypes
sCode[sCodeLen++] = b; static void emit16(int16_t v);
} static void emit8(uint8_t b);
static void emitU16(uint16_t v);
static void test1(void);
static void test2(void);
static void test3(void);
static void test4(void);
static void emit16(int16_t v) { static void emit16(int16_t v) {
@ -30,16 +35,17 @@ static void emit16(int16_t v) {
} }
static void emit8(uint8_t b) {
sCode[sCodeLen++] = b;
}
static void emitU16(uint16_t v) { static void emitU16(uint16_t v) {
memcpy(&sCode[sCodeLen], &v, 2); memcpy(&sCode[sCodeLen], &v, 2);
sCodeLen += 2; sCodeLen += 2;
} }
// ============================================================
// Test 1: PRINT "Hello, World!"
// ============================================================
static void test1(void) { static void test1(void) {
printf("--- Test 1: PRINT \"Hello, World!\" ---\n"); printf("--- Test 1: PRINT \"Hello, World!\" ---\n");
@ -73,10 +79,6 @@ static void test1(void) {
} }
// ============================================================
// Test 2: Arithmetic: PRINT 2 + 3 * 4
// ============================================================
static void test2(void) { static void test2(void) {
printf("--- Test 2: PRINT 2 + 3 * 4 (expect 14) ---\n"); printf("--- Test 2: PRINT 2 + 3 * 4 (expect 14) ---\n");
@ -109,10 +111,6 @@ static void test2(void) {
} }
// ============================================================
// Test 3: String concatenation
// ============================================================
static void test3(void) { static void test3(void) {
printf("--- Test 3: PRINT \"Hello\" & \" \" & \"BASIC\" ---\n"); printf("--- Test 3: PRINT \"Hello\" & \" \" & \"BASIC\" ---\n");
@ -153,10 +151,6 @@ static void test3(void) {
} }
// ============================================================
// Test 4: FOR loop -- PRINT 1 to 5
// ============================================================
static void test4(void) { static void test4(void) {
printf("--- Test 4: FOR i = 1 TO 5: PRINT i: NEXT ---\n"); printf("--- Test 4: FOR i = 1 TO 5: PRINT i: NEXT ---\n");
@ -213,10 +207,6 @@ static void test4(void) {
} }
// ============================================================
// main
// ============================================================
int main(void) { int main(void) {
printf("DVX BASIC VM Tests\n"); printf("DVX BASIC VM Tests\n");
printf("==================\n\n"); printf("==================\n\n");

View file

@ -86,6 +86,26 @@
#define CMD_CTX_SELALL 504 #define CMD_CTX_SELALL 504
#define CMD_CTX_PROPS 505 #define CMD_CTX_PROPS 505
// ============================================================
// Module state
// ============================================================
static AppContextT *sAc = NULL;
static DxeAppContextT *sDxeCtx = NULL;
static const FileFilterT sFileFilters[] = {
{"All Files (*.*)"},
{"Text Files (*.txt)"},
{"Batch Files (*.bat)"},
{"Executables (*.exe)"},
{"Bitmap Files (*.bmp)"}
};
// Item arrays are static because dropdown/combobox widgets store pointers,
// not copies. They must outlive the widgets that reference them.
static const char *colorItems[] = {"Red", "Green", "Blue", "Yellow", "Cyan", "Magenta"};
static const char *sizeItems[] = {"Small", "Medium", "Large", "Extra Large"};
// ============================================================ // ============================================================
// Prototypes // Prototypes
// ============================================================ // ============================================================
@ -105,13 +125,6 @@ static void setupMainWindow(void);
static void setupTerminalWindow(void); static void setupTerminalWindow(void);
static void setupWidgetDemo(void); static void setupWidgetDemo(void);
// ============================================================
// Module state
// ============================================================
static AppContextT *sAc = NULL;
static DxeAppContextT *sDxeCtx = NULL;
// ============================================================ // ============================================================
// App descriptor // App descriptor
// ============================================================ // ============================================================
@ -126,9 +139,6 @@ AppDescriptorT appDescriptor = {
.priority = TS_PRIORITY_NORMAL .priority = TS_PRIORITY_NORMAL
}; };
// ============================================================
// Callbacks
// ============================================================
static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag) { static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag) {
(void)drag; (void)drag;
@ -152,14 +162,6 @@ static void onCloseMainCb(WindowT *win) {
} }
static const FileFilterT sFileFilters[] = {
{"All Files (*.*)"},
{"Text Files (*.txt)"},
{"Batch Files (*.bat)"},
{"Executables (*.exe)"},
{"Bitmap Files (*.bmp)"}
};
static void onMenuCb(WindowT *win, int32_t menuId) { static void onMenuCb(WindowT *win, int32_t menuId) {
switch (menuId) { switch (menuId) {
case CMD_FILE_OPEN: { case CMD_FILE_OPEN: {
@ -168,7 +170,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
if (dvxFileDialog(sAc, "Open File", FD_OPEN, NULL, sFileFilters, 5, path, sizeof(path))) { if (dvxFileDialog(sAc, "Open File", FD_OPEN, NULL, sFileFilters, 5, path, sizeof(path))) {
char msg[300]; char msg[300];
snprintf(msg, sizeof(msg), "Selected: %s", path); snprintf(msg, sizeof(msg), "Selected: %s", path);
dvxMessageBox(sAc, "Open", msg, MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "Open", msg);
} }
break; break;
@ -180,7 +182,7 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
if (dvxFileDialog(sAc, "Save As", FD_SAVE, NULL, sFileFilters, 5, path, sizeof(path))) { if (dvxFileDialog(sAc, "Save As", FD_SAVE, NULL, sFileFilters, 5, path, sizeof(path))) {
char msg[300]; char msg[300];
snprintf(msg, sizeof(msg), "Save to: %s", path); snprintf(msg, sizeof(msg), "Save to: %s", path);
dvxMessageBox(sAc, "Save", msg, MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "Save", msg);
} }
break; break;
@ -222,34 +224,33 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
break; break;
case CMD_HELP_ABOUT: case CMD_HELP_ABOUT:
dvxMessageBox(sAc, "About DVX Demo", dvxInfoBox(sAc, "About DVX Demo",
"DVX GUI Demonstration\n" "DVX GUI Demonstration\n"
"A DOS Visual eXecutive windowing system for DOS.", "A DOS Visual eXecutive windowing system for DOS.");
MB_OK | MB_ICONINFO);
break; break;
case CMD_CTX_CUT: case CMD_CTX_CUT:
dvxMessageBox(sAc, "Context Menu", "Cut selected.", MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "Context Menu", "Cut selected.");
break; break;
case CMD_CTX_COPY: case CMD_CTX_COPY:
dvxMessageBox(sAc, "Context Menu", "Copied to clipboard.", MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "Context Menu", "Copied to clipboard.");
break; break;
case CMD_CTX_PASTE: case CMD_CTX_PASTE:
dvxMessageBox(sAc, "Context Menu", "Pasted from clipboard.", MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "Context Menu", "Pasted from clipboard.");
break; break;
case CMD_CTX_DELETE: case CMD_CTX_DELETE:
dvxMessageBox(sAc, "Context Menu", "Deleted.", MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "Context Menu", "Deleted.");
break; break;
case CMD_CTX_SELALL: case CMD_CTX_SELALL:
dvxMessageBox(sAc, "Context Menu", "Selected all.", MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "Context Menu", "Selected all.");
break; break;
case CMD_CTX_PROPS: case CMD_CTX_PROPS:
dvxMessageBox(sAc, "Properties", "Window properties dialog would appear here.", MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "Properties", "Window properties dialog would appear here.");
break; break;
} }
} }
@ -446,15 +447,6 @@ static void onToolbarClick(WidgetT *w) {
} }
// ============================================================
// setupControlsWindow -- advanced widgets with tabs
// ============================================================
// Item arrays are static because dropdown/combobox widgets store pointers,
// not copies. They must outlive the widgets that reference them.
static const char *colorItems[] = {"Red", "Green", "Blue", "Yellow", "Cyan", "Magenta"};
static const char *sizeItems[] = {"Small", "Medium", "Large", "Extra Large"};
// Demonstrates every advanced widget type, organized into tabs. The TabControl // Demonstrates every advanced widget type, organized into tabs. The TabControl
// widget manages tab pages; each page is a container that shows/hides based // widget manages tab pages; each page is a container that shows/hides based
// on the selected tab. This is the densest widget test in the project. // on the selected tab. This is the densest widget test in the project.
@ -810,10 +802,6 @@ static void setupControlsWindow(void) {
} }
// ============================================================
// setupMainWindow -- info window + paint demos
// ============================================================
// Creates three windows that demonstrate raw onPaint rendering (no widgets): // Creates three windows that demonstrate raw onPaint rendering (no widgets):
// 1. Text info window with full menu bar, accelerators, and context menu // 1. Text info window with full menu bar, accelerators, and context menu
// 2. Color gradient window // 2. Color gradient window
@ -951,10 +939,6 @@ static void setupMainWindow(void) {
} }
// ============================================================
// setupTerminalWindow -- ANSI terminal widget demo
// ============================================================
// Creates an ANSI terminal widget with sample output demonstrating all // Creates an ANSI terminal widget with sample output demonstrating all
// supported attributes (bold, reverse, blink), 8+8 colors, background // supported attributes (bold, reverse, blink), 8+8 colors, background
// colors, and CP437 box-drawing/graphic characters. The terminal is // colors, and CP437 box-drawing/graphic characters. The terminal is
@ -1034,10 +1018,6 @@ static void setupTerminalWindow(void) {
} }
// ============================================================
// setupWidgetDemo -- form with accelerators
// ============================================================
// Demonstrates the standard form pattern: labeled inputs in frames, checkbox // Demonstrates the standard form pattern: labeled inputs in frames, checkbox
// and radio groups, list boxes (single and multi-select with context menus), // and radio groups, list boxes (single and multi-select with context menus),
// and a button row. The '&' in label text marks the Alt+key mnemonic that // and a button row. The '&' in label text marks the Alt+key mnemonic that
@ -1127,10 +1107,6 @@ static void setupWidgetDemo(void) {
} }
// ============================================================
// Entry point
// ============================================================
// Create all demo windows at once. Because this is a callback-only app, // Create all demo windows at once. Because this is a callback-only app,
// appMain returns immediately and the shell's event loop takes over. // appMain returns immediately and the shell's event loop takes over.
// Each window is self-contained with its own callbacks. // Each window is self-contained with its own callbacks.

View file

@ -1,5 +1,7 @@
# dvxdemo.res -- Resource manifest for DVX Demo # dvxdemo.res -- Resource manifest for DVX Demo
icon32 icon icon32.bmp icon32 icon icon32.bmp
name text "DVX Demo" name text "DVX Demo"
author text "DVX Project" author text "Scott Duensing"
copyright text "Copyright 2026 Scott Duensing"
publisher text "Kangaroo Punch Studios"
description text "Widget toolkit demonstration" description text "Widget toolkit demonstration"

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,7 @@
# dvxhelp.res -- Resource manifest for DVX Help Viewer # dvxhelp.res -- Resource manifest for DVX Help Viewer
icon32 icon icon32.bmp icon32 icon icon32.bmp
name text "DVX Help" name text "DVX Help"
author text "DVX Project" author text "Scott Duensing"
copyright text "Copyright 2026 Scott Duensing"
publisher text "Kangaroo Punch Studios"
description text "Help file viewer" description text "Help file viewer"

View file

@ -1,6 +1,8 @@
[Project] [Project]
Name = Help Editor Name = Help Editor
Author = DVX Project Author = Scott Duensing
Publisher = Kangaroo Punch Studios
Copyright = Copyright 2026 Scott Duensing
Description = DVX Help source editor with syntax highlighting and preview Description = DVX Help source editor with syntax highlighting and preview
Icon = ICON32.BMP Icon = ICON32.BMP

View file

@ -1,7 +1,7 @@
[Project] [Project]
Name = Icon Editor Name = Icon Editor
Author = Scott Duensing Author = Scott Duensing
Company = Kangaroo Punch Studios Publisher = Kangaroo Punch Studios
Version = 1.00 Version = 1.00
Copyright = Copyright 2026 Scott Duensing Copyright = Copyright 2026 Scott Duensing
Description = Icon editor for DVX. Description = Icon editor for DVX.

View file

@ -1,6 +1,8 @@
[Project] [Project]
Name = Image Viewer Name = Image Viewer
Author = DVX Project Author = Scott Duensing
Publisher = Kangaroo Punch Studios
Copyright = Copyright 2026 Scott Duensing
Description = BMP, PNG, JPEG, and GIF viewer Description = BMP, PNG, JPEG, and GIF viewer
Icon = ICON32.BMP Icon = ICON32.BMP

View file

@ -1,6 +1,8 @@
[Project] [Project]
Name = Notepad Name = Notepad
Author = DVX Project Author = Scott Duensing
Publisher = Kangaroo Punch Studios
Copyright = Copyright 2026 Scott Duensing
Description = Simple text editor Description = Simple text editor
Icon = ICON32.BMP Icon = ICON32.BMP

View file

@ -50,8 +50,6 @@
// Constants // Constants
// ============================================================ // ============================================================
// DOS 8.3 paths are short, but long names under DJGPP can reach ~260
#define MAX_PATH_LEN 260
// Grid layout for app buttons: 4 columns, rows created dynamically // Grid layout for app buttons: 4 columns, rows created dynamically
#define PM_TOOLTIP_LEN 128 #define PM_TOOLTIP_LEN 128
#define PM_BTN_W 100 #define PM_BTN_W 100
@ -87,7 +85,7 @@
typedef struct { typedef struct {
char name[SHELL_APP_NAME_MAX]; // short display name (from "name" resource or filename) char name[SHELL_APP_NAME_MAX]; // short display name (from "name" resource or filename)
char tooltip[PM_TOOLTIP_LEN]; // description for tooltip (from "description" resource) char tooltip[PM_TOOLTIP_LEN]; // description for tooltip (from "description" resource)
char path[MAX_PATH_LEN]; // full path char path[DVX_MAX_PATH]; // full path
uint8_t *iconData; // 32x32 icon in display format (NULL if none) uint8_t *iconData; // 32x32 icon in display format (NULL if none)
int32_t iconW; int32_t iconW;
int32_t iconH; int32_t iconH;
@ -112,6 +110,7 @@ static int32_t sAppCount = 0;
// ============================================================ // ============================================================
int32_t appMain(DxeAppContextT *ctx); int32_t appMain(DxeAppContextT *ctx);
void appShutdown(void);
static void buildPmWindow(void); static void buildPmWindow(void);
static void desktopUpdate(void); static void desktopUpdate(void);
static void onAppButtonClick(WidgetT *w); static void onAppButtonClick(WidgetT *w);
@ -139,9 +138,12 @@ AppDescriptorT appDescriptor = {
.priority = TS_PRIORITY_NORMAL .priority = TS_PRIORITY_NORMAL
}; };
// ============================================================
// Static functions (alphabetical) void appShutdown(void) {
// ============================================================ shellUnregisterDesktopUpdate(desktopUpdate);
dvxQuit(sAc);
}
// Build the main Program Manager window with app buttons, menus, and status bar. // Build the main Program Manager window with app buttons, menus, and status bar.
// Window is centered horizontally and placed in the upper quarter vertically // Window is centered horizontally and placed in the upper quarter vertically
@ -318,7 +320,7 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
{ "Applications (*.app)" }, { "Applications (*.app)" },
{ "All Files (*.*)" } { "All Files (*.*)" }
}; };
char path[MAX_PATH_LEN]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sAc, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) { if (dvxFileDialog(sAc, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
shellLoadApp(sAc, path); shellLoadApp(sAc, path);
@ -376,8 +378,8 @@ static void onPmMenu(WindowT *win, int32_t menuId) {
case CMD_HELP: { case CMD_HELP: {
char viewerPath[DVX_MAX_PATH]; char viewerPath[DVX_MAX_PATH];
char sysHlp[DVX_MAX_PATH]; char sysHlp[DVX_MAX_PATH];
snprintf(viewerPath, sizeof(viewerPath), "APPS%cKPUNCH%cDVXHELP%cDVXHELP.APP", DVX_PATH_SEP, DVX_PATH_SEP, DVX_PATH_SEP); snprintf(viewerPath, sizeof(viewerPath), "APPS" DVX_PATH_SEP "KPUNCH" DVX_PATH_SEP "DVXHELP" DVX_PATH_SEP "DVXHELP.APP");
snprintf(sysHlp, sizeof(sysHlp), "%s%c%s", sCtx->appDir, DVX_PATH_SEP, "dvxhelp.hlp"); snprintf(sysHlp, sizeof(sysHlp), "%s" DVX_PATH_SEP "%s", sCtx->appDir, "dvxhelp.hlp");
shellLoadAppWithArgs(sAc, viewerPath, sysHlp); shellLoadAppWithArgs(sAc, viewerPath, sysHlp);
break; break;
} }
@ -450,8 +452,8 @@ static void scanAppsDirRecurse(const char *dirPath) {
int32_t nEntries = (int32_t)arrlen(names); int32_t nEntries = (int32_t)arrlen(names);
for (int32_t i = 0; i < nEntries; i++) { for (int32_t i = 0; i < nEntries; i++) {
char fullPath[MAX_PATH_LEN]; char fullPath[DVX_MAX_PATH];
snprintf(fullPath, sizeof(fullPath), "%s%c%s", dirPath, DVX_PATH_SEP, names[i]); snprintf(fullPath, sizeof(fullPath), "%s" DVX_PATH_SEP "%s", dirPath, names[i]);
struct stat st; struct stat st;
@ -519,7 +521,7 @@ static void showSystemInfo(void) {
const char *info = shellGetSystemInfo(); const char *info = shellGetSystemInfo();
if (!info || !info[0]) { if (!info || !info[0]) {
dvxMessageBox(sAc, "System Information", "No system information available.", MB_OK | MB_ICONINFO); dvxInfoBox(sAc, "System Information", "No system information available.");
return; return;
} }
@ -566,9 +568,6 @@ static void updateStatusText(void) {
wgtSetText(sStatusLabel, buf); wgtSetText(sStatusLabel, buf);
} }
// ============================================================
// Entry point
// ============================================================
// The shell calls appMain exactly once after dlopen() resolves our symbols. // The shell calls appMain exactly once after dlopen() resolves our symbols.
// We scan for apps, build the UI, register our desktop update callback, then // We scan for apps, build the UI, register our desktop update callback, then
@ -579,7 +578,7 @@ int32_t appMain(DxeAppContextT *ctx) {
sAc = ctx->shellCtx; sAc = ctx->shellCtx;
// Set help file for F1 — system help lives in progman's app directory // Set help file for F1 — system help lives in progman's app directory
snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s%c%s", ctx->appDir, DVX_PATH_SEP, "dvxhelp.hlp"); snprintf(ctx->helpFile, sizeof(ctx->helpFile), "%s" DVX_PATH_SEP "%s", ctx->appDir, "dvxhelp.hlp");
// Load saved preferences // Load saved preferences
char prefsPath[DVX_MAX_PATH]; char prefsPath[DVX_MAX_PATH];
@ -599,9 +598,3 @@ int32_t appMain(DxeAppContextT *ctx) {
return 0; return 0;
} }
void appShutdown(void) {
shellUnregisterDesktopUpdate(desktopUpdate);
dvxQuit(sAc);
}

View file

@ -1,6 +1,8 @@
[Project] [Project]
Name = Resource Editor Name = Resource Editor
Author = DVX Project Author = Scott Duensing
Publisher = Kangaroo Punch Studios
Copyright = Copyright 2026 Scott Duensing
Description = DVX resource file editor Description = DVX resource file editor
Icon = ICON32.BMP Icon = ICON32.BMP

View file

@ -33,25 +33,26 @@ void (*shellCtrlEscFn)(AppContextT *ctx) = NULL;
// Prototypes // Prototypes
// ============================================================ // ============================================================
static int32_t allocSlot(void); static int32_t allocSlot(void);
static void appTaskWrapper(void *arg); static void appTaskWrapper(void *arg);
static const char *baseName(const char *path); static void cleanupTempFile(ShellAppT *app);
static void cleanupTempFile(ShellAppT *app); static int32_t copyFile(const char *src, const char *dst);
static int32_t copyFile(const char *src, const char *dst); static ShellAppT *findLoadedPath(const char *path);
static ShellAppT *findLoadedPath(const char *path); static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize);
static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize); void shellAppInit(void);
void shellAppInit(void); int32_t shellAppSlotCount(void);
void shellForceKillApp(AppContextT *ctx, ShellAppT *app); void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize);
ShellAppT *shellGetApp(int32_t appId); int32_t shellEnsureConfigDir(const DxeAppContextT *ctx);
int32_t shellLoadApp(AppContextT *ctx, const char *path); void shellForceKillApp(AppContextT *ctx, ShellAppT *app);
void shellReapApp(AppContextT *ctx, ShellAppT *app); ShellAppT *shellGetApp(int32_t appId);
bool shellReapApps(AppContextT *ctx); int32_t shellLoadApp(AppContextT *ctx, const char *path);
int32_t shellRunningAppCount(void); static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const char *args);
void shellTerminateAllApps(AppContextT *ctx); int32_t shellLoadAppWithArgs(AppContextT *ctx, const char *path, const char *args);
void shellReapApp(AppContextT *ctx, ShellAppT *app);
bool shellReapApps(AppContextT *ctx);
int32_t shellRunningAppCount(void);
void shellTerminateAllApps(AppContextT *ctx);
// ============================================================
// Static helpers
// ============================================================
// Find the first free slot, starting at 1 (slot 0 is the shell). // Find the first free slot, starting at 1 (slot 0 is the shell).
// Returns the slot index which also serves as the app's unique ID. // Returns the slot index which also serves as the app's unique ID.
@ -97,17 +98,6 @@ static void appTaskWrapper(void *arg) {
} }
static const char *baseName(const char *path) {
const char *slash = strrchr(path, '/');
if (!slash) {
slash = strrchr(path, '\\');
}
return slash ? slash + 1 : path;
}
// Delete the temp file for a multi-instance app copy, if one exists. // Delete the temp file for a multi-instance app copy, if one exists.
static void cleanupTempFile(ShellAppT *app) { static void cleanupTempFile(ShellAppT *app) {
if (app->tempPath[0] != '\0') { if (app->tempPath[0] != '\0') {
@ -172,14 +162,8 @@ static ShellAppT *findLoadedPath(const char *path) {
// Example: TEMP=C:\TEMP -> "C:\TEMP\_dvx02.app" // Example: TEMP=C:\TEMP -> "C:\TEMP\_dvx02.app"
static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize) { static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t outSize) {
// Find extension from original path // Find extension from original path
const char *lastSlash = strrchr(origPath, '/'); const char *lastSlash = platformPathDirEnd(origPath);
const char *lastBack = strrchr(origPath, '\\'); const char *dot = strrchr(origPath, '.');
if (lastBack > lastSlash) {
lastSlash = lastBack;
}
const char *dot = strrchr(origPath, '.');
if (!dot || (lastSlash && dot < lastSlash)) { if (!dot || (lastSlash && dot < lastSlash)) {
dot = origPath + strlen(origPath); dot = origPath + strlen(origPath);
@ -201,10 +185,6 @@ static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t
} }
// ============================================================
// Public API (alphabetical)
// ============================================================
void shellAppInit(void) { void shellAppInit(void) {
// Seed slot 0 (reserved for the shell) // Seed slot 0 (reserved for the shell)
ShellAppT slot0; ShellAppT slot0;
@ -214,18 +194,15 @@ void shellAppInit(void) {
} }
// ============================================================ int32_t shellAppSlotCount(void) {
// shellConfigPath return arrlen(sApps);
// ============================================================
void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize) {
snprintf(outPath, outSize, "%s%c%s", ctx->configDir, DVX_PATH_SEP, filename);
} }
// ============================================================ void shellConfigPath(const DxeAppContextT *ctx, const char *filename, char *outPath, int32_t outSize) {
// shellEnsureConfigDir snprintf(outPath, outSize, "%s" DVX_PATH_SEP "%s", ctx->configDir, filename);
// ============================================================ }
int32_t shellEnsureConfigDir(const DxeAppContextT *ctx) { int32_t shellEnsureConfigDir(const DxeAppContextT *ctx) {
return platformMkdirRecursive(ctx->configDir); return platformMkdirRecursive(ctx->configDir);
@ -308,25 +285,17 @@ ShellAppT *shellGetApp(int32_t appId) {
// unique temp file before dlopen, giving each instance its own code+data. // unique temp file before dlopen, giving each instance its own code+data.
// If multiInstance=false (the default), a second load of the same path is // If multiInstance=false (the default), a second load of the same path is
// blocked with an error message. // blocked with an error message.
static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const char *args);
int32_t shellLoadApp(AppContextT *ctx, const char *path) { int32_t shellLoadApp(AppContextT *ctx, const char *path) {
return shellLoadAppInternal(ctx, path, NULL); return shellLoadAppInternal(ctx, path, NULL);
} }
int32_t shellLoadAppWithArgs(AppContextT *ctx, const char *path, const char *args) {
return shellLoadAppInternal(ctx, path, args);
}
static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const char *args) { static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const char *args) {
// Allocate a slot // Allocate a slot
int32_t id = allocSlot(); int32_t id = allocSlot();
if (id < 0) { if (id < 0) {
dvxMessageBox(ctx, "Error", "Maximum number of applications reached.", MB_OK | MB_ICONERROR); dvxErrorBox(ctx, NULL, "Maximum number of applications reached.");
return -1; return -1;
} }
@ -343,8 +312,8 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
if (!existDesc || !existDesc->multiInstance) { if (!existDesc || !existDesc->multiInstance) {
char msg[320]; char msg[320];
snprintf(msg, sizeof(msg), "%s is already running.", baseName(path)); snprintf(msg, sizeof(msg), "%s is already running.", platformPathBaseName(path));
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR); dvxErrorBox(ctx, NULL, msg);
return -1; return -1;
} }
@ -354,8 +323,8 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
if (copyFile(path, tempPath) != 0) { if (copyFile(path, tempPath) != 0) {
char msg[320]; char msg[320];
snprintf(msg, sizeof(msg), "Failed to create instance copy of %s.", baseName(path)); snprintf(msg, sizeof(msg), "Failed to create instance copy of %s.", platformPathBaseName(path));
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR); dvxErrorBox(ctx, NULL, msg);
return -1; return -1;
} }
@ -373,10 +342,10 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
if (!handle) { if (!handle) {
char msg[512]; char msg[512];
snprintf(msg, sizeof(msg), "Failed to load %s:\n%s", baseName(path), dlerror()); snprintf(msg, sizeof(msg), "Failed to load %s:\n%s", platformPathBaseName(path), dlerror());
dvxLog("DXE load failed: %s", msg); dvxLog("DXE load failed: %s", msg);
dvxSetBusy(ctx, false); dvxSetBusy(ctx, false);
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR); dvxErrorBox(ctx, NULL, msg);
if (tempPath[0]) { if (tempPath[0]) {
remove(tempPath); remove(tempPath);
@ -390,10 +359,10 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
if (!desc) { if (!desc) {
char msg[256]; char msg[256];
snprintf(msg, sizeof(msg), "%s: missing appDescriptor", baseName(path)); snprintf(msg, sizeof(msg), "%s: missing appDescriptor", platformPathBaseName(path));
dvxLog("DXE symbol error: %s", msg); dvxLog("DXE symbol error: %s", msg);
dvxSetBusy(ctx, false); dvxSetBusy(ctx, false);
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR); dvxErrorBox(ctx, NULL, msg);
dlclose(handle); dlclose(handle);
if (tempPath[0]) { if (tempPath[0]) {
@ -407,9 +376,9 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
if (!entry) { if (!entry) {
char msg[256]; char msg[256];
snprintf(msg, sizeof(msg), "%s: missing appMain", baseName(path)); snprintf(msg, sizeof(msg), "%s: missing appMain", platformPathBaseName(path));
dvxSetBusy(ctx, false); dvxSetBusy(ctx, false);
dvxMessageBox(ctx, "Error", msg, MB_OK | MB_ICONERROR); dvxErrorBox(ctx, NULL, msg);
dlclose(handle); dlclose(handle);
if (tempPath[0]) { if (tempPath[0]) {
@ -497,7 +466,7 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
if (taskId < 0) { if (taskId < 0) {
ctx->currentAppId = 0; ctx->currentAppId = 0;
dvxSetBusy(ctx, false); dvxSetBusy(ctx, false);
dvxMessageBox(ctx, "Error", "Failed to create task for application.", MB_OK | MB_ICONERROR); dvxErrorBox(ctx, NULL, "Failed to create task for application.");
dlclose(handle); dlclose(handle);
free(app->dxeCtx); free(app->dxeCtx);
app->dxeCtx = NULL; app->dxeCtx = NULL;
@ -524,6 +493,11 @@ static int32_t shellLoadAppInternal(AppContextT *ctx, const char *path, const ch
} }
int32_t shellLoadAppWithArgs(AppContextT *ctx, const char *path, const char *args) {
return shellLoadAppInternal(ctx, path, args);
}
// Graceful reap -- called from shellReapApps when an app has reached // Graceful reap -- called from shellReapApps when an app has reached
// the Terminating state. Unlike forceKill, this calls the app's // the Terminating state. Unlike forceKill, this calls the app's
// shutdown hook (if provided) giving it a chance to save state, close // shutdown hook (if provided) giving it a chance to save state, close
@ -610,11 +584,6 @@ bool shellReapApps(AppContextT *ctx) {
} }
int32_t shellAppSlotCount(void) {
return arrlen(sApps);
}
int32_t shellRunningAppCount(void) { int32_t shellRunningAppCount(void) {
int32_t count = 0; int32_t count = 0;

View file

@ -114,6 +114,12 @@ typedef struct {
// Initialize the app slot table // Initialize the app slot table
void shellAppInit(void); void shellAppInit(void);
// Canonical path of the DVX help viewer app, relative to the DVX root.
// Uses the platform path separator so displayed paths match the user's
// convention.
#define DVX_HELP_VIEWER_APP_LITERAL \
"APPS" DVX_PATH_SEP "KPUNCH" DVX_PATH_SEP "DVXHELP" DVX_PATH_SEP "DVXHELP.APP"
// Load and start an app from a DXE file. Returns app ID (>= 1) or -1 on error. // Load and start an app from a DXE file. Returns app ID (>= 1) or -1 on error.
// For callback-only apps, appMain runs synchronously and returns before // For callback-only apps, appMain runs synchronously and returns before
// shellLoadApp returns. For main-loop apps, a task is created and the // shellLoadApp returns. For main-loop apps, a task is created and the

View file

@ -19,18 +19,18 @@
static const char *sCachedInfo = NULL; static const char *sCachedInfo = NULL;
// ============================================================ // ============================================================
// shellGetSystemInfo -- return the cached info text // Prototypes
// ============================================================ // ============================================================
const char *shellGetSystemInfo(void);
void shellInfoInit(AppContextT *ctx);
const char *shellGetSystemInfo(void) { const char *shellGetSystemInfo(void) {
return sCachedInfo ? sCachedInfo : ""; return sCachedInfo ? sCachedInfo : "";
} }
// ============================================================
// shellInfoInit -- gather info and log it
// ============================================================
void shellInfoInit(AppContextT *ctx) { void shellInfoInit(AppContextT *ctx) {
sCachedInfo = platformGetSystemInfo(&ctx->display); sCachedInfo = platformGetSystemInfo(&ctx->display);

View file

@ -62,23 +62,16 @@ static DesktopUpdateFnT *sDesktopUpdateFns = NULL;
// Prototypes // Prototypes
// ============================================================ // ============================================================
static void ctrlEscHandler(void *ctx);
static void f1HelpHandler(void *ctx);
static void idleYield(void *ctx); static void idleYield(void *ctx);
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData); static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
void shellDesktopUpdate(void);
int shellMain(int argc, char *argv[]);
void shellRegisterDesktopUpdate(void (*updateFn)(void));
void shellUnregisterDesktopUpdate(void (*updateFn)(void));
static void titleChangeHandler(void *ctx);
// ============================================================
// shellDesktopUpdate -- notify desktop app of state change
// ============================================================
void shellDesktopUpdate(void) {
for (int32_t i = 0; i < arrlen(sDesktopUpdateFns); i++) {
sDesktopUpdateFns[i]();
}
}
// ============================================================
// ctrlEscHandler -- system-wide Ctrl+Esc callback
// ============================================================
static void ctrlEscHandler(void *ctx) { static void ctrlEscHandler(void *ctx) {
if (shellCtrlEscFn) { if (shellCtrlEscFn) {
@ -87,10 +80,6 @@ static void ctrlEscHandler(void *ctx) {
} }
// ============================================================
// f1HelpHandler -- system-wide F1 launches context-sensitive help
// ============================================================
static void f1HelpHandler(void *ctx) { static void f1HelpHandler(void *ctx) {
AppContextT *ac = (AppContextT *)ctx; AppContextT *ac = (AppContextT *)ctx;
@ -125,30 +114,16 @@ static void f1HelpHandler(void *ctx) {
// Fall back to system help if no app-specific help // Fall back to system help if no app-specific help
char viewerPath[DVX_MAX_PATH]; char viewerPath[DVX_MAX_PATH];
snprintf(viewerPath, sizeof(viewerPath), "APPS%cKPUNCH%cDVXHELP%cDVXHELP.APP", DVX_PATH_SEP, DVX_PATH_SEP, DVX_PATH_SEP); snprintf(viewerPath, sizeof(viewerPath), "APPS" DVX_PATH_SEP "KPUNCH" DVX_PATH_SEP "DVXHELP" DVX_PATH_SEP "DVXHELP.APP");
if (!args[0]) { if (!args[0]) {
snprintf(args, sizeof(args), "APPS%cKPUNCH%cPROGMAN%cDVXHELP.HLP", DVX_PATH_SEP, DVX_PATH_SEP, DVX_PATH_SEP); snprintf(args, sizeof(args), "APPS" DVX_PATH_SEP "KPUNCH" DVX_PATH_SEP "PROGMAN" DVX_PATH_SEP "DVXHELP.HLP");
} }
shellLoadAppWithArgs(ac, viewerPath, args); shellLoadAppWithArgs(ac, viewerPath, args);
} }
// ============================================================
// titleChangeHandler -- refresh listeners when a window title changes
// ============================================================
static void titleChangeHandler(void *ctx) {
(void)ctx;
shellDesktopUpdate();
}
// ============================================================
// idleYield -- called when no dirty rects need compositing
// ============================================================
// Registered as sCtx.idleCallback. dvxUpdate calls this when it has // Registered as sCtx.idleCallback. dvxUpdate calls this when it has
// processed all pending events and there are no dirty rects to composite. // processed all pending events and there are no dirty rects to composite.
// Instead of busy-spinning, we yield to app tasks -- this is where most // Instead of busy-spinning, we yield to app tasks -- this is where most
@ -170,22 +145,18 @@ static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData) {
} }
// ============================================================ void shellDesktopUpdate(void) {
for (int32_t i = 0; i < arrlen(sDesktopUpdateFns); i++) {
sDesktopUpdateFns[i]();
}
}
// ============================================================
// shellRegisterDesktopUpdate
// ============================================================
void shellRegisterDesktopUpdate(void (*updateFn)(void)) { void shellRegisterDesktopUpdate(void (*updateFn)(void)) {
arrput(sDesktopUpdateFns, updateFn); arrput(sDesktopUpdateFns, updateFn);
} }
// ============================================================
// shellUnregisterDesktopUpdate
// ============================================================
void shellUnregisterDesktopUpdate(void (*updateFn)(void)) { void shellUnregisterDesktopUpdate(void (*updateFn)(void)) {
for (int32_t i = 0; i < arrlen(sDesktopUpdateFns); i++) { for (int32_t i = 0; i < arrlen(sDesktopUpdateFns); i++) {
if (sDesktopUpdateFns[i] == updateFn) { if (sDesktopUpdateFns[i] == updateFn) {
@ -196,9 +167,11 @@ void shellUnregisterDesktopUpdate(void (*updateFn)(void)) {
} }
// ============================================================ static void titleChangeHandler(void *ctx) {
// shellMain -- entry point called by the DVX loader (void)ctx;
// ============================================================ shellDesktopUpdate();
}
int shellMain(int argc, char *argv[]) { int shellMain(int argc, char *argv[]) {
(void)argc; (void)argc;
@ -207,7 +180,7 @@ int shellMain(int argc, char *argv[]) {
dvxLog("DVX Shell starting..."); dvxLog("DVX Shell starting...");
// Load preferences (missing file or keys silently use defaults) // Load preferences (missing file or keys silently use defaults)
sPrefs = prefsLoad("CONFIG/DVX.INI"); sPrefs = prefsLoad(DVX_INI_PATH);
// Initialize task system (no GUI needed) // Initialize task system (no GUI needed)
if (tsInit() != TS_OK) { if (tsInit() != TS_OK) {
@ -272,10 +245,10 @@ int shellMain(int argc, char *argv[]) {
dvxLog("Selected: %ldx%ld %ldbpp (pitch %ld)", (long)sCtx.display.width, (long)sCtx.display.height, (long)sCtx.display.format.bitsPerPixel, (long)sCtx.display.pitch); dvxLog("Selected: %ldx%ld %ldbpp (pitch %ld)", (long)sCtx.display.width, (long)sCtx.display.height, (long)sCtx.display.format.bitsPerPixel, (long)sCtx.display.pitch);
// Apply mouse preferences // Apply mouse preferences
const char *wheelStr = prefsGetString(sPrefs, "mouse", "wheel", "normal"); const char *wheelStr = prefsGetString(sPrefs, "mouse", "wheel", MOUSE_WHEEL_DIR_DEFAULT);
int32_t wheelDir = (strcmp(wheelStr, "reversed") == 0) ? -1 : 1; int32_t wheelDir = (strcmp(wheelStr, "reversed") == 0) ? -1 : 1;
int32_t dblClick = prefsGetInt(sPrefs, "mouse", "doubleclick", 500); int32_t dblClick = prefsGetInt(sPrefs, "mouse", "doubleclick", MOUSE_DBLCLICK_DEFAULT_MS);
const char *accelStr = prefsGetString(sPrefs, "mouse", "acceleration", "medium"); const char *accelStr = prefsGetString(sPrefs, "mouse", "acceleration", MOUSE_ACCEL_DEFAULT);
int32_t accelVal = 0; int32_t accelVal = 0;
if (strcmp(accelStr, "off") == 0) { accelVal = 10000; } if (strcmp(accelStr, "off") == 0) { accelVal = 10000; }
@ -283,7 +256,7 @@ int shellMain(int argc, char *argv[]) {
else if (strcmp(accelStr, "medium") == 0) { accelVal = 64; } else if (strcmp(accelStr, "medium") == 0) { accelVal = 64; }
else if (strcmp(accelStr, "high") == 0) { accelVal = 32; } else if (strcmp(accelStr, "high") == 0) { accelVal = 32; }
int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", 8); int32_t speed = prefsGetInt(sPrefs, "mouse", "speed", MOUSE_SPEED_DEFAULT);
int32_t wheelStep = prefsGetInt(sPrefs, "mouse", "wheelspeed", MOUSE_WHEEL_STEP_DEFAULT); int32_t wheelStep = prefsGetInt(sPrefs, "mouse", "wheelspeed", MOUSE_WHEEL_STEP_DEFAULT);
dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal, speed, wheelStep); dvxSetMouseConfig(&sCtx, wheelDir, dblClick, accelVal, speed, wheelStep);
@ -385,7 +358,7 @@ int shellMain(int argc, char *argv[]) {
snprintf(msg, sizeof(msg), "'%s' has caused a fault and will be terminated.", app->name); snprintf(msg, sizeof(msg), "'%s' has caused a fault and will be terminated.", app->name);
shellForceKillApp(&sCtx, app); shellForceKillApp(&sCtx, app);
sCtx.currentAppId = 0; sCtx.currentAppId = 0;
dvxMessageBox(&sCtx, "Application Error", msg, MB_OK | MB_ICONERROR); dvxErrorBox(&sCtx, "Application Error", msg);
} }
} }

View file

@ -3193,10 +3193,8 @@ static void repositionWindow(AppContextT *ctx, WindowT *win, int32_t x, int32_t
// Dirty old position // Dirty old position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h); dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
// Un-maximize if needed // Explicit reposition drops maximized state.
if (win->maximized) { win->maximized = false;
win->maximized = false;
}
win->x = x; win->x = x;
win->y = y; win->y = y;
@ -3752,107 +3750,6 @@ const char *dvxClipboardGet(int32_t *outLen) {
} }
// ============================================================
// dvxResLoadIcon
// ============================================================
uint8_t *dvxResLoadIcon(AppContextT *ctx, const char *dxePath, const char *resName, int32_t *outW, int32_t *outH, int32_t *outPitch) {
if (!ctx || !dxePath || !resName) {
return NULL;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return NULL;
}
uint32_t size = 0;
void *buf = dvxResRead(res, resName, &size);
dvxResClose(res);
if (!buf) {
return NULL;
}
uint8_t *pixels = dvxLoadImageFromMemory(ctx, (const uint8_t *)buf, (int32_t)size, outW, outH, outPitch);
free(buf);
return pixels;
}
// ============================================================
// dvxResLoadText
// ============================================================
bool dvxResLoadText(const char *dxePath, const char *resName, char *buf, int32_t bufSize) {
if (!dxePath || !resName || !buf || bufSize <= 0) {
return false;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return false;
}
uint32_t size = 0;
void *data = dvxResRead(res, resName, &size);
dvxResClose(res);
if (!data) {
return false;
}
int32_t copyLen = (int32_t)size < bufSize - 1 ? (int32_t)size : bufSize - 1;
memcpy(buf, data, copyLen);
buf[copyLen] = '\0';
free(data);
return true;
}
// ============================================================
// dvxResLoadData
// ============================================================
void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize) {
if (!dxePath || !resName) {
return NULL;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return NULL;
}
void *data = dvxResRead(res, resName, outSize);
dvxResClose(res);
return data;
}
// ============================================================
// dvxTextHash
// ============================================================
uint32_t dvxTextHash(const char *text) {
if (!text) {
return 0;
}
uint32_t h = 5381;
while (*text) {
h = ((h << 5) + h) ^ (uint8_t)*text;
text++;
}
return h;
}
// ============================================================ // ============================================================
// dvxColorLabel // dvxColorLabel
// ============================================================ // ============================================================
@ -3973,38 +3870,6 @@ WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w,
} }
// ============================================================
// dvxHideWindow
// ============================================================
void dvxHideWindow(AppContextT *ctx, WindowT *win) {
if (!win || !win->visible) {
return;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
win->visible = false;
}
// ============================================================
// dvxShowWindow
// ============================================================
void dvxShowWindow(AppContextT *ctx, WindowT *win) {
if (!win || win->visible) {
return;
}
win->visible = true;
dvxInvalidateWindow(ctx, win);
}
// ============================================================
// dvxDestroyWindow
// ============================================================
void dvxDestroyWindow(AppContextT *ctx, WindowT *win) { void dvxDestroyWindow(AppContextT *ctx, WindowT *win) {
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h); dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
@ -4034,68 +3899,11 @@ 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
// ============================================================
//
// Resizes a window to exactly fit its widget tree's minimum size, // Resizes a window to exactly fit its widget tree's minimum size,
// accounting for chrome overhead (title bar, borders, optional menu bar). // accounting for chrome overhead (title bar, borders, optional menu bar).
// Used after building a dialog's widget tree to size the dialog // Used after building a dialog's widget tree to size the dialog
// automatically rather than requiring the caller to compute sizes manually. // automatically rather than requiring the caller to compute sizes manually.
// dvxResizeWindow -- resize a window to the given outer dimensions
void dvxResizeWindow(AppContextT *ctx, WindowT *win, int32_t newW, int32_t newH) {
// Dirty old position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
// Resize
win->w = newW;
win->h = newH;
// Shift position so the window stays fully on screen
if (win->x + newW > ctx->display.width) {
win->x = ctx->display.width - newW;
}
if (win->y + newH > ctx->display.height) {
win->y = ctx->display.height - newH;
}
if (win->x < 0) {
win->x = 0;
}
if (win->y < 0) {
win->y = 0;
}
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
// Dirty new position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
// Invalidate widget tree so it repaints at the new size
wgtInvalidate(win->widgetRoot);
}
void dvxFitWindow(AppContextT *ctx, WindowT *win) { void dvxFitWindow(AppContextT *ctx, WindowT *win) {
if (!ctx || !win || !win->widgetRoot) { if (!ctx || !win || !win->widgetRoot) {
return; return;
@ -4184,33 +3992,6 @@ void dvxFreeImage(uint8_t *data) {
} }
// ============================================================
// dvxImageInfo
// ============================================================
bool dvxImageInfo(const char *path, int32_t *outW, int32_t *outH) {
if (!path) {
return false;
}
int w = 0;
int h = 0;
int comp = 0;
if (stbi_info(path, &w, &h, &comp)) {
if (outW) { *outW = w; }
if (outH) { *outH = h; }
return true;
}
return false;
}
// ============================================================
// dvxGetBlitOps
// ============================================================
const BlitOpsT *dvxGetBlitOps(const AppContextT *ctx) { const BlitOpsT *dvxGetBlitOps(const AppContextT *ctx) {
return &ctx->blitOps; return &ctx->blitOps;
} }
@ -4347,9 +4128,34 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
} }
// ============================================================ void dvxHideWindow(AppContextT *ctx, WindowT *win) {
// dvxInvalidateRect if (!win || !win->visible) {
// ============================================================ return;
}
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
win->visible = false;
}
bool dvxImageInfo(const char *path, int32_t *outW, int32_t *outH) {
if (!path) {
return false;
}
int w = 0;
int h = 0;
int comp = 0;
if (stbi_info(path, &w, &h, &comp)) {
if (outW) { *outW = w; }
if (outH) { *outH = h; }
return true;
}
return false;
}
void dvxInvalidateRect(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h) { void dvxInvalidateRect(AppContextT *ctx, WindowT *win, int32_t x, int32_t y, int32_t w, int32_t h) {
// Convert from content-relative to screen coordinates // Convert from content-relative to screen coordinates
@ -4447,76 +4253,6 @@ uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, i
} }
// ============================================================
// dvxLoadImageFromMemory
// ============================================================
//
// Same as dvxLoadImage but loads from a memory buffer (e.g. a
// resource read via dvxResRead). Supports BMP, PNG, JPEG, GIF.
uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch) {
if (!ctx || !data || dataLen <= 0) {
return NULL;
}
const DisplayT *d = &ctx->display;
int imgW;
int imgH;
int channels;
uint8_t *rgb = stbi_load_from_memory(data, dataLen, &imgW, &imgH, &channels, 3);
if (!rgb) {
return NULL;
}
int32_t bpp = d->format.bytesPerPixel;
int32_t pitch = imgW * bpp;
uint8_t *buf = (uint8_t *)malloc(pitch * imgH);
if (!buf) {
stbi_image_free(rgb);
return NULL;
}
for (int32_t y = 0; y < imgH; y++) {
for (int32_t x = 0; x < imgW; x++) {
const uint8_t *src = rgb + (y * imgW + x) * 3;
uint32_t color = packColor(d, src[0], src[1], src[2]);
uint8_t *dst = buf + y * pitch + x * bpp;
if (bpp == 1) {
*dst = (uint8_t)color;
} else if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)color;
} else {
*(uint32_t *)dst = color;
}
}
}
stbi_image_free(rgb);
if (outW) {
*outW = imgW;
}
if (outH) {
*outH = imgH;
}
if (outPitch) {
*outPitch = pitch;
}
return buf;
}
// ============================================================
// dvxLoadImageAlpha
// ============================================================
uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch, bool *outHasAlpha, uint32_t *outKeyColor) { uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch, bool *outHasAlpha, uint32_t *outKeyColor) {
if (!ctx || !data || dataLen <= 0) { if (!ctx || !data || dataLen <= 0) {
return NULL; return NULL;
@ -4580,9 +4316,64 @@ uint8_t *dvxLoadImageAlpha(const AppContextT *ctx, const uint8_t *data, int32_t
} }
// ============================================================ uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch) {
// dvxLoadTheme if (!ctx || !data || dataLen <= 0) {
// ============================================================ return NULL;
}
const DisplayT *d = &ctx->display;
int imgW;
int imgH;
int channels;
uint8_t *rgb = stbi_load_from_memory(data, dataLen, &imgW, &imgH, &channels, 3);
if (!rgb) {
return NULL;
}
int32_t bpp = d->format.bytesPerPixel;
int32_t pitch = imgW * bpp;
uint8_t *buf = (uint8_t *)malloc(pitch * imgH);
if (!buf) {
stbi_image_free(rgb);
return NULL;
}
for (int32_t y = 0; y < imgH; y++) {
for (int32_t x = 0; x < imgW; x++) {
const uint8_t *src = rgb + (y * imgW + x) * 3;
uint32_t color = packColor(d, src[0], src[1], src[2]);
uint8_t *dst = buf + y * pitch + x * bpp;
if (bpp == 1) {
*dst = (uint8_t)color;
} else if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)color;
} else {
*(uint32_t *)dst = color;
}
}
}
stbi_image_free(rgb);
if (outW) {
*outW = imgW;
}
if (outH) {
*outH = imgH;
}
if (outPitch) {
*outPitch = pitch;
}
return buf;
}
bool dvxLoadTheme(AppContextT *ctx, const char *filename) { bool dvxLoadTheme(AppContextT *ctx, const char *filename) {
FILE *fp = fopen(filename, "rb"); FILE *fp = fopen(filename, "rb");
@ -4601,11 +4392,7 @@ bool dvxLoadTheme(AppContextT *ctx, const char *filename) {
*end-- = '\0'; *end-- = '\0';
} }
char *p = line; char *p = (char *)dvxSkipWs(line);
while (*p == ' ' || *p == '\t') {
p++;
}
// Skip comments, blank lines, section headers // Skip comments, blank lines, section headers
if (*p == '\0' || *p == ';' || *p == '#' || *p == '[') { if (*p == '\0' || *p == ';' || *p == '#' || *p == '[') {
@ -4630,11 +4417,7 @@ bool dvxLoadTheme(AppContextT *ctx, const char *filename) {
} }
// Parse r,g,b // Parse r,g,b
char *val = eq + 1; const char *val = dvxSkipWs(eq + 1);
while (*val == ' ' || *val == '\t') {
val++;
}
int r; int r;
int g; int g;
@ -4688,9 +4471,16 @@ void dvxQuit(AppContextT *ctx) {
} }
// ============================================================ void dvxRaiseWindow(AppContextT *ctx, WindowT *win) {
// dvxResetColorScheme 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;
}
}
}
void dvxResetColorScheme(AppContextT *ctx) { void dvxResetColorScheme(AppContextT *ctx) {
memcpy(ctx->colorRgb, sDefaultColors, sizeof(sDefaultColors)); memcpy(ctx->colorRgb, sDefaultColors, sizeof(sDefaultColors));
@ -4698,9 +4488,110 @@ void dvxResetColorScheme(AppContextT *ctx) {
} }
// ============================================================ void dvxResizeWindow(AppContextT *ctx, WindowT *win, int32_t newW, int32_t newH) {
// dvxRun // Dirty old position
// ============================================================ dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
// Resize
win->w = newW;
win->h = newH;
// Shift position so the window stays fully on screen
if (win->x + newW > ctx->display.width) {
win->x = ctx->display.width - newW;
}
if (win->y + newH > ctx->display.height) {
win->y = ctx->display.height - newH;
}
if (win->x < 0) {
win->x = 0;
}
if (win->y < 0) {
win->y = 0;
}
wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx->display);
// Dirty new position
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
// Invalidate widget tree so it repaints at the new size
wgtInvalidate(win->widgetRoot);
}
void *dvxResLoadData(const char *dxePath, const char *resName, uint32_t *outSize) {
if (!dxePath || !resName) {
return NULL;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return NULL;
}
void *data = dvxResRead(res, resName, outSize);
dvxResClose(res);
return data;
}
uint8_t *dvxResLoadIcon(AppContextT *ctx, const char *dxePath, const char *resName, int32_t *outW, int32_t *outH, int32_t *outPitch) {
if (!ctx || !dxePath || !resName) {
return NULL;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return NULL;
}
uint32_t size = 0;
void *buf = dvxResRead(res, resName, &size);
dvxResClose(res);
if (!buf) {
return NULL;
}
uint8_t *pixels = dvxLoadImageFromMemory(ctx, (const uint8_t *)buf, (int32_t)size, outW, outH, outPitch);
free(buf);
return pixels;
}
bool dvxResLoadText(const char *dxePath, const char *resName, char *buf, int32_t bufSize) {
if (!dxePath || !resName || !buf || bufSize <= 0) {
return false;
}
DvxResHandleT *res = dvxResOpen(dxePath);
if (!res) {
return false;
}
uint32_t size = 0;
void *data = dvxResRead(res, resName, &size);
dvxResClose(res);
if (!data) {
return false;
}
int32_t copyLen = (int32_t)size < bufSize - 1 ? (int32_t)size : bufSize - 1;
memcpy(buf, data, copyLen);
buf[copyLen] = '\0';
free(data);
return true;
}
void dvxRun(AppContextT *ctx) { void dvxRun(AppContextT *ctx) {
while (dvxUpdate(ctx)) { while (dvxUpdate(ctx)) {
@ -4962,9 +4853,15 @@ int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path) {
} }
// ============================================================ void dvxShowWindow(AppContextT *ctx, WindowT *win) {
// dvxShutdown if (!win || win->visible) {
// ============================================================ return;
}
win->visible = true;
dvxInvalidateWindow(ctx, win);
}
void dvxShutdown(AppContextT *ctx) { void dvxShutdown(AppContextT *ctx) {
platformKeyUpShutdown(); platformKeyUpShutdown();
@ -4997,10 +4894,22 @@ void dvxShutdown(AppContextT *ctx) {
} }
// ============================================================ uint32_t dvxTextHash(const char *text) {
// dvxTileWindows if (!text) {
// ============================================================ return 0;
// }
uint32_t h = 5381;
while (*text) {
h = ((h << 5) + h) ^ (uint8_t)*text;
text++;
}
return h;
}
// Tile windows in a grid. The grid dimensions are chosen so columns = // Tile windows in a grid. The grid dimensions are chosen so columns =
// ceil(sqrt(n)), which produces a roughly square grid. This is better than // ceil(sqrt(n)), which produces a roughly square grid. This is better than
// always using rows or columns because it maximizes the minimum dimension // always using rows or columns because it maximizes the minimum dimension

View file

@ -40,10 +40,6 @@ static inline bool rectsOverlapOrAdjacent(const RectT *a, const RectT *b, int32_
static inline void rectUnion(const RectT *a, const RectT *b, RectT *result); static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
// ============================================================
// dirtyListAdd
// ============================================================
//
// Appends a dirty rect to the list. The array grows dynamically but a // Appends a dirty rect to the list. The array grows dynamically but a
// merge pass fires at 128 entries to keep the list short. If the list // merge pass fires at 128 entries to keep the list short. If the list
// is still long after merging (pathological scatter), everything collapses // is still long after merging (pathological scatter), everything collapses
@ -98,28 +94,16 @@ void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
} }
// ============================================================
// dirtyListClear
// ============================================================
void dirtyListClear(DirtyListT *dl) { void dirtyListClear(DirtyListT *dl) {
dl->count = 0; dl->count = 0;
} }
// ============================================================
// dirtyListInit
// ============================================================
void dirtyListInit(DirtyListT *dl) { void dirtyListInit(DirtyListT *dl) {
dl->count = 0; dl->count = 0;
} }
// ============================================================
// dirtyListMerge
// ============================================================
//
// Coalesces overlapping or nearby dirty rects to reduce the number of // Coalesces overlapping or nearby dirty rects to reduce the number of
// composite+flush passes. The trade-off: merging two rects into their // composite+flush passes. The trade-off: merging two rects into their
// bounding box may add "clean" pixels that get needlessly repainted, but // bounding box may add "clean" pixels that get needlessly repainted, but
@ -168,10 +152,6 @@ void dirtyListMerge(DirtyListT *dl) {
} }
// ============================================================
// flushRect
// ============================================================
//
// Copies one dirty rect from the system RAM backbuffer to the VESA LFB. // Copies one dirty rect from the system RAM backbuffer to the VESA LFB.
// This is the single most bandwidth-sensitive operation in the entire GUI: // This is the single most bandwidth-sensitive operation in the entire GUI:
// the LFB lives behind the ISA/PCI bus, so every byte written here is a // the LFB lives behind the ISA/PCI bus, so every byte written here is a
@ -187,10 +167,6 @@ void flushRect(DisplayT *d, const RectT *r) {
} }
// ============================================================
// rectIntersect
// ============================================================
//
// Used heavily in the compositing loop to test whether a window overlaps // Used heavily in the compositing loop to test whether a window overlaps
// a dirty rect before painting it. The branch hint marks the non-overlapping // a dirty rect before painting it. The branch hint marks the non-overlapping
// case as unlikely because the compositing loop already does a coarse AABB // case as unlikely because the compositing loop already does a coarse AABB
@ -216,19 +192,11 @@ bool rectIntersect(const RectT *a, const RectT *b, RectT *result) {
} }
// ============================================================
// rectIsEmpty
// ============================================================
bool rectIsEmpty(const RectT *r) { bool rectIsEmpty(const RectT *r) {
return (r->w <= 0 || r->h <= 0); return (r->w <= 0 || r->h <= 0);
} }
// ============================================================
// rectsOverlapOrAdjacent
// ============================================================
//
// Separating-axis test with a gap tolerance. Two rects merge if they // Separating-axis test with a gap tolerance. Two rects merge if they
// overlap OR if the gap between them is <= DIRTY_MERGE_GAP pixels. // overlap OR if the gap between them is <= DIRTY_MERGE_GAP pixels.
// The gap tolerance is the key tuning parameter for the merge algorithm: // The gap tolerance is the key tuning parameter for the merge algorithm:
@ -247,10 +215,6 @@ static inline bool rectsOverlapOrAdjacent(const RectT *a, const RectT *b, int32_
} }
// ============================================================
// rectUnion
// ============================================================
//
// Axis-aligned bounding box of two rects. Supports in-place operation // Axis-aligned bounding box of two rects. Supports in-place operation
// (result == a) for the merge loop. Note that this may grow the rect // (result == a) for the merge loop. Note that this may grow the rect
// substantially if the two inputs are far apart -- this is the inherent // substantially if the two inputs are far apart -- this is the inherent

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,13 @@
// that was pressed. The dialog window is automatically destroyed on return. // that was pressed. The dialog window is automatically destroyed on return.
int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags); int32_t dvxMessageBox(AppContextT *ctx, const char *title, const char *message, int32_t flags);
// Convenience wrapper: MB_OK | MB_ICONERROR. Title defaults to "Error"
// when NULL. Returns ID_OK.
int32_t dvxErrorBox(AppContextT *ctx, const char *title, const char *message);
// Convenience wrapper: MB_OK | MB_ICONINFO. Title defaults to NULL-permitted.
int32_t dvxInfoBox(AppContextT *ctx, const char *title, const char *message);
// ============================================================ // ============================================================
// File dialog flags // File dialog flags
// ============================================================ // ============================================================

View file

@ -482,175 +482,6 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
} }
// ============================================================
// drawTextN
// ============================================================
//
// Renders exactly 'count' characters from a buffer in one pass.
// Same idea as drawTermRow but for uniform fg/bg text runs.
// Avoids per-character function call overhead, redundant clip
// calculation, and spanFill startup costs.
//
// The key optimization over calling drawChar() in a loop is the
// bg fill strategy: in opaque mode, instead of calling spanFill
// once per character cell per row (count * charHeight spanFill
// calls), we fill the entire visible span's background in one
// spanFill per scanline (just charHeight calls total). Then we
// overlay only the fg glyph pixels. For an 80-column line this
// reduces spanFill calls from 80*16=1280 to just 16. Each
// spanFill maps to a single rep stosl, so we're also getting
// better write-combine utilization from the larger sequential
// stores.
//
// Horizontal clipping is done at the character level (firstChar/
// lastChar) to avoid iterating invisible characters, with per-pixel
// edge clipping only for the partially visible first and last chars.
void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t fg, uint32_t bg, bool opaque) {
if (count <= 0) {
return;
}
int32_t cw = font->charWidth;
int32_t ch = font->charHeight;
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
// Row-level clip: reject if entirely outside vertically
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
if (y + ch <= clipY1 || y >= clipY2) {
return;
}
int32_t totalW = count * cw;
if (x + totalW <= clipX1 || x >= clipX2) {
return;
}
// Vertical clip for glyph scanlines
int32_t rowStart = 0;
int32_t rowEnd = ch;
if (y < clipY1) { rowStart = clipY1 - y; }
if (y + ch > clipY2) { rowEnd = clipY2 - y; }
// Horizontal clip: find first and last visible column (character index)
int32_t firstChar = 0;
int32_t lastChar = count;
if (x < clipX1) {
firstChar = (clipX1 - x) / cw;
}
if (x + totalW > clipX2) {
lastChar = (clipX2 - x + cw - 1) / cw;
if (lastChar > count) { lastChar = count; }
}
// Per-pixel clip for partially visible edge characters
int32_t edgeColStart = 0;
if (x + firstChar * cw < clipX1) {
edgeColStart = clipX1 - (x + firstChar * cw);
}
if (opaque) {
// Opaque: fill background for the entire visible span once per scanline,
// then overlay foreground glyph pixels
int32_t fillX1 = x + firstChar * cw;
int32_t fillX2 = x + lastChar * cw;
if (fillX1 < clipX1) { fillX1 = clipX1; }
if (fillX2 > clipX2) { fillX2 = clipX2; }
int32_t fillW = fillX2 - fillX1;
if (fillW > 0) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t *dst = d->backBuf + (y + row) * pitch + fillX1 * bpp;
ops->spanFill(dst, bg, fillW);
}
}
}
// Render glyph foreground pixels
for (int32_t ci = firstChar; ci < lastChar; ci++) {
int32_t cx = x + ci * cw;
int32_t cStart = 0;
int32_t cEnd = cw;
if (ci == firstChar) {
cStart = edgeColStart;
}
if (cx + cw > clipX2) {
cEnd = clipX2 - cx;
}
int32_t idx = (uint8_t)text[ci] - font->firstChar;
const uint8_t *glyph = NULL;
if (idx >= 0 && idx < font->numChars) {
glyph = font->glyphData + idx * ch;
}
if (!glyph) {
continue;
}
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint16_t *dst = (uint16_t *)(d->backBuf + (y + row) * pitch + cx * 2);
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg16;
}
}
}
} else if (bpp == 4) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint32_t *dst = (uint32_t *)(d->backBuf + (y + row) * pitch + cx * 4);
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg;
}
}
}
} else {
uint8_t fg8 = (uint8_t)fg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint8_t *dst = d->backBuf + (y + row) * pitch + cx;
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg8;
}
}
}
}
}
}
// ============================================================ // ============================================================
// drawFocusRect // drawFocusRect
// ============================================================ // ============================================================
@ -1133,6 +964,175 @@ void drawTextAccel(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, in
} }
// ============================================================
// drawTextN
// ============================================================
//
// Renders exactly 'count' characters from a buffer in one pass.
// Same idea as drawTermRow but for uniform fg/bg text runs.
// Avoids per-character function call overhead, redundant clip
// calculation, and spanFill startup costs.
//
// The key optimization over calling drawChar() in a loop is the
// bg fill strategy: in opaque mode, instead of calling spanFill
// once per character cell per row (count * charHeight spanFill
// calls), we fill the entire visible span's background in one
// spanFill per scanline (just charHeight calls total). Then we
// overlay only the fg glyph pixels. For an 80-column line this
// reduces spanFill calls from 80*16=1280 to just 16. Each
// spanFill maps to a single rep stosl, so we're also getting
// better write-combine utilization from the larger sequential
// stores.
//
// Horizontal clipping is done at the character level (firstChar/
// lastChar) to avoid iterating invisible characters, with per-pixel
// edge clipping only for the partially visible first and last chars.
void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t fg, uint32_t bg, bool opaque) {
if (count <= 0) {
return;
}
int32_t cw = font->charWidth;
int32_t ch = font->charHeight;
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
// Row-level clip: reject if entirely outside vertically
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
if (y + ch <= clipY1 || y >= clipY2) {
return;
}
int32_t totalW = count * cw;
if (x + totalW <= clipX1 || x >= clipX2) {
return;
}
// Vertical clip for glyph scanlines
int32_t rowStart = 0;
int32_t rowEnd = ch;
if (y < clipY1) { rowStart = clipY1 - y; }
if (y + ch > clipY2) { rowEnd = clipY2 - y; }
// Horizontal clip: find first and last visible column (character index)
int32_t firstChar = 0;
int32_t lastChar = count;
if (x < clipX1) {
firstChar = (clipX1 - x) / cw;
}
if (x + totalW > clipX2) {
lastChar = (clipX2 - x + cw - 1) / cw;
if (lastChar > count) { lastChar = count; }
}
// Per-pixel clip for partially visible edge characters
int32_t edgeColStart = 0;
if (x + firstChar * cw < clipX1) {
edgeColStart = clipX1 - (x + firstChar * cw);
}
if (opaque) {
// Opaque: fill background for the entire visible span once per scanline,
// then overlay foreground glyph pixels
int32_t fillX1 = x + firstChar * cw;
int32_t fillX2 = x + lastChar * cw;
if (fillX1 < clipX1) { fillX1 = clipX1; }
if (fillX2 > clipX2) { fillX2 = clipX2; }
int32_t fillW = fillX2 - fillX1;
if (fillW > 0) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t *dst = d->backBuf + (y + row) * pitch + fillX1 * bpp;
ops->spanFill(dst, bg, fillW);
}
}
}
// Render glyph foreground pixels
for (int32_t ci = firstChar; ci < lastChar; ci++) {
int32_t cx = x + ci * cw;
int32_t cStart = 0;
int32_t cEnd = cw;
if (ci == firstChar) {
cStart = edgeColStart;
}
if (cx + cw > clipX2) {
cEnd = clipX2 - cx;
}
int32_t idx = (uint8_t)text[ci] - font->firstChar;
const uint8_t *glyph = NULL;
if (idx >= 0 && idx < font->numChars) {
glyph = font->glyphData + idx * ch;
}
if (!glyph) {
continue;
}
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint16_t *dst = (uint16_t *)(d->backBuf + (y + row) * pitch + cx * 2);
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg16;
}
}
}
} else if (bpp == 4) {
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint32_t *dst = (uint32_t *)(d->backBuf + (y + row) * pitch + cx * 4);
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg;
}
}
}
} else {
uint8_t fg8 = (uint8_t)fg;
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) { continue; }
uint8_t *dst = d->backBuf + (y + row) * pitch + cx;
for (int32_t p = cStart; p < cEnd; p++) {
if (bits & sGlyphBit[p]) {
dst[p] = fg8;
}
}
}
}
}
}
// ============================================================ // ============================================================
// drawVLine // drawVLine
// ============================================================ // ============================================================

View file

@ -4,6 +4,7 @@
// path. Multiple INI files can be open simultaneously. // path. Multiple INI files can be open simultaneously.
#include "dvxPrefs.h" #include "dvxPrefs.h"
#include "platform/dvxPlat.h"
#include <ctype.h> #include <ctype.h>
#include <stdio.h> #include <stdio.h>
@ -34,6 +35,18 @@ struct PrefsHandleT {
}; };
// ============================================================
// Prototypes
// ============================================================
static char *dupStr(const char *s);
static int32_t findEntry(PrefsHandleT *h, const char *section, const char *key);
static int32_t findSection(PrefsHandleT *h, const char *section);
static void freeEntry(PrefsEntryT *e);
static int strcmpci(const char *a, const char *b);
static char *trimInPlace(char *buf);
// ============================================================ // ============================================================
// Helpers // Helpers
// ============================================================ // ============================================================
@ -54,30 +67,6 @@ static char *dupStr(const char *s) {
} }
static void freeEntry(PrefsEntryT *e) {
free(e->section);
free(e->key);
free(e->value);
e->section = NULL;
e->key = NULL;
e->value = NULL;
}
static int strcmpci(const char *a, const char *b) {
for (;;) {
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
if (d != 0 || !*a) {
return d;
}
a++;
b++;
}
}
static int32_t findEntry(PrefsHandleT *h, const char *section, const char *key) { static int32_t findEntry(PrefsHandleT *h, const char *section, const char *key) {
for (int32_t i = 0; i < arrlen(h->entries); i++) { for (int32_t i = 0; i < arrlen(h->entries); i++) {
PrefsEntryT *e = &h->entries[i]; PrefsEntryT *e = &h->entries[i];
@ -107,23 +96,39 @@ static int32_t findSection(PrefsHandleT *h, const char *section) {
} }
static void freeEntry(PrefsEntryT *e) {
free(e->section);
free(e->key);
free(e->value);
e->section = NULL;
e->key = NULL;
e->value = NULL;
}
static int strcmpci(const char *a, const char *b) {
for (;;) {
int d = tolower((unsigned char)*a) - tolower((unsigned char)*b);
if (d != 0 || !*a) {
return d;
}
a++;
b++;
}
}
static char *trimInPlace(char *buf) { static char *trimInPlace(char *buf) {
while (*buf == ' ' || *buf == '\t') { char *p = (char *)dvxSkipWs(buf);
buf++; dvxTrimRight(p);
} return p;
char *end = buf + strlen(buf) - 1;
while (end >= buf && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) {
*end-- = '\0';
}
return buf;
} }
// ============================================================ // ============================================================
// prefsClose // Public API
// ============================================================ // ============================================================
void prefsClose(PrefsHandleT *h) { void prefsClose(PrefsHandleT *h) {
@ -141,20 +146,12 @@ void prefsClose(PrefsHandleT *h) {
} }
// ============================================================
// prefsCreate
// ============================================================
PrefsHandleT *prefsCreate(void) { PrefsHandleT *prefsCreate(void) {
PrefsHandleT *h = (PrefsHandleT *)calloc(1, sizeof(PrefsHandleT)); PrefsHandleT *h = (PrefsHandleT *)calloc(1, sizeof(PrefsHandleT));
return h; return h;
} }
// ============================================================
// prefsGetBool
// ============================================================
bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool defaultVal) { bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool defaultVal) {
const char *val = prefsGetString(h, section, key, NULL); const char *val = prefsGetString(h, section, key, NULL);
@ -176,10 +173,6 @@ bool prefsGetBool(PrefsHandleT *h, const char *section, const char *key, bool de
} }
// ============================================================
// prefsGetInt
// ============================================================
int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32_t defaultVal) { int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32_t defaultVal) {
const char *val = prefsGetString(h, section, key, NULL); const char *val = prefsGetString(h, section, key, NULL);
@ -198,10 +191,6 @@ int32_t prefsGetInt(PrefsHandleT *h, const char *section, const char *key, int32
} }
// ============================================================
// prefsGetString
// ============================================================
const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key, const char *defaultVal) { const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key, const char *defaultVal) {
if (!h) { if (!h) {
return defaultVal; return defaultVal;
@ -217,10 +206,6 @@ const char *prefsGetString(PrefsHandleT *h, const char *section, const char *key
} }
// ============================================================
// prefsLoad
// ============================================================
PrefsHandleT *prefsLoad(const char *filename) { PrefsHandleT *prefsLoad(const char *filename) {
PrefsHandleT *h = prefsCreate(); PrefsHandleT *h = prefsCreate();
@ -240,17 +225,9 @@ PrefsHandleT *prefsLoad(const char *filename) {
char *currentSection = dupStr(""); char *currentSection = dupStr("");
while (fgets(line, sizeof(line), fp)) { while (fgets(line, sizeof(line), fp)) {
char *end = line + strlen(line) - 1; dvxTrimRight(line);
while (end >= line && (*end == '\r' || *end == '\n' || *end == ' ' || *end == '\t')) { char *p = (char *)dvxSkipWs(line);
*end-- = '\0';
}
char *p = line;
while (*p == ' ' || *p == '\t') {
p++;
}
if (*p == '\0') { if (*p == '\0') {
PrefsEntryT e = {0}; PrefsEntryT e = {0};
@ -303,10 +280,6 @@ PrefsHandleT *prefsLoad(const char *filename) {
} }
// ============================================================
// prefsRemove
// ============================================================
void prefsRemove(PrefsHandleT *h, const char *section, const char *key) { void prefsRemove(PrefsHandleT *h, const char *section, const char *key) {
if (!h) { if (!h) {
return; return;
@ -321,10 +294,6 @@ void prefsRemove(PrefsHandleT *h, const char *section, const char *key) {
} }
// ============================================================
// prefsSave
// ============================================================
bool prefsSave(PrefsHandleT *h) { bool prefsSave(PrefsHandleT *h) {
if (!h || !h->filePath) { if (!h || !h->filePath) {
return false; return false;
@ -334,10 +303,6 @@ bool prefsSave(PrefsHandleT *h) {
} }
// ============================================================
// prefsSaveAs
// ============================================================
bool prefsSaveAs(PrefsHandleT *h, const char *filename) { bool prefsSaveAs(PrefsHandleT *h, const char *filename) {
if (!h) { if (!h) {
return false; return false;
@ -372,19 +337,11 @@ bool prefsSaveAs(PrefsHandleT *h, const char *filename) {
} }
// ============================================================
// prefsSetBool
// ============================================================
void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value) { void prefsSetBool(PrefsHandleT *h, const char *section, const char *key, bool value) {
prefsSetString(h, section, key, value ? "true" : "false"); prefsSetString(h, section, key, value ? "true" : "false");
} }
// ============================================================
// prefsSetInt
// ============================================================
void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t value) { void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t value) {
char buf[32]; char buf[32];
snprintf(buf, sizeof(buf), "%ld", (long)value); snprintf(buf, sizeof(buf), "%ld", (long)value);
@ -392,10 +349,6 @@ void prefsSetInt(PrefsHandleT *h, const char *section, const char *key, int32_t
} }
// ============================================================
// prefsSetString
// ============================================================
void prefsSetString(PrefsHandleT *h, const char *section, const char *key, const char *value) { void prefsSetString(PrefsHandleT *h, const char *section, const char *key, const char *value) {
if (!h) { if (!h) {
return; return;

View file

@ -7,9 +7,17 @@
#ifndef DVX_PREFS_H #ifndef DVX_PREFS_H
#define DVX_PREFS_H #define DVX_PREFS_H
#include "platform/dvxPlat.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
// Canonical path of the system INI, relative to the DVX root. Every
// component that reads or writes DVX.INI must use this macro so the
// location only has to change in one place. Uses the platform path
// separator so displayed paths match the user's convention.
#define DVX_INI_PATH "CONFIG" DVX_PATH_SEP "DVX.INI"
// Opaque handle to a loaded preferences file. // Opaque handle to a loaded preferences file.
typedef struct PrefsHandleT PrefsHandleT; typedef struct PrefsHandleT PrefsHandleT;

View file

@ -11,6 +11,34 @@
#include <string.h> #include <string.h>
int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize) {
return dvxResAppendEntry(path, name, type, data, dataSize);
}
void dvxResClose(DvxResHandleT *h) {
if (h) {
free(h->entries);
free(h);
}
}
const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name) {
if (!h || !name) {
return NULL;
}
for (uint32_t i = 0; i < h->entryCount; i++) {
if (strcmp(h->entries[i].name, name) == 0) {
return &h->entries[i];
}
}
return NULL;
}
DvxResHandleT *dvxResOpen(const char *path) { DvxResHandleT *dvxResOpen(const char *path) {
if (!path) { if (!path) {
return NULL; return NULL;
@ -77,21 +105,6 @@ DvxResHandleT *dvxResOpen(const char *path) {
} }
const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name) {
if (!h || !name) {
return NULL;
}
for (uint32_t i = 0; i < h->entryCount; i++) {
if (strcmp(h->entries[i].name, name) == 0) {
return &h->entries[i];
}
}
return NULL;
}
void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize) { void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize) {
const DvxResDirEntryT *entry = dvxResFind(h, name); const DvxResDirEntryT *entry = dvxResFind(h, name);
@ -133,19 +146,6 @@ void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize) {
} }
void dvxResClose(DvxResHandleT *h) {
if (h) {
free(h->entries);
free(h);
}
}
int32_t dvxResAppend(const char *path, const char *name, uint32_t type, const void *data, uint32_t dataSize) {
return dvxResAppendEntry(path, name, type, data, dataSize);
}
int32_t dvxResRemove(const char *path, const char *name) { int32_t dvxResRemove(const char *path, const char *name) {
if (!path || !name) { if (!path || !name) {
return -1; return -1;

View file

@ -616,6 +616,12 @@ typedef struct {
#define MOUSE_WHEEL_STEP_MIN 1 #define MOUSE_WHEEL_STEP_MIN 1
#define MOUSE_WHEEL_STEP_MAX 10 #define MOUSE_WHEEL_STEP_MAX 10
// Default mouse INI values (single source of truth for cpanel writer and shell reader).
#define MOUSE_DBLCLICK_DEFAULT_MS 500
#define MOUSE_SPEED_DEFAULT 8
#define MOUSE_ACCEL_DEFAULT "medium"
#define MOUSE_WHEEL_DIR_DEFAULT "normal"
// ============================================================ // ============================================================
// Mouse cursor // Mouse cursor
// ============================================================ // ============================================================

View file

@ -50,10 +50,6 @@
#include "dvxMem.h" #include "dvxMem.h"
// ============================================================
// packColor
// ============================================================
//
// Converts an 8-bit-per-channel RGB triple into whatever pixel // Converts an 8-bit-per-channel RGB triple into whatever pixel
// representation the current display mode uses. The result is // representation the current display mode uses. The result is
// always returned as a uint32_t regardless of actual pixel width, // always returned as a uint32_t regardless of actual pixel width,
@ -85,36 +81,6 @@ uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b) {
} }
// ============================================================
// unpackColor
// ============================================================
void unpackColor(const DisplayT *d, uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b) {
if (d->format.bitsPerPixel == 8) {
if (color < 256 && d->palette) {
*r = d->palette[color * 3];
*g = d->palette[color * 3 + 1];
*b = d->palette[color * 3 + 2];
} else {
*r = *g = *b = 0;
}
return;
}
uint32_t rv = (color >> d->format.redShift) & ((1u << d->format.redBits) - 1);
uint32_t gv = (color >> d->format.greenShift) & ((1u << d->format.greenBits) - 1);
uint32_t bv = (color >> d->format.blueShift) & ((1u << d->format.blueBits) - 1);
*r = (uint8_t)(rv << (8 - d->format.redBits));
*g = (uint8_t)(gv << (8 - d->format.greenBits));
*b = (uint8_t)(bv << (8 - d->format.blueBits));
}
// ============================================================
// resetClipRect
// ============================================================
//
// Restores the clip rect to the full screen. Called after the // Restores the clip rect to the full screen. Called after the
// compositor finishes flushing dirty rects, and during init. // compositor finishes flushing dirty rects, and during init.
@ -126,10 +92,6 @@ void resetClipRect(DisplayT *d) {
} }
// ============================================================
// setClipRect
// ============================================================
//
// Sets the active clip rectangle, clamping to screen bounds. // Sets the active clip rectangle, clamping to screen bounds.
// The clip rect is stored directly on DisplayT so every draw call // The clip rect is stored directly on DisplayT so every draw call
// in layer 2 can read it without an extra parameter. This is the // in layer 2 can read it without an extra parameter. This is the
@ -159,10 +121,28 @@ void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) {
} }
// ============================================================ void unpackColor(const DisplayT *d, uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b) {
// videoInit if (d->format.bitsPerPixel == 8) {
// ============================================================ if (color < 256 && d->palette) {
// *r = d->palette[color * 3];
*g = d->palette[color * 3 + 1];
*b = d->palette[color * 3 + 2];
} else {
*r = *g = *b = 0;
}
return;
}
uint32_t rv = (color >> d->format.redShift) & ((1u << d->format.redBits) - 1);
uint32_t gv = (color >> d->format.greenShift) & ((1u << d->format.greenBits) - 1);
uint32_t bv = (color >> d->format.blueShift) & ((1u << d->format.blueBits) - 1);
*r = (uint8_t)(rv << (8 - d->format.redBits));
*g = (uint8_t)(gv << (8 - d->format.greenBits));
*b = (uint8_t)(bv << (8 - d->format.blueBits));
}
// Thin wrapper that delegates to the platform layer. All the heavy // Thin wrapper that delegates to the platform layer. All the heavy
// lifting -- VBE enumeration, mode scoring, LFB DPMI mapping, // lifting -- VBE enumeration, mode scoring, LFB DPMI mapping,
// backbuffer allocation -- lives in dvxPlatformDos.c so this file // backbuffer allocation -- lives in dvxPlatformDos.c so this file
@ -175,10 +155,6 @@ int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t p
} }
// ============================================================
// videoShutdown
// ============================================================
//
// Restores text mode, frees the backbuffer and palette, unmaps the // Restores text mode, frees the backbuffer and palette, unmaps the
// LFB, and disables DJGPP near pointers. Must be called before // LFB, and disables DJGPP near pointers. Must be called before
// exit or the console will be left in graphics mode. // exit or the console will be left in graphics mode.

View file

@ -32,6 +32,7 @@
#include "dvxTypes.h" #include "dvxTypes.h"
#include <stdlib.h>
#include <time.h> #include <time.h>
// Forward declarations // Forward declarations
@ -148,6 +149,7 @@ typedef enum {
#define WCLASS_RELAYOUT_ON_SCROLL 0x00000800 // full relayout on scrollbar drag #define WCLASS_RELAYOUT_ON_SCROLL 0x00000800 // full relayout on scrollbar drag
#define WCLASS_PRESS_RELEASE 0x00001000 // click = press+release (Button, ImageButton) #define WCLASS_PRESS_RELEASE 0x00001000 // click = press+release (Button, ImageButton)
#define WCLASS_ACCEL_WHEN_HIDDEN 0x00002000 // accel matching works even when invisible #define WCLASS_ACCEL_WHEN_HIDDEN 0x00002000 // accel matching works even when invisible
#define WCLASS_HAS_TEXT 0x00004000 // first field of w->data is (const char *), owned by widget (strdup/free)
// Method IDs -- stable ABI, never reorder, never reuse. // Method IDs -- stable ABI, never reorder, never reuse.
// New methods are appended at the end with the next sequential ID. // New methods are appended at the end with the next sequential ID.
@ -250,6 +252,7 @@ typedef struct WidgetT {
bool swallowTab; // Tab key goes to widget, not focus nav bool swallowTab; // Tab key goes to widget, not focus nav
bool paintDirty; // needs repaint (set by wgtInvalidatePaint) bool paintDirty; // needs repaint (set by wgtInvalidatePaint)
bool childDirty; // a descendant needs repaint (for WCLASS_PAINTS_CHILDREN) bool childDirty; // a descendant needs repaint (for WCLASS_PAINTS_CHILDREN)
bool pressed; // WCLASS_PRESS_RELEASE: set while button is pressed
char accelKey; // lowercase accelerator character, 0 if none char accelKey; // lowercase accelerator character, 0 if none
// Content offset: mouse event coordinates are adjusted by this amount // Content offset: mouse event coordinates are adjusted by this amount
@ -343,10 +346,26 @@ static inline void wclsOnAccelActivate(WidgetT *w, WidgetT *root) {
if (fn) { fn(w, root); } if (fn) { fn(w, root); }
} }
// Default destroy behavior: if the widget's class does not supply an
// explicit WGT_METHOD_DESTROY handler, free w->data automatically (and
// the text-at-offset-0 first, if WCLASS_HAS_TEXT is set). This removes
// the need for one-line free(w->data) destroy handlers in every widget.
// Widget classes that need to free multiple allocations or unregister
// from global lists must still provide an explicit handler.
static inline void wclsDestroy(WidgetT *w) { static inline void wclsDestroy(WidgetT *w) {
typedef void (*FnT)(WidgetT *); typedef void (*FnT)(WidgetT *);
FnT fn = w->wclass ? (FnT)w->wclass->handlers[WGT_METHOD_DESTROY] : NULL; FnT fn = w->wclass ? (FnT)w->wclass->handlers[WGT_METHOD_DESTROY] : NULL;
if (fn) { fn(w); } if (fn) {
fn(w);
return;
}
if (w->data) {
if (w->wclass && (w->wclass->flags & WCLASS_HAS_TEXT)) {
free(*(char **)w->data);
}
free(w->data);
w->data = NULL;
}
} }
static inline void wclsOnChildChanged(WidgetT *parent, WidgetT *child) { static inline void wclsOnChildChanged(WidgetT *parent, WidgetT *child) {

View file

@ -157,7 +157,9 @@ typedef enum {
} ScrollHitE; } ScrollHitE;
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
void widgetDrawScrollbarHEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW);
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
void widgetDrawScrollbarVEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW);
ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos); ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
// ============================================================ // ============================================================
@ -190,6 +192,34 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value);
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
// ============================================================
// Pressable helpers (widgetOps.c)
// ============================================================
//
// Shared drag/press state machine for WCLASS_PRESS_RELEASE widgets
// (Button, ImageButton). The widget's w->pressed flag tracks the
// visual state; these helpers handle mouse/key activation and fire
// the widget's onClick callback on release-within-bounds.
void widgetPressableOnAccelActivate(WidgetT *w, WidgetT *root);
void widgetPressableOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
void widgetPressableOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
void widgetPressableOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetPressableOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
// ============================================================
// Text helpers (widgetOps.c)
// ============================================================
//
// Generic text get/set for widgets whose DataT struct has
// `const char *text` as the first field (Button, Checkbox, Label,
// Radio). The widget class must set WCLASS_HAS_TEXT so destroy
// frees the text automatically. Widgets register these directly
// as their WGT_METHOD_GET_TEXT / WGT_METHOD_SET_TEXT handlers.
const char *widgetTextGet(const WidgetT *w);
void widgetTextSet(WidgetT *w, const char *text);
// Shared helper libraries: // Shared helper libraries:
// texthelp/textHelp.h -- clipboard, text editing, word boundaries // texthelp/textHelp.h -- clipboard, text editing, word boundaries
// listhelp/listHelp.h -- dropdown arrow, popup list, keyboard nav // listhelp/listHelp.h -- dropdown arrow, popup list, keyboard nav

File diff suppressed because it is too large Load diff

View file

@ -271,9 +271,32 @@ void platformVideoFreeBuffers(DisplayT *d);
// accepts either. // accepts either.
char *platformPathDirEnd(const char *path); char *platformPathDirEnd(const char *path);
// The platform's native directory separator character. // Return a pointer to the leaf (basename) portion of path. Never returns
// '/' on DJGPP (which accepts both '/' and '\\'). // NULL -- if the path has no separator, the whole path is the basename.
#define DVX_PATH_SEP '/' const char *platformPathBaseName(const char *path);
// Slurp a whole file into a freshly-malloc'd, NUL-terminated buffer.
// Returns NULL on error (file missing, OOM, read truncated). On success,
// *outLen (if non-NULL) receives the byte length (not counting the NUL).
// Caller must free() the returned buffer. Binary-safe: the NUL is past
// the end of the reported length.
char *platformReadFile(const char *path, int32_t *outLen);
// Advance past leading ' ' and '\t' characters. Returns s unchanged if
// it's NULL or already points at a non-whitespace byte.
const char *dvxSkipWs(const char *s);
// Strip trailing ' ', '\t', '\r', '\n' from buf in place. Returns the
// new length. buf may be NULL or empty.
int32_t dvxTrimRight(char *buf);
// The platform's native directory separator as a string literal. Use
// with string-literal concatenation in format strings or path constants:
// snprintf(buf, sz, "%s" DVX_PATH_SEP "%s", dir, name);
// #define SOMEDIR "CONFIG" DVX_PATH_SEP "WPAPER"
// DJGPP accepts both '/' and '\\' for fopen/stat/etc., so either would
// work, but '\\' is what DOS users expect to see in displayed paths.
#define DVX_PATH_SEP "\\"
// Simple glob pattern matching for filenames. Case-insensitive. // Simple glob pattern matching for filenames. Case-insensitive.
// Supports * (zero or more chars) and ? (one char). // Supports * (zero or more chars) and ? (one char).

View file

@ -1,30 +0,0 @@
// dvxPlatformChdir.c -- platformChdir implementation
//
// Extracted from dvxPlatformDos.c so it can be linked into builds that
// don't want the full platform layer (the BASIC VM standalone compiler,
// host-side test binaries). Changes the working directory, switching
// the active DOS drive first when the path contains a drive letter
// (e.g. "A:\DVX"). Standard chdir() under DJGPP only changes the
// directory on the current drive. On non-DOS hosts drive letters are
// meaningless, so this collapses to a plain chdir().
#include "dvxPlat.h"
#include <ctype.h>
#include <unistd.h>
#ifdef __DJGPP__
#include <dir.h>
#endif
int32_t platformChdir(const char *path) {
#ifdef __DJGPP__
if (path[0] && path[1] == ':') {
int drive = toupper((unsigned char)path[0]) - 'A';
setdisk(drive);
}
#endif
return chdir(path) == 0 ? 0 : -1;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,147 @@
// dvxPlatformUtil.c -- small cross-build platform utilities
//
// Functions here are linked into every DVX build variant: DOS dvx.exe,
// the BASIC standalone compiler BASCOMP.EXE, and the host-side test
// binaries. Kept independent of the VESA / DPMI / INT-33h code in
// dvxPlatformDos.c so minimal builds without the full platform layer
// can still use them.
#include "dvxPlat.h"
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef __DJGPP__
#include <dir.h>
#endif
const char *dvxSkipWs(const char *s) {
if (!s) {
return s;
}
while (*s == ' ' || *s == '\t') {
s++;
}
return s;
}
int32_t dvxTrimRight(char *buf) {
if (!buf) {
return 0;
}
int32_t len = (int32_t)strlen(buf);
while (len > 0 && (buf[len - 1] == ' ' || buf[len - 1] == '\t' ||
buf[len - 1] == '\r' || buf[len - 1] == '\n')) {
buf[--len] = '\0';
}
return len;
}
int32_t platformChdir(const char *path) {
#ifdef __DJGPP__
if (path[0] && path[1] == ':') {
int drive = toupper((unsigned char)path[0]) - 'A';
setdisk(drive);
}
#endif
return chdir(path) == 0 ? 0 : -1;
}
const char *platformPathBaseName(const char *path) {
if (!path) {
return "";
}
const char *sep = platformPathDirEnd(path);
return sep ? sep + 1 : path;
}
// Return the last path separator in `path`, or NULL if none.
//
// Platform-specific, because what counts as a separator varies:
// - DJGPP/DOS and Win32 accept both '/' and '\\' -- we have to check
// both and return whichever appears last.
// - Unix-like systems use only '/'. '\\' is a legal filename
// character there, so treating it as a separator would incorrectly
// split paths containing literal backslashes.
char *platformPathDirEnd(const char *path) {
#if defined(__DJGPP__) || defined(_WIN32) || defined(_WIN64)
char *fwd = strrchr(path, '/');
char *back = strrchr(path, '\\');
if (back > fwd) {
return back;
}
return fwd;
#else
return strrchr(path, '/');
#endif
}
char *platformReadFile(const char *path, int32_t *outLen) {
if (outLen) {
*outLen = 0;
}
FILE *f = fopen(path, "rb");
if (!f) {
return NULL;
}
if (fseek(f, 0, SEEK_END) != 0) {
fclose(f);
return NULL;
}
long size = ftell(f);
if (size < 0) {
fclose(f);
return NULL;
}
if (fseek(f, 0, SEEK_SET) != 0) {
fclose(f);
return NULL;
}
char *buf = (char *)malloc((size_t)size + 1);
if (!buf) {
fclose(f);
return NULL;
}
size_t got = fread(buf, 1, (size_t)size, f);
fclose(f);
if (got != (size_t)size) {
free(buf);
return NULL;
}
buf[size] = '\0';
if (outLen) {
*outLen = (int32_t)size;
}
return buf;
}

View file

@ -44,32 +44,6 @@ typedef struct {
static IfaceMapEntryT *sIfaceMap = NULL; static IfaceMapEntryT *sIfaceMap = NULL;
// ============================================================
// wgtGetApi
// ============================================================
//
// Look up a widget API by name. O(1) via stb_ds string hashmap.
// Returns NULL if the widget is not loaded. Callers should still
// cache the result in a static local to skip even the hash.
const void *wgtGetApi(const char *name) {
if (!name) {
return NULL;
}
int32_t idx = shgeti(sApiMap, name);
if (idx < 0) {
return NULL;
}
return sApiMap[idx].value;
}
// ============================================================
// wgtFindByBasName
// ============================================================
// //
// Scan all registered interfaces for one whose basName matches // Scan all registered interfaces for one whose basName matches
// (case-insensitive). Returns the widget type name, or NULL. // (case-insensitive). Returns the widget type name, or NULL.
@ -91,9 +65,26 @@ const char *wgtFindByBasName(const char *basName) {
} }
// ============================================================ //
// wgtGetIface // Look up a widget API by name. O(1) via stb_ds string hashmap.
// ============================================================ // Returns NULL if the widget is not loaded. Callers should still
// cache the result in a static local to skip even the hash.
const void *wgtGetApi(const char *name) {
if (!name) {
return NULL;
}
int32_t idx = shgeti(sApiMap, name);
if (idx < 0) {
return NULL;
}
return sApiMap[idx].value;
}
// //
// Look up a widget interface descriptor by type name. // Look up a widget interface descriptor by type name.
@ -112,16 +103,8 @@ const WgtIfaceT *wgtGetIface(const char *name) {
} }
// ============================================================
// wgtIfaceCount / wgtIfaceAt
// ============================================================
// //
// Enumerate all registered widget interfaces. // Enumerate all registered widget interfaces (indexed access).
int32_t wgtIfaceCount(void) {
return sIfaceMap ? (int32_t)shlenu(sIfaceMap) : 0;
}
const WgtIfaceT *wgtIfaceAt(int32_t idx, const char **outName) { const WgtIfaceT *wgtIfaceAt(int32_t idx, const char **outName) {
if (!sIfaceMap || idx < 0 || idx >= (int32_t)shlenu(sIfaceMap)) { if (!sIfaceMap || idx < 0 || idx >= (int32_t)shlenu(sIfaceMap)) {
@ -136,9 +119,13 @@ const WgtIfaceT *wgtIfaceAt(int32_t idx, const char **outName) {
} }
// ============================================================ //
// wgtIfaceGetPath // Return the count of registered widget interfaces.
// ============================================================
int32_t wgtIfaceCount(void) {
return sIfaceMap ? (int32_t)shlenu(sIfaceMap) : 0;
}
const char *wgtIfaceGetPath(const char *name) { const char *wgtIfaceGetPath(const char *name) {
if (!name || !sIfaceMap) { if (!name || !sIfaceMap) {
@ -155,10 +142,6 @@ const char *wgtIfaceGetPath(const char *name) {
} }
// ============================================================
// wgtIfaceGetPathIndex
// ============================================================
int32_t wgtIfaceGetPathIndex(const char *name) { int32_t wgtIfaceGetPathIndex(const char *name) {
if (!name || !sIfaceMap) { if (!name || !sIfaceMap) {
return 1; return 1;
@ -174,10 +157,6 @@ int32_t wgtIfaceGetPathIndex(const char *name) {
} }
// ============================================================
// wgtIfaceSetPath
// ============================================================
void wgtIfaceSetPath(const char *name, const char *path) { void wgtIfaceSetPath(const char *name, const char *path) {
if (!name || !path || !sIfaceMap) { if (!name || !path || !sIfaceMap) {
return; return;
@ -202,9 +181,6 @@ void wgtIfaceSetPath(const char *name, const char *path) {
} }
// ============================================================
// wgtRegisterApi
// ============================================================
// //
// Register a widget's public API struct under a name. Called by // Register a widget's public API struct under a name. Called by
// each widget DXE during wgtRegister(). // each widget DXE during wgtRegister().
@ -218,9 +194,21 @@ void wgtRegisterApi(const char *name, const void *api) {
} }
// ============================================================ //
// wgtRegisterIface // Appends a class to the table and returns the assigned type ID.
// ============================================================ // The ID is simply the array index.
int32_t wgtRegisterClass(const WidgetClassT *wclass) {
if (!wclass) {
return -1;
}
int32_t id = arrlen(widgetClassTable);
arrput(widgetClassTable, wclass);
return id;
}
// //
// Register a widget's interface descriptor under its type name. // Register a widget's interface descriptor under its type name.
// Called by widget DXEs during wgtRegister(). // Called by widget DXEs during wgtRegister().
@ -235,21 +223,3 @@ void wgtRegisterIface(const char *name, const WgtIfaceT *iface) {
entry.iface = iface; entry.iface = iface;
shput(sIfaceMap, name, entry); shput(sIfaceMap, name, entry);
} }
// ============================================================
// wgtRegisterClass
// ============================================================
//
// Appends a class to the table and returns the assigned type ID.
// The ID is simply the array index.
int32_t wgtRegisterClass(const WidgetClassT *wclass) {
if (!wclass) {
return -1;
}
int32_t id = arrlen(widgetClassTable);
arrput(widgetClassTable, wclass);
return id;
}

View file

@ -64,9 +64,12 @@ static int32_t sClickCount = 0;
// ============================================================ // ============================================================
// clipboardCopy // Prototypes
// ============================================================ // ============================================================
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter);
void clipboardCopy(const char *text, int32_t len) { void clipboardCopy(const char *text, int32_t len) {
if (!text || len <= 0) { if (!text || len <= 0) {
return; return;
@ -90,10 +93,6 @@ void clipboardCopy(const char *text, int32_t len) {
} }
// ============================================================
// clipboardGet
// ============================================================
const char *clipboardGet(int32_t *outLen) { const char *clipboardGet(int32_t *outLen) {
if (outLen) { if (outLen) {
*outLen = sClipboardLen; *outLen = sClipboardLen;
@ -103,18 +102,47 @@ const char *clipboardGet(int32_t *outLen) {
} }
// ============================================================
// clipboardMaxLen
// ============================================================
int32_t clipboardMaxLen(void) { int32_t clipboardMaxLen(void) {
return 65536; return 65536;
} }
// ============================================================ // Implements Tab-order navigation: finds the next focusable widget
// multiClickDetect // after 'after' in depth-first tree order. The two-pass approach
// ============================================================ // (search from 'after' to end, then wrap to start) ensures circular
// tabbing -- Tab on the last focusable widget wraps to the first.
//
// The pastAfter flag tracks whether we've passed the 'after' widget
// during traversal. Once past it, the next focusable widget is the
// answer. This avoids collecting all focusable widgets into an array
// just to find the next one -- the common case returns quickly.
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) {
if (!w->visible || !w->enabled) {
return NULL;
}
if (after == NULL) {
*pastAfter = true;
}
if (w == after) {
*pastAfter = true;
} else if (*pastAfter && widgetIsFocusable(w->type)) {
return w;
}
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
WidgetT *found = findNextFocusableImpl(c, after, pastAfter);
if (found) {
return found;
}
}
return NULL;
}
int32_t multiClickDetect(int32_t vx, int32_t vy) { int32_t multiClickDetect(int32_t vx, int32_t vy) {
clock_t now = clock(); clock_t now = clock();
@ -141,10 +169,6 @@ int32_t multiClickDetect(int32_t vx, int32_t vy) {
} }
// ============================================================
// widgetAddChild
// ============================================================
//
// Appends a child to the end of the parent's child list. O(1) // Appends a child to the end of the parent's child list. O(1)
// thanks to the lastChild tail pointer. The child list is singly- // thanks to the lastChild tail pointer. The child list is singly-
// linked (nextSibling), which saves 4 bytes per widget vs doubly- // linked (nextSibling), which saves 4 bytes per widget vs doubly-
@ -164,10 +188,6 @@ void widgetAddChild(WidgetT *parent, WidgetT *child) {
} }
// ============================================================
// widgetAlloc
// ============================================================
//
// Allocates and zero-initializes a new widget, links it to its // Allocates and zero-initializes a new widget, links it to its
// class vtable via widgetClassTable[], and optionally adds it as // class vtable via widgetClassTable[], and optionally adds it as
// a child of the given parent. // a child of the given parent.
@ -212,10 +232,6 @@ WidgetT *widgetAlloc(WidgetT *parent, int32_t type) {
} }
// ============================================================
// widgetCountVisibleChildren
// ============================================================
int32_t widgetCountVisibleChildren(const WidgetT *w) { int32_t widgetCountVisibleChildren(const WidgetT *w) {
int32_t count = 0; int32_t count = 0;
@ -229,10 +245,6 @@ int32_t widgetCountVisibleChildren(const WidgetT *w) {
} }
// ============================================================
// widgetDestroyChildren
// ============================================================
//
// Recursively destroys all descendants of a widget. Processes // Recursively destroys all descendants of a widget. Processes
// children depth-first (destroy grandchildren before the child // children depth-first (destroy grandchildren before the child
// itself) so that per-widget destroy callbacks see a consistent // itself) so that per-widget destroy callbacks see a consistent
@ -275,10 +287,6 @@ void widgetDestroyChildren(WidgetT *w) {
} }
// ============================================================
// widgetFindByAccel
// ============================================================
//
// Finds a widget with the given Alt+key accelerator. Recurses the // Finds a widget with the given Alt+key accelerator. Recurses the
// tree depth-first, respecting visibility and enabled state. // tree depth-first, respecting visibility and enabled state.
// //
@ -319,46 +327,6 @@ WidgetT *widgetFindByAccel(WidgetT *root, char key) {
} }
// ============================================================
// widgetFindNextFocusable
// ============================================================
//
// Implements Tab-order navigation: finds the next focusable widget
// after 'after' in depth-first tree order. The two-pass approach
// (search from 'after' to end, then wrap to start) ensures circular
// tabbing -- Tab on the last focusable widget wraps to the first.
//
// The pastAfter flag tracks whether we've passed the 'after' widget
// during traversal. Once past it, the next focusable widget is the
// answer. This avoids collecting all focusable widgets into an array
// just to find the next one -- the common case returns quickly.
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) {
if (!w->visible || !w->enabled) {
return NULL;
}
if (after == NULL) {
*pastAfter = true;
}
if (w == after) {
*pastAfter = true;
} else if (*pastAfter && widgetIsFocusable(w->type)) {
return w;
}
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
WidgetT *found = findNextFocusableImpl(c, after, pastAfter);
if (found) {
return found;
}
}
return NULL;
}
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) { WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
bool pastAfter = false; bool pastAfter = false;
WidgetT *found = findNextFocusableImpl(root, after, &pastAfter); WidgetT *found = findNextFocusableImpl(root, after, &pastAfter);
@ -373,10 +341,6 @@ WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
} }
// ============================================================
// widgetFindPrevFocusable
// ============================================================
//
// Shift+Tab navigation: finds the previous focusable widget. // Shift+Tab navigation: finds the previous focusable widget.
// Collects all focusable widgets via DFS, then returns the one // Collects all focusable widgets via DFS, then returns the one
// before 'before' (with wraparound). Uses stb_ds dynamic arrays // before 'before' (with wraparound). Uses stb_ds dynamic arrays
@ -437,10 +401,6 @@ WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) {
} }
// ============================================================
// widgetFrameBorderWidth
// ============================================================
int32_t widgetFrameBorderWidth(const WidgetT *w) { int32_t widgetFrameBorderWidth(const WidgetT *w) {
if (!wclsHas(w, WGT_METHOD_GET_LAYOUT_METRICS)) { if (!wclsHas(w, WGT_METHOD_GET_LAYOUT_METRICS)) {
return 0; return 0;
@ -457,10 +417,6 @@ int32_t widgetFrameBorderWidth(const WidgetT *w) {
} }
// ============================================================
// widgetHitTest
// ============================================================
//
// Recursive hit testing: finds the deepest (most specific) widget // Recursive hit testing: finds the deepest (most specific) widget
// under the given coordinates. Returns the widget itself if no // under the given coordinates. Returns the widget itself if no
// child is hit, or NULL if the point is outside this widget. // child is hit, or NULL if the point is outside this widget.
@ -506,10 +462,6 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) {
} }
// ============================================================
// widgetIsFocusable
// ============================================================
bool widgetIsFocusable(int32_t type) { bool widgetIsFocusable(int32_t type) {
if (type < 0 || type >= arrlen(widgetClassTable) || !widgetClassTable[type]) { if (type < 0 || type >= arrlen(widgetClassTable) || !widgetClassTable[type]) {
return false; return false;
@ -519,10 +471,6 @@ bool widgetIsFocusable(int32_t type) {
} }
// ============================================================
// widgetIsHorizContainer
// ============================================================
//
// Returns true for container types that lay out children horizontally. // Returns true for container types that lay out children horizontally.
bool widgetIsHorizContainer(int32_t type) { bool widgetIsHorizContainer(int32_t type) {
@ -534,44 +482,6 @@ bool widgetIsHorizContainer(int32_t type) {
} }
// ============================================================
// widgetScrollbarThumb
// ============================================================
//
// Calculates thumb position and size for a scrollbar track.
// Used by both the WM-level scrollbars and widget-internal scrollbars
// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior.
//
// The thumb size is proportional to visibleSize/totalSize -- a larger
// visible area means a larger thumb, giving visual feedback about how
// much content is scrollable. SB_MIN_THUMB prevents the thumb from
// becoming too small to grab with a mouse.
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize) {
*thumbSize = (trackLen * visibleSize) / totalSize;
if (*thumbSize < SB_MIN_THUMB) {
*thumbSize = SB_MIN_THUMB;
}
if (*thumbSize > trackLen) {
*thumbSize = trackLen;
}
int32_t maxScroll = totalSize - visibleSize;
if (maxScroll > 0) {
*thumbPos = ((trackLen - *thumbSize) * scrollPos) / maxScroll;
} else {
*thumbPos = 0;
}
}
// ============================================================
// widgetRemoveChild
// ============================================================
//
// Unlinks a child from its parent's child list. O(n) in the number // Unlinks a child from its parent's child list. O(n) in the number
// of children because the singly-linked list requires walking to // of children because the singly-linked list requires walking to
// find the predecessor. This is acceptable because child removal // find the predecessor. This is acceptable because child removal
@ -602,3 +512,31 @@ void widgetRemoveChild(WidgetT *parent, WidgetT *child) {
} }
// Calculates thumb position and size for a scrollbar track.
// Used by both the WM-level scrollbars and widget-internal scrollbars
// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior.
//
// The thumb size is proportional to visibleSize/totalSize -- a larger
// visible area means a larger thumb, giving visual feedback about how
// much content is scrollable. SB_MIN_THUMB prevents the thumb from
// becoming too small to grab with a mouse.
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize) {
*thumbSize = (trackLen * visibleSize) / totalSize;
if (*thumbSize < SB_MIN_THUMB) {
*thumbSize = SB_MIN_THUMB;
}
if (*thumbSize > trackLen) {
*thumbSize = trackLen;
}
int32_t maxScroll = totalSize - visibleSize;
if (maxScroll > 0) {
*thumbPos = ((trackLen - *thumbSize) * scrollPos) / maxScroll;
} else {
*thumbPos = 0;
}
}

View file

@ -34,9 +34,12 @@ static int32_t sPrevMouseY = -1;
// ============================================================ // ============================================================
// widgetManageScrollbars // Prototypes
// ============================================================ // ============================================================
//
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons);
// Manages automatic scrollbar addition/removal for widget-based windows. // Manages automatic scrollbar addition/removal for widget-based windows.
// Called on every invalidation to ensure scrollbars match the current // Called on every invalidation to ensure scrollbars match the current
// widget tree's minimum size requirements. // widget tree's minimum size requirements.
@ -146,10 +149,36 @@ void widgetManageScrollbars(WindowT *win, AppContextT *ctx) {
} }
// ============================================================ // widgetOnBlur -- window lost focus
// widgetOnKey
// ============================================================
// //
// When a widget-managed window loses WM focus (user clicked another
// window's title bar, or the window was minimized), clear the widget
// focus so the cursor/highlight is removed. Without this, the text
// cursor stays visible in the unfocused window.
void widgetOnBlur(WindowT *win) {
if (sFocusedWidget && sFocusedWidget->window == win) {
WidgetT *prev = sFocusedWidget;
sFocusedWidget = NULL;
wgtInvalidatePaint(prev);
if (prev->onBlur) {
prev->onBlur(prev);
}
}
}
// widgetOnFocus -- window gained focus
//
// Mark the window's content dirty so the compositor knows to
// refresh its minimized icon thumbnail if needed.
void widgetOnFocus(WindowT *win) {
win->iconNeedsRefresh = true;
}
// Keyboard event dispatch. Unlike mouse events which use hit testing, // Keyboard event dispatch. Unlike mouse events which use hit testing,
// keyboard events go directly to the focused widget (sFocusedWidget). // keyboard events go directly to the focused widget (sFocusedWidget).
// The cached pointer avoids an O(n) tree walk to find the focused // The cached pointer avoids an O(n) tree walk to find the focused
@ -198,10 +227,6 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
} }
// ============================================================
// widgetOnKeyUp
// ============================================================
void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) { void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) {
WidgetT *root = win->widgetRoot; WidgetT *root = win->widgetRoot;
@ -231,10 +256,6 @@ void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) {
} }
// ============================================================
// widgetOnMouse
// ============================================================
//
// Main mouse event handler. This is the most complex event handler // Main mouse event handler. This is the most complex event handler
// because it must manage multiple overlapping interaction states. // because it must manage multiple overlapping interaction states.
// //
@ -249,8 +270,6 @@ void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) {
// are also in content-buffer space (set during layout), so no // are also in content-buffer space (set during layout), so no
// coordinate transform is needed for hit testing. // coordinate transform is needed for hit testing.
static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y, int32_t buttons);
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) { void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
WidgetT *root = win->widgetRoot; WidgetT *root = win->widgetRoot;
sClosedPopup = NULL; sClosedPopup = NULL;
@ -477,44 +496,6 @@ static void widgetOnMouseInner(WindowT *win, WidgetT *root, int32_t x, int32_t y
} }
// ============================================================
// widgetOnBlur -- window lost focus
// ============================================================
//
// When a widget-managed window loses WM focus (user clicked another
// window's title bar, or the window was minimized), clear the widget
// focus so the cursor/highlight is removed. Without this, the text
// cursor stays visible in the unfocused window.
void widgetOnBlur(WindowT *win) {
if (sFocusedWidget && sFocusedWidget->window == win) {
WidgetT *prev = sFocusedWidget;
sFocusedWidget = NULL;
wgtInvalidatePaint(prev);
if (prev->onBlur) {
prev->onBlur(prev);
}
}
}
// ============================================================
// widgetOnFocus -- window gained focus
// ============================================================
//
// Mark the window's content dirty so the compositor knows to
// refresh its minimized icon thumbnail if needed.
void widgetOnFocus(WindowT *win) {
win->iconNeedsRefresh = true;
}
// ============================================================
// widgetOnPaint
// ============================================================
//
// Paints the entire widget tree into the window's content buffer. // Paints the entire widget tree into the window's content buffer.
// Called whenever the window needs a redraw (invalidation, scroll, // Called whenever the window needs a redraw (invalidation, scroll,
// resize). // resize).
@ -600,10 +581,6 @@ void widgetOnPaint(WindowT *win, RectT *dirtyArea) {
} }
// ============================================================
// widgetOnResize
// ============================================================
//
// Called when the window is resized. Triggers a full scrollbar // Called when the window is resized. Triggers a full scrollbar
// re-evaluation and relayout, since the available content area // re-evaluation and relayout, since the available content area
// changed and scrollbars may need to be added or removed. // changed and scrollbars may need to be added or removed.
@ -627,10 +604,6 @@ void widgetOnResize(WindowT *win, int32_t newW, int32_t newH) {
} }
// ============================================================
// widgetOnScroll
// ============================================================
//
// Called by the WM when a window scrollbar value changes (user dragged // Called by the WM when a window scrollbar value changes (user dragged
// the thumb, clicked the track, or used arrow buttons). Triggers a // the thumb, clicked the track, or used arrow buttons). Triggers a
// full repaint so the widget tree is redrawn at the new scroll offset. // full repaint so the widget tree is redrawn at the new scroll offset.
@ -650,5 +623,3 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
} }
} }
} }

View file

@ -30,10 +30,6 @@
#include "dvxWgtP.h" #include "dvxWgtP.h"
// ============================================================
// widgetCalcMinSizeBox
// ============================================================
//
// Measure pass for box containers (VBox, HBox, RadioGroup, StatusBar, // Measure pass for box containers (VBox, HBox, RadioGroup, StatusBar,
// Toolbar, Frame, TabPage). Recursively measures all visible children, // Toolbar, Frame, TabPage). Recursively measures all visible children,
// then computes this container's minimum size as: // then computes this container's minimum size as:
@ -111,10 +107,6 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
} }
// ============================================================
// widgetCalcMinSizeTree
// ============================================================
//
// Top-level measure dispatcher. Routes to the appropriate measure // Top-level measure dispatcher. Routes to the appropriate measure
// function based on widget type: // function based on widget type:
// - Box containers use the generic widgetCalcMinSizeBox() // - Box containers use the generic widgetCalcMinSizeBox()
@ -157,10 +149,6 @@ void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font) {
} }
// ============================================================
// widgetLayoutBox
// ============================================================
//
// Arrange pass for box containers. This is the core of the flexbox- // Arrange pass for box containers. This is the core of the flexbox-
// like layout algorithm, working in two sub-passes: // like layout algorithm, working in two sub-passes:
// //
@ -344,10 +332,6 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
} }
// ============================================================
// widgetLayoutChildren
// ============================================================
//
// Top-level layout dispatcher. Mirrors the measure dispatcher // Top-level layout dispatcher. Mirrors the measure dispatcher
// in widgetCalcMinSizeTree(): box containers use the generic // in widgetCalcMinSizeTree(): box containers use the generic
// algorithm, widgets with custom layout (TabControl, TreeView, // algorithm, widgets with custom layout (TabControl, TreeView,
@ -361,10 +345,6 @@ void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font) {
} }
// ============================================================
// wgtLayout
// ============================================================
//
// Public entry point: runs both passes on the entire widget tree. // Public entry point: runs both passes on the entire widget tree.
// The root widget is positioned at (0,0) and given the full available // The root widget is positioned at (0,0) and given the full available
// area, then the arrange pass distributes space to its children. // area, then the arrange pass distributes space to its children.
@ -391,10 +371,6 @@ void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT
} }
// ============================================================
// wgtResolveSize
// ============================================================
//
// Decodes a tagged size value into an actual pixel count. // Decodes a tagged size value into an actual pixel count.
// //
// The tagged integer format uses the high 2 bits as a type tag: // The tagged integer format uses the high 2 bits as a type tag:

View file

@ -20,9 +20,14 @@ static bool sFullRepaint = false;
// ============================================================ // ============================================================
// debugContainerBorder // Prototypes
// ============================================================ // ============================================================
//
static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops);
static bool pressableHitTest(const WidgetT *w, const WidgetT *root, int32_t x, int32_t y);
static WidgetT *wgtFindImpl(WidgetT *w, const char *name);
// Draws a 1px border in a neon color derived from the widget pointer. // Draws a 1px border in a neon color derived from the widget pointer.
// The Knuth multiplicative hash (2654435761) distributes pointer values // The Knuth multiplicative hash (2654435761) distributes pointer values
// across the palette evenly so adjacent containers get different colors. // across the palette evenly so adjacent containers get different colors.
@ -57,10 +62,373 @@ static void debugContainerBorder(WidgetT *w, DisplayT *d, const BlitOpsT *ops) {
} }
// ============================================================ // Destroys a widget and its entire subtree. The order is:
// widgetPaintOne // 1. Unlink from parent (so the parent doesn't reference freed memory)
// ============================================================ // 2. Recursively destroy all children (depth-first)
// 3. Call the widget's own destroy callback (free buffers, etc.)
// 4. Clear any global state that references this widget
// 5. Clear the window's root pointer if this was the root
// 6. Free the widget memory
// //
// This ordering ensures that per-widget destroy callbacks can still
// access the widget's data (step 3 comes after child cleanup but
// before the widget itself is freed).
// Shared hit test for pressable widgets: true when (x,y) falls within
// the widget bounds and the drag originated in the same window.
static bool pressableHitTest(const WidgetT *w, const WidgetT *root, int32_t x, int32_t y) {
return w->window == root->window &&
x >= w->x && x < w->x + w->w &&
y >= w->y && y < w->y + w->h;
}
void wgtDestroy(WidgetT *w) {
if (!w) {
return;
}
// Notify parent chain of child destruction via onChildChanged vtable
if (w->parent) {
for (WidgetT *p = w->parent; p; p = p->parent) {
if (wclsHas(p, WGT_METHOD_ON_CHILD_CHANGED)) {
wclsOnChildChanged(p, w);
break;
}
}
}
if (w->parent) {
widgetRemoveChild(w->parent, w);
}
widgetDestroyChildren(w);
wclsDestroy(w);
// Clear static references
if (sFocusedWidget == w) {
sFocusedWidget = NULL;
}
if (sOpenPopup == w) {
sOpenPopup = NULL;
}
if (sDragWidget == w) {
sDragWidget = NULL;
}
if (w->wclass && (w->wclass->flags & WCLASS_NEEDS_POLL)) {
for (int32_t i = 0; i < sPollWidgetCount; i++) {
if (sPollWidgets[i] == w) {
arrdel(sPollWidgets, i);
sPollWidgetCount = (int32_t)arrlen(sPollWidgets);
break;
}
}
}
// If this is the root, clear the window's reference
if (w->window && w->window->widgetRoot == w) {
w->window->widgetRoot = NULL;
}
free(w);
}
WidgetT *wgtFind(WidgetT *root, const char *name) {
if (!root || !name) {
return NULL;
}
return wgtFindImpl(root, name);
}
static WidgetT *wgtFindImpl(WidgetT *w, const char *name) {
if (w->name[0] && strcmp(w->name, name) == 0) {
return w;
}
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
WidgetT *found = wgtFindImpl(c, name);
if (found) {
return found;
}
}
return NULL;
}
// Retrieves the AppContextT from any widget by walking up to the root.
// The root widget stores the context in its userData field (set during
// wgtInitWindow). This is the only way to get the AppContextT from
// deep inside the widget tree without passing it as a parameter
// through every function call. The walk is O(depth) but widget trees
// are shallow (typically 3-6 levels deep).
AppContextT *wgtGetContext(const WidgetT *w) {
if (!w) {
return NULL;
}
const WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
return (AppContextT *)root->userData;
}
WidgetT *wgtGetFocused(void) {
return sFocusedWidget;
}
// Polymorphic text getter -- dispatches through the vtable to the
// appropriate getText implementation for the widget's type. Returns
// an empty string (not NULL) if the widget has no text or no getText
// handler, so callers don't need NULL checks.
const char *wgtGetText(const WidgetT *w) {
if (!w) {
return "";
}
return wclsGetText(w);
}
// Sets up a window for widget-based content. Creates a root VBox
// container and installs the four window callbacks (onPaint, onMouse,
// onKey, onResize) that bridge WM events into the widget system.
//
// The root widget's userData points to the AppContextT, which is
// the bridge back to the display, font, colors, and blitOps needed
// for painting. This avoids threading the context through every
// widget function -- any widget can retrieve it via wgtGetContext().
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
WidgetT *root = dvxBoxApi() ? dvxBoxApi()->vBox(NULL) : NULL;
if (!root) {
dvxLog("Widget: wgtInitWindow failed (%s)", dvxBoxApi() ? "vBox returned NULL" : "dvxBoxApi returned NULL");
return NULL;
}
root->window = win;
root->userData = ctx;
win->widgetRoot = root;
win->onPaint = widgetOnPaint;
win->onMouse = widgetOnMouse;
win->onKey = widgetOnKey;
win->onKeyUp = widgetOnKeyUp;
win->onResize = widgetOnResize;
win->onBlur = widgetOnBlur;
win->onFocus = widgetOnFocus;
return root;
}
// Full invalidation: re-measures the widget tree, manages scrollbars,
// re-lays out, repaints, and dirties the window on screen.
//
// This is the "something structural changed" path -- use when widget
// sizes may have changed (text changed, children added/removed,
// visibility toggled). If only visual state changed (cursor blink,
// selection highlight), use wgtInvalidatePaint() instead to skip
// the expensive measure/layout passes.
//
// The widgetOnPaint check ensures that custom paint handlers (used
// by some dialog implementations) aren't bypassed by the scrollbar
// management code.
void wgtInvalidate(WidgetT *w) {
if (!w || !w->window) {
return;
}
// Find the root
WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
AppContextT *ctx = (AppContextT *)root->userData;
if (!ctx) {
return;
}
// Manage scrollbars (measures, adds/removes scrollbars, relayouts)
// Skip if window has a custom paint handler (e.g. dialog) that manages its own layout
if (w->window->onPaint == widgetOnPaint) {
widgetManageScrollbars(w->window, ctx);
}
// Full repaint — layout changed, all widgets need redrawing
w->window->paintNeeded = PAINT_FULL;
dvxInvalidateWindow(ctx, w->window);
}
// Lightweight repaint -- skips measure/layout/scrollbar management.
// Use when only visual state changed (slider value, cursor blink,
// selection highlight, checkbox toggle) but widget sizes are stable.
void wgtInvalidatePaint(WidgetT *w) {
if (!w || !w->window) {
return;
}
// Mark only this widget as needing repaint
w->paintDirty = true;
// Propagate childDirty up through WCLASS_PAINTS_CHILDREN ancestors
// so they know to recurse into children during partial repaints.
WidgetT *root = w;
while (root->parent) {
root = root->parent;
if (root->wclass && (root->wclass->flags & WCLASS_PAINTS_CHILDREN)) {
root->childDirty = true;
}
}
// Defer the actual paint — it will happen once in the main loop
// before compositing, batching multiple invalidations into one
// tree walk instead of one per call. Don't downgrade FULL to PARTIAL.
if (w->window->paintNeeded < PAINT_PARTIAL) {
w->window->paintNeeded = PAINT_PARTIAL;
}
}
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, bool fullRepaint) {
if (!root) {
return;
}
sFullRepaint = fullRepaint;
widgetPaintOne(root, d, ops, font, colors);
sFullRepaint = false;
}
void wgtSetDebugLayout(AppContextT *ctx, bool enabled) {
sDebugLayout = enabled;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->widgetRoot) {
wgtInvalidate(win->widgetRoot);
}
}
}
void wgtSetEnabled(WidgetT *w, bool enabled) {
if (w) {
w->enabled = enabled;
wgtInvalidatePaint(w);
}
}
void wgtSetFocused(WidgetT *w) {
if (!w || !w->enabled) {
return;
}
WidgetT *prev = sFocusedWidget;
if (prev && prev != w) {
wgtInvalidatePaint(prev);
}
sFocusedWidget = w;
wgtInvalidatePaint(w);
if (prev && prev != w && prev->onBlur) {
prev->onBlur(prev);
}
if (w->onFocus) {
w->onFocus(w);
}
}
void wgtSetName(WidgetT *w, const char *name) {
if (!w || !name) {
return;
}
strncpy(w->name, name, MAX_WIDGET_NAME - 1);
w->name[MAX_WIDGET_NAME - 1] = '\0';
}
void wgtSetReadOnly(WidgetT *w, bool readOnly) {
if (w) {
w->readOnly = readOnly;
}
}
// Polymorphic text setter. Dispatches to the type-specific setText
// via vtable, then does a full invalidation because changing text
// can change the widget's minimum size (triggering relayout).
void wgtSetText(WidgetT *w, const char *text) {
if (!w) {
return;
}
wclsSetText(w, text);
wgtInvalidate(w);
}
void wgtSetTooltip(WidgetT *w, const char *text) {
if (w) {
w->tooltip = text;
}
}
void wgtSetVisible(WidgetT *w, bool visible) {
if (w) {
w->visible = visible;
// Notify parent chain of child visibility change via onChildChanged vtable
if (w->parent) {
for (WidgetT *p = w->parent; p; p = p->parent) {
if (wclsHas(p, WGT_METHOD_ON_CHILD_CHANGED)) {
wclsOnChildChanged(p, w);
break;
}
}
}
wgtInvalidate(w);
}
}
// Recursive paint walker. For each visible widget: // Recursive paint walker. For each visible widget:
// 1. Call the widget's paint function (if any) via vtable. // 1. Call the widget's paint function (if any) via vtable.
// 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion -- // 2. If the widget has WCLASS_PAINTS_CHILDREN, stop recursion --
@ -139,10 +507,6 @@ void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFo
} }
// ============================================================
// widgetPaintOverlays
// ============================================================
//
// Paints popup overlays (open dropdowns/comboboxes) on top of // Paints popup overlays (open dropdowns/comboboxes) on top of
// the widget tree. Called AFTER the main paint pass so popups // the widget tree. Called AFTER the main paint pass so popups
// always render above all other widgets regardless of tree position. // always render above all other widgets regardless of tree position.
@ -171,425 +535,88 @@ void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const
} }
// ============================================================ // Accelerator key activation: mimics keyboard press. The matching key-up
// wgtDestroy // event clears sKeyPressedBtn and fires onClick via wclsOnDragEnd.
// ============================================================ void widgetPressableOnAccelActivate(WidgetT *w, WidgetT *root) {
// (void)root;
// Destroys a widget and its entire subtree. The order is: w->pressed = true;
// 1. Unlink from parent (so the parent doesn't reference freed memory) sKeyPressedBtn = w;
// 2. Recursively destroy all children (depth-first) wgtInvalidatePaint(w);
// 3. Call the widget's own destroy callback (free buffers, etc.) }
// 4. Clear any global state that references this widget
// 5. Clear the window's root pointer if this was the root
// 6. Free the widget memory
//
// This ordering ensures that per-widget destroy callbacks can still
// access the widget's data (step 3 comes after child cleanup but
// before the widget itself is freed).
void wgtDestroy(WidgetT *w) {
if (!w) {
return;
}
// Notify parent chain of child destruction via onChildChanged vtable // End of drag: clear pressed, fire onClick if released inside bounds.
if (w->parent) { void widgetPressableOnDragEnd(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
for (WidgetT *p = w->parent; p; p = p->parent) { w->pressed = false;
if (wclsHas(p, WGT_METHOD_ON_CHILD_CHANGED)) {
wclsOnChildChanged(p, w); if (pressableHitTest(w, root, x, y)) {
break; if (w->onClick) {
} w->onClick(w);
} }
} }
if (w->parent) { wgtInvalidatePaint(w);
widgetRemoveChild(w->parent, w);
}
widgetDestroyChildren(w);
wclsDestroy(w);
// Clear static references
if (sFocusedWidget == w) {
sFocusedWidget = NULL;
}
if (sOpenPopup == w) {
sOpenPopup = NULL;
}
if (sDragWidget == w) {
sDragWidget = NULL;
}
if (w->wclass && (w->wclass->flags & WCLASS_NEEDS_POLL)) {
for (int32_t i = 0; i < sPollWidgetCount; i++) {
if (sPollWidgets[i] == w) {
arrdel(sPollWidgets, i);
sPollWidgetCount = (int32_t)arrlen(sPollWidgets);
break;
}
}
}
// If this is the root, clear the window's reference
if (w->window && w->window->widgetRoot == w) {
w->window->widgetRoot = NULL;
}
free(w);
} }
// ============================================================ // Drag update: pressed tracks whether the cursor is still inside bounds,
// wgtFind // giving visual feedback as the user drags away or back onto the button.
// ============================================================ void widgetPressableOnDragUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
w->pressed = pressableHitTest(w, root, x, y);
static WidgetT *wgtFindImpl(WidgetT *w, const char *name) { wgtInvalidatePaint(w);
if (w->name[0] && strcmp(w->name, name) == 0) {
return w;
}
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
WidgetT *found = wgtFindImpl(c, name);
if (found) {
return found;
}
}
return NULL;
}
WidgetT *wgtFind(WidgetT *root, const char *name) {
if (!root || !name) {
return NULL;
}
return wgtFindImpl(root, name);
} }
// ============================================================ // Keyboard activation (Space/Enter): sets pressed and stores the widget
// wgtSetName // in sKeyPressedBtn so the key-up handler can fire onClick.
// ============================================================ void widgetPressableOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
void wgtSetName(WidgetT *w, const char *name) { if (key == ' ' || key == 0x0D) {
if (!w || !name) { w->pressed = true;
return; sKeyPressedBtn = w;
}
strncpy(w->name, name, MAX_WIDGET_NAME - 1);
w->name[MAX_WIDGET_NAME - 1] = '\0';
}
// ============================================================
// wgtGetContext
// ============================================================
//
// Retrieves the AppContextT from any widget by walking up to the root.
// The root widget stores the context in its userData field (set during
// wgtInitWindow). This is the only way to get the AppContextT from
// deep inside the widget tree without passing it as a parameter
// through every function call. The walk is O(depth) but widget trees
// are shallow (typically 3-6 levels deep).
AppContextT *wgtGetContext(const WidgetT *w) {
if (!w) {
return NULL;
}
const WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
return (AppContextT *)root->userData;
}
// ============================================================
// wgtGetText
// ============================================================
//
// Polymorphic text getter -- dispatches through the vtable to the
// appropriate getText implementation for the widget's type. Returns
// an empty string (not NULL) if the widget has no text or no getText
// handler, so callers don't need NULL checks.
const char *wgtGetText(const WidgetT *w) {
if (!w) {
return "";
}
return wclsGetText(w);
}
// ============================================================
// wgtInitWindow
// ============================================================
//
// Sets up a window for widget-based content. Creates a root VBox
// container and installs the four window callbacks (onPaint, onMouse,
// onKey, onResize) that bridge WM events into the widget system.
//
// The root widget's userData points to the AppContextT, which is
// the bridge back to the display, font, colors, and blitOps needed
// for painting. This avoids threading the context through every
// widget function -- any widget can retrieve it via wgtGetContext().
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
WidgetT *root = dvxBoxApi() ? dvxBoxApi()->vBox(NULL) : NULL;
if (!root) {
dvxLog("Widget: wgtInitWindow failed (%s)", dvxBoxApi() ? "vBox returned NULL" : "dvxBoxApi returned NULL");
return NULL;
}
root->window = win;
root->userData = ctx;
win->widgetRoot = root;
win->onPaint = widgetOnPaint;
win->onMouse = widgetOnMouse;
win->onKey = widgetOnKey;
win->onKeyUp = widgetOnKeyUp;
win->onResize = widgetOnResize;
win->onBlur = widgetOnBlur;
win->onFocus = widgetOnFocus;
return root;
}
// ============================================================
// wgtInvalidate
// ============================================================
//
// Full invalidation: re-measures the widget tree, manages scrollbars,
// re-lays out, repaints, and dirties the window on screen.
//
// This is the "something structural changed" path -- use when widget
// sizes may have changed (text changed, children added/removed,
// visibility toggled). If only visual state changed (cursor blink,
// selection highlight), use wgtInvalidatePaint() instead to skip
// the expensive measure/layout passes.
//
// The widgetOnPaint check ensures that custom paint handlers (used
// by some dialog implementations) aren't bypassed by the scrollbar
// management code.
void wgtInvalidate(WidgetT *w) {
if (!w || !w->window) {
return;
}
// Find the root
WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
AppContextT *ctx = (AppContextT *)root->userData;
if (!ctx) {
return;
}
// Manage scrollbars (measures, adds/removes scrollbars, relayouts)
// Skip if window has a custom paint handler (e.g. dialog) that manages its own layout
if (w->window->onPaint == widgetOnPaint) {
widgetManageScrollbars(w->window, ctx);
}
// Full repaint — layout changed, all widgets need redrawing
w->window->paintNeeded = PAINT_FULL;
dvxInvalidateWindow(ctx, w->window);
}
// ============================================================
// wgtInvalidatePaint
// ============================================================
//
// Lightweight repaint -- skips measure/layout/scrollbar management.
// Use when only visual state changed (slider value, cursor blink,
// selection highlight, checkbox toggle) but widget sizes are stable.
void wgtInvalidatePaint(WidgetT *w) {
if (!w || !w->window) {
return;
}
// Mark only this widget as needing repaint
w->paintDirty = true;
// Propagate childDirty up through WCLASS_PAINTS_CHILDREN ancestors
// so they know to recurse into children during partial repaints.
WidgetT *root = w;
while (root->parent) {
root = root->parent;
if (root->wclass && (root->wclass->flags & WCLASS_PAINTS_CHILDREN)) {
root->childDirty = true;
}
}
// Defer the actual paint — it will happen once in the main loop
// before compositing, batching multiple invalidations into one
// tree walk instead of one per call. Don't downgrade FULL to PARTIAL.
if (w->window->paintNeeded < PAINT_PARTIAL) {
w->window->paintNeeded = PAINT_PARTIAL;
}
}
// ============================================================
// wgtPaint
// ============================================================
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, bool fullRepaint) {
if (!root) {
return;
}
sFullRepaint = fullRepaint;
widgetPaintOne(root, d, ops, font, colors);
sFullRepaint = false;
}
// ============================================================
// wgtSetDebugLayout
// ============================================================
void wgtSetDebugLayout(AppContextT *ctx, bool enabled) {
sDebugLayout = enabled;
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->widgetRoot) {
wgtInvalidate(win->widgetRoot);
}
}
}
// ============================================================
// wgtGetFocused
// ============================================================
WidgetT *wgtGetFocused(void) {
return sFocusedWidget;
}
// ============================================================
// wgtSetEnabled
// ============================================================
void wgtSetEnabled(WidgetT *w, bool enabled) {
if (w) {
w->enabled = enabled;
wgtInvalidatePaint(w); wgtInvalidatePaint(w);
} }
} }
// ============================================================ // Mouse press: take focus, mark pressed, register as the drag widget so
// wgtSetFocused // the event dispatcher routes subsequent drag updates/end back here.
// ============================================================ void widgetPressableOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
void wgtSetFocused(WidgetT *w) { (void)vx;
if (!w || !w->enabled) { (void)vy;
return;
}
WidgetT *prev = sFocusedWidget;
if (prev && prev != w) {
wgtInvalidatePaint(prev);
}
sFocusedWidget = w; sFocusedWidget = w;
wgtInvalidatePaint(w); w->pressed = true;
sDragWidget = w;
if (prev && prev != w && prev->onBlur) {
prev->onBlur(prev);
}
if (w->onFocus) {
w->onFocus(w);
}
} }
// ============================================================ // Shared text getter for widgets whose DataT first field is
// wgtSetReadOnly // `const char *text`. Returns empty string when nothing is set so
// ============================================================ // callers never need a NULL check.
const char *widgetTextGet(const WidgetT *w) {
void wgtSetReadOnly(WidgetT *w, bool readOnly) { if (!w || !w->data) {
if (w) { return "";
w->readOnly = readOnly;
} }
const char *t = *(const char **)w->data;
return t ? t : "";
} }
// ============================================================ // Shared text setter for widgets whose DataT first field is
// wgtSetText // `const char *text`. Replaces the owned strdup'd string and
// ============================================================ // recomputes the accelerator from the '&' prefix.
// void widgetTextSet(WidgetT *w, const char *text) {
// Polymorphic text setter. Dispatches to the type-specific setText if (!w || !w->data) {
// via vtable, then does a full invalidation because changing text
// can change the widget's minimum size (triggering relayout).
void wgtSetText(WidgetT *w, const char *text) {
if (!w) {
return; return;
} }
wclsSetText(w, text); const char **slot = (const char **)w->data;
wgtInvalidate(w); free((void *)*slot);
} *slot = text ? strdup(text) : NULL;
w->accelKey = accelParse(text);
// ============================================================
// wgtSetTooltip
// ============================================================
void wgtSetTooltip(WidgetT *w, const char *text) {
if (w) {
w->tooltip = text;
}
}
// ============================================================
// wgtSetVisible
// ============================================================
void wgtSetVisible(WidgetT *w, bool visible) {
if (w) {
w->visible = visible;
// Notify parent chain of child visibility change via onChildChanged vtable
if (w->parent) {
for (WidgetT *p = w->parent; p; p = p->parent) {
if (wclsHas(p, WGT_METHOD_ON_CHILD_CHANGED)) {
wclsOnChildChanged(p, w);
break;
}
}
}
wgtInvalidate(w);
}
} }

View file

@ -16,35 +16,40 @@
// producing 7-pixel-wide arrow glyphs. This avoids any font or bitmap // producing 7-pixel-wide arrow glyphs. This avoids any font or bitmap
// dependency for the scrollbar chrome. // dependency for the scrollbar chrome.
// //
// The minimum scrollbar length guard (sbW < WGT_SB_W * 3) ensures // The minimum scrollbar length guard (sbW < barW * 3) ensures
// there is at least room for both arrow buttons plus a minimal track. // there is at least room for both arrow buttons plus a minimal track.
// If the container is too small, the scrollbar is simply not drawn // If the container is too small, the scrollbar is simply not drawn
// rather than rendering a corrupted mess. // rather than rendering a corrupted mess.
//
// The *Ex variants accept a caller-supplied bar thickness for widgets
// (such as ScrollPane) that need a non-default scrollbar width. The
// plain variants forward to the Ex variants using WGT_SB_W.
#include "dvxWgtP.h" #include "dvxWgtP.h"
// ============================================================
// widgetDrawScrollbarH
// ============================================================
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) { void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) {
if (sbW < WGT_SB_W * 3) { widgetDrawScrollbarHEx(d, ops, colors, sbX, sbY, sbW, totalSize, visibleSize, scrollPos, WGT_SB_W);
}
void widgetDrawScrollbarHEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW) {
if (sbW < barW * 3) {
return; return;
} }
// Trough background // Trough background
BevelStyleT troughBevel = BEVEL_TROUGH(colors); BevelStyleT troughBevel = BEVEL_TROUGH(colors);
drawBevel(d, ops, sbX, sbY, sbW, WGT_SB_W, &troughBevel); drawBevel(d, ops, sbX, sbY, sbW, barW, &troughBevel);
// Left arrow button // Left arrow button
BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors); BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors);
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel); drawBevel(d, ops, sbX, sbY, barW, barW, &btnBevel);
// Left arrow triangle // Left arrow triangle
{ {
int32_t cx = sbX + WGT_SB_W / 2; int32_t cx = sbX + barW / 2;
int32_t cy = sbY + WGT_SB_W / 2; int32_t cy = sbY + barW / 2;
uint32_t fg = colors->scrollbarFg; uint32_t fg = colors->scrollbarFg;
for (int32_t i = 0; i < 4; i++) { for (int32_t i = 0; i < 4; i++) {
@ -53,13 +58,13 @@ void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
} }
// Right arrow button // Right arrow button
int32_t rightX = sbX + sbW - WGT_SB_W; int32_t rightX = sbX + sbW - barW;
drawBevel(d, ops, rightX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel); drawBevel(d, ops, rightX, sbY, barW, barW, &btnBevel);
// Right arrow triangle // Right arrow triangle
{ {
int32_t cx = rightX + WGT_SB_W / 2; int32_t cx = rightX + barW / 2;
int32_t cy = sbY + WGT_SB_W / 2; int32_t cy = sbY + barW / 2;
uint32_t fg = colors->scrollbarFg; uint32_t fg = colors->scrollbarFg;
for (int32_t i = 0; i < 4; i++) { for (int32_t i = 0; i < 4; i++) {
@ -68,39 +73,40 @@ void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
} }
// Thumb // Thumb
int32_t trackLen = sbW - WGT_SB_W * 2; int32_t trackLen = sbW - barW * 2;
if (trackLen > 0 && totalSize > 0) { if (trackLen > 0 && totalSize > 0) {
int32_t thumbPos; int32_t thumbPos;
int32_t thumbSize; int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize); widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX + WGT_SB_W + thumbPos, sbY, thumbSize, WGT_SB_W, &btnBevel); drawBevel(d, ops, sbX + barW + thumbPos, sbY, thumbSize, barW, &btnBevel);
} }
} }
// ============================================================
// widgetDrawScrollbarV
// ============================================================
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) { void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos) {
if (sbH < WGT_SB_W * 3) { widgetDrawScrollbarVEx(d, ops, colors, sbX, sbY, sbH, totalSize, visibleSize, scrollPos, WGT_SB_W);
}
void widgetDrawScrollbarVEx(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t barW) {
if (sbH < barW * 3) {
return; return;
} }
// Trough background // Trough background
BevelStyleT troughBevel = BEVEL_TROUGH(colors); BevelStyleT troughBevel = BEVEL_TROUGH(colors);
drawBevel(d, ops, sbX, sbY, WGT_SB_W, sbH, &troughBevel); drawBevel(d, ops, sbX, sbY, barW, sbH, &troughBevel);
// Up arrow button // Up arrow button
BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors); BevelStyleT btnBevel = BEVEL_SB_BUTTON(colors);
drawBevel(d, ops, sbX, sbY, WGT_SB_W, WGT_SB_W, &btnBevel); drawBevel(d, ops, sbX, sbY, barW, barW, &btnBevel);
// Up arrow triangle // Up arrow triangle
{ {
int32_t cx = sbX + WGT_SB_W / 2; int32_t cx = sbX + barW / 2;
int32_t cy = sbY + WGT_SB_W / 2; int32_t cy = sbY + barW / 2;
uint32_t fg = colors->scrollbarFg; uint32_t fg = colors->scrollbarFg;
for (int32_t i = 0; i < 4; i++) { for (int32_t i = 0; i < 4; i++) {
@ -109,13 +115,13 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
} }
// Down arrow button // Down arrow button
int32_t downY = sbY + sbH - WGT_SB_W; int32_t downY = sbY + sbH - barW;
drawBevel(d, ops, sbX, downY, WGT_SB_W, WGT_SB_W, &btnBevel); drawBevel(d, ops, sbX, downY, barW, barW, &btnBevel);
// Down arrow triangle // Down arrow triangle
{ {
int32_t cx = sbX + WGT_SB_W / 2; int32_t cx = sbX + barW / 2;
int32_t cy = downY + WGT_SB_W / 2; int32_t cy = downY + barW / 2;
uint32_t fg = colors->scrollbarFg; uint32_t fg = colors->scrollbarFg;
for (int32_t i = 0; i < 4; i++) { for (int32_t i = 0; i < 4; i++) {
@ -124,22 +130,18 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
} }
// Thumb // Thumb
int32_t trackLen = sbH - WGT_SB_W * 2; int32_t trackLen = sbH - barW * 2;
if (trackLen > 0 && totalSize > 0) { if (trackLen > 0 && totalSize > 0) {
int32_t thumbPos; int32_t thumbPos;
int32_t thumbSize; int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize); widgetScrollbarThumb(trackLen, totalSize, visibleSize, scrollPos, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX, sbY + WGT_SB_W + thumbPos, WGT_SB_W, thumbSize, &btnBevel); drawBevel(d, ops, sbX, sbY + barW + thumbPos, barW, thumbSize, &btnBevel);
} }
} }
// ============================================================
// widgetScrollbarHitTest
// ============================================================
// Axis-agnostic hit test. The caller converts (vx,vy) into a 1D // Axis-agnostic hit test. The caller converts (vx,vy) into a 1D
// position along the scrollbar axis (relPos) and the scrollbar // position along the scrollbar axis (relPos) and the scrollbar
// length (sbLen). Returns which zone was hit: arrow buttons, // length (sbLen). Returns which zone was hit: arrow buttons,

View file

@ -97,35 +97,31 @@ static uint32_t currentIdx = 0;
static bool initialized = false; static bool initialized = false;
// ============================================================================ // ============================================================================
// Forward declarations // Forward declarations (alphabetical)
// ============================================================================ // ============================================================================
// Static helpers
static void contextSwitch(TaskContextT *save, TaskContextT *restore); static void contextSwitch(TaskContextT *save, TaskContextT *restore);
static int32_t findFreeSlot(void); static int32_t findFreeSlot(void);
static uint32_t scheduleNext(void); static uint32_t scheduleNext(void);
static void taskTrampoline(void); static void taskTrampoline(void);
uint32_t tsActiveCount(void);
// Public API prototypes are provided by taskswitch.h via #include. int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSize, int32_t priority);
// Explicit prototypes repeated here per project convention: uint32_t tsCurrentId(void);
uint32_t tsActiveCount(void); void tsExit(void);
int32_t tsCreate(const char *name, TaskEntryT entry, void *arg, uint32_t stackSize, int32_t priority); const char *tsGetName(uint32_t taskId);
uint32_t tsCurrentId(void); int32_t tsGetPriority(uint32_t taskId);
void tsExit(void); TaskStateE tsGetState(uint32_t taskId);
const char *tsGetName(uint32_t taskId); int32_t tsInit(void);
int32_t tsGetPriority(uint32_t taskId); int32_t tsKill(uint32_t taskId);
TaskStateE tsGetState(uint32_t taskId); int32_t tsPause(uint32_t taskId);
int32_t tsInit(void); void tsRecoverToMain(void);
int32_t tsKill(uint32_t taskId); int32_t tsResume(uint32_t taskId);
int32_t tsPause(uint32_t taskId); int32_t tsSetPriority(uint32_t taskId, int32_t priority);
void tsRecoverToMain(void); void tsShutdown(void);
int32_t tsResume(uint32_t taskId); void tsYield(void);
int32_t tsSetPriority(uint32_t taskId, int32_t priority);
void tsShutdown(void);
void tsYield(void);
// ============================================================================ // ============================================================================
// Static functions (alphabetical) // Functions (alphabetical)
// ============================================================================ // ============================================================================
// Switch execution from the current task to another by saving and restoring // Switch execution from the current task to another by saving and restoring
@ -300,10 +296,6 @@ static void taskTrampoline(void) {
tsExit(); tsExit();
} }
// ============================================================================
// Public API (alphabetical, main-equivalent functions last if applicable)
// ============================================================================
uint32_t tsActiveCount(void) { uint32_t tsActiveCount(void) {
if (!initialized) { if (!initialized) {
return 0; return 0;

View file

@ -9,15 +9,22 @@
#include <string.h> #include <string.h>
// ============================================================
// Prototypes
// ============================================================
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color);
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t itemCount, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
int32_t widgetMaxItemLen(const char **items, int32_t count);
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize);
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos);
bool widgetPopupScrollbarClick(int32_t x, int32_t y, int32_t popX, int32_t popY, int32_t popW, int32_t popH, int32_t itemCount, int32_t visibleItems, int32_t *scrollPos);
int32_t widgetTypeAheadSearch(char ch, const char **items, int32_t itemCount, int32_t currentIdx);
// ============================================================
// widgetDrawDropdownArrow
// ============================================================
//
// Draws a small downward-pointing filled triangle (7, 5, 3, 1 pixels // Draws a small downward-pointing filled triangle (7, 5, 3, 1 pixels
// wide across 4 rows) centered at the given position. Used by both // wide across 4 rows) centered at the given position. Used by both
// Dropdown and ComboBox for the drop button arrow glyph. // Dropdown and ComboBox for the drop button arrow glyph.
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color) { void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color) {
for (int32_t i = 0; i < 4; i++) { for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, centerX - 3 + i, centerY + i, 7 - i * 2, color); drawHLine(d, ops, centerX - 3 + i, centerY + i, 7 - i * 2, color);
@ -25,14 +32,44 @@ void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX,
} }
// ============================================================ // Calculates the screen rectangle for a dropdown/combobox popup list.
// widgetMaxItemLen // Shared between Dropdown and ComboBox since they have identical
// ============================================================ // popup positioning logic.
// void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t itemCount, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
int32_t visibleItems = itemCount;
if (visibleItems > DROPDOWN_MAX_VISIBLE) {
visibleItems = DROPDOWN_MAX_VISIBLE;
}
if (visibleItems < 1) {
visibleItems = 1;
}
*popX = w->x;
*popW = w->w;
*popH = visibleItems * font->charHeight + 4;
// Add scrollbar width when list exceeds visible area
if (itemCount > DROPDOWN_MAX_VISIBLE) {
*popW += POPUP_SCROLLBAR_W;
}
if (w->y + w->h + *popH <= contentH) {
*popY = w->y + w->h;
} else {
*popY = w->y - *popH;
if (*popY < 0) {
*popY = 0;
}
}
}
// Scans an array of string items and returns the maximum strlen. // Scans an array of string items and returns the maximum strlen.
// Shared by ListBox, Dropdown, and ComboBox to cache the widest // Shared by ListBox, Dropdown, and ComboBox to cache the widest
// item length for calcMinSize without duplicating the loop. // item length for calcMinSize without duplicating the loop.
int32_t widgetMaxItemLen(const char **items, int32_t count) { int32_t widgetMaxItemLen(const char **items, int32_t count) {
int32_t maxLen = 0; int32_t maxLen = 0;
@ -48,10 +85,6 @@ int32_t widgetMaxItemLen(const char **items, int32_t count) {
} }
// ============================================================
// widgetNavigateIndex
// ============================================================
//
// Shared keyboard navigation for list-like widgets (ListBox, Dropdown, // Shared keyboard navigation for list-like widgets (ListBox, Dropdown,
// ListView, etc.). Encapsulates the Up/Down/Home/End/PgUp/PgDn logic // ListView, etc.). Encapsulates the Up/Down/Home/End/PgUp/PgDn logic
// so each widget doesn't have to reimplement index clamping. // so each widget doesn't have to reimplement index clamping.
@ -62,7 +95,6 @@ int32_t widgetMaxItemLen(const char **items, int32_t count) {
// //
// Returns -1 for unrecognized keys so callers can check whether the // Returns -1 for unrecognized keys so callers can check whether the
// key was consumed. // key was consumed.
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize) { int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize) {
if (key == (0x50 | 0x100)) { if (key == (0x50 | 0x100)) {
// Down arrow // Down arrow
@ -108,41 +140,7 @@ int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t
} }
// ============================================================
// widgetTypeAheadSearch
// ============================================================
int32_t widgetTypeAheadSearch(char ch, const char **items, int32_t itemCount, int32_t currentIdx) {
if (itemCount <= 0 || !items) {
return -1;
}
char upper = (ch >= 'a' && ch <= 'z') ? ch - 32 : ch;
// Search forward from currentIdx+1, wrapping around
for (int32_t i = 1; i <= itemCount; i++) {
int32_t idx = (currentIdx + i) % itemCount;
char first = items[idx][0];
if (first >= 'a' && first <= 'z') {
first -= 32;
}
if (first == upper) {
return idx;
}
}
return -1;
}
// ============================================================
// widgetPaintPopupList
// ============================================================
//
// Shared popup list painting for Dropdown and ComboBox. // Shared popup list painting for Dropdown and ComboBox.
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos) { void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos) {
bool hasScrollbar = (itemCount > DROPDOWN_MAX_VISIBLE); bool hasScrollbar = (itemCount > DROPDOWN_MAX_VISIBLE);
int32_t visibleItems = popH / font->charHeight; int32_t visibleItems = popH / font->charHeight;
@ -183,10 +181,6 @@ void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *f
} }
// ============================================================
// widgetPopupScrollbarClick
// ============================================================
bool widgetPopupScrollbarClick(int32_t x, int32_t y, int32_t popX, int32_t popY, int32_t popW, int32_t popH, int32_t itemCount, int32_t visibleItems, int32_t *scrollPos) { bool widgetPopupScrollbarClick(int32_t x, int32_t y, int32_t popX, int32_t popY, int32_t popW, int32_t popH, int32_t itemCount, int32_t visibleItems, int32_t *scrollPos) {
if (itemCount <= DROPDOWN_MAX_VISIBLE) { if (itemCount <= DROPDOWN_MAX_VISIBLE) {
return false; return false;
@ -250,41 +244,26 @@ bool widgetPopupScrollbarClick(int32_t x, int32_t y, int32_t popX, int32_t popY,
} }
// ============================================================ int32_t widgetTypeAheadSearch(char ch, const char **items, int32_t itemCount, int32_t currentIdx) {
// widgetDropdownPopupRect if (itemCount <= 0 || !items) {
// ============================================================ return -1;
//
// Calculates the screen rectangle for a dropdown/combobox popup list.
// Shared between Dropdown and ComboBox since they have identical
// popup positioning logic.
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t itemCount, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
int32_t visibleItems = itemCount;
if (visibleItems > DROPDOWN_MAX_VISIBLE) {
visibleItems = DROPDOWN_MAX_VISIBLE;
} }
if (visibleItems < 1) { char upper = (ch >= 'a' && ch <= 'z') ? ch - 32 : ch;
visibleItems = 1;
}
*popX = w->x; // Search forward from currentIdx+1, wrapping around
*popW = w->w; for (int32_t i = 1; i <= itemCount; i++) {
*popH = visibleItems * font->charHeight + 4; int32_t idx = (currentIdx + i) % itemCount;
char first = items[idx][0];
// Add scrollbar width when list exceeds visible area if (first >= 'a' && first <= 'z') {
if (itemCount > DROPDOWN_MAX_VISIBLE) { first -= 32;
*popW += POPUP_SCROLLBAR_W; }
}
if (w->y + w->h + *popH <= contentH) { if (first == upper) {
*popY = w->y + w->h; return idx;
} else {
*popY = w->y - *popH;
if (*popY < 0) {
*popY = 0;
} }
} }
return -1;
} }

View file

@ -109,6 +109,16 @@ struct PktConnS {
}; };
// ========================================================================
// Open-connection registry: one slot per COM port so pktOpen can reject
// a second open on a port that already has an active connection. Two
// packet conns on the same wire would race on the framing stream and
// produce silent corruption.
// ========================================================================
static PktConnT *sOpenConns[RS232_NUM_PORTS];
// ======================================================================== // ========================================================================
// CRC-16-CCITT lookup table // CRC-16-CCITT lookup table
// ======================================================================== // ========================================================================
@ -150,25 +160,28 @@ static const uint16_t sCrcTable[256] = {
// ======================================================================== // ========================================================================
// Static prototypes (alphabetical) // Prototypes (alphabetical)
// ======================================================================== // ========================================================================
static uint16_t crcCalc(const uint8_t *data, int len); static uint16_t crcCalc(const uint8_t *data, int len);
static void processFrame(PktConnT *conn, const uint8_t *frame, int len); bool pktCanSend(PktConnT *conn);
static void retransmitCheck(PktConnT *conn); void pktClose(PktConnT *conn);
static void rxProcessByte(PktConnT *conn, uint8_t byte); int pktGetPending(PktConnT *conn);
static void sendAck(PktConnT *conn, uint8_t seq); PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *callbackCtx);
static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t *payload, int len); int pktPoll(PktConnT *conn);
static void sendNak(PktConnT *conn, uint8_t seq); int pktReset(PktConnT *conn);
static void sendRst(PktConnT *conn); int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block);
static int seqInWindow(uint8_t seq, uint8_t base, int size); static void processFrame(PktConnT *conn, const uint8_t *frame, int len);
static int txSlotIndex(PktConnT *conn, uint8_t seq); static void retransmitCheck(PktConnT *conn);
static void rxProcessByte(PktConnT *conn, uint8_t byte);
static void sendAck(PktConnT *conn, uint8_t seq);
static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t *payload, int len);
static void sendNak(PktConnT *conn, uint8_t seq);
static void sendRst(PktConnT *conn);
static int seqInWindow(uint8_t seq, uint8_t base, int size);
static int txSlotIndex(PktConnT *conn, uint8_t seq);
// ========================================================================
// CRC computation
// ========================================================================
// Table-driven CRC-16-CCITT. Processing one byte per iteration with a // Table-driven CRC-16-CCITT. Processing one byte per iteration with a
// 256-entry table is ~10x faster than bit-by-bit on a 486. The table // 256-entry table is ~10x faster than bit-by-bit on a 486. The table
// costs 512 bytes of .rodata -- a worthwhile trade for a function called // costs 512 bytes of .rodata -- a worthwhile trade for a function called
@ -184,280 +197,6 @@ static uint16_t crcCalc(const uint8_t *data, int len) {
} }
// ========================================================================
// Frame transmission
// ========================================================================
// Build a raw frame, compute CRC, byte-stuff it, and transmit.
// The stuffed buffer can be up to 2x the raw size (every byte might need
// escaping) plus the flags. This is stack-allocated because frames are small
// and we're not in a deeply recursive call path.
static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t *payload, int len) {
uint8_t raw[MAX_FRAME_SIZE];
uint8_t stuffed[MAX_STUFFED_SIZE];
int rawLen;
uint16_t crc;
int out;
// Build raw frame: SEQ + TYPE + LEN + PAYLOAD
raw[0] = seq;
raw[1] = type;
raw[2] = (uint8_t)len;
if (payload && len > 0) {
memcpy(&raw[3], payload, len);
}
rawLen = HEADER_SIZE + len;
// Append CRC
crc = crcCalc(raw, rawLen);
raw[rawLen] = (uint8_t)(crc & 0xFF);
raw[rawLen + 1] = (uint8_t)((crc >> 8) & 0xFF);
rawLen += CRC_SIZE;
// Byte-stuff into output buffer with leading flag
out = 0;
stuffed[out++] = FLAG_BYTE;
for (int i = 0; i < rawLen; i++) {
if (raw[i] == FLAG_BYTE) {
stuffed[out++] = ESC_BYTE;
stuffed[out++] = FLAG_BYTE ^ ESC_XOR;
} else if (raw[i] == ESC_BYTE) {
stuffed[out++] = ESC_BYTE;
stuffed[out++] = ESC_BYTE ^ ESC_XOR;
} else {
stuffed[out++] = raw[i];
}
}
// Trailing flag to close the frame immediately
stuffed[out++] = FLAG_BYTE;
// Send via serial port (blocking write)
rs232Write(conn->com, (const char *)stuffed, out);
}
static void sendAck(PktConnT *conn, uint8_t seq) {
sendFrame(conn, seq, FRAME_ACK, 0, 0);
}
static void sendNak(PktConnT *conn, uint8_t seq) {
sendFrame(conn, seq, FRAME_NAK, 0, 0);
}
static void sendRst(PktConnT *conn) {
sendFrame(conn, 0, FRAME_RST, 0, 0);
}
// ========================================================================
// Sequence number helpers
// ========================================================================
// Check if a sequence number falls within a window starting at base.
// Works correctly with 8-bit wrap-around because unsigned subtraction
// wraps mod 256 -- if seq is "ahead" of base by less than size, diff
// will be a small positive number.
static int seqInWindow(uint8_t seq, uint8_t base, int size) {
uint8_t diff = seq - base;
return diff < (uint8_t)size;
}
static int txSlotIndex(PktConnT *conn, uint8_t seq) {
uint8_t diff = seq - conn->txAckSeq;
if (diff >= (uint8_t)conn->txCount) {
return -1;
}
return diff;
}
// ========================================================================
// Frame processing
// ========================================================================
// Handle a complete, de-stuffed frame. CRC is verified first; on failure,
// we NAK to request retransmission of the frame we actually expected.
//
// For DATA frames: Go-Back-N receiver logic -- only accept if seq matches
// rxExpectSeq (strictly in-order). Out-of-order frames within the window
// trigger a NAK; duplicates and out-of-window frames are silently dropped.
//
// For ACK frames: cumulative acknowledgement. The ACK carries the next
// expected sequence number, so we free all slots up to that point.
//
// For NAK frames: the receiver wants us to retransmit from a specific
// sequence. Go-Back-N retransmits that frame AND all subsequent ones.
static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
uint8_t seq;
uint8_t type;
int payloadLen;
uint16_t rxCrc;
uint16_t calcCrc;
if (len < MIN_FRAME_SIZE) {
return;
}
// Verify CRC
calcCrc = crcCalc(frame, len - CRC_SIZE);
rxCrc = frame[len - 2] | ((uint16_t)frame[len - 1] << 8);
if (calcCrc != rxCrc) {
// CRC mismatch -- request retransmit of what we expect
sendNak(conn, conn->rxExpectSeq);
return;
}
seq = frame[0];
type = frame[1];
payloadLen = frame[2];
// Validate payload length against actual frame size
if (payloadLen + MIN_FRAME_SIZE != len) {
return;
}
switch (type) {
case FRAME_DATA:
if (seq == conn->rxExpectSeq) {
// In-order delivery
if (conn->callback) {
conn->callback(conn->callbackCtx, &frame[HEADER_SIZE], payloadLen);
}
conn->rxExpectSeq++;
sendAck(conn, conn->rxExpectSeq);
} else if (seqInWindow(seq, conn->rxExpectSeq, conn->windowSize)) {
// Out of order but in window -- NAK the one we want
sendNak(conn, conn->rxExpectSeq);
}
// else: duplicate or out of window, silently discard
break;
case FRAME_ACK: {
// ACK carries the next expected sequence number (cumulative)
// Advance txAckSeq and free all slots up to seq
while (conn->txCount > 0 && conn->txAckSeq != seq) {
conn->txAckSeq++;
conn->txCount--;
}
break;
}
case FRAME_NAK: {
// Retransmit from the requested sequence
int idx = txSlotIndex(conn, seq);
if (idx >= 0) {
// Retransmit this slot and all after it (go-back-N)
for (int i = idx; i < conn->txCount; i++) {
TxSlotT *slot = &conn->txSlots[i];
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
slot->timer = clock();
}
}
break;
}
case FRAME_RST:
// Remote requested reset -- clear state and respond with RST
conn->txNextSeq = 0;
conn->txAckSeq = 0;
conn->txCount = 0;
conn->rxExpectSeq = 0;
sendRst(conn);
break;
}
}
// ========================================================================
// Receive byte processing (state machine)
// ========================================================================
// Feed one byte from the serial port into the HDLC deframing state machine.
// The flag byte (0x7E) serves double duty: it ends the current frame AND
// starts the next one. This means back-to-back frames share a single flag
// byte, saving bandwidth. A frame is only processed if it meets the minimum
// size requirement (header + CRC), so spurious flags between frames are
// harmless (they just produce zero-length "frames" that are discarded).
static void rxProcessByte(PktConnT *conn, uint8_t byte) {
switch (conn->rxState) {
case RX_STATE_HUNT:
if (byte == FLAG_BYTE) {
conn->rxState = RX_STATE_ACTIVE;
conn->rxFrameLen = 0;
}
break;
case RX_STATE_ACTIVE:
if (byte == FLAG_BYTE) {
// End of frame (or start of next)
if (conn->rxFrameLen >= MIN_FRAME_SIZE) {
processFrame(conn, conn->rxFrame, conn->rxFrameLen);
}
// Reset for next frame
conn->rxFrameLen = 0;
} else if (byte == ESC_BYTE) {
conn->rxState = RX_STATE_ESCAPE;
} else {
if (conn->rxFrameLen < MAX_FRAME_SIZE) {
conn->rxFrame[conn->rxFrameLen++] = byte;
} else {
// Frame too large, discard and hunt for next flag
conn->rxState = RX_STATE_HUNT;
}
}
break;
case RX_STATE_ESCAPE:
conn->rxState = RX_STATE_ACTIVE;
byte ^= ESC_XOR;
if (conn->rxFrameLen < MAX_FRAME_SIZE) {
conn->rxFrame[conn->rxFrameLen++] = byte;
} else {
conn->rxState = RX_STATE_HUNT;
}
break;
}
}
// ========================================================================
// Retransmit check
// ========================================================================
// Timer-based retransmission for slots that haven't been ACK'd within the
// timeout. This handles the case where an ACK or NAK was lost -- without
// this, the connection would stall forever. Each slot is retransmitted
// independently and its timer is reset, creating exponential backoff
// behavior naturally (each retransmit resets the timer).
static void retransmitCheck(PktConnT *conn) {
clock_t now = clock();
clock_t timeout = (clock_t)RETRANSMIT_TIMEOUT_MS * CLOCKS_PER_SEC / 1000;
for (int i = 0; i < conn->txCount; i++) {
TxSlotT *slot = &conn->txSlots[i];
if (now - slot->timer >= timeout) {
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
slot->timer = now;
}
}
}
// ========================================================================
// Public functions (alphabetical)
// ========================================================================
void pktClose(PktConnT *conn) {
if (conn) {
free(conn);
}
}
bool pktCanSend(PktConnT *conn) { bool pktCanSend(PktConnT *conn) {
if (!conn) { if (!conn) {
return false; return false;
@ -466,6 +205,19 @@ bool pktCanSend(PktConnT *conn) {
} }
void pktClose(PktConnT *conn) {
if (!conn) {
return;
}
if (conn->com >= 0 && conn->com < RS232_NUM_PORTS && sOpenConns[conn->com] == conn) {
sOpenConns[conn->com] = NULL;
}
free(conn);
}
int pktGetPending(PktConnT *conn) { int pktGetPending(PktConnT *conn) {
if (!conn) { if (!conn) {
return PKT_ERR_INVALID_PARAM; return PKT_ERR_INVALID_PARAM;
@ -475,7 +227,17 @@ int pktGetPending(PktConnT *conn) {
PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *callbackCtx) { PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *callbackCtx) {
PktConnT *conn; // Reject out-of-range COMs and duplicate opens. Two packet
// connections on the same wire would both try to frame/deframe
// the same byte stream -- catching this here avoids a subtle
// protocol corruption downstream.
if (com < 0 || com >= RS232_NUM_PORTS) {
return NULL;
}
if (sOpenConns[com]) {
return NULL;
}
if (windowSize <= 0) { if (windowSize <= 0) {
windowSize = PKT_DEFAULT_WINDOW; windowSize = PKT_DEFAULT_WINDOW;
@ -484,9 +246,10 @@ PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *call
windowSize = PKT_MAX_WINDOW; windowSize = PKT_MAX_WINDOW;
} }
conn = (PktConnT *)calloc(1, sizeof(PktConnT)); PktConnT *conn = (PktConnT *)calloc(1, sizeof(PktConnT));
if (!conn) { if (!conn) {
return 0; return NULL;
} }
conn->com = com; conn->com = com;
@ -500,6 +263,7 @@ PktConnT *pktOpen(int com, int windowSize, PktRecvCallbackT callback, void *call
conn->rxState = RX_STATE_HUNT; conn->rxState = RX_STATE_HUNT;
conn->rxFrameLen = 0; conn->rxFrameLen = 0;
sOpenConns[com] = conn;
return conn; return conn;
} }
@ -602,3 +366,246 @@ int pktSend(PktConnT *conn, const uint8_t *data, int len, bool block) {
return PKT_SUCCESS; return PKT_SUCCESS;
} }
// Handle a complete, de-stuffed frame. CRC is verified first; on failure,
// we NAK to request retransmission of the frame we actually expected.
//
// For DATA frames: Go-Back-N receiver logic -- only accept if seq matches
// rxExpectSeq (strictly in-order). Out-of-order frames within the window
// trigger a NAK; duplicates and out-of-window frames are silently dropped.
//
// For ACK frames: cumulative acknowledgement. The ACK carries the next
// expected sequence number, so we free all slots up to that point.
//
// For NAK frames: the receiver wants us to retransmit from a specific
// sequence. Go-Back-N retransmits that frame AND all subsequent ones.
static void processFrame(PktConnT *conn, const uint8_t *frame, int len) {
uint8_t seq;
uint8_t type;
int payloadLen;
uint16_t rxCrc;
uint16_t calcCrc;
if (len < MIN_FRAME_SIZE) {
return;
}
// Verify CRC
calcCrc = crcCalc(frame, len - CRC_SIZE);
rxCrc = frame[len - 2] | ((uint16_t)frame[len - 1] << 8);
if (calcCrc != rxCrc) {
// CRC mismatch -- request retransmit of what we expect
sendNak(conn, conn->rxExpectSeq);
return;
}
seq = frame[0];
type = frame[1];
payloadLen = frame[2];
// Validate payload length against actual frame size
if (payloadLen + MIN_FRAME_SIZE != len) {
return;
}
switch (type) {
case FRAME_DATA:
if (seq == conn->rxExpectSeq) {
// In-order delivery
if (conn->callback) {
conn->callback(conn->callbackCtx, &frame[HEADER_SIZE], payloadLen);
}
conn->rxExpectSeq++;
sendAck(conn, conn->rxExpectSeq);
} else if (seqInWindow(seq, conn->rxExpectSeq, conn->windowSize)) {
// Out of order but in window -- NAK the one we want
sendNak(conn, conn->rxExpectSeq);
}
// else: duplicate or out of window, silently discard
break;
case FRAME_ACK: {
// ACK carries the next expected sequence number (cumulative)
// Advance txAckSeq and free all slots up to seq
while (conn->txCount > 0 && conn->txAckSeq != seq) {
conn->txAckSeq++;
conn->txCount--;
}
break;
}
case FRAME_NAK: {
// Retransmit from the requested sequence
int idx = txSlotIndex(conn, seq);
if (idx >= 0) {
// Retransmit this slot and all after it (go-back-N)
for (int i = idx; i < conn->txCount; i++) {
TxSlotT *slot = &conn->txSlots[i];
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
slot->timer = clock();
}
}
break;
}
case FRAME_RST:
// Remote requested reset -- clear state and respond with RST
conn->txNextSeq = 0;
conn->txAckSeq = 0;
conn->txCount = 0;
conn->rxExpectSeq = 0;
sendRst(conn);
break;
}
}
// Timer-based retransmission for slots that haven't been ACK'd within the
// timeout. This handles the case where an ACK or NAK was lost -- without
// this, the connection would stall forever. Each slot is retransmitted
// independently and its timer is reset, creating exponential backoff
// behavior naturally (each retransmit resets the timer).
static void retransmitCheck(PktConnT *conn) {
clock_t now = clock();
clock_t timeout = (clock_t)RETRANSMIT_TIMEOUT_MS * CLOCKS_PER_SEC / 1000;
for (int i = 0; i < conn->txCount; i++) {
TxSlotT *slot = &conn->txSlots[i];
if (now - slot->timer >= timeout) {
sendFrame(conn, slot->seq, FRAME_DATA, slot->data, slot->len);
slot->timer = now;
}
}
}
// Feed one byte from the serial port into the HDLC deframing state machine.
// The flag byte (0x7E) serves double duty: it ends the current frame AND
// starts the next one. This means back-to-back frames share a single flag
// byte, saving bandwidth. A frame is only processed if it meets the minimum
// size requirement (header + CRC), so spurious flags between frames are
// harmless (they just produce zero-length "frames" that are discarded).
static void rxProcessByte(PktConnT *conn, uint8_t byte) {
switch (conn->rxState) {
case RX_STATE_HUNT:
if (byte == FLAG_BYTE) {
conn->rxState = RX_STATE_ACTIVE;
conn->rxFrameLen = 0;
}
break;
case RX_STATE_ACTIVE:
if (byte == FLAG_BYTE) {
// End of frame (or start of next)
if (conn->rxFrameLen >= MIN_FRAME_SIZE) {
processFrame(conn, conn->rxFrame, conn->rxFrameLen);
}
// Reset for next frame
conn->rxFrameLen = 0;
} else if (byte == ESC_BYTE) {
conn->rxState = RX_STATE_ESCAPE;
} else {
if (conn->rxFrameLen < MAX_FRAME_SIZE) {
conn->rxFrame[conn->rxFrameLen++] = byte;
} else {
// Frame too large, discard and hunt for next flag
conn->rxState = RX_STATE_HUNT;
}
}
break;
case RX_STATE_ESCAPE:
conn->rxState = RX_STATE_ACTIVE;
byte ^= ESC_XOR;
if (conn->rxFrameLen < MAX_FRAME_SIZE) {
conn->rxFrame[conn->rxFrameLen++] = byte;
} else {
conn->rxState = RX_STATE_HUNT;
}
break;
}
}
static void sendAck(PktConnT *conn, uint8_t seq) {
sendFrame(conn, seq, FRAME_ACK, 0, 0);
}
// Build a raw frame, compute CRC, byte-stuff it, and transmit.
// The stuffed buffer can be up to 2x the raw size (every byte might need
// escaping) plus the flags. This is stack-allocated because frames are small
// and we're not in a deeply recursive call path.
static void sendFrame(PktConnT *conn, uint8_t seq, uint8_t type, const uint8_t *payload, int len) {
uint8_t raw[MAX_FRAME_SIZE];
uint8_t stuffed[MAX_STUFFED_SIZE];
int rawLen;
uint16_t crc;
int out;
// Build raw frame: SEQ + TYPE + LEN + PAYLOAD
raw[0] = seq;
raw[1] = type;
raw[2] = (uint8_t)len;
if (payload && len > 0) {
memcpy(&raw[3], payload, len);
}
rawLen = HEADER_SIZE + len;
// Append CRC
crc = crcCalc(raw, rawLen);
raw[rawLen] = (uint8_t)(crc & 0xFF);
raw[rawLen + 1] = (uint8_t)((crc >> 8) & 0xFF);
rawLen += CRC_SIZE;
// Byte-stuff into output buffer with leading flag
out = 0;
stuffed[out++] = FLAG_BYTE;
for (int i = 0; i < rawLen; i++) {
if (raw[i] == FLAG_BYTE) {
stuffed[out++] = ESC_BYTE;
stuffed[out++] = FLAG_BYTE ^ ESC_XOR;
} else if (raw[i] == ESC_BYTE) {
stuffed[out++] = ESC_BYTE;
stuffed[out++] = ESC_BYTE ^ ESC_XOR;
} else {
stuffed[out++] = raw[i];
}
}
// Trailing flag to close the frame immediately
stuffed[out++] = FLAG_BYTE;
// Send via serial port (blocking write)
rs232Write(conn->com, (const char *)stuffed, out);
}
static void sendNak(PktConnT *conn, uint8_t seq) {
sendFrame(conn, seq, FRAME_NAK, 0, 0);
}
static void sendRst(PktConnT *conn) {
sendFrame(conn, 0, FRAME_RST, 0, 0);
}
// Check if a sequence number falls within a window starting at base.
// Works correctly with 8-bit wrap-around because unsigned subtraction
// wraps mod 256 -- if seq is "ahead" of base by less than size, diff
// will be a small positive number.
static int seqInWindow(uint8_t seq, uint8_t base, int size) {
uint8_t diff = seq - base;
return diff < (uint8_t)size;
}
static int txSlotIndex(PktConnT *conn, uint8_t seq) {
uint8_t diff = seq - conn->txAckSeq;
if (diff >= (uint8_t)conn->txCount) {
return -1;
}
return diff;
}

View file

@ -46,7 +46,6 @@
#define PKT_SUCCESS 0 #define PKT_SUCCESS 0
#define PKT_ERR_INVALID_PORT -1 #define PKT_ERR_INVALID_PORT -1
#define PKT_ERR_NOT_OPEN -2 #define PKT_ERR_NOT_OPEN -2
#define PKT_ERR_ALREADY_OPEN -3
#define PKT_ERR_WOULD_BLOCK -4 #define PKT_ERR_WOULD_BLOCK -4
#define PKT_ERR_OVERFLOW -5 #define PKT_ERR_OVERFLOW -5
#define PKT_ERR_INVALID_PARAM -6 #define PKT_ERR_INVALID_PARAM -6

View file

@ -295,7 +295,7 @@ static Rs232StateT sComPorts[COM_MAX + 1] = {
// ======================================================================== // ========================================================================
// Static prototypes (alphabetical) // Prototypes (alphabetical)
// ======================================================================== // ========================================================================
static void comGeneralIsr(void); static void comGeneralIsr(void);
@ -308,6 +308,42 @@ static void freeIrq(int com);
static int installIrqHandler(int irq); static int installIrqHandler(int irq);
static uint8_t picReadIrr(uint16_t port); static uint8_t picReadIrr(uint16_t port);
static void removeIrqHandler(int irq); static void removeIrqHandler(int irq);
int rs232ClearRxBuffer(int com);
int rs232ClearTxBuffer(int com);
int rs232Close(int com);
int rs232GetBase(int com);
int32_t rs232GetBps(int com);
int rs232GetCts(int com);
int rs232GetData(int com);
int rs232GetDsr(int com);
int rs232GetDtr(int com);
int rs232GetHandshake(int com);
int rs232GetIrq(int com);
int rs232GetLsr(int com);
int rs232GetMcr(int com);
int rs232GetMsr(int com);
char rs232GetParity(int com);
int rs232GetRts(int com);
int rs232GetRxBuffered(int com);
int rs232GetStop(int com);
int rs232GetTxBuffered(int com);
int rs232GetUartType(int com);
int rs232Open(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake);
int rs232Read(int com, char *data, int len);
int rs232Set(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake);
int rs232SetBase(int com, int base);
int rs232SetBps(int com, int32_t bps);
int rs232SetData(int com, int dataBits);
int rs232SetDtr(int com, bool dtr);
int rs232SetFifoThreshold(int com, int threshold);
int rs232SetHandshake(int com, int handshake);
int rs232SetIrq(int com, int irq);
int rs232SetMcr(int com, int mcr);
int rs232SetParity(int com, char parity);
int rs232SetRts(int com, bool rts);
int rs232SetStop(int com, int stopBits);
int rs232Write(int com, const char *data, int len);
int rs232WriteBuf(int com, const char *data, int len);
// ======================================================================== // ========================================================================

View file

@ -34,6 +34,7 @@
#define RS232_COM2 1 #define RS232_COM2 1
#define RS232_COM3 2 #define RS232_COM3 2
#define RS232_COM4 3 #define RS232_COM4 3
#define RS232_NUM_PORTS 4
// Handshaking Modes // Handshaking Modes
#define RS232_HANDSHAKE_NONE 0 #define RS232_HANDSHAKE_NONE 0

View file

@ -65,11 +65,19 @@ struct SecLinkS {
// ======================================================================== // ========================================================================
// Static prototypes (alphabetical) // Prototypes (alphabetical)
// ======================================================================== // ========================================================================
static void completeHandshake(SecLinkT *link); static void completeHandshake(SecLinkT *link);
static void internalRecv(void *ctx, const uint8_t *data, int len); static void internalRecv(void *ctx, const uint8_t *data, int len);
void secLinkClose(SecLinkT *link);
int secLinkGetPending(SecLinkT *link);
int secLinkHandshake(SecLinkT *link);
bool secLinkIsReady(SecLinkT *link);
SecLinkT *secLinkOpen(int com, int32_t bps, int dataBits, char parity, int stopBits, int handshake, SecLinkRecvT callback, void *ctx);
int secLinkPoll(SecLinkT *link);
int secLinkSend(SecLinkT *link, const uint8_t *data, int len, uint8_t channel, bool encrypt, bool block);
int secLinkSendBuf(SecLinkT *link, const uint8_t *data, int len, uint8_t channel, bool encrypt);
// ======================================================================== // ========================================================================

View file

@ -43,8 +43,15 @@
// Max plaintext payload per send (packet max minus 1-byte channel header) // Max plaintext payload per send (packet max minus 1-byte channel header)
#define SECLINK_MAX_PAYLOAD 254 #define SECLINK_MAX_PAYLOAD 254
// Channel limits // Channel limits. MAX_CHANNEL is the highest valid channel index;
// NUM_CHANNELS is the array size needed to hold one slot per channel.
#define SECLINK_MAX_CHANNEL 127 #define SECLINK_MAX_CHANNEL 127
#define SECLINK_NUM_CHANNELS (SECLINK_MAX_CHANNEL + 1)
// Recommended per-channel receive buffer size for callers that buffer
// inbound data between the SecLinkRecvT callback and application read.
// 4096 bytes holds many full secLink payloads (SECLINK_MAX_PAYLOAD=254).
#define SECLINK_CHAN_BUF_SIZE 4096
// Receive callback -- delivers plaintext with channel number // Receive callback -- delivers plaintext with channel number
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel); typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);

View file

@ -94,10 +94,9 @@ static RngStateT sRng = { .seeded = false };
// ======================================================================== // ========================================================================
// Static prototypes (alphabetical) // Prototypes (alphabetical)
// ======================================================================== // ========================================================================
static int bnAdd(BigNumT *result, const BigNumT *a, const BigNumT *b);
static int bnBit(const BigNumT *a, int n); static int bnBit(const BigNumT *a, int n);
static int bnBitLength(const BigNumT *a); static int bnBitLength(const BigNumT *a);
static void bnClear(BigNumT *a); static void bnClear(BigNumT *a);
@ -113,27 +112,28 @@ static void bnToBytes(uint8_t *buf, const BigNumT *a);
static uint32_t computeM0Inv(uint32_t m0); static uint32_t computeM0Inv(uint32_t m0);
static void computeR2(BigNumT *r2, const BigNumT *m); static void computeR2(BigNumT *r2, const BigNumT *m);
static void dhInit(void); static void dhInit(void);
SecCipherT *secCipherCreate(const uint8_t *key);
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len);
void secCipherDestroy(SecCipherT *c);
void secCipherSetNonce(SecCipherT *c, uint32_t nonceLo, uint32_t nonceHi);
int secDhComputeSecret(SecDhT *dh, const uint8_t *remotePub, int len);
SecDhT *secDhCreate(void);
int secDhDeriveKey(SecDhT *dh, uint8_t *key, int keyLen);
void secDhDestroy(SecDhT *dh);
int secDhGenerateKeys(SecDhT *dh);
int secDhGetPublicKey(SecDhT *dh, uint8_t *buf, int *len);
void secRngAddEntropy(const uint8_t *data, int len);
void secRngBytes(uint8_t *buf, int len);
int secRngGatherEntropy(uint8_t *buf, int len);
void secRngSeed(const uint8_t *entropy, int len);
static void secureZero(void *ptr, int len); static void secureZero(void *ptr, int len);
static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]); static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]);
// ======================================================================== // ========================================================================
// BigNum functions (alphabetical) // Functions (alphabetical)
// ======================================================================== // ========================================================================
static int __attribute__((unused)) bnAdd(BigNumT *result, const BigNumT *a, const BigNumT *b) {
uint64_t carry = 0;
for (int i = 0; i < BN_WORDS; i++) {
uint64_t sum = (uint64_t)a->w[i] + b->w[i] + carry;
result->w[i] = (uint32_t)sum;
carry = sum >> 32;
}
return (int)carry;
}
static int bnBit(const BigNumT *a, int n) { static int bnBit(const BigNumT *a, int n) {
return (a->w[n / 32] >> (n % 32)) & 1; return (a->w[n / 32] >> (n % 32)) & 1;
} }
@ -344,10 +344,6 @@ static void bnToBytes(uint8_t *buf, const BigNumT *a) {
} }
// ========================================================================
// Helper functions (alphabetical)
// ========================================================================
// Compute -m0^(-1) mod 2^32 using Newton's method for modular inverse. // Compute -m0^(-1) mod 2^32 using Newton's method for modular inverse.
// Starting from x=1 (which is always a valid initial approximation for // Starting from x=1 (which is always a valid initial approximation for
// odd m0), each iteration doubles the number of correct bits. After 5 // odd m0), each iteration doubles the number of correct bits. After 5
@ -392,165 +388,89 @@ static void dhInit(void) {
} }
// Volatile pointer prevents the compiler from optimizing away the zeroing SecCipherT *secCipherCreate(const uint8_t *key) {
// as a dead store. Critical for clearing key material -- without volatile, SecCipherT *c;
// the compiler sees that ptr is about to be freed and removes the memset.
static void secureZero(void *ptr, int len) {
volatile uint8_t *p = (volatile uint8_t *)ptr;
for (int i = 0; i < len; i++) { if (!key) {
p[i] = 0; return 0;
} }
c = (SecCipherT *)calloc(1, sizeof(SecCipherT));
if (!c) {
return 0;
}
memcpy(c->key, key, SEC_XTEA_KEY_SIZE);
return c;
} }
// XTEA block cipher: encrypts an 8-byte block in-place. The Feistel network // CTR-mode encryption/decryption. The counter is encrypted to produce
// uses 32 rounds (vs TEA's 32 or 64). Each round mixes the halves using // an 8-byte keystream block, which is XOR'd with the data. The counter
// shifts, adds, and XORs -- no S-boxes, no lookup tables, no key schedule. // increments after each block. Because XOR is its own inverse, the same
// The delta constant (golden ratio * 2^32) ensures each round uses a // function handles both encryption and decryption.
// different effective key, preventing slide attacks. //
static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]) { // IMPORTANT: the counter is internal state that advances with each call.
uint32_t v0 = v[0]; // The secLink layer ensures that TX and RX use separate cipher instances
uint32_t v1 = v[1]; // with separate counters, and that the same counter value is never reused
uint32_t sum = 0; // with the same key (which would be catastrophic for CTR mode security).
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len) {
for (int i = 0; i < XTEA_ROUNDS; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += XTEA_DELTA;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
}
v[0] = v0;
v[1] = v1;
}
// ========================================================================
// RNG functions (alphabetical)
// ========================================================================
// Mix additional entropy into the RNG state. XOR-folding into the key is
// simple and cannot reduce entropy (XOR with random data is a bijection).
// The re-mix step (encrypting the key with itself) diffuses the new entropy
// across all key bits so that even a single byte of good entropy improves
// the entire key state.
void secRngAddEntropy(const uint8_t *data, int len) {
for (int i = 0; i < len; i++) {
((uint8_t *)sRng.key)[i % 16] ^= data[i];
}
// Re-mix: encrypt the key with itself
uint32_t block[2]; uint32_t block[2];
block[0] = sRng.key[0] ^ sRng.key[2]; uint8_t *keystream;
block[1] = sRng.key[1] ^ sRng.key[3]; int pos;
xteaEncryptBlock(block, sRng.key); int take;
sRng.key[0] ^= block[0];
sRng.key[1] ^= block[1];
block[0] = sRng.key[2] ^ sRng.key[0];
block[1] = sRng.key[3] ^ sRng.key[1];
xteaEncryptBlock(block, sRng.key);
sRng.key[2] ^= block[0];
sRng.key[3] ^= block[1];
}
if (!c || !data || len <= 0) {
// Generate pseudorandom bytes using XTEA-CTR DRBG. Each call encrypts return;
// the monotonically increasing counter with the RNG key, producing 8 bytes
// of keystream per block. The counter never repeats (64-bit space), so
// the output is a pseudorandom stream as long as the key has sufficient
// entropy. Auto-seeds from hardware entropy on first use as a safety net.
void secRngBytes(uint8_t *buf, int len) {
if (!sRng.seeded) {
uint8_t entropy[16];
int got = secRngGatherEntropy(entropy, sizeof(entropy));
secRngSeed(entropy, got);
} }
uint32_t block[2]; keystream = (uint8_t *)block;
int pos = 0; pos = 0;
while (pos < len) { while (pos < len) {
block[0] = sRng.counter[0]; // Encrypt counter to generate keystream
block[1] = sRng.counter[1]; block[0] = c->counter[0];
xteaEncryptBlock(block, sRng.key); block[1] = c->counter[1];
xteaEncryptBlock(block, c->key);
int take = len - pos; // XOR keystream with data
take = len - pos;
if (take > 8) { if (take > 8) {
take = 8; take = 8;
} }
memcpy(buf + pos, block, take); for (int i = 0; i < take; i++) {
data[pos + i] ^= keystream[i];
}
pos += take; pos += take;
// Increment counter // Increment counter
if (++sRng.counter[0] == 0) { if (++c->counter[0] == 0) {
sRng.counter[1]++; c->counter[1]++;
} }
} }
} }
// Gather hardware entropy from the PIT (Programmable Interval Timer) and void secCipherDestroy(SecCipherT *c) {
// BIOS tick count. The PIT runs at 1.193182 MHz, so its LSBs change rapidly if (c) {
// and provide ~10 bits of entropy per read (depending on timing jitter). secureZero(c, sizeof(SecCipherT));
// The BIOS tick at 18.2 Hz adds a few more bits. Two PIT readings with free(c);
// the intervening code execution provide some jitter. Total: roughly 20 }
// bits of real entropy -- not enough alone, but sufficient to seed the DRBG
// when supplemented by user interaction timing.
int secRngGatherEntropy(uint8_t *buf, int len) {
int out = 0;
outportb(0x43, 0x00);
uint8_t pitLo = inportb(0x40);
uint8_t pitHi = inportb(0x40);
// BIOS tick count (18.2 Hz)
uint32_t ticks = _farpeekl(_dos_ds, 0x46C);
if (out < len) { buf[out++] = pitLo; }
if (out < len) { buf[out++] = pitHi; }
if (out < len) { buf[out++] = (uint8_t)(ticks); }
if (out < len) { buf[out++] = (uint8_t)(ticks >> 8); }
if (out < len) { buf[out++] = (uint8_t)(ticks >> 16); }
if (out < len) { buf[out++] = (uint8_t)(ticks >> 24); }
// Second PIT reading for jitter
outportb(0x43, 0x00);
pitLo = inportb(0x40);
pitHi = inportb(0x40);
if (out < len) { buf[out++] = pitLo; }
if (out < len) { buf[out++] = pitHi; }
return out;
} }
// Initialize the RNG from entropy. The 64-byte discard at the end is void secCipherSetNonce(SecCipherT *c, uint32_t nonceLo, uint32_t nonceHi) {
// standard DRBG practice -- it advances the state past any weak initial if (!c) {
// output that might leak information about the seed material. return;
void secRngSeed(const uint8_t *entropy, int len) {
memset(&sRng, 0, sizeof(sRng));
// XOR-fold entropy into the key
for (int i = 0; i < len; i++) {
((uint8_t *)sRng.key)[i % 16] ^= entropy[i];
} }
// Derive counter from key bits c->nonce[0] = nonceLo;
sRng.counter[0] = sRng.key[2] ^ sRng.key[0]; c->nonce[1] = nonceHi;
sRng.counter[1] = sRng.key[3] ^ sRng.key[1]; c->counter[0] = nonceLo;
sRng.seeded = true; c->counter[1] = nonceHi;
// Mix state by generating and discarding 64 bytes
uint8_t discard[64];
sRng.seeded = true; // prevent recursion in secRngBytes
secRngBytes(discard, sizeof(discard));
secureZero(discard, sizeof(discard));
} }
// ========================================================================
// DH functions (alphabetical)
// ========================================================================
// Compute the shared secret from the remote side's public key. // Compute the shared secret from the remote side's public key.
// Validates that the remote key is in [2, p-2] to prevent small-subgroup // Validates that the remote key is in [2, p-2] to prevent small-subgroup
// attacks (keys of 0, 1, or p-1 would produce trivially guessable secrets). // attacks (keys of 0, 1, or p-1 would produce trivially guessable secrets).
@ -686,88 +606,152 @@ int secDhGetPublicKey(SecDhT *dh, uint8_t *buf, int *len) {
} }
// ======================================================================== // Mix additional entropy into the RNG state. XOR-folding into the key is
// Cipher functions (alphabetical) // simple and cannot reduce entropy (XOR with random data is a bijection).
// ======================================================================== // The re-mix step (encrypting the key with itself) diffuses the new entropy
// across all key bits so that even a single byte of good entropy improves
SecCipherT *secCipherCreate(const uint8_t *key) { // the entire key state.
SecCipherT *c; void secRngAddEntropy(const uint8_t *data, int len) {
for (int i = 0; i < len; i++) {
if (!key) { ((uint8_t *)sRng.key)[i % 16] ^= data[i];
return 0;
} }
c = (SecCipherT *)calloc(1, sizeof(SecCipherT)); // Re-mix: encrypt the key with itself
if (!c) { uint32_t block[2];
return 0; block[0] = sRng.key[0] ^ sRng.key[2];
} block[1] = sRng.key[1] ^ sRng.key[3];
xteaEncryptBlock(block, sRng.key);
memcpy(c->key, key, SEC_XTEA_KEY_SIZE); sRng.key[0] ^= block[0];
return c; sRng.key[1] ^= block[1];
block[0] = sRng.key[2] ^ sRng.key[0];
block[1] = sRng.key[3] ^ sRng.key[1];
xteaEncryptBlock(block, sRng.key);
sRng.key[2] ^= block[0];
sRng.key[3] ^= block[1];
} }
// CTR-mode encryption/decryption. The counter is encrypted to produce // Generate pseudorandom bytes using XTEA-CTR DRBG. Each call encrypts
// an 8-byte keystream block, which is XOR'd with the data. The counter // the monotonically increasing counter with the RNG key, producing 8 bytes
// increments after each block. Because XOR is its own inverse, the same // of keystream per block. The counter never repeats (64-bit space), so
// function handles both encryption and decryption. // the output is a pseudorandom stream as long as the key has sufficient
// // entropy. Auto-seeds from hardware entropy on first use as a safety net.
// IMPORTANT: the counter is internal state that advances with each call. void secRngBytes(uint8_t *buf, int len) {
// The secLink layer ensures that TX and RX use separate cipher instances if (!sRng.seeded) {
// with separate counters, and that the same counter value is never reused uint8_t entropy[16];
// with the same key (which would be catastrophic for CTR mode security). int got = secRngGatherEntropy(entropy, sizeof(entropy));
void secCipherCrypt(SecCipherT *c, uint8_t *data, int len) { secRngSeed(entropy, got);
uint32_t block[2];
uint8_t *keystream;
int pos;
int take;
if (!c || !data || len <= 0) {
return;
} }
keystream = (uint8_t *)block; uint32_t block[2];
pos = 0; int pos = 0;
while (pos < len) { while (pos < len) {
// Encrypt counter to generate keystream block[0] = sRng.counter[0];
block[0] = c->counter[0]; block[1] = sRng.counter[1];
block[1] = c->counter[1]; xteaEncryptBlock(block, sRng.key);
xteaEncryptBlock(block, c->key);
// XOR keystream with data int take = len - pos;
take = len - pos;
if (take > 8) { if (take > 8) {
take = 8; take = 8;
} }
for (int i = 0; i < take; i++) { memcpy(buf + pos, block, take);
data[pos + i] ^= keystream[i];
}
pos += take; pos += take;
// Increment counter // Increment counter
if (++c->counter[0] == 0) { if (++sRng.counter[0] == 0) {
c->counter[1]++; sRng.counter[1]++;
} }
} }
} }
void secCipherDestroy(SecCipherT *c) { // Gather hardware entropy from the PIT (Programmable Interval Timer) and
if (c) { // BIOS tick count. The PIT runs at 1.193182 MHz, so its LSBs change rapidly
secureZero(c, sizeof(SecCipherT)); // and provide ~10 bits of entropy per read (depending on timing jitter).
free(c); // The BIOS tick at 18.2 Hz adds a few more bits. Two PIT readings with
// the intervening code execution provide some jitter. Total: roughly 20
// bits of real entropy -- not enough alone, but sufficient to seed the DRBG
// when supplemented by user interaction timing.
int secRngGatherEntropy(uint8_t *buf, int len) {
int out = 0;
outportb(0x43, 0x00);
uint8_t pitLo = inportb(0x40);
uint8_t pitHi = inportb(0x40);
// BIOS tick count (18.2 Hz)
uint32_t ticks = _farpeekl(_dos_ds, 0x46C);
if (out < len) { buf[out++] = pitLo; }
if (out < len) { buf[out++] = pitHi; }
if (out < len) { buf[out++] = (uint8_t)(ticks); }
if (out < len) { buf[out++] = (uint8_t)(ticks >> 8); }
if (out < len) { buf[out++] = (uint8_t)(ticks >> 16); }
if (out < len) { buf[out++] = (uint8_t)(ticks >> 24); }
// Second PIT reading for jitter
outportb(0x43, 0x00);
pitLo = inportb(0x40);
pitHi = inportb(0x40);
if (out < len) { buf[out++] = pitLo; }
if (out < len) { buf[out++] = pitHi; }
return out;
}
// Initialize the RNG from entropy. The 64-byte discard at the end is
// standard DRBG practice -- it advances the state past any weak initial
// output that might leak information about the seed material.
void secRngSeed(const uint8_t *entropy, int len) {
memset(&sRng, 0, sizeof(sRng));
// XOR-fold entropy into the key
for (int i = 0; i < len; i++) {
((uint8_t *)sRng.key)[i % 16] ^= entropy[i];
}
// Derive counter from key bits
sRng.counter[0] = sRng.key[2] ^ sRng.key[0];
sRng.counter[1] = sRng.key[3] ^ sRng.key[1];
sRng.seeded = true;
// Mix state by generating and discarding 64 bytes
uint8_t discard[64];
sRng.seeded = true; // prevent recursion in secRngBytes
secRngBytes(discard, sizeof(discard));
secureZero(discard, sizeof(discard));
}
// Volatile pointer prevents the compiler from optimizing away the zeroing
// as a dead store. Critical for clearing key material -- without volatile,
// the compiler sees that ptr is about to be freed and removes the memset.
static void secureZero(void *ptr, int len) {
volatile uint8_t *p = (volatile uint8_t *)ptr;
for (int i = 0; i < len; i++) {
p[i] = 0;
} }
} }
void secCipherSetNonce(SecCipherT *c, uint32_t nonceLo, uint32_t nonceHi) { // XTEA block cipher: encrypts an 8-byte block in-place. The Feistel network
if (!c) { // uses 32 rounds (vs TEA's 32 or 64). Each round mixes the halves using
return; // shifts, adds, and XORs -- no S-boxes, no lookup tables, no key schedule.
// The delta constant (golden ratio * 2^32) ensures each round uses a
// different effective key, preventing slide attacks.
static void xteaEncryptBlock(uint32_t v[2], const uint32_t key[4]) {
uint32_t v0 = v[0];
uint32_t v1 = v[1];
uint32_t sum = 0;
for (int i = 0; i < XTEA_ROUNDS; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += XTEA_DELTA;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
} }
c->nonce[0] = nonceLo; v[0] = v0;
c->nonce[1] = nonceHi; v[1] = v1;
c->counter[0] = nonceLo;
c->counter[1] = nonceHi;
} }

View file

@ -275,7 +275,6 @@ Header: packet/packet.h
PKT_SUCCESS 0 Operation succeeded. PKT_SUCCESS 0 Operation succeeded.
PKT_ERR_INVALID_PORT -1 Invalid COM port index. PKT_ERR_INVALID_PORT -1 Invalid COM port index.
PKT_ERR_NOT_OPEN -2 Connection is not open. PKT_ERR_NOT_OPEN -2 Connection is not open.
PKT_ERR_ALREADY_OPEN -3 Connection is already open.
PKT_ERR_WOULD_BLOCK -4 Operation would block. PKT_ERR_WOULD_BLOCK -4 Operation would block.
PKT_ERR_OVERFLOW -5 Buffer overflow. PKT_ERR_OVERFLOW -5 Buffer overflow.
PKT_ERR_INVALID_PARAM -6 Invalid parameter. PKT_ERR_INVALID_PARAM -6 Invalid parameter.

View file

@ -1,8 +1,8 @@
// dvxSql.c -- DVX SQL database implementation // dvxSql.c -- DVX SQL database implementation
// //
// Wraps SQLite3 with integer handle management. Databases and // Wraps SQLite3 with integer handle management. Databases and
// cursors are stored in static tables indexed by handle. Handle 0 // cursors are stored in dynamically-grown tables indexed by handle.
// is reserved (invalid). // Handle 0 is reserved (invalid); handles are 1-based indices.
#include "dvxSql.h" #include "dvxSql.h"
#include "thirdparty/sqlite/examples/sqlite3.h" #include "thirdparty/sqlite/examples/sqlite3.h"
@ -16,9 +16,6 @@
// Handle tables // Handle tables
// ============================================================ // ============================================================
#define MAX_DBS 16
#define MAX_CURSORS 64
typedef struct { typedef struct {
sqlite3 *db; sqlite3 *db;
char lastError[256]; char lastError[256];
@ -32,147 +29,35 @@ typedef struct {
bool started; // true after first dvxSqlNext call bool started; // true after first dvxSqlNext call
} CursorEntryT; } CursorEntryT;
static DbEntryT sDbs[MAX_DBS]; // Dynamically grown handle tables. sDbsCount/sCursorsCount track allocated slots.
static CursorEntryT sCursors[MAX_CURSORS]; static DbEntryT *sDbs = NULL;
static int32_t sDbsCount = 0;
static CursorEntryT *sCursors = NULL;
static int32_t sCursorsCount = 0;
// ============================================================ // ============================================================
// Internal helpers // Prototypes
// ============================================================ // ============================================================
static DbEntryT *getDb(int32_t handle) { int32_t dvxSqlAffectedRows(int32_t db);
if (handle <= 0 || handle > MAX_DBS) { void dvxSqlClose(int32_t db);
return NULL; bool dvxSqlEof(int32_t rs);
} const char *dvxSqlError(int32_t db);
int32_t dvxSqlEscape(const char *src, char *dst, int32_t dstSize);
bool dvxSqlExec(int32_t db, const char *sql);
const char *dvxSqlFieldByName(int32_t rs, const char *name);
int32_t dvxSqlFieldCount(int32_t rs);
double dvxSqlFieldDbl(int32_t rs, int32_t col);
int32_t dvxSqlFieldInt(int32_t rs, int32_t col);
const char *dvxSqlFieldName(int32_t rs, int32_t col);
const char *dvxSqlFieldText(int32_t rs, int32_t col);
void dvxSqlFreeResult(int32_t rs);
bool dvxSqlNext(int32_t rs);
int32_t dvxSqlOpen(const char *path);
int32_t dvxSqlQuery(int32_t db, const char *sql);
static CursorEntryT *getCursor(int32_t handle);
static DbEntryT *getDb(int32_t handle);
if (!sDbs[handle - 1].db) {
return NULL;
}
return &sDbs[handle - 1];
}
static CursorEntryT *getCursor(int32_t handle) {
if (handle <= 0 || handle > MAX_CURSORS) {
return NULL;
}
if (!sCursors[handle - 1].stmt) {
return NULL;
}
return &sCursors[handle - 1];
}
// ============================================================
// dvxSqlOpen
// ============================================================
int32_t dvxSqlOpen(const char *path) {
if (!path) {
return 0;
}
// Find a free slot
for (int32_t i = 0; i < MAX_DBS; i++) {
if (!sDbs[i].db) {
int rc = sqlite3_open(path, &sDbs[i].db);
if (rc != SQLITE_OK) {
if (sDbs[i].db) {
snprintf(sDbs[i].lastError, sizeof(sDbs[i].lastError), "%s", sqlite3_errmsg(sDbs[i].db));
sqlite3_close(sDbs[i].db);
sDbs[i].db = NULL;
}
return 0;
}
sDbs[i].lastError[0] = '\0';
sDbs[i].affectedRows = 0;
return i + 1; // 1-based handle
}
}
return 0; // no free slots
}
// ============================================================
// dvxSqlClose
// ============================================================
void dvxSqlClose(int32_t db) {
DbEntryT *entry = getDb(db);
if (!entry) {
return;
}
// Close any open cursors belonging to this database
for (int32_t i = 0; i < MAX_CURSORS; i++) {
if (sCursors[i].stmt && sCursors[i].dbHandle == db) {
sqlite3_finalize(sCursors[i].stmt);
memset(&sCursors[i], 0, sizeof(CursorEntryT));
}
}
sqlite3_close(entry->db);
memset(entry, 0, sizeof(DbEntryT));
}
// ============================================================
// dvxSqlExec
// ============================================================
bool dvxSqlExec(int32_t db, const char *sql) {
DbEntryT *entry = getDb(db);
if (!entry || !sql) {
return false;
}
char *errMsg = NULL;
int rc = sqlite3_exec(entry->db, sql, NULL, NULL, &errMsg);
if (rc != SQLITE_OK) {
if (errMsg) {
snprintf(entry->lastError, sizeof(entry->lastError), "%s", errMsg);
sqlite3_free(errMsg);
} else {
snprintf(entry->lastError, sizeof(entry->lastError), "%s", sqlite3_errmsg(entry->db));
}
return false;
}
entry->lastError[0] = '\0';
entry->affectedRows = sqlite3_changes(entry->db);
return true;
}
// ============================================================
// dvxSqlError
// ============================================================
const char *dvxSqlError(int32_t db) {
DbEntryT *entry = getDb(db);
if (!entry) {
return "Invalid database handle";
}
return entry->lastError;
}
// ============================================================
// dvxSqlAffectedRows
// ============================================================
int32_t dvxSqlAffectedRows(int32_t db) { int32_t dvxSqlAffectedRows(int32_t db) {
DbEntryT *entry = getDb(db); DbEntryT *entry = getDb(db);
@ -185,69 +70,26 @@ int32_t dvxSqlAffectedRows(int32_t db) {
} }
// ============================================================ void dvxSqlClose(int32_t db) {
// dvxSqlQuery
// ============================================================
int32_t dvxSqlQuery(int32_t db, const char *sql) {
DbEntryT *entry = getDb(db); DbEntryT *entry = getDb(db);
if (!entry || !sql) { if (!entry) {
return 0; return;
} }
// Find a free cursor slot // Close any open cursors belonging to this database
for (int32_t i = 0; i < MAX_CURSORS; i++) { for (int32_t i = 0; i < sCursorsCount; i++) {
if (!sCursors[i].stmt) { if (sCursors[i].stmt && sCursors[i].dbHandle == db) {
sqlite3_stmt *stmt = NULL; sqlite3_finalize(sCursors[i].stmt);
int rc = sqlite3_prepare_v2(entry->db, sql, -1, &stmt, NULL); memset(&sCursors[i], 0, sizeof(CursorEntryT));
if (rc != SQLITE_OK) {
snprintf(entry->lastError, sizeof(entry->lastError), "%s", sqlite3_errmsg(entry->db));
return 0;
}
entry->lastError[0] = '\0';
sCursors[i].stmt = stmt;
sCursors[i].dbHandle = db;
sCursors[i].eof = false;
sCursors[i].started = false;
return i + 1; // 1-based handle
} }
} }
snprintf(entry->lastError, sizeof(entry->lastError), "Too many open cursors"); sqlite3_close(entry->db);
return 0; memset(entry, 0, sizeof(DbEntryT));
} }
// ============================================================
// dvxSqlNext
// ============================================================
bool dvxSqlNext(int32_t rs) {
CursorEntryT *cur = getCursor(rs);
if (!cur || cur->eof) {
return false;
}
int rc = sqlite3_step(cur->stmt);
cur->started = true;
if (rc == SQLITE_ROW) {
return true;
}
cur->eof = true;
return false;
}
// ============================================================
// dvxSqlEof
// ============================================================
bool dvxSqlEof(int32_t rs) { bool dvxSqlEof(int32_t rs) {
CursorEntryT *cur = getCursor(rs); CursorEntryT *cur = getCursor(rs);
@ -259,129 +101,17 @@ bool dvxSqlEof(int32_t rs) {
} }
// ============================================================ const char *dvxSqlError(int32_t db) {
// dvxSqlFieldCount DbEntryT *entry = getDb(db);
// ============================================================
int32_t dvxSqlFieldCount(int32_t rs) { if (!entry) {
CursorEntryT *cur = getCursor(rs); return "Invalid database handle";
if (!cur) {
return 0;
} }
return sqlite3_column_count(cur->stmt); return entry->lastError;
} }
// ============================================================
// dvxSqlFieldName
// ============================================================
const char *dvxSqlFieldName(int32_t rs, int32_t col) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return "";
}
const char *name = sqlite3_column_name(cur->stmt, col);
return name ? name : "";
}
// ============================================================
// dvxSqlFieldText
// ============================================================
const char *dvxSqlFieldText(int32_t rs, int32_t col) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return "";
}
const char *text = (const char *)sqlite3_column_text(cur->stmt, col);
return text ? text : "";
}
// ============================================================
// dvxSqlFieldByName
// ============================================================
const char *dvxSqlFieldByName(int32_t rs, const char *name) {
CursorEntryT *cur = getCursor(rs);
if (!cur || !name) {
return "";
}
int32_t colCount = sqlite3_column_count(cur->stmt);
for (int32_t i = 0; i < colCount; i++) {
const char *colName = sqlite3_column_name(cur->stmt, i);
if (colName && strcasecmp(colName, name) == 0) {
const char *text = (const char *)sqlite3_column_text(cur->stmt, i);
return text ? text : "";
}
}
return "";
}
// ============================================================
// dvxSqlFieldInt
// ============================================================
int32_t dvxSqlFieldInt(int32_t rs, int32_t col) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return 0;
}
return sqlite3_column_int(cur->stmt, col);
}
// ============================================================
// dvxSqlFieldDbl
// ============================================================
double dvxSqlFieldDbl(int32_t rs, int32_t col) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return 0.0;
}
return sqlite3_column_double(cur->stmt, col);
}
// ============================================================
// dvxSqlFreeResult
// ============================================================
void dvxSqlFreeResult(int32_t rs) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return;
}
sqlite3_finalize(cur->stmt);
memset(cur, 0, sizeof(CursorEntryT));
}
// ============================================================
// dvxSqlEscape
// ============================================================
int32_t dvxSqlEscape(const char *src, char *dst, int32_t dstSize) { int32_t dvxSqlEscape(const char *src, char *dst, int32_t dstSize) {
if (!src || !dst || dstSize <= 0) { if (!src || !dst || dstSize <= 0) {
return -1; return -1;
@ -409,3 +139,264 @@ int32_t dvxSqlEscape(const char *src, char *dst, int32_t dstSize) {
dst[di] = '\0'; dst[di] = '\0';
return di; return di;
} }
bool dvxSqlExec(int32_t db, const char *sql) {
DbEntryT *entry = getDb(db);
if (!entry || !sql) {
return false;
}
char *errMsg = NULL;
int rc = sqlite3_exec(entry->db, sql, NULL, NULL, &errMsg);
if (rc != SQLITE_OK) {
if (errMsg) {
snprintf(entry->lastError, sizeof(entry->lastError), "%s", errMsg);
sqlite3_free(errMsg);
} else {
snprintf(entry->lastError, sizeof(entry->lastError), "%s", sqlite3_errmsg(entry->db));
}
return false;
}
entry->lastError[0] = '\0';
entry->affectedRows = sqlite3_changes(entry->db);
return true;
}
const char *dvxSqlFieldByName(int32_t rs, const char *name) {
CursorEntryT *cur = getCursor(rs);
if (!cur || !name) {
return "";
}
int32_t colCount = sqlite3_column_count(cur->stmt);
for (int32_t i = 0; i < colCount; i++) {
const char *colName = sqlite3_column_name(cur->stmt, i);
if (colName && strcasecmp(colName, name) == 0) {
const char *text = (const char *)sqlite3_column_text(cur->stmt, i);
return text ? text : "";
}
}
return "";
}
int32_t dvxSqlFieldCount(int32_t rs) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return 0;
}
return sqlite3_column_count(cur->stmt);
}
double dvxSqlFieldDbl(int32_t rs, int32_t col) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return 0.0;
}
return sqlite3_column_double(cur->stmt, col);
}
int32_t dvxSqlFieldInt(int32_t rs, int32_t col) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return 0;
}
return sqlite3_column_int(cur->stmt, col);
}
const char *dvxSqlFieldName(int32_t rs, int32_t col) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return "";
}
const char *name = sqlite3_column_name(cur->stmt, col);
return name ? name : "";
}
const char *dvxSqlFieldText(int32_t rs, int32_t col) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return "";
}
const char *text = (const char *)sqlite3_column_text(cur->stmt, col);
return text ? text : "";
}
void dvxSqlFreeResult(int32_t rs) {
CursorEntryT *cur = getCursor(rs);
if (!cur) {
return;
}
sqlite3_finalize(cur->stmt);
memset(cur, 0, sizeof(CursorEntryT));
}
bool dvxSqlNext(int32_t rs) {
CursorEntryT *cur = getCursor(rs);
if (!cur || cur->eof) {
return false;
}
int rc = sqlite3_step(cur->stmt);
cur->started = true;
if (rc == SQLITE_ROW) {
return true;
}
cur->eof = true;
return false;
}
int32_t dvxSqlOpen(const char *path) {
if (!path) {
return 0;
}
// Find a free slot (reuse after close)
int32_t slot = -1;
for (int32_t i = 0; i < sDbsCount; i++) {
if (!sDbs[i].db) {
slot = i;
break;
}
}
// No free slot -- grow the table
if (slot < 0) {
int32_t newCount = sDbsCount == 0 ? 8 : sDbsCount * 2;
DbEntryT *grown = (DbEntryT *)realloc(sDbs, sizeof(DbEntryT) * newCount);
if (!grown) {
return 0;
}
memset(grown + sDbsCount, 0, sizeof(DbEntryT) * (newCount - sDbsCount));
sDbs = grown;
slot = sDbsCount;
sDbsCount = newCount;
}
int rc = sqlite3_open(path, &sDbs[slot].db);
if (rc != SQLITE_OK) {
if (sDbs[slot].db) {
snprintf(sDbs[slot].lastError, sizeof(sDbs[slot].lastError), "%s", sqlite3_errmsg(sDbs[slot].db));
sqlite3_close(sDbs[slot].db);
sDbs[slot].db = NULL;
}
return 0;
}
sDbs[slot].lastError[0] = '\0';
sDbs[slot].affectedRows = 0;
return slot + 1; // 1-based handle
}
int32_t dvxSqlQuery(int32_t db, const char *sql) {
DbEntryT *entry = getDb(db);
if (!entry || !sql) {
return 0;
}
// Find a free cursor slot (reuse after free)
int32_t slot = -1;
for (int32_t i = 0; i < sCursorsCount; i++) {
if (!sCursors[i].stmt) {
slot = i;
break;
}
}
// No free slot -- grow the table
if (slot < 0) {
int32_t newCount = sCursorsCount == 0 ? 16 : sCursorsCount * 2;
CursorEntryT *grown = (CursorEntryT *)realloc(sCursors, sizeof(CursorEntryT) * newCount);
if (!grown) {
snprintf(entry->lastError, sizeof(entry->lastError), "Out of memory growing cursor table");
return 0;
}
memset(grown + sCursorsCount, 0, sizeof(CursorEntryT) * (newCount - sCursorsCount));
sCursors = grown;
slot = sCursorsCount;
sCursorsCount = newCount;
}
sqlite3_stmt *stmt = NULL;
int rc = sqlite3_prepare_v2(entry->db, sql, -1, &stmt, NULL);
if (rc != SQLITE_OK) {
snprintf(entry->lastError, sizeof(entry->lastError), "%s", sqlite3_errmsg(entry->db));
return 0;
}
entry->lastError[0] = '\0';
sCursors[slot].stmt = stmt;
sCursors[slot].dbHandle = db;
sCursors[slot].eof = false;
sCursors[slot].started = false;
return slot + 1; // 1-based handle
}
static CursorEntryT *getCursor(int32_t handle) {
if (handle <= 0 || handle > sCursorsCount) {
return NULL;
}
if (!sCursors[handle - 1].stmt) {
return NULL;
}
return &sCursors[handle - 1];
}
static DbEntryT *getDb(int32_t handle) {
if (handle <= 0 || handle > sDbsCount) {
return NULL;
}
if (!sDbs[handle - 1].db) {
return NULL;
}
return &sDbs[handle - 1];
}

View file

@ -14,15 +14,6 @@ High-level wrapper around SQLite3 for DVX applications. Manages database connect
Header: sql/dvxSql.h Header: sql/dvxSql.h
.h3 Limits
.table
Constant Value Description
-------- ----- -----------
MAX_DBS 16 Maximum number of simultaneously open databases.
MAX_CURSORS 64 Maximum number of simultaneously open result set cursors.
.endtable
.h3 Handle Model .h3 Handle Model
Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it. Database and cursor handles are int32_t values. A successful open or query returns a handle greater than zero. Handle 0 is reserved as the invalid/error sentinel. Closing a database automatically finalizes all cursors that belong to it.

View file

@ -25,7 +25,6 @@
// ============================================================ // ============================================================
#define TM_COL_COUNT 6 #define TM_COL_COUNT 6
#define TM_MAX_PATH 260
#define TM_WIN_W 580 #define TM_WIN_W 580
#define TM_WIN_H 280 #define TM_WIN_H 280
#define TM_LIST_PREF_H 160 #define TM_LIST_PREF_H 160
@ -42,6 +41,9 @@ static void onTmEndTask(WidgetT *w);
static void onTmRun(WidgetT *w); static void onTmRun(WidgetT *w);
static void onTmSwitchTo(WidgetT *w); static void onTmSwitchTo(WidgetT *w);
static void refreshTaskList(void); static void refreshTaskList(void);
void shellTaskMgrOpen(AppContextT *ctx);
void shellTaskMgrRefresh(void);
static void taskmgrInit(void) __attribute__((constructor));
static void updateStatusText(void); static void updateStatusText(void);
// ============================================================ // ============================================================
@ -64,10 +66,6 @@ static const char **sCells = NULL; // dynamic array of cell pointers
static TmRowStringsT *sRowStrs = NULL; // dynamic array of per-row strings static TmRowStringsT *sRowStrs = NULL; // dynamic array of per-row strings
// ============================================================
// getRunningAppByIndex
// ============================================================
static ShellAppT *getRunningAppByIndex(int32_t sel) { static ShellAppT *getRunningAppByIndex(int32_t sel) {
int32_t idx = 0; int32_t idx = 0;
@ -86,10 +84,6 @@ static ShellAppT *getRunningAppByIndex(int32_t sel) {
} }
// ============================================================
// onTmClose
// ============================================================
static void onTmClose(WindowT *win) { static void onTmClose(WindowT *win) {
shellUnregisterDesktopUpdate(shellTaskMgrRefresh); shellUnregisterDesktopUpdate(shellTaskMgrRefresh);
arrfree(sCells); arrfree(sCells);
@ -103,10 +97,6 @@ static void onTmClose(WindowT *win) {
} }
// ============================================================
// onTmEndTask
// ============================================================
static void onTmEndTask(WidgetT *w) { static void onTmEndTask(WidgetT *w) {
(void)w; (void)w;
@ -129,10 +119,6 @@ static void onTmEndTask(WidgetT *w) {
} }
// ============================================================
// onTmRun
// ============================================================
static void onTmRun(WidgetT *w) { static void onTmRun(WidgetT *w) {
(void)w; (void)w;
@ -140,7 +126,7 @@ static void onTmRun(WidgetT *w) {
{ "Applications (*.app)" }, { "Applications (*.app)" },
{ "All Files (*.*)" } { "All Files (*.*)" }
}; };
char path[TM_MAX_PATH]; char path[DVX_MAX_PATH];
if (dvxFileDialog(sCtx, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) { if (dvxFileDialog(sCtx, "Run Application", FD_OPEN, "apps", filters, 2, path, sizeof(path))) {
shellLoadApp(sCtx, path); shellLoadApp(sCtx, path);
@ -148,10 +134,6 @@ static void onTmRun(WidgetT *w) {
} }
// ============================================================
// onTmSwitchTo
// ============================================================
static void onTmSwitchTo(WidgetT *w) { static void onTmSwitchTo(WidgetT *w) {
(void)w; (void)w;
@ -189,10 +171,6 @@ static void onTmSwitchTo(WidgetT *w) {
} }
// ============================================================
// refreshTaskList
// ============================================================
static void refreshTaskList(void) { static void refreshTaskList(void) {
if (!sTmListView) { if (!sTmListView) {
return; return;
@ -259,37 +237,6 @@ static void refreshTaskList(void) {
} }
// ============================================================
// updateStatusText
// ============================================================
static void updateStatusText(void) {
if (!sTmStatusLbl) {
return;
}
static char buf[128];
int32_t count = shellRunningAppCount();
uint32_t totalKb;
uint32_t freeKb;
bool hasMem = platformGetMemoryInfo(&totalKb, &freeKb);
int32_t pos = snprintf(buf, sizeof(buf), "Applications: %ld", (long)count);
if (hasMem && totalKb > 0) {
uint32_t usedKb = totalKb - freeKb;
snprintf(buf + pos, sizeof(buf) - pos, " Memory: %lu/%lu MB", (unsigned long)(usedKb / 1024), (unsigned long)(totalKb / 1024));
}
wgtSetText(sTmStatusLbl, buf);
}
// ============================================================
// shellTaskMgrOpen
// ============================================================
void shellTaskMgrOpen(AppContextT *ctx) { void shellTaskMgrOpen(AppContextT *ctx) {
sCtx = ctx; sCtx = ctx;
@ -373,10 +320,6 @@ void shellTaskMgrOpen(AppContextT *ctx) {
} }
// ============================================================
// shellTaskMgrRefresh
// ============================================================
void shellTaskMgrRefresh(void) { void shellTaskMgrRefresh(void) {
if (sTmWindow) { if (sTmWindow) {
refreshTaskList(); refreshTaskList();
@ -385,11 +328,30 @@ void shellTaskMgrRefresh(void) {
} }
// ============================================================
// DXE constructor -- register Ctrl+Esc handler with the shell // DXE constructor -- register Ctrl+Esc handler with the shell
// ============================================================
static void taskmgrInit(void) __attribute__((constructor));
static void taskmgrInit(void) { static void taskmgrInit(void) {
shellCtrlEscFn = shellTaskMgrOpen; shellCtrlEscFn = shellTaskMgrOpen;
} }
static void updateStatusText(void) {
if (!sTmStatusLbl) {
return;
}
static char buf[128];
int32_t count = shellRunningAppCount();
uint32_t totalKb;
uint32_t freeKb;
bool hasMem = platformGetMemoryInfo(&totalKb, &freeKb);
int32_t pos = snprintf(buf, sizeof(buf), "Applications: %ld", (long)count);
if (hasMem && totalKb > 0) {
uint32_t usedKb = totalKb - freeKb;
snprintf(buf + pos, sizeof(buf) - pos, " Memory: %lu/%lu MB", (unsigned long)(usedKb / 1024), (unsigned long)(totalKb / 1024));
}
wgtSetText(sTmStatusLbl, buf);
}

View file

@ -23,52 +23,32 @@ static clock_t sCursorBlinkTime = 0;
static WidgetT *sLastSelectedWidget = NULL; static WidgetT *sLastSelectedWidget = NULL;
// ============================================================ // ============================================================
// Static helper prototypes // Prototypes
// ============================================================ // ============================================================
static bool clearSelectionOnWidget(WidgetT *w); void clearOtherSelections(WidgetT *except);
static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd); static bool clearSelectionOnWidget(WidgetT *w);
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize); bool isWordChar(char c);
static int32_t wordBoundaryLeft(const char *buf, int32_t pos); static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd);
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos); void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);
static void textHelpInit(void) __attribute__((constructor));
void wgtUpdateCursorBlink(void);
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect);
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t fieldWidth);
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
int32_t wordBoundaryLeft(const char *buf, int32_t pos);
int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
int32_t wordStart(const char *buf, int32_t pos);
// ============================================================
// wgtUpdateCursorBlink
// ============================================================
static void textHelpInit(void) __attribute__((constructor));
static void textHelpInit(void) {
sCursorBlinkFn = wgtUpdateCursorBlink;
}
void wgtUpdateCursorBlink(void) {
clock_t now = clock();
clock_t interval = (clock_t)CURSOR_BLINK_MS * CLOCKS_PER_SEC / 1000;
if ((now - sCursorBlinkTime) >= interval) {
sCursorBlinkTime = now;
sCursorBlinkOn = !sCursorBlinkOn;
if (sFocusedWidget) {
wgtInvalidatePaint(sFocusedWidget);
}
}
}
// ============================================================
// clearOtherSelections
// ============================================================
//
// Clears selection on the previously-selected widget (if different // Clears selection on the previously-selected widget (if different
// from the newly-focused one). Validates that the previous widget's // from the newly-focused one). Validates that the previous widget's
// window is still in the window stack before touching it -- the // window is still in the window stack before touching it -- the
// window may have been closed since sLastSelectedWidget was set. // window may have been closed since sLastSelectedWidget was set.
// If the previous widget was in a different window, that window // If the previous widget was in a different window, that window
// gets a full repaint to clear the stale selection highlight. // gets a full repaint to clear the stale selection highlight.
void clearOtherSelections(WidgetT *except) { void clearOtherSelections(WidgetT *except) {
if (!except || !except->window || !except->window->widgetRoot) { if (!except || !except->window || !except->window->widgetRoot) {
return; return;
@ -118,17 +98,11 @@ static bool clearSelectionOnWidget(WidgetT *w) {
} }
bool isWordChar(char c) { bool isWordChar(char c) {
return isalnum((unsigned char)c) || c == '_'; return isalnum((unsigned char)c) || c == '_';
} }
// ============================================================
// Shared undo/selection helpers
// ============================================================
static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd) { static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd) {
int32_t lo = *pSelStart < *pSelEnd ? *pSelStart : *pSelEnd; int32_t lo = *pSelStart < *pSelEnd ? *pSelStart : *pSelEnd;
int32_t hi = *pSelStart < *pSelEnd ? *pSelEnd : *pSelStart; int32_t hi = *pSelStart < *pSelEnd ? *pSelEnd : *pSelStart;
@ -155,7 +129,7 @@ static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor,
} }
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize) { void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize) {
if (!undoBuf) { if (!undoBuf) {
return; return;
} }
@ -168,14 +142,29 @@ static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoB
} }
// ============================================================ static void textHelpInit(void) {
// widgetTextEditDragUpdateLine sCursorBlinkFn = wgtUpdateCursorBlink;
// ============================================================ }
//
void wgtUpdateCursorBlink(void) {
clock_t now = clock();
clock_t interval = (clock_t)CURSOR_BLINK_MS * CLOCKS_PER_SEC / 1000;
if ((now - sCursorBlinkTime) >= interval) {
sCursorBlinkTime = now;
sCursorBlinkOn = !sCursorBlinkOn;
if (sFocusedWidget) {
wgtInvalidatePaint(sFocusedWidget);
}
}
}
// Called during mouse drag to extend the selection for single-line // Called during mouse drag to extend the selection for single-line
// text widgets. Auto-scrolls when the mouse moves past the visible // text widgets. Auto-scrolls when the mouse moves past the visible
// text edges. // text edges.
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd) { void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd) {
int32_t rightEdge = leftEdge + maxChars * font->charWidth; int32_t rightEdge = leftEdge + maxChars * font->charWidth;
@ -201,14 +190,9 @@ void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars
} }
// ============================================================
// widgetTextEditMouseClick
// ============================================================
//
// Computes cursor position from pixel coordinates, handles multi-click // Computes cursor position from pixel coordinates, handles multi-click
// (double = word select, triple = select all), and optionally starts // (double = word select, triple = select all), and optionally starts
// drag-select. // drag-select.
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect) { void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect) {
int32_t relX = vx - textLeftX; int32_t relX = vx - textLeftX;
int32_t charPos = relX / font->charWidth + scrollOff; int32_t charPos = relX / font->charWidth + scrollOff;
@ -256,13 +240,8 @@ void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLe
} }
// ============================================================
// widgetTextEditOnKey -- shared single-line text editing logic
// ============================================================
//
// This is the core single-line text editing engine, parameterized by // This is the core single-line text editing engine, parameterized by
// pointer to allow reuse across TextInput, Spinner, and ComboBox. // pointer to allow reuse across TextInput, Spinner, and ComboBox.
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t fieldWidth) { void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t fieldWidth) {
bool shift = (mod & KEY_MOD_SHIFT) != 0; bool shift = (mod & KEY_MOD_SHIFT) != 0;
bool hasSel = (pSelStart && pSelEnd && *pSelStart >= 0 && *pSelEnd >= 0 && *pSelStart != *pSelEnd); bool hasSel = (pSelStart && pSelEnd && *pSelStart >= 0 && *pSelEnd >= 0 && *pSelStart != *pSelEnd);
@ -651,13 +630,8 @@ adjustScroll:
} }
// ============================================================
// widgetTextEditPaintLine
// ============================================================
//
// Renders a single line of text with optional selection highlighting // Renders a single line of text with optional selection highlighting
// and a blinking cursor. // and a blinking cursor.
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX) { void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX) {
// Normalize selection to low/high // Normalize selection to low/high
int32_t selLo = -1; int32_t selLo = -1;
@ -700,11 +674,7 @@ void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT
} }
// ============================================================ int32_t wordBoundaryLeft(const char *buf, int32_t pos) {
// Word boundary helpers
// ============================================================
static int32_t wordBoundaryLeft(const char *buf, int32_t pos) {
if (pos <= 0) { if (pos <= 0) {
return 0; return 0;
} }
@ -723,7 +693,7 @@ static int32_t wordBoundaryLeft(const char *buf, int32_t pos) {
} }
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos) { int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos) {
if (pos >= len) { if (pos >= len) {
return len; return len;
} }

View file

@ -28,10 +28,18 @@ void clearOtherSelections(WidgetT *except);
// Word / character helpers // Word / character helpers
// ============================================================ // ============================================================
bool isWordChar(char c); bool isWordChar(char c);
int32_t wordBoundaryLeft(const char *buf, int32_t pos);
int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
int32_t wordEnd(const char *buf, int32_t len, int32_t pos); int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
int32_t wordStart(const char *buf, int32_t pos); int32_t wordStart(const char *buf, int32_t pos);
// ============================================================
// Undo buffer snapshot
// ============================================================
void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);
// ============================================================ // ============================================================
// Single-line text editing engine // Single-line text editing engine
// ============================================================ // ============================================================

View file

@ -18,7 +18,7 @@ BINDIR = ../../bin
SRCS = loaderMain.c SRCS = loaderMain.c
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
POBJS = $(POBJDIR)/dvxPlatformDos.o $(POBJDIR)/dvxPlatformChdir.o $(POBJDIR)/dvxPrefs.o $(OBJDIR)/dvxhlpc.o POBJS = $(POBJDIR)/dvxPlatformDos.o $(POBJDIR)/dvxPlatformUtil.o $(POBJDIR)/dvxPrefs.o $(OBJDIR)/dvxhlpc.o
TARGET = $(BINDIR)/dvx.exe TARGET = $(BINDIR)/dvx.exe
.PHONY: all clean .PHONY: all clean
@ -37,7 +37,7 @@ $(OBJDIR)/%.o: %.c | $(OBJDIR)
$(POBJDIR)/dvxPlatformDos.o: ../libs/kpunch/libdvx/platform/dvxPlatformDos.c | $(POBJDIR) $(POBJDIR)/dvxPlatformDos.o: ../libs/kpunch/libdvx/platform/dvxPlatformDos.c | $(POBJDIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
$(POBJDIR)/dvxPlatformChdir.o: ../libs/kpunch/libdvx/platform/dvxPlatformChdir.c | $(POBJDIR) $(POBJDIR)/dvxPlatformUtil.o: ../libs/kpunch/libdvx/platform/dvxPlatformUtil.c | $(POBJDIR)
$(CC) $(CFLAGS) -c -o $@ $< $(CC) $(CFLAGS) -c -o $@ $<
$(POBJDIR)/dvxPrefs.o: ../libs/kpunch/libdvx/dvxPrefs.c | $(POBJDIR) $(POBJDIR)/dvxPrefs.o: ../libs/kpunch/libdvx/dvxPrefs.c | $(POBJDIR)
@ -58,7 +58,7 @@ $(BINDIR):
# Dependencies # Dependencies
$(OBJDIR)/loaderMain.o: loaderMain.c ../libs/kpunch/libdvx/platform/dvxPlat.h ../libs/kpunch/libdvx/dvxTypes.h $(OBJDIR)/loaderMain.o: loaderMain.c ../libs/kpunch/libdvx/platform/dvxPlat.h ../libs/kpunch/libdvx/dvxTypes.h
$(POBJDIR)/dvxPlatformDos.o: ../libs/kpunch/libdvx/platform/dvxPlatformDos.c ../libs/kpunch/libdvx/platform/dvxPlat.h ../libs/kpunch/libdvx/dvxTypes.h ../libs/kpunch/libdvx/dvxPal.h $(POBJDIR)/dvxPlatformDos.o: ../libs/kpunch/libdvx/platform/dvxPlatformDos.c ../libs/kpunch/libdvx/platform/dvxPlat.h ../libs/kpunch/libdvx/dvxTypes.h ../libs/kpunch/libdvx/dvxPal.h
$(POBJDIR)/dvxPlatformChdir.o: ../libs/kpunch/libdvx/platform/dvxPlatformChdir.c ../libs/kpunch/libdvx/platform/dvxPlat.h ../libs/kpunch/libdvx/dvxTypes.h $(POBJDIR)/dvxPlatformUtil.o: ../libs/kpunch/libdvx/platform/dvxPlatformUtil.c ../libs/kpunch/libdvx/platform/dvxPlat.h ../libs/kpunch/libdvx/dvxTypes.h
$(POBJDIR)/dvxPrefs.o: ../libs/kpunch/libdvx/dvxPrefs.c ../libs/kpunch/libdvx/dvxPrefs.h $(POBJDIR)/dvxPrefs.o: ../libs/kpunch/libdvx/dvxPrefs.c ../libs/kpunch/libdvx/dvxPrefs.h
clean: clean:

File diff suppressed because it is too large Load diff

95
src/loader/stddclmr.h Normal file
View file

@ -0,0 +1,95 @@
#ifndef STDDCLMR_H
#define STDDCLMR_H
/*
Action figures sold separately. Add toner. All models over 18 years of age.
All rights reserved. Allow four to six weeks for delivery. An equal
opportunity employer. Any resemblance to actual persons, living or dead, is
unintentional and purely coincidental. Apply only to affected area. Approved
for veterans. As seen on TV. At participating locations only. Avoid contact
with mucous membranes. Avoid contact with skin. Avoid extreme temperatures
and store in a cool dry place. Batteries not included. Be sure each item is
properly endorsed. Beware of dog. Booths for two or more. Breaking seal
constitutes acceptance of agreement. Call toll free number before digging.
Caveat emptor. Check here if tax deductible. Close cover before striking
Colors may fade. Contains a substantial amount of non-tobacco ingredients.
Contents may settle during shipment. Contestants have been briefed on some
questions before the show. Copyright 1995 Joker's Wild. Disclaimer does
not cover hurricane, lightning, tornado, tsunami, volcanic eruption,
earthquake, flood, and other Acts of God, misuse, neglect, unauthorized
repair, damage from improper installation, broken antenna or marred cabinet,
incorrect line voltage, missing or altered serial numbers, sonic boom
vibrations, electromagnetic radiation from nuclear blasts, customer
adjustments that are not covered in the joke list, and incidents owing to
airplane crash, ship sinking, motor vehicle accidents, leaky roof, broken
glass, falling rocks, mud slides, forest fire, flying projectiles, or
dropping the item. Do not bend, fold, mutilate, or spindle. Do not place
near flammable or magnetic source. Do not puncture, incinerate, or store
above 120 degrees Fahrenheit. Do not stamp. Use other side for additional
listings. Do not use while operating a motor vehicle or heavy equipment. Do
not write below this line. Documents are provided "as is" without any
warranties expressed or implied. Don't quote me on anything. Don't quote me
on that. Driver does not carry cash. Drop in any mailbox. Edited for
television. Employees and their families are not eligible. Falling rock.
First pull up, then pull down. Flames redirected to /dev/null. For a
limited time only. For external use only. For off-road use only. For office
use only. For recreational use only. Do not disturb. Freshest if eaten
before date on carton. Hand wash only, tumble dry on low heat. If a rash,
redness, irritation, or swelling develops, discontinue use. If condition
persists, consult your physician. If defects are discovered, do not attempt
to fix them yourself, but return to an authorized service center. If
ingested, do not induce vomiting, if symptoms persist, consult a doctor.
Keep away from open flames and avoid inhaling fumes. Keep away from
sunlight, pets, and small children. Keep cool; process promptly. Limit
one-per-family please. Limited time offer, call now to ensure prompt
delivery. List at least two alternate dates. List each check separately by
bank number. List was current at time of printing. Lost ticket pays maximum
rate. May be too intense for some viewers. Must be 18 to enter. No Canadian
coins. No alcohol, dogs or horses. No anchovies unless otherwise specified.
No animals were harmed in the production of these documents. No money down.
No other warranty expressed or implied. No passes accepted for this
engagement. No postage necessary if mailed in the United States. No
preservatives added. No purchase necessary. No salt, MSG, artificial color
or flavor added. No shoes, no shirt, no service, no kidding. No solicitors.
No substitutions allowed. No transfers issued until the bus comes to a
complete stop. No user-serviceable parts inside. Not affiliated with the
American Red Cross. Not liable for damages due to use or misuse. Not
recommended for children. Not responsible for direct, indirect, incidental
or consequential damages resulting from any defect, error or failure to
perform. Not the Beatles. Objects in mirror may be closer than they appear.
One size fits all. Many suitcases look alike. Other copyright laws for
specific entries apply wherever noted. Other restrictions may apply. Package
sold by weight, not volume. Parental advisory - explicit lyrics. Penalty for
private use. Place stamp here. Please remain seated until the ride has come
to a complete stop. Possible penalties for early withdrawal. Post office will
not deliver without postage. Postage will be paid by addressee. Prerecorded
for this time zone. Price does not include taxes. Processed at location
stamped in code at top of carton. Quantities are limited while supplies last.
Read at your own risk. Record additional transactions on back of previous
stub. Replace with same type. Reproduction strictly prohibited. Restaurant
package, not for resale. Return to sender, no forwarding order on file,
unable to forward. Safety goggles may be required during use. Sanitized for
your protection. Sealed for your protection, do not use if the safety seal is
broken. See label for sequence. Shading within a garment may occur. Sign here
without admitting guilt. Simulated picture. Slightly enlarged to show detail.
Slightly higher west of the Rockies. Slippery when wet. Smoking these may be
hazardous to your health. Some assembly required. Some equipment shown is
optional. Some of the trademarks mentioned in this product appear for
identification purposes only. Subject to FCC approval. Subject to change
without notice. Substantial penalty for early withdrawal. Text may contain
material some readers may find objectionable, parental guidance is advised.
Text used in these documents is made from 100% recycled electrons and magnetic
particles. These documents do not reflect the thoughts or opinions of either
myself, my company, my friends, or my rabbit. This is not an offer to sell
securities. This offer is void where prohibited, taxed, or otherwise
restricted. This product is meant for educational purposes only. Times
approximate. Unix is a registered trademark of AT&T. Use only as directed. Use
only in a well-ventilated are. User assumes full liabilities. Void where
prohibited. We have sent the forms which seem right for you. You must be
present to win. You need not be present to win. Your canceled check is your
receipt. Your mileage may vary. I didn't do it. You can't prove anything.
This supersedes all previous notices.
*/
#endif // STDDCLMR_H

View file

@ -20,23 +20,25 @@ SYSTEMDIR = ../../bin/system
all: $(HOSTDIR)/dvxres $(HOSTDIR)/mkicon $(HOSTDIR)/mktbicon $(HOSTDIR)/mkwgticon $(HOSTDIR)/bmp2raw $(HOSTDIR)/dvxhlpc $(SYSTEMDIR)/SPLASH.RAW $(SYSTEMDIR)/DVXHLPC.EXE $(SYSTEMDIR)/DVXRES.EXE all: $(HOSTDIR)/dvxres $(HOSTDIR)/mkicon $(HOSTDIR)/mktbicon $(HOSTDIR)/mkwgticon $(HOSTDIR)/bmp2raw $(HOSTDIR)/dvxhlpc $(SYSTEMDIR)/SPLASH.RAW $(SYSTEMDIR)/DVXHLPC.EXE $(SYSTEMDIR)/DVXRES.EXE
$(HOSTDIR)/dvxres: dvxres.c ../libs/kpunch/libdvx/dvxResource.c ../libs/kpunch/libdvx/dvxRes.h | $(HOSTDIR) PLATFORM_UTIL = ../libs/kpunch/libdvx/platform/dvxPlatformUtil.c
$(CC) $(CFLAGS) -o $@ dvxres.c ../libs/kpunch/libdvx/dvxResource.c
$(HOSTDIR)/mkicon: mkicon.c | $(HOSTDIR) $(HOSTDIR)/dvxres: dvxres.c ../libs/kpunch/libdvx/dvxResource.c ../libs/kpunch/libdvx/dvxRes.h $(PLATFORM_UTIL) | $(HOSTDIR)
$(CC) $(CFLAGS) -o $@ mkicon.c -lm $(CC) $(CFLAGS) -o $@ dvxres.c ../libs/kpunch/libdvx/dvxResource.c $(PLATFORM_UTIL)
$(HOSTDIR)/mktbicon: mktbicon.c | $(HOSTDIR) $(HOSTDIR)/mkicon: mkicon.c bmpDraw.c bmpDraw.h | $(HOSTDIR)
$(CC) $(CFLAGS) -o $@ mktbicon.c $(CC) $(CFLAGS) -o $@ mkicon.c bmpDraw.c -lm
$(HOSTDIR)/mkwgticon: mkwgticon.c | $(HOSTDIR) $(HOSTDIR)/mktbicon: mktbicon.c bmpDraw.c bmpDraw.h | $(HOSTDIR)
$(CC) $(CFLAGS) -o $@ mkwgticon.c $(CC) $(CFLAGS) -o $@ mktbicon.c bmpDraw.c
$(HOSTDIR)/mkwgticon: mkwgticon.c bmpDraw.c bmpDraw.h | $(HOSTDIR)
$(CC) $(CFLAGS) -o $@ mkwgticon.c bmpDraw.c
$(HOSTDIR)/bmp2raw: bmp2raw.c | $(HOSTDIR) $(HOSTDIR)/bmp2raw: bmp2raw.c | $(HOSTDIR)
$(CC) $(CFLAGS) -o $@ bmp2raw.c $(CC) $(CFLAGS) -o $@ bmp2raw.c
$(HOSTDIR)/dvxhlpc: dvxhlpc.c ../apps/kpunch/dvxhelp/hlpformat.h | $(HOSTDIR) $(HOSTDIR)/dvxhlpc: dvxhlpc.c ../apps/kpunch/dvxhelp/hlpformat.h $(PLATFORM_UTIL) | $(HOSTDIR)
$(CC) $(CFLAGS) -o $@ dvxhlpc.c $(CC) $(CFLAGS) -o $@ dvxhlpc.c $(PLATFORM_UTIL)
$(HOSTDIR): $(HOSTDIR):
mkdir -p $(HOSTDIR) mkdir -p $(HOSTDIR)
@ -50,8 +52,8 @@ $(BINDIR):
$(CONFIGDIR): $(CONFIGDIR):
mkdir -p $(CONFIGDIR) mkdir -p $(CONFIGDIR)
$(SYSTEMDIR)/DVXHLPC.EXE: dvxhlpc.c hlpcCompile.h ../apps/kpunch/dvxhelp/hlpformat.h | $(SYSTEMDIR) $(SYSTEMDIR)/DVXHLPC.EXE: dvxhlpc.c hlpcCompile.h ../apps/kpunch/dvxhelp/hlpformat.h $(PLATFORM_UTIL) | $(SYSTEMDIR)
$(DOSCC) $(DOSCFLAGS) -o $(SYSTEMDIR)/dvxhlpc.exe dvxhlpc.c $(DOSCC) $(DOSCFLAGS) -o $(SYSTEMDIR)/dvxhlpc.exe dvxhlpc.c $(PLATFORM_UTIL)
$(EXE2COFF) $(SYSTEMDIR)/dvxhlpc.exe $(EXE2COFF) $(SYSTEMDIR)/dvxhlpc.exe
cat $(CWSDSTUB) $(SYSTEMDIR)/dvxhlpc > $@ cat $(CWSDSTUB) $(SYSTEMDIR)/dvxhlpc > $@
rm -f $(SYSTEMDIR)/dvxhlpc $(SYSTEMDIR)/dvxhlpc.exe rm -f $(SYSTEMDIR)/dvxhlpc $(SYSTEMDIR)/dvxhlpc.exe
@ -63,8 +65,8 @@ $(SYSTEMDIR)/DVXHLPC.EXE: dvxhlpc.c hlpcCompile.h ../apps/kpunch/dvxhelp/hlpform
../../obj/loader: ../../obj/loader:
mkdir -p ../../obj/loader mkdir -p ../../obj/loader
$(SYSTEMDIR)/DVXRES.EXE: dvxres.c ../libs/kpunch/libdvx/dvxResource.c ../libs/kpunch/libdvx/dvxRes.h | $(SYSTEMDIR) $(SYSTEMDIR)/DVXRES.EXE: dvxres.c ../libs/kpunch/libdvx/dvxResource.c ../libs/kpunch/libdvx/dvxRes.h $(PLATFORM_UTIL) | $(SYSTEMDIR)
$(DOSCC) $(DOSCFLAGS) -o $(SYSTEMDIR)/dvxres.exe dvxres.c ../libs/kpunch/libdvx/dvxResource.c $(DOSCC) $(DOSCFLAGS) -o $(SYSTEMDIR)/dvxres.exe dvxres.c ../libs/kpunch/libdvx/dvxResource.c $(PLATFORM_UTIL)
$(EXE2COFF) $(SYSTEMDIR)/dvxres.exe $(EXE2COFF) $(SYSTEMDIR)/dvxres.exe
cat $(CWSDSTUB) $(SYSTEMDIR)/dvxres > $@ cat $(CWSDSTUB) $(SYSTEMDIR)/dvxres > $@
rm -f $(SYSTEMDIR)/dvxres $(SYSTEMDIR)/dvxres.exe rm -f $(SYSTEMDIR)/dvxres $(SYSTEMDIR)/dvxres.exe

152
src/tools/bmpDraw.c Normal file
View file

@ -0,0 +1,152 @@
// bmpDraw.c -- Shared 24-bit BMP canvas + drawing primitives for host tools
#include "bmpDraw.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BMP_BYTES_PER_PIXEL 3
#define BMP_HEADER_SIZE 54
#define BMP_DIB_SIZE 40
#define BMP_PLANES 1
#define BMP_BITS_PER_PIXEL 24
#define BMP_ROW_ALIGN 4
void bmpClear(BmpCanvasT *c, uint8_t r, uint8_t g, uint8_t b) {
bmpFillRect(c, 0, 0, c->w, c->h, r, g, b);
}
void bmpFillCircle(BmpCanvasT *c, int32_t cx, int32_t cy, int32_t radius, uint8_t r, uint8_t g, uint8_t b) {
int32_t r2 = radius * radius;
for (int32_t y = -radius; y <= radius; y++) {
for (int32_t x = -radius; x <= radius; x++) {
if (x * x + y * y <= r2) {
bmpPixel(c, cx + x, cy + y, r, g, b);
}
}
}
}
void bmpFillRect(BmpCanvasT *c, int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t r, uint8_t g, uint8_t b) {
int32_t yEnd = y0 + h;
int32_t xEnd = x0 + w;
if (yEnd > c->h) {
yEnd = c->h;
}
if (xEnd > c->w) {
xEnd = c->w;
}
for (int32_t y = y0; y < yEnd; y++) {
for (int32_t x = x0; x < xEnd; x++) {
bmpPixel(c, x, y, r, g, b);
}
}
}
void bmpFree(BmpCanvasT *c) {
free(c->pixels);
c->pixels = NULL;
c->w = 0;
c->h = 0;
}
void bmpHLine(BmpCanvasT *c, int32_t x0, int32_t y, int32_t w, uint8_t r, uint8_t g, uint8_t b) {
for (int32_t x = x0; x < x0 + w; x++) {
bmpPixel(c, x, y, r, g, b);
}
}
bool bmpInit(BmpCanvasT *c, int32_t w, int32_t h) {
size_t bytes = (size_t)w * (size_t)h * BMP_BYTES_PER_PIXEL;
c->pixels = (uint8_t *)calloc(1, bytes);
c->w = w;
c->h = h;
return c->pixels != NULL;
}
void bmpPixel(BmpCanvasT *c, int32_t x, int32_t y, uint8_t r, uint8_t g, uint8_t b) {
if (x >= 0 && x < c->w && y >= 0 && y < c->h) {
uint8_t *p = c->pixels + ((size_t)y * c->w + x) * BMP_BYTES_PER_PIXEL;
p[0] = b;
p[1] = g;
p[2] = r;
}
}
void bmpRect(BmpCanvasT *c, int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t r, uint8_t g, uint8_t b) {
bmpHLine(c, x0, y0, w, r, g, b);
bmpHLine(c, x0, y0 + h - 1, w, r, g, b);
bmpVLine(c, x0, y0, h, r, g, b);
bmpVLine(c, x0 + w - 1, y0, h, r, g, b);
}
void bmpVLine(BmpCanvasT *c, int32_t x, int32_t y0, int32_t h, uint8_t r, uint8_t g, uint8_t b) {
for (int32_t y = y0; y < y0 + h; y++) {
bmpPixel(c, x, y, r, g, b);
}
}
bool bmpWrite(const BmpCanvasT *c, const char *path) {
int32_t rowBytes = c->w * BMP_BYTES_PER_PIXEL;
int32_t rowPad = (BMP_ROW_ALIGN - rowBytes % BMP_ROW_ALIGN) % BMP_ROW_ALIGN;
int32_t rowSize = rowBytes + rowPad;
int32_t dataSize = rowSize * c->h;
int32_t fileSize = BMP_HEADER_SIZE + dataSize;
uint8_t header[BMP_HEADER_SIZE];
memset(header, 0, sizeof(header));
header[0] = 'B';
header[1] = 'M';
*(int32_t *)&header[2] = fileSize;
*(int32_t *)&header[10] = BMP_HEADER_SIZE;
*(int32_t *)&header[14] = BMP_DIB_SIZE;
*(int32_t *)&header[18] = c->w;
*(int32_t *)&header[22] = c->h;
*(int16_t *)&header[26] = BMP_PLANES;
*(int16_t *)&header[28] = BMP_BITS_PER_PIXEL;
*(int32_t *)&header[34] = dataSize;
FILE *f = fopen(path, "wb");
if (!f) {
fprintf(stderr, "Cannot write: %s\n", path);
return false;
}
fwrite(header, 1, BMP_HEADER_SIZE, f);
uint8_t pad[BMP_ROW_ALIGN - 1] = {0};
// BMP rows are bottom-to-top
for (int32_t y = c->h - 1; y >= 0; y--) {
uint8_t *row = c->pixels + (size_t)y * c->w * BMP_BYTES_PER_PIXEL;
fwrite(row, 1, rowBytes, f);
if (rowPad > 0) {
fwrite(pad, 1, rowPad, f);
}
}
fclose(f);
return true;
}

32
src/tools/bmpDraw.h Normal file
View file

@ -0,0 +1,32 @@
// bmpDraw.h -- Shared 24-bit BMP canvas + drawing primitives for host tools
//
// All drawing functions clip to the canvas bounds. Pixel data is stored
// in BGR order, 3 bytes per pixel, stride = w * 3 (no row padding in memory;
// padding is added only during bmpWrite).
#ifndef BMPDRAW_H
#define BMPDRAW_H
#include <stdbool.h>
#include <stdint.h>
typedef struct {
uint8_t *pixels; // caller-owned or malloc'd; BGR, 3 bytes per pixel
int32_t w;
int32_t h;
} BmpCanvasT;
void bmpClear(BmpCanvasT *c, uint8_t r, uint8_t g, uint8_t b);
void bmpFillCircle(BmpCanvasT *c, int32_t cx, int32_t cy, int32_t radius, uint8_t r, uint8_t g, uint8_t b);
void bmpFillRect(BmpCanvasT *c, int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t r, uint8_t g, uint8_t b);
void bmpFree(BmpCanvasT *c);
void bmpHLine(BmpCanvasT *c, int32_t x0, int32_t y, int32_t w, uint8_t r, uint8_t g, uint8_t b);
bool bmpInit(BmpCanvasT *c, int32_t w, int32_t h);
void bmpPixel(BmpCanvasT *c, int32_t x, int32_t y, uint8_t r, uint8_t g, uint8_t b);
void bmpRect(BmpCanvasT *c, int32_t x0, int32_t y0, int32_t w, int32_t h, uint8_t r, uint8_t g, uint8_t b);
void bmpVLine(BmpCanvasT *c, int32_t x, int32_t y0, int32_t h, uint8_t r, uint8_t g, uint8_t b);
bool bmpWrite(const BmpCanvasT *c, const char *path);
#endif

File diff suppressed because it is too large Load diff

View file

@ -34,31 +34,6 @@ static int parseType(const char *typeStr);
static void usage(void); static void usage(void);
// ============================================================
// parseType
// ============================================================
static int parseType(const char *typeStr) {
if (strcmp(typeStr, "icon") == 0 || strcmp(typeStr, "image") == 0) {
return DVX_RES_ICON;
}
if (strcmp(typeStr, "text") == 0) {
return DVX_RES_TEXT;
}
if (strcmp(typeStr, "binary") == 0) {
return DVX_RES_BINARY;
}
return -1;
}
// ============================================================
// cmdAdd
// ============================================================
static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, const char *dataArg) { static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, const char *dataArg) {
int type = parseType(typeStr); int type = parseType(typeStr);
@ -94,33 +69,15 @@ static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, co
filePath++; filePath++;
} }
FILE *df = fopen(filePath, "rb"); int32_t readLen = 0;
newData = (uint8_t *)platformReadFile(filePath, &readLen);
if (!df) {
fprintf(stderr, "Cannot open data file: %s\n", filePath);
return 1;
}
fseek(df, 0, SEEK_END);
newSize = (uint32_t)ftell(df);
fseek(df, 0, SEEK_SET);
newData = (uint8_t *)malloc(newSize);
if (!newData) { if (!newData) {
fclose(df); fprintf(stderr, "Cannot open or read data file: %s\n", filePath);
fprintf(stderr, "Out of memory\n");
return 1; return 1;
} }
if (fread(newData, 1, newSize, df) != newSize) { newSize = (uint32_t)readLen;
fclose(df);
free(newData);
fprintf(stderr, "Failed to read: %s\n", filePath);
return 1;
}
fclose(df);
} }
int result = dvxResAppendEntry(dxePath, name, (uint32_t)type, newData, newSize); int result = dvxResAppendEntry(dxePath, name, (uint32_t)type, newData, newSize);
@ -136,10 +93,6 @@ static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, co
} }
// ============================================================
// cmdBuild
// ============================================================
static int cmdBuild(const char *dxePath, const char *manifestPath) { static int cmdBuild(const char *dxePath, const char *manifestPath) {
FILE *mf = fopen(manifestPath, "r"); FILE *mf = fopen(manifestPath, "r");
@ -258,33 +211,15 @@ static int cmdBuild(const char *dxePath, const char *manifestPath) {
memcpy(newData, dataArg, newSize); memcpy(newData, dataArg, newSize);
} else { } else {
FILE *df = fopen(dataArg, "rb"); int32_t readLen = 0;
newData = (uint8_t *)platformReadFile(dataArg, &readLen);
if (!df) {
fprintf(stderr, "%s:%d: cannot open: %s\n", manifestPath, lineNum, dataArg);
goto fail;
}
fseek(df, 0, SEEK_END);
newSize = (uint32_t)ftell(df);
fseek(df, 0, SEEK_SET);
newData = (uint8_t *)malloc(newSize);
if (!newData) { if (!newData) {
fclose(df); fprintf(stderr, "%s:%d: cannot open or read: %s\n", manifestPath, lineNum, dataArg);
fprintf(stderr, "Out of memory\n");
goto fail; goto fail;
} }
if (fread(newData, 1, newSize, df) != newSize) { newSize = (uint32_t)readLen;
fclose(df);
free(newData);
fprintf(stderr, "%s:%d: failed to read: %s\n", manifestPath, lineNum, dataArg);
goto fail;
}
fclose(df);
} }
// Append // Append
@ -340,9 +275,45 @@ fail:
} }
// ============================================================ static int cmdGet(const char *dxePath, const char *name, const char *outPath) {
// cmdList DvxResHandleT *h = dvxResOpen(dxePath);
// ============================================================
if (!h) {
fprintf(stderr, "No resources in %s\n", dxePath);
return 1;
}
uint32_t size = 0;
void *buf = dvxResRead(h, name, &size);
dvxResClose(h);
if (!buf) {
fprintf(stderr, "Resource not found: %s\n", name);
return 1;
}
if (outPath) {
FILE *f = fopen(outPath, "wb");
if (!f) {
free(buf);
fprintf(stderr, "Cannot write: %s\n", outPath);
return 1;
}
fwrite(buf, 1, size, f);
fclose(f);
printf("Extracted '%s' (%u bytes) to %s\n", name, (unsigned)size, outPath);
} else {
// Write to stdout
fwrite(buf, 1, size, stdout);
}
free(buf);
return 0;
}
static int cmdList(const char *dxePath) { static int cmdList(const char *dxePath) {
FILE *f = fopen(dxePath, "rb"); FILE *f = fopen(dxePath, "rb");
@ -392,54 +363,6 @@ static int cmdList(const char *dxePath) {
} }
// ============================================================
// cmdGet
// ============================================================
static int cmdGet(const char *dxePath, const char *name, const char *outPath) {
DvxResHandleT *h = dvxResOpen(dxePath);
if (!h) {
fprintf(stderr, "No resources in %s\n", dxePath);
return 1;
}
uint32_t size = 0;
void *buf = dvxResRead(h, name, &size);
dvxResClose(h);
if (!buf) {
fprintf(stderr, "Resource not found: %s\n", name);
return 1;
}
if (outPath) {
FILE *f = fopen(outPath, "wb");
if (!f) {
free(buf);
fprintf(stderr, "Cannot write: %s\n", outPath);
return 1;
}
fwrite(buf, 1, size, f);
fclose(f);
printf("Extracted '%s' (%u bytes) to %s\n", name, (unsigned)size, outPath);
} else {
// Write to stdout
fwrite(buf, 1, size, stdout);
}
free(buf);
return 0;
}
// ============================================================
// cmdStrip
// ============================================================
static int cmdStrip(const char *dxePath) { static int cmdStrip(const char *dxePath) {
long dxeSize = dvxResDxeContentSize(dxePath); long dxeSize = dvxResDxeContentSize(dxePath);
@ -500,9 +423,22 @@ static int cmdStrip(const char *dxePath) {
} }
// ============================================================ static int parseType(const char *typeStr) {
// usage if (strcmp(typeStr, "icon") == 0 || strcmp(typeStr, "image") == 0) {
// ============================================================ return DVX_RES_ICON;
}
if (strcmp(typeStr, "text") == 0) {
return DVX_RES_TEXT;
}
if (strcmp(typeStr, "binary") == 0) {
return DVX_RES_BINARY;
}
return -1;
}
static void usage(void) { static void usage(void) {
fprintf(stderr, "DVX Resource Tool\n\n"); fprintf(stderr, "DVX Resource Tool\n\n");
@ -521,10 +457,6 @@ static void usage(void) {
} }
// ============================================================
// main
// ============================================================
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (argc < 3) { if (argc < 3) {
usage(); usage();

View file

@ -5,175 +5,149 @@
// //
// Generates recognizable pixel-art icons at 32x32 24-bit BMP. // Generates recognizable pixel-art icons at 32x32 24-bit BMP.
#include "bmpDraw.h"
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#define W 32 #define ICON_W 32
#define H 32 #define ICON_H 32
#define PI 3.14159265
#define CLOCK_RIM 360
#define CLOCK_HOURS 12
#define DEG_PER_HOUR 30.0
static uint8_t pixels[H][W][3]; // BGR static BmpCanvasT canvas;
static void clear(uint8_t r, uint8_t g, uint8_t b) { // ============================================================
for (int y = 0; y < H; y++) { // Prototypes
for (int x = 0; x < W; x++) { // ============================================================
pixels[y][x][0] = b;
pixels[y][x][1] = g;
pixels[y][x][2] = r;
}
}
}
static void pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) { static void iconBasic(void);
if (x >= 0 && x < W && y >= 0 && y < H) { static void iconClock(void);
pixels[y][x][0] = b; static void iconCpanel(void);
pixels[y][x][1] = g; static void iconDvxdemo(void);
pixels[y][x][2] = r; static void iconHelp(void);
} static void iconIconEd(void);
} static void iconImgview(void);
static void iconNoIcon(void);
static void iconNotepad(void);
static void iconResedit(void);
static void rect(int x0, int y0, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
for (int y = y0; y < y0 + h && y < H; y++) {
for (int x = x0; x < x0 + w && x < W; x++) {
pixel(x, y, r, g, b);
}
}
}
static void circle(int cx, int cy, int radius, uint8_t r, uint8_t g, uint8_t b) { // DVX BASIC icon: code brackets with "B" letterform
for (int y = -radius; y <= radius; y++) { static void iconBasic(void) {
for (int x = -radius; x <= radius; x++) { bmpClear(&canvas, 192, 192, 192);
if (x * x + y * y <= radius * radius) {
pixel(cx + x, cy + y, r, g, b);
}
}
}
}
static void hline(int x0, int y, int w, uint8_t r, uint8_t g, uint8_t b) { // Blue background rectangle (code editor feel)
for (int x = x0; x < x0 + w; x++) { bmpFillRect(&canvas, 2, 2, 28, 28, 0, 0, 128);
pixel(x, y, r, g, b);
}
}
static void vline(int x, int y0, int h, uint8_t r, uint8_t g, uint8_t b) { // Left bracket {
for (int y = y0; y < y0 + h; y++) { bmpVLine(&canvas, 6, 6, 20, 255, 255, 0);
pixel(x, y, r, g, b); bmpHLine(&canvas, 7, 6, 3, 255, 255, 0);
} bmpHLine(&canvas, 7, 25, 3, 255, 255, 0);
} bmpHLine(&canvas, 4, 15, 2, 255, 255, 0);
bmpPixel(&canvas, 5, 14, 255, 255, 0);
bmpPixel(&canvas, 5, 16, 255, 255, 0);
// No-icon placeholder: grey square with red diagonal X // "B" letter in white (bold, centered)
static void iconNoIcon(void) { // Vertical stroke
clear(192, 192, 192); bmpVLine(&canvas, 14, 7, 18, 255, 255, 255);
// Border // Top horizontal
rect(0, 0, 32, 1, 128, 128, 128); bmpHLine(&canvas, 14, 7, 7, 255, 255, 255);
rect(0, 31, 32, 1, 128, 128, 128); bmpHLine(&canvas, 14, 8, 7, 255, 255, 255);
rect(0, 0, 1, 32, 128, 128, 128);
rect(31, 0, 1, 32, 128, 128, 128);
// Red X (two diagonal lines, 2px thick) // Middle horizontal
for (int i = 4; i < 28; i++) { bmpHLine(&canvas, 14, 15, 7, 255, 255, 255);
pixel(i, i, 200, 40, 40); bmpHLine(&canvas, 14, 16, 7, 255, 255, 255);
pixel(i + 1, i, 200, 40, 40);
pixel(31 - i, i, 200, 40, 40);
pixel(30 - i, i, 200, 40, 40);
}
// "?" in the center // Bottom horizontal
for (int x = 13; x <= 18; x++) { pixel(x, 8, 80, 80, 80); } bmpHLine(&canvas, 14, 23, 7, 255, 255, 255);
pixel(19, 9, 80, 80, 80); bmpHLine(&canvas, 14, 24, 7, 255, 255, 255);
pixel(19, 10, 80, 80, 80);
for (int x = 15; x <= 18; x++) { pixel(x, 11, 80, 80, 80); } // Right curves of B (top bump)
pixel(15, 12, 80, 80, 80); bmpVLine(&canvas, 21, 8, 7, 255, 255, 255);
pixel(15, 13, 80, 80, 80); bmpPixel(&canvas, 20, 8, 255, 255, 255);
pixel(15, 15, 80, 80, 80); bmpPixel(&canvas, 20, 14, 255, 255, 255);
pixel(16, 15, 80, 80, 80);
// Right curves of B (bottom bump)
bmpVLine(&canvas, 21, 17, 6, 255, 255, 255);
bmpPixel(&canvas, 20, 17, 255, 255, 255);
bmpPixel(&canvas, 20, 23, 255, 255, 255);
// Right bracket }
bmpVLine(&canvas, 25, 6, 20, 255, 255, 0);
bmpHLine(&canvas, 22, 6, 3, 255, 255, 0);
bmpHLine(&canvas, 22, 25, 3, 255, 255, 0);
bmpHLine(&canvas, 26, 15, 2, 255, 255, 0);
bmpPixel(&canvas, 26, 14, 255, 255, 0);
bmpPixel(&canvas, 26, 16, 255, 255, 0);
} }
// Clock icon: circle with hands // Clock icon: circle with hands
static void iconClock(void) { static void iconClock(void) {
clear(192, 192, 192); bmpClear(&canvas, 192, 192, 192);
circle(15, 15, 13, 255, 255, 255); bmpFillCircle(&canvas, 15, 15, 13, 255, 255, 255);
circle(15, 15, 12, 240, 240, 255); bmpFillCircle(&canvas, 15, 15, 12, 240, 240, 255);
// Rim // Rim
for (int a = 0; a < 360; a++) { for (int a = 0; a < CLOCK_RIM; a++) {
double rad = a * 3.14159265 / 180.0; double rad = a * PI / 180.0;
int x = 15 + (int)(13.0 * __builtin_cos(rad)); int x = 15 + (int)(13.0 * __builtin_cos(rad));
int y = 15 + (int)(13.0 * __builtin_sin(rad)); int y = 15 + (int)(13.0 * __builtin_sin(rad));
pixel(x, y, 0, 0, 128); bmpPixel(&canvas, x, y, 0, 0, 128);
} }
// Hour marks // Hour marks
for (int i = 0; i < 12; i++) { for (int i = 0; i < CLOCK_HOURS; i++) {
double rad = i * 30.0 * 3.14159265 / 180.0; double rad = i * DEG_PER_HOUR * PI / 180.0;
int x = 15 + (int)(11.0 * __builtin_cos(rad)); int x = 15 + (int)(11.0 * __builtin_cos(rad));
int y = 15 + (int)(11.0 * __builtin_sin(rad)); int y = 15 + (int)(11.0 * __builtin_sin(rad));
pixel(x, y, 0, 0, 128); bmpPixel(&canvas, x, y, 0, 0, 128);
} }
// Hour hand (pointing ~10 o'clock) // Hour hand (pointing ~10 o'clock)
vline(15, 7, 8, 0, 0, 128); bmpVLine(&canvas, 15, 7, 8, 0, 0, 128);
// Minute hand (pointing ~2 o'clock) // Minute hand (pointing ~2 o'clock)
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
pixel(15 + i * 2 / 3, 15 - i * 2 / 3, 0, 0, 200); bmpPixel(&canvas, 15 + i * 2 / 3, 15 - i * 2 / 3, 0, 0, 200);
} }
// Center dot // Center dot
pixel(15, 15, 200, 0, 0); bmpPixel(&canvas, 15, 15, 200, 0, 0);
} }
// Notepad icon: page with lines
static void iconNotepad(void) {
clear(192, 192, 192);
rect(6, 3, 20, 26, 255, 255, 240);
// Border
hline(6, 3, 20, 0, 0, 0);
hline(6, 28, 20, 0, 0, 0);
vline(6, 3, 26, 0, 0, 0);
vline(25, 3, 26, 0, 0, 0);
// Folded corner
for (int i = 0; i < 5; i++) {
hline(21 + i, 3 + i, 5 - i, 200, 200, 180);
}
// Text lines
for (int i = 0; i < 7; i++) {
int lineW = (i == 3) ? 12 : (i == 6) ? 8 : 16;
hline(9, 8 + i * 3, lineW, 0, 0, 128);
}
}
// Control Panel icon: gear/wrench // Control Panel icon: gear/wrench
static void iconCpanel(void) { static void iconCpanel(void) {
clear(192, 192, 192); bmpClear(&canvas, 192, 192, 192);
// Gear body // Gear body
circle(15, 15, 8, 128, 128, 128); bmpFillCircle(&canvas, 15, 15, 8, 128, 128, 128);
circle(15, 15, 5, 192, 192, 192); bmpFillCircle(&canvas, 15, 15, 5, 192, 192, 192);
// Gear teeth (N/S/E/W and diagonals) // Gear teeth (N/S/E/W and diagonals)
rect(13, 2, 5, 4, 128, 128, 128); bmpFillRect(&canvas, 13, 2, 5, 4, 128, 128, 128);
rect(13, 26, 5, 4, 128, 128, 128); bmpFillRect(&canvas, 13, 26, 5, 4, 128, 128, 128);
rect(2, 13, 4, 5, 128, 128, 128); bmpFillRect(&canvas, 2, 13, 4, 5, 128, 128, 128);
rect(26, 13, 4, 5, 128, 128, 128); bmpFillRect(&canvas, 26, 13, 4, 5, 128, 128, 128);
rect(4, 4, 4, 4, 128, 128, 128); bmpFillRect(&canvas, 4, 4, 4, 4, 128, 128, 128);
rect(24, 4, 4, 4, 128, 128, 128); bmpFillRect(&canvas, 24, 4, 4, 4, 128, 128, 128);
rect(4, 24, 4, 4, 128, 128, 128); bmpFillRect(&canvas, 4, 24, 4, 4, 128, 128, 128);
rect(24, 24, 4, 4, 128, 128, 128); bmpFillRect(&canvas, 24, 24, 4, 4, 128, 128, 128);
// Inner circle (hole) // Inner circle (hole)
circle(15, 15, 3, 64, 64, 64); bmpFillCircle(&canvas, 15, 15, 3, 64, 64, 64);
} }
// DVX Demo icon: colorful diamond // DVX Demo icon: colorful diamond
static void iconDvxdemo(void) { static void iconDvxdemo(void) {
clear(192, 192, 192); bmpClear(&canvas, 192, 192, 192);
// Diamond shape with color gradient // Diamond shape with color gradient
for (int y = 0; y < 16; y++) { for (int y = 0; y < 16; y++) {
@ -185,7 +159,7 @@ static void iconDvxdemo(void) {
uint8_t r = (uint8_t)(128 + y * 8); uint8_t r = (uint8_t)(128 + y * 8);
uint8_t g = (uint8_t)(64 + (hw - (x < 0 ? -x : x)) * 8); uint8_t g = (uint8_t)(64 + (hw - (x < 0 ? -x : x)) * 8);
uint8_t b = (uint8_t)(200 - y * 4); uint8_t b = (uint8_t)(200 - y * 4);
pixel(px, py, r, g, b); bmpPixel(&canvas, px, py, r, g, b);
} }
} }
@ -198,164 +172,46 @@ static void iconDvxdemo(void) {
uint8_t r = (uint8_t)(255 - y * 8); uint8_t r = (uint8_t)(255 - y * 8);
uint8_t g = (uint8_t)(128 - y * 4); uint8_t g = (uint8_t)(128 - y * 4);
uint8_t b = (uint8_t)(100 + y * 8); uint8_t b = (uint8_t)(100 + y * 8);
pixel(px, py, r, g, b); bmpPixel(&canvas, px, py, r, g, b);
} }
} }
} }
// Image Viewer icon: landscape in a frame
static void iconImgview(void) {
clear(192, 192, 192);
// Frame
rect(3, 5, 26, 22, 100, 80, 60);
rect(5, 7, 22, 18, 135, 200, 235);
// Sun
circle(21, 11, 3, 255, 220, 50);
// Mountain
for (int y = 0; y < 10; y++) {
int w = y * 2 + 1;
hline(15 - y, 15 + y, w, 80, 140, 80);
}
// Ground
rect(5, 22, 22, 3, 60, 120, 60);
}
// DVX BASIC icon: code brackets with "B" letterform
static void iconBasic(void) {
clear(192, 192, 192);
// Blue background rectangle (code editor feel)
rect(2, 2, 28, 28, 0, 0, 128);
// Left bracket {
vline(6, 6, 20, 255, 255, 0);
hline(7, 6, 3, 255, 255, 0);
hline(7, 25, 3, 255, 255, 0);
hline(4, 15, 2, 255, 255, 0);
pixel(5, 14, 255, 255, 0);
pixel(5, 16, 255, 255, 0);
// "B" letter in white (bold, centered)
// Vertical stroke
vline(14, 7, 18, 255, 255, 255);
// Top horizontal
hline(14, 7, 7, 255, 255, 255);
hline(14, 8, 7, 255, 255, 255);
// Middle horizontal
hline(14, 15, 7, 255, 255, 255);
hline(14, 16, 7, 255, 255, 255);
// Bottom horizontal
hline(14, 23, 7, 255, 255, 255);
hline(14, 24, 7, 255, 255, 255);
// Right curves of B (top bump)
vline(21, 8, 7, 255, 255, 255);
pixel(20, 8, 255, 255, 255);
pixel(20, 14, 255, 255, 255);
// Right curves of B (bottom bump)
vline(21, 17, 6, 255, 255, 255);
pixel(20, 17, 255, 255, 255);
pixel(20, 23, 255, 255, 255);
// Right bracket }
vline(25, 6, 20, 255, 255, 0);
hline(22, 6, 3, 255, 255, 0);
hline(22, 25, 3, 255, 255, 0);
hline(26, 15, 2, 255, 255, 0);
pixel(26, 14, 255, 255, 0);
pixel(26, 16, 255, 255, 0);
}
static void iconHelp(void) { static void iconHelp(void) {
// Blue book with white "?" on it // Blue book with white "?" on it
clear(192, 192, 192); bmpClear(&canvas, 192, 192, 192);
// Book body (blue) // Book body (blue)
rect(6, 4, 20, 24, 0, 0, 180); bmpFillRect(&canvas, 6, 4, 20, 24, 0, 0, 180);
// Book spine (darker blue) // Book spine (darker blue)
rect(6, 4, 3, 24, 0, 0, 130); bmpFillRect(&canvas, 6, 4, 3, 24, 0, 0, 130);
// Book pages (white edge) // Book pages (white edge)
vline(25, 5, 22, 255, 255, 255); bmpVLine(&canvas, 25, 5, 22, 255, 255, 255);
// Question mark in white // Question mark in white
// Top curve // Top curve
hline(14, 9, 5, 255, 255, 255); bmpHLine(&canvas, 14, 9, 5, 255, 255, 255);
pixel(13, 10, 255, 255, 255); bmpPixel(&canvas, 13, 10, 255, 255, 255);
pixel(19, 10, 255, 255, 255); bmpPixel(&canvas, 19, 10, 255, 255, 255);
pixel(19, 11, 255, 255, 255); bmpPixel(&canvas, 19, 11, 255, 255, 255);
pixel(18, 12, 255, 255, 255); bmpPixel(&canvas, 18, 12, 255, 255, 255);
pixel(17, 13, 255, 255, 255); bmpPixel(&canvas, 17, 13, 255, 255, 255);
// Stem // Stem
pixel(16, 14, 255, 255, 255); bmpPixel(&canvas, 16, 14, 255, 255, 255);
pixel(16, 15, 255, 255, 255); bmpPixel(&canvas, 16, 15, 255, 255, 255);
pixel(16, 16, 255, 255, 255); bmpPixel(&canvas, 16, 16, 255, 255, 255);
// Dot // Dot
pixel(16, 19, 255, 255, 255); bmpPixel(&canvas, 16, 19, 255, 255, 255);
pixel(16, 20, 255, 255, 255); bmpPixel(&canvas, 16, 20, 255, 255, 255);
}
static void writeBmp(const char *path) {
int32_t rowPad = (4 - (W * 3) % 4) % 4;
int32_t rowSize = W * 3 + rowPad;
int32_t dataSize = rowSize * H;
int32_t fileSize = 54 + dataSize;
uint8_t header[54];
memset(header, 0, sizeof(header));
// BMP header
header[0] = 'B';
header[1] = 'M';
*(int32_t *)&header[2] = fileSize;
*(int32_t *)&header[10] = 54;
// DIB header
*(int32_t *)&header[14] = 40;
*(int32_t *)&header[18] = W;
*(int32_t *)&header[22] = H;
*(int16_t *)&header[26] = 1;
*(int16_t *)&header[28] = 24;
*(int32_t *)&header[34] = dataSize;
FILE *f = fopen(path, "wb");
if (!f) {
fprintf(stderr, "Cannot write: %s\n", path);
return;
}
fwrite(header, 1, 54, f);
// BMP rows are bottom-to-top
uint8_t pad[3] = {0};
for (int y = H - 1; y >= 0; y--) {
fwrite(pixels[y], 1, W * 3, f);
if (rowPad > 0) {
fwrite(pad, 1, rowPad, f);
}
}
fclose(f);
} }
// Icon editor: pixel grid with pencil drawing a pixel // Icon editor: pixel grid with pencil drawing a pixel
static void iconIconEd(void) { static void iconIconEd(void) {
clear(220, 220, 220); bmpClear(&canvas, 220, 220, 220);
// Draw a 5x5 grid of colored pixels (the "canvas") // Draw a 5x5 grid of colored pixels (the "canvas")
int gx = 3; int gx = 3;
@ -364,84 +220,160 @@ static void iconIconEd(void) {
// Grid lines // Grid lines
for (int i = 0; i <= 5; i++) { for (int i = 0; i <= 5; i++) {
hline(gx, gy + i * cs, 5 * cs + 1, 160, 160, 160); bmpHLine(&canvas, gx, gy + i * cs, 5 * cs + 1, 160, 160, 160);
vline(gx + i * cs, gy, 5 * cs + 1, 160, 160, 160); bmpVLine(&canvas, gx + i * cs, gy, 5 * cs + 1, 160, 160, 160);
} }
// Some colored pixels in the grid // Some colored pixels in the grid
rect(gx + 1, gy + 1, cs - 1, cs - 1, 255, 0, 0); bmpFillRect(&canvas, gx + 1, gy + 1, cs - 1, cs - 1, 255, 0, 0);
rect(gx + cs + 1, gy + 1, cs - 1, cs - 1, 0, 0, 255); bmpFillRect(&canvas, gx + cs + 1, gy + 1, cs - 1, cs - 1, 0, 0, 255);
rect(gx + 1, gy + cs + 1, cs - 1, cs - 1, 0, 180, 0); bmpFillRect(&canvas, gx + 1, gy + cs + 1, cs - 1, cs - 1, 0, 180, 0);
rect(gx + cs + 1, gy + cs + 1, cs - 1, cs - 1, 255, 255, 0); bmpFillRect(&canvas, gx + cs + 1, gy + cs + 1, cs - 1, cs - 1, 255, 255, 0);
rect(gx + 2*cs+1, gy + 2*cs+1, cs - 1, cs - 1, 255, 128, 0); bmpFillRect(&canvas, gx + 2*cs+1, gy + 2*cs+1, cs - 1, cs - 1, 255, 128, 0);
rect(gx + 3*cs+1, gy + 1, cs - 1, cs - 1, 128, 0, 255); bmpFillRect(&canvas, gx + 3*cs+1, gy + 1, cs - 1, cs - 1, 128, 0, 255);
// Pencil (diagonal, bottom-right area) // Pencil (diagonal, bottom-right area)
for (int i = 0; i < 12; i++) { for (int i = 0; i < 12; i++) {
pixel(18 + i, 28 - i, 230, 200, 80); bmpPixel(&canvas, 18 + i, 28 - i, 230, 200, 80);
pixel(19 + i, 28 - i, 230, 200, 80); bmpPixel(&canvas, 19 + i, 28 - i, 230, 200, 80);
pixel(18 + i, 29 - i, 230, 200, 80); bmpPixel(&canvas, 18 + i, 29 - i, 230, 200, 80);
} }
// Pencil tip // Pencil tip
pixel(17, 29, 40, 40, 40); bmpPixel(&canvas, 17, 29, 40, 40, 40);
pixel(17, 30, 40, 40, 40); bmpPixel(&canvas, 17, 30, 40, 40, 40);
pixel(18, 30, 40, 40, 40); bmpPixel(&canvas, 18, 30, 40, 40, 40);
// Eraser end // Eraser end
pixel(29, 17, 240, 150, 150); bmpPixel(&canvas, 29, 17, 240, 150, 150);
pixel(30, 16, 240, 150, 150); bmpPixel(&canvas, 30, 16, 240, 150, 150);
pixel(30, 17, 240, 150, 150); bmpPixel(&canvas, 30, 17, 240, 150, 150);
pixel(29, 16, 240, 150, 150); bmpPixel(&canvas, 29, 16, 240, 150, 150);
// Border // Border
rect(0, 0, 32, 1, 128, 128, 128); bmpFillRect(&canvas, 0, 0, 32, 1, 128, 128, 128);
rect(0, 31, 32, 1, 128, 128, 128); bmpFillRect(&canvas, 0, 31, 32, 1, 128, 128, 128);
rect(0, 0, 1, 32, 128, 128, 128); bmpFillRect(&canvas, 0, 0, 1, 32, 128, 128, 128);
rect(31, 0, 1, 32, 128, 128, 128); bmpFillRect(&canvas, 31, 0, 1, 32, 128, 128, 128);
}
// Image Viewer icon: landscape in a frame
static void iconImgview(void) {
bmpClear(&canvas, 192, 192, 192);
// Frame
bmpFillRect(&canvas, 3, 5, 26, 22, 100, 80, 60);
bmpFillRect(&canvas, 5, 7, 22, 18, 135, 200, 235);
// Sun
bmpFillCircle(&canvas, 21, 11, 3, 255, 220, 50);
// Mountain
for (int y = 0; y < 10; y++) {
int w = y * 2 + 1;
bmpHLine(&canvas, 15 - y, 15 + y, w, 80, 140, 80);
}
// Ground
bmpFillRect(&canvas, 5, 22, 22, 3, 60, 120, 60);
}
// No-icon placeholder: grey square with red diagonal X
static void iconNoIcon(void) {
bmpClear(&canvas, 192, 192, 192);
// Border
bmpFillRect(&canvas, 0, 0, 32, 1, 128, 128, 128);
bmpFillRect(&canvas, 0, 31, 32, 1, 128, 128, 128);
bmpFillRect(&canvas, 0, 0, 1, 32, 128, 128, 128);
bmpFillRect(&canvas, 31, 0, 1, 32, 128, 128, 128);
// Red X (two diagonal lines, 2px thick)
for (int i = 4; i < 28; i++) {
bmpPixel(&canvas, i, i, 200, 40, 40);
bmpPixel(&canvas, i + 1, i, 200, 40, 40);
bmpPixel(&canvas, 31 - i, i, 200, 40, 40);
bmpPixel(&canvas, 30 - i, i, 200, 40, 40);
}
// "?" in the center
for (int x = 13; x <= 18; x++) { bmpPixel(&canvas, x, 8, 80, 80, 80); }
bmpPixel(&canvas, 19, 9, 80, 80, 80);
bmpPixel(&canvas, 19, 10, 80, 80, 80);
for (int x = 15; x <= 18; x++) { bmpPixel(&canvas, x, 11, 80, 80, 80); }
bmpPixel(&canvas, 15, 12, 80, 80, 80);
bmpPixel(&canvas, 15, 13, 80, 80, 80);
bmpPixel(&canvas, 15, 15, 80, 80, 80);
bmpPixel(&canvas, 16, 15, 80, 80, 80);
}
// Notepad icon: page with lines
static void iconNotepad(void) {
bmpClear(&canvas, 192, 192, 192);
bmpFillRect(&canvas, 6, 3, 20, 26, 255, 255, 240);
// Border
bmpHLine(&canvas, 6, 3, 20, 0, 0, 0);
bmpHLine(&canvas, 6, 28, 20, 0, 0, 0);
bmpVLine(&canvas, 6, 3, 26, 0, 0, 0);
bmpVLine(&canvas, 25, 3, 26, 0, 0, 0);
// Folded corner
for (int i = 0; i < 5; i++) {
bmpHLine(&canvas, 21 + i, 3 + i, 5 - i, 200, 200, 180);
}
// Text lines
for (int i = 0; i < 7; i++) {
int lineW = (i == 3) ? 12 : (i == 6) ? 8 : 16;
bmpHLine(&canvas, 9, 8 + i * 3, lineW, 0, 0, 128);
}
} }
// Resource editor: box with stacked resource entries // Resource editor: box with stacked resource entries
static void iconResedit(void) { static void iconResedit(void) {
clear(220, 220, 220); bmpClear(&canvas, 220, 220, 220);
// Outer box (document frame) // Outer box (document frame)
rect(3, 2, 26, 28, 255, 255, 255); bmpFillRect(&canvas, 3, 2, 26, 28, 255, 255, 255);
rect(3, 2, 26, 1, 80, 80, 80); bmpFillRect(&canvas, 3, 2, 26, 1, 80, 80, 80);
rect(3, 29, 26, 1, 80, 80, 80); bmpFillRect(&canvas, 3, 29, 26, 1, 80, 80, 80);
rect(3, 2, 1, 28, 80, 80, 80); bmpFillRect(&canvas, 3, 2, 1, 28, 80, 80, 80);
rect(28, 2, 1, 28, 80, 80, 80); bmpFillRect(&canvas, 28, 2, 1, 28, 80, 80, 80);
// Title bar // Title bar
rect(4, 3, 24, 4, 0, 0, 128); bmpFillRect(&canvas, 4, 3, 24, 4, 0, 0, 128);
// Resource entry rows (alternating light shading) // Resource entry rows (alternating light shading)
rect(4, 8, 24, 4, 240, 240, 255); bmpFillRect(&canvas, 4, 8, 24, 4, 240, 240, 255);
rect(4, 12, 24, 1, 180, 180, 200); bmpFillRect(&canvas, 4, 12, 24, 1, 180, 180, 200);
rect(4, 13, 24, 4, 255, 255, 255); bmpFillRect(&canvas, 4, 13, 24, 4, 255, 255, 255);
rect(4, 17, 24, 1, 180, 180, 200); bmpFillRect(&canvas, 4, 17, 24, 1, 180, 180, 200);
rect(4, 18, 24, 4, 240, 240, 255); bmpFillRect(&canvas, 4, 18, 24, 4, 240, 240, 255);
rect(4, 22, 24, 1, 180, 180, 200); bmpFillRect(&canvas, 4, 22, 24, 1, 180, 180, 200);
rect(4, 23, 24, 4, 255, 255, 255); bmpFillRect(&canvas, 4, 23, 24, 4, 255, 255, 255);
// Type indicator colored dots in each row // Type indicator colored dots in each row
circle(8, 10, 1, 0, 160, 0); // icon (green) bmpFillCircle(&canvas, 8, 10, 1, 0, 160, 0); // icon (green)
circle(8, 15, 1, 0, 0, 200); // text (blue) bmpFillCircle(&canvas, 8, 15, 1, 0, 0, 200); // text (blue)
circle(8, 20, 1, 200, 0, 0); // binary (red) bmpFillCircle(&canvas, 8, 20, 1, 200, 0, 0); // binary (red)
circle(8, 25, 1, 0, 0, 200); // text (blue) bmpFillCircle(&canvas, 8, 25, 1, 0, 0, 200); // text (blue)
// Short "name" bars in each row // Short "name" bars in each row
rect(11, 9, 10, 2, 60, 60, 60); bmpFillRect(&canvas, 11, 9, 10, 2, 60, 60, 60);
rect(11, 14, 14, 2, 60, 60, 60); bmpFillRect(&canvas, 11, 14, 14, 2, 60, 60, 60);
rect(11, 19, 8, 2, 60, 60, 60); bmpFillRect(&canvas, 11, 19, 8, 2, 60, 60, 60);
rect(11, 24, 12, 2, 60, 60, 60); bmpFillRect(&canvas, 11, 24, 12, 2, 60, 60, 60);
// Border // Border
rect(0, 0, 32, 1, 128, 128, 128); bmpFillRect(&canvas, 0, 0, 32, 1, 128, 128, 128);
rect(0, 31, 32, 1, 128, 128, 128); bmpFillRect(&canvas, 0, 31, 32, 1, 128, 128, 128);
rect(0, 0, 1, 32, 128, 128, 128); bmpFillRect(&canvas, 0, 0, 1, 32, 128, 128, 128);
rect(31, 0, 1, 32, 128, 128, 128); bmpFillRect(&canvas, 31, 0, 1, 32, 128, 128, 128);
} }
@ -455,6 +387,11 @@ int main(int argc, char **argv) {
const char *path = argv[1]; const char *path = argv[1];
const char *type = argv[2]; const char *type = argv[2];
if (!bmpInit(&canvas, ICON_W, ICON_H)) {
fprintf(stderr, "Out of memory\n");
return 1;
}
if (strcmp(type, "noicon") == 0) { if (strcmp(type, "noicon") == 0) {
iconNoIcon(); iconNoIcon();
} else if (strcmp(type, "clock") == 0) { } else if (strcmp(type, "clock") == 0) {
@ -477,10 +414,12 @@ int main(int argc, char **argv) {
iconResedit(); iconResedit();
} else { } else {
fprintf(stderr, "Unknown icon type: %s\n", type); fprintf(stderr, "Unknown icon type: %s\n", type);
bmpFree(&canvas);
return 1; return 1;
} }
writeBmp(path); bmpWrite(&canvas, path);
bmpFree(&canvas);
printf("Generated %s (%s)\n", path, type); printf("Generated %s (%s)\n", path, type);
return 0; return 0;
} }

Some files were not shown because too many files have changed in this diff Show more