Major code cleanup.
This commit is contained in:
parent
e6305db3b5
commit
48fb1c30ae
162 changed files with 30429 additions and 33139 deletions
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 $@ $<
|
||||||
|
|
||||||
|
|
|
||||||
116
src/apps/kpunch/dvxbasic/basBuild.c
Normal file
116
src/apps/kpunch/dvxbasic/basBuild.c
Normal 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;
|
||||||
|
}
|
||||||
65
src/apps/kpunch/dvxbasic/basBuild.h
Normal file
65
src/apps/kpunch/dvxbasic/basBuild.h
Normal 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
|
||||||
56
src/apps/kpunch/dvxbasic/basRes.h
Normal file
56
src/apps/kpunch/dvxbasic/basRes.h
Normal 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
|
||||||
17
src/apps/kpunch/dvxbasic/compiler/basEvents.h
Normal file
17
src/apps/kpunch/dvxbasic/compiler/basEvents.h
Normal 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
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
|
@ -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];
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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++];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
@ -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).
|
||||||
|
|
|
||||||
|
|
@ -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
147
src/libs/kpunch/libdvx/platform/dvxPlatformUtil.c
Normal file
147
src/libs/kpunch/libdvx/platform/dvxPlatformUtil.c
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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:
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -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
95
src/loader/stddclmr.h
Normal 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
|
||||||
|
|
@ -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
152
src/tools/bmpDraw.c
Normal 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
32
src/tools/bmpDraw.h
Normal 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
|
||||||
1735
src/tools/dvxhlpc.c
1735
src/tools/dvxhlpc.c
File diff suppressed because it is too large
Load diff
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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
Loading…
Add table
Reference in a new issue