More event work, code cleanup.
This commit is contained in:
parent
88746ec2ba
commit
6d75e4996a
13 changed files with 937 additions and 498 deletions
|
|
@ -66,7 +66,9 @@ static void freeListBoxItems(BasControlT *ctrl);
|
||||||
static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled);
|
static BasValueT getCommonProp(BasControlT *ctrl, const char *propName, bool *handled);
|
||||||
static BasValueT getIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, bool *handled);
|
static BasValueT getIfaceProp(const WgtIfaceT *iface, WidgetT *w, const char *propName, bool *handled);
|
||||||
static ListBoxItemsT *getListBoxItems(BasControlT *ctrl);
|
static ListBoxItemsT *getListBoxItems(BasControlT *ctrl);
|
||||||
|
static void onFormActivate(WindowT *win);
|
||||||
static void onFormClose(WindowT *win);
|
static void onFormClose(WindowT *win);
|
||||||
|
static void onFormDeactivate(WindowT *win);
|
||||||
static void onFormResize(WindowT *win, int32_t newW, int32_t newH);
|
static void onFormResize(WindowT *win, int32_t newW, int32_t newH);
|
||||||
static void onWidgetBlur(WidgetT *w);
|
static void onWidgetBlur(WidgetT *w);
|
||||||
static void onWidgetChange(WidgetT *w);
|
static void onWidgetChange(WidgetT *w);
|
||||||
|
|
@ -75,6 +77,7 @@ static void onWidgetDblClick(WidgetT *w);
|
||||||
static void onWidgetFocus(WidgetT *w);
|
static void onWidgetFocus(WidgetT *w);
|
||||||
static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii);
|
static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii);
|
||||||
static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift);
|
static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift);
|
||||||
|
static void onWidgetKeyUp(WidgetT *w, int32_t keyCode, int32_t shift);
|
||||||
static void onWidgetMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y);
|
static void onWidgetMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y);
|
||||||
static void onWidgetMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y);
|
static void onWidgetMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y);
|
||||||
static void onWidgetMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y);
|
static void onWidgetMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y);
|
||||||
|
|
@ -392,6 +395,57 @@ bool basFormRtFireEventArgs(BasFormRtT *rt, BasFormT *form, const char *ctrlName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// basFormRtFireEventWithCancel -- fire an event that has a Cancel
|
||||||
|
// parameter (first arg, Integer). Returns true if Cancel was set
|
||||||
|
// to non-zero by the event handler.
|
||||||
|
|
||||||
|
static bool basFormRtFireEventWithCancel(BasFormRtT *rt, BasFormT *form, const char *ctrlName, const char *eventName) {
|
||||||
|
if (!rt || !form || !rt->vm || !rt->module) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char handlerName[MAX_EVENT_NAME_LEN];
|
||||||
|
snprintf(handlerName, sizeof(handlerName), "%s_%s", ctrlName, eventName);
|
||||||
|
|
||||||
|
const BasProcEntryT *proc = basModuleFindProc(rt->module, handlerName);
|
||||||
|
|
||||||
|
if (!proc || proc->isFunction) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must accept 0 or 1 parameter
|
||||||
|
if (proc->paramCount != 0 && proc->paramCount != 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasFormT *prevForm = rt->currentForm;
|
||||||
|
rt->currentForm = form;
|
||||||
|
basVmSetCurrentForm(rt->vm, form);
|
||||||
|
|
||||||
|
bool cancelled = false;
|
||||||
|
|
||||||
|
if (proc->paramCount == 1) {
|
||||||
|
BasValueT args[1];
|
||||||
|
args[0] = basValLong(0); // Cancel = 0 (don't cancel)
|
||||||
|
|
||||||
|
BasValueT outArgs[1];
|
||||||
|
memset(outArgs, 0, sizeof(outArgs));
|
||||||
|
|
||||||
|
if (basVmCallSubWithArgsOut(rt->vm, proc->codeAddr, args, 1, outArgs, 1)) {
|
||||||
|
cancelled = (basValToNumber(outArgs[0]) != 0);
|
||||||
|
basValRelease(&outArgs[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
basVmCallSub(rt->vm, proc->codeAddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
rt->currentForm = prevForm;
|
||||||
|
basVmSetCurrentForm(rt->vm, prevForm);
|
||||||
|
return cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// basFormRtGetProp
|
// basFormRtGetProp
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -515,6 +569,8 @@ void *basFormRtLoadForm(void *ctx, const char *formName) {
|
||||||
snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
|
snprintf(form->name, BAS_MAX_CTRL_NAME, "%s", formName);
|
||||||
win->onClose = onFormClose;
|
win->onClose = onFormClose;
|
||||||
win->onResize = onFormResize;
|
win->onResize = onFormResize;
|
||||||
|
win->onFocus = onFormActivate;
|
||||||
|
win->onBlur = onFormDeactivate;
|
||||||
form->window = win;
|
form->window = win;
|
||||||
form->root = root;
|
form->root = root;
|
||||||
form->contentBox = NULL; // created lazily after Layout property is known
|
form->contentBox = NULL; // created lazily after Layout property is known
|
||||||
|
|
@ -679,6 +735,7 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen
|
||||||
widget->onBlur = onWidgetBlur;
|
widget->onBlur = onWidgetBlur;
|
||||||
widget->onKeyPress = onWidgetKeyPress;
|
widget->onKeyPress = onWidgetKeyPress;
|
||||||
widget->onKeyDown = onWidgetKeyDown;
|
widget->onKeyDown = onWidgetKeyDown;
|
||||||
|
widget->onKeyUp = onWidgetKeyUp;
|
||||||
widget->onMouseDown = onWidgetMouseDown;
|
widget->onMouseDown = onWidgetMouseDown;
|
||||||
widget->onMouseUp = onWidgetMouseUp;
|
widget->onMouseUp = onWidgetMouseUp;
|
||||||
widget->onMouseMove = onWidgetMouseMove;
|
widget->onMouseMove = onWidgetMouseMove;
|
||||||
|
|
@ -1296,9 +1353,6 @@ static ListBoxItemsT *getListBoxItems(BasControlT *ctrl) {
|
||||||
// Form_Unload event and stops the VM so the program exits.
|
// Form_Unload event and stops the VM so the program exits.
|
||||||
|
|
||||||
static void onFormClose(WindowT *win) {
|
static void onFormClose(WindowT *win) {
|
||||||
// Find which form owns this window
|
|
||||||
// The window's userData stores nothing useful, so we search
|
|
||||||
// by window pointer. We get the form runtime from sFormRt.
|
|
||||||
if (!sFormRt) {
|
if (!sFormRt) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1307,6 +1361,11 @@ static void onFormClose(WindowT *win) {
|
||||||
BasFormT *form = &sFormRt->forms[i];
|
BasFormT *form = &sFormRt->forms[i];
|
||||||
|
|
||||||
if (form->window == win) {
|
if (form->window == win) {
|
||||||
|
// QueryUnload: if Cancel is set, abort the close
|
||||||
|
if (basFormRtFireEventWithCancel(sFormRt, form, form->name, "QueryUnload")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
basFormRtFireEvent(sFormRt, form, form->name, "Unload");
|
basFormRtFireEvent(sFormRt, form, form->name, "Unload");
|
||||||
|
|
||||||
// Free control resources
|
// Free control resources
|
||||||
|
|
@ -1367,6 +1426,42 @@ static void onFormResize(WindowT *win, int32_t newW, int32_t newH) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// onFormActivate / onFormDeactivate
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void onFormActivate(WindowT *win) {
|
||||||
|
if (!sFormRt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < sFormRt->formCount; i++) {
|
||||||
|
BasFormT *form = &sFormRt->forms[i];
|
||||||
|
|
||||||
|
if (form->window == win) {
|
||||||
|
basFormRtFireEvent(sFormRt, form, form->name, "Activate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void onFormDeactivate(WindowT *win) {
|
||||||
|
if (!sFormRt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < sFormRt->formCount; i++) {
|
||||||
|
BasFormT *form = &sFormRt->forms[i];
|
||||||
|
|
||||||
|
if (form->window == win) {
|
||||||
|
basFormRtFireEvent(sFormRt, form, form->name, "Deactivate");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// onWidgetBlur
|
// onWidgetBlur
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -1408,7 +1503,9 @@ static void onWidgetChange(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rt) {
|
if (rt) {
|
||||||
basFormRtFireEvent(rt, ctrl->form, ctrl->name, "Change");
|
// Timer widgets fire "Timer" event, everything else fires "Change"
|
||||||
|
const char *evtName = (strcasecmp(ctrl->typeName, "Timer") == 0) ? "Timer" : "Change";
|
||||||
|
basFormRtFireEvent(rt, ctrl->form, ctrl->name, evtName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1521,6 +1618,28 @@ static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// onWidgetKeyUp
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void onWidgetKeyUp(WidgetT *w, int32_t keyCode, int32_t shift) {
|
||||||
|
BasControlT *ctrl = (BasControlT *)w->userData;
|
||||||
|
|
||||||
|
if (!ctrl || !ctrl->form || !ctrl->form->vm) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx;
|
||||||
|
|
||||||
|
if (rt) {
|
||||||
|
BasValueT args[2];
|
||||||
|
args[0] = basValLong(keyCode);
|
||||||
|
args[1] = basValLong(shift);
|
||||||
|
basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "KeyUp", args, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// onWidgetMouseDown / onWidgetMouseUp / onWidgetMouseMove
|
// onWidgetMouseDown / onWidgetMouseUp / onWidgetMouseMove
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -70,27 +70,16 @@ bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
|
||||||
vm->pc = codeAddr;
|
vm->pc = codeAddr;
|
||||||
vm->running = true;
|
vm->running = true;
|
||||||
|
|
||||||
// Step until the SUB returns (callDepth drops back)
|
// Run until the SUB returns (callDepth drops back).
|
||||||
int32_t steps = 0;
|
// No step limit -- event handlers must run to completion.
|
||||||
|
|
||||||
while (vm->running && vm->callDepth > savedCallDepth) {
|
while (vm->running && vm->callDepth > savedCallDepth) {
|
||||||
if (vm->stepLimit > 0 && steps >= vm->stepLimit) {
|
|
||||||
// Unwind the call and restore state
|
|
||||||
vm->callDepth = savedCallDepth;
|
|
||||||
vm->pc = savedPc;
|
|
||||||
vm->running = savedRunning;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasVmResultE result = basVmStep(vm);
|
BasVmResultE result = basVmStep(vm);
|
||||||
steps++;
|
|
||||||
|
|
||||||
if (result == BAS_VM_HALTED) {
|
if (result == BAS_VM_HALTED) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result != BAS_VM_OK) {
|
if (result != BAS_VM_OK) {
|
||||||
// Runtime error in the event handler
|
|
||||||
vm->pc = savedPc;
|
vm->pc = savedPc;
|
||||||
vm->callDepth = savedCallDepth;
|
vm->callDepth = savedCallDepth;
|
||||||
vm->running = savedRunning;
|
vm->running = savedRunning;
|
||||||
|
|
@ -98,7 +87,6 @@ bool basVmCallSub(BasVmT *vm, int32_t codeAddr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore VM state
|
|
||||||
vm->pc = savedPc;
|
vm->pc = savedPc;
|
||||||
vm->running = savedRunning;
|
vm->running = savedRunning;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -139,18 +127,8 @@ bool basVmCallSubWithArgs(BasVmT *vm, int32_t codeAddr, const BasValueT *args, i
|
||||||
vm->pc = codeAddr;
|
vm->pc = codeAddr;
|
||||||
vm->running = true;
|
vm->running = true;
|
||||||
|
|
||||||
int32_t steps = 0;
|
|
||||||
|
|
||||||
while (vm->running && vm->callDepth > savedCallDepth) {
|
while (vm->running && vm->callDepth > savedCallDepth) {
|
||||||
if (vm->stepLimit > 0 && steps >= vm->stepLimit) {
|
|
||||||
vm->callDepth = savedCallDepth;
|
|
||||||
vm->pc = savedPc;
|
|
||||||
vm->running = savedRunning;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasVmResultE result = basVmStep(vm);
|
BasVmResultE result = basVmStep(vm);
|
||||||
steps++;
|
|
||||||
|
|
||||||
if (result == BAS_VM_HALTED) {
|
if (result == BAS_VM_HALTED) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -170,6 +148,71 @@ 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) {
|
||||||
|
if (!vm || !vm->module) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codeAddr < 0 || codeAddr >= vm->module->codeLen) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vm->callDepth >= BAS_VM_CALL_STACK_SIZE - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t savedPc = vm->pc;
|
||||||
|
int32_t savedCallDepth = vm->callDepth;
|
||||||
|
bool savedRunning = vm->running;
|
||||||
|
|
||||||
|
BasCallFrameT *frame = &vm->callStack[vm->callDepth++];
|
||||||
|
frame->returnPc = savedPc;
|
||||||
|
frame->localCount = BAS_VM_MAX_LOCALS;
|
||||||
|
memset(frame->locals, 0, sizeof(frame->locals));
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < argCount && i < BAS_VM_MAX_LOCALS; i++) {
|
||||||
|
frame->locals[i] = basValCopy(args[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vm->pc = codeAddr;
|
||||||
|
vm->running = true;
|
||||||
|
|
||||||
|
while (vm->running && vm->callDepth > savedCallDepth) {
|
||||||
|
BasVmResultE result = basVmStep(vm);
|
||||||
|
|
||||||
|
if (result == BAS_VM_HALTED) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != BAS_VM_OK) {
|
||||||
|
vm->pc = savedPc;
|
||||||
|
vm->callDepth = savedCallDepth;
|
||||||
|
vm->running = savedRunning;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read back modified locals before restoring state.
|
||||||
|
// The frame at savedCallDepth still has the data even
|
||||||
|
// though callDepth was decremented by RET.
|
||||||
|
if (outArgs && outCount > 0) {
|
||||||
|
BasCallFrameT *doneFrame = &vm->callStack[savedCallDepth];
|
||||||
|
|
||||||
|
for (int32_t i = 0; i < outCount && i < BAS_VM_MAX_LOCALS; i++) {
|
||||||
|
outArgs[i] = basValCopy(doneFrame->locals[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vm->pc = savedPc;
|
||||||
|
vm->running = savedRunning;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// basVmCreate
|
// basVmCreate
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -364,4 +364,9 @@ const char *basVmGetError(const BasVmT *vm);
|
||||||
bool basVmCallSub(BasVmT *vm, int32_t codeAddr);
|
bool basVmCallSub(BasVmT *vm, int32_t codeAddr);
|
||||||
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);
|
||||||
|
|
||||||
|
// Call a SUB and read back modified argument values.
|
||||||
|
// outArgs receives copies of the locals after the SUB returns.
|
||||||
|
// outCount specifies how many args to read back.
|
||||||
|
bool basVmCallSubWithArgsOut(BasVmT *vm, int32_t codeAddr, const BasValueT *args, int32_t argCount, BasValueT *outArgs, int32_t outCount);
|
||||||
|
|
||||||
#endif // DVXBASIC_VM_H
|
#endif // DVXBASIC_VM_H
|
||||||
|
|
|
||||||
|
|
@ -2273,6 +2273,167 @@ int main(void) {
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: Bare sub call (no CALL keyword, no parens)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
runProgram("Bare sub call",
|
||||||
|
"Sub Greet ()\n"
|
||||||
|
" PRINT \"hello\"\n"
|
||||||
|
"End Sub\n"
|
||||||
|
"\n"
|
||||||
|
"Greet\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: Bare sub call with forward reference
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
runProgram("Bare sub call forward ref",
|
||||||
|
"DoWork\n"
|
||||||
|
"\n"
|
||||||
|
"Sub DoWork ()\n"
|
||||||
|
" PRINT \"worked\"\n"
|
||||||
|
"End Sub\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: Unresolved forward reference error
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
{
|
||||||
|
printf("=== Unresolved forward reference ===\n");
|
||||||
|
|
||||||
|
const char *src = "NeverDefined\n";
|
||||||
|
int32_t len = (int32_t)strlen(src);
|
||||||
|
BasParserT parser;
|
||||||
|
basParserInit(&parser, src, len);
|
||||||
|
|
||||||
|
if (!basParse(&parser)) {
|
||||||
|
printf("Correctly caught: %s\n", parser.error);
|
||||||
|
} else {
|
||||||
|
printf("ERROR: should have failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
basParserFree(&parser);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: END statement terminates (distinct from HALT)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
runProgram("END statement",
|
||||||
|
"PRINT \"before\"\n"
|
||||||
|
"END\n"
|
||||||
|
"PRINT \"after\"\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: Nested UDT field store
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
runProgram("Nested UDT store and load",
|
||||||
|
"TYPE InnerT\n"
|
||||||
|
" val AS INTEGER\n"
|
||||||
|
"END TYPE\n"
|
||||||
|
"\n"
|
||||||
|
"TYPE OuterT\n"
|
||||||
|
" child AS InnerT\n"
|
||||||
|
" name AS STRING\n"
|
||||||
|
"END TYPE\n"
|
||||||
|
"\n"
|
||||||
|
"DIM o AS OuterT\n"
|
||||||
|
"o.name = \"test\"\n"
|
||||||
|
"o.child.val = 42\n"
|
||||||
|
"PRINT o.name\n"
|
||||||
|
"PRINT o.child.val\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: Array of UDT field store
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
runProgram("Array of UDT field store",
|
||||||
|
"TYPE PointT\n"
|
||||||
|
" x AS INTEGER\n"
|
||||||
|
" y AS INTEGER\n"
|
||||||
|
"END TYPE\n"
|
||||||
|
"\n"
|
||||||
|
"DIM pts(5) AS PointT\n"
|
||||||
|
"pts(1).x = 10\n"
|
||||||
|
"pts(1).y = 20\n"
|
||||||
|
"pts(3).x = 30\n"
|
||||||
|
"pts(3).y = 40\n"
|
||||||
|
"PRINT pts(1).x; pts(1).y\n"
|
||||||
|
"PRINT pts(3).x; pts(3).y\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: VB operator precedence (^ binds tighter than unary -)
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
runProgram("Exponent precedence",
|
||||||
|
"PRINT -2 ^ 2\n" // -(2^2) = -4
|
||||||
|
"PRINT (-2) ^ 2\n" // (-2)^2 = 4
|
||||||
|
"PRINT 3 ^ 2 + 1\n" // 9 + 1 = 10
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: Me.Show compiles in a Sub
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
{
|
||||||
|
printf("=== Me.Show in Sub ===\n");
|
||||||
|
|
||||||
|
const char *src =
|
||||||
|
"Sub Form1_Load ()\n"
|
||||||
|
" Me.Show\n"
|
||||||
|
" Me.Caption = \"Hello\"\n"
|
||||||
|
" Me.Hide\n"
|
||||||
|
"End Sub\n";
|
||||||
|
|
||||||
|
int32_t len = (int32_t)strlen(src);
|
||||||
|
BasParserT parser;
|
||||||
|
basParserInit(&parser, src, len);
|
||||||
|
|
||||||
|
if (!basParse(&parser)) {
|
||||||
|
printf("COMPILE ERROR: %s\n", parser.error);
|
||||||
|
} else {
|
||||||
|
printf("OK\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
basParserFree(&parser);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: OPTION EXPLICIT with valid declaration
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
runProgram("OPTION EXPLICIT valid",
|
||||||
|
"OPTION EXPLICIT\n"
|
||||||
|
"DIM x AS INTEGER\n"
|
||||||
|
"x = 42\n"
|
||||||
|
"PRINT x\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Coverage: STATIC variable retains value
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
runProgram("STATIC in sub",
|
||||||
|
"Sub Counter ()\n"
|
||||||
|
" STATIC n AS INTEGER\n"
|
||||||
|
" n = n + 1\n"
|
||||||
|
" PRINT n\n"
|
||||||
|
"End Sub\n"
|
||||||
|
"\n"
|
||||||
|
"Counter\n"
|
||||||
|
"Counter\n"
|
||||||
|
"Counter\n"
|
||||||
|
);
|
||||||
|
|
||||||
printf("All tests complete.\n");
|
printf("All tests complete.\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3005,6 +3005,20 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
continue;
|
continue;
|
||||||
nextKey:;
|
nextKey:;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Key-up events: dispatch to the focused window's onKeyUp callback
|
||||||
|
PlatformKeyEventT upEvt;
|
||||||
|
|
||||||
|
while (platformKeyUpRead(&upEvt)) {
|
||||||
|
if (ctx->stack.focusedIdx >= 0) {
|
||||||
|
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||||
|
int32_t mod = platformKeyboardGetModifiers();
|
||||||
|
|
||||||
|
if (win->onKeyUp) {
|
||||||
|
win->onKeyUp(win, upEvt.scancode | 0x100, mod);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -4182,6 +4196,7 @@ int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_
|
||||||
memset(ctx, 0, sizeof(*ctx));
|
memset(ctx, 0, sizeof(*ctx));
|
||||||
|
|
||||||
platformInit();
|
platformInit();
|
||||||
|
platformKeyUpInit();
|
||||||
|
|
||||||
// Enumerate available video modes BEFORE setting one. Some VBE
|
// Enumerate available video modes BEFORE setting one. Some VBE
|
||||||
// BIOSes return a stale or truncated mode list once a graphics
|
// BIOSes return a stale or truncated mode list once a graphics
|
||||||
|
|
@ -4784,6 +4799,8 @@ int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void dvxShutdown(AppContextT *ctx) {
|
void dvxShutdown(AppContextT *ctx) {
|
||||||
|
platformKeyUpShutdown();
|
||||||
|
|
||||||
// Destroy all remaining windows
|
// Destroy all remaining windows
|
||||||
while (ctx->stack.count > 0) {
|
while (ctx->stack.count > 0) {
|
||||||
wmDestroyWindow(&ctx->stack, ctx->stack.windows[ctx->stack.count - 1]);
|
wmDestroyWindow(&ctx->stack, ctx->stack.windows[ctx->stack.count - 1]);
|
||||||
|
|
|
||||||
|
|
@ -560,6 +560,7 @@ typedef struct WindowT {
|
||||||
void *userData;
|
void *userData;
|
||||||
void (*onPaint)(struct WindowT *win, RectT *dirtyArea);
|
void (*onPaint)(struct WindowT *win, RectT *dirtyArea);
|
||||||
void (*onKey)(struct WindowT *win, int32_t key, int32_t mod);
|
void (*onKey)(struct WindowT *win, int32_t key, int32_t mod);
|
||||||
|
void (*onKeyUp)(struct WindowT *win, int32_t scancode, int32_t mod);
|
||||||
void (*onMouse)(struct WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
void (*onMouse)(struct WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||||
void (*onResize)(struct WindowT *win, int32_t newW, int32_t newH);
|
void (*onResize)(struct WindowT *win, int32_t newW, int32_t newH);
|
||||||
void (*onClose)(struct WindowT *win);
|
void (*onClose)(struct WindowT *win);
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,7 @@ void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);
|
||||||
|
|
||||||
void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
|
void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
|
||||||
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
|
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
|
||||||
|
void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod);
|
||||||
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);
|
||||||
void widgetOnPaint(WindowT *win, RectT *dirtyArea);
|
void widgetOnPaint(WindowT *win, RectT *dirtyArea);
|
||||||
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
|
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,19 @@ int32_t platformKeyboardGetModifiers(void);
|
||||||
// them unambiguously.
|
// them unambiguously.
|
||||||
bool platformKeyboardRead(PlatformKeyEventT *evt);
|
bool platformKeyboardRead(PlatformKeyEventT *evt);
|
||||||
|
|
||||||
|
// Non-blocking read of the next key-up event. Returns true if a
|
||||||
|
// key release was detected. On DOS this requires an INT 9 hook to
|
||||||
|
// detect break codes (scan code with bit 7 set). On Linux this
|
||||||
|
// uses SDL_KEYUP events.
|
||||||
|
bool platformKeyUpRead(PlatformKeyEventT *evt);
|
||||||
|
|
||||||
|
// Install/remove the INT 9 hook for key-up detection. On DOS this
|
||||||
|
// chains the hardware keyboard interrupt. On Linux this is a no-op
|
||||||
|
// (SDL provides key-up events natively). Call Init before using
|
||||||
|
// platformKeyUpRead, and Shutdown before exit.
|
||||||
|
void platformKeyUpInit(void);
|
||||||
|
void platformKeyUpShutdown(void);
|
||||||
|
|
||||||
// Translate an Alt+key scancode to its corresponding ASCII character.
|
// Translate an Alt+key scancode to its corresponding ASCII character.
|
||||||
// When Alt is held, DOS doesn't provide the ASCII value -- only the
|
// When Alt is held, DOS doesn't provide the ASCII value -- only the
|
||||||
// scancode. This function contains a lookup table mapping scancodes
|
// scancode. This function contains a lookup table mapping scancodes
|
||||||
|
|
|
||||||
|
|
@ -1430,6 +1430,87 @@ bool platformKeyboardRead(PlatformKeyEventT *evt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Key-up detection via INT 9 hook
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// The BIOS keyboard interrupt (INT 16h) only reports key presses.
|
||||||
|
// To detect key releases we chain INT 9 (the hardware keyboard IRQ)
|
||||||
|
// and read the scan code directly from port 0x60. Break codes have
|
||||||
|
// bit 7 set. We queue them in a small ring buffer that
|
||||||
|
// platformKeyUpRead drains.
|
||||||
|
|
||||||
|
#define KEYUP_BUF_SIZE 16
|
||||||
|
|
||||||
|
static PlatformKeyEventT sKeyUpBuf[KEYUP_BUF_SIZE];
|
||||||
|
static volatile int32_t sKeyUpHead = 0;
|
||||||
|
static volatile int32_t sKeyUpTail = 0;
|
||||||
|
static _go32_dpmi_seginfo sOldInt9;
|
||||||
|
static _go32_dpmi_seginfo sNewInt9;
|
||||||
|
static bool sKeyUpInstalled = false;
|
||||||
|
|
||||||
|
|
||||||
|
// INT 9 handler: reads port 0x60 and queues break codes.
|
||||||
|
// DJGPP chains to the original handler automatically when using
|
||||||
|
// _go32_dpmi_chain_protected_mode_interrupt_vector.
|
||||||
|
|
||||||
|
static void int9Handler(void) {
|
||||||
|
uint8_t scan = inportb(0x60);
|
||||||
|
|
||||||
|
// Break code: bit 7 set and not the 0xE0 prefix
|
||||||
|
if ((scan & 0x80) && scan != 0xE0) {
|
||||||
|
int32_t next = (sKeyUpHead + 1) % KEYUP_BUF_SIZE;
|
||||||
|
|
||||||
|
if (next != sKeyUpTail) {
|
||||||
|
sKeyUpBuf[sKeyUpHead].scancode = scan & 0x7F;
|
||||||
|
sKeyUpBuf[sKeyUpHead].ascii = 0;
|
||||||
|
sKeyUpHead = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original handler is called automatically by DJGPP's chain wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void platformKeyUpInit(void) {
|
||||||
|
if (sKeyUpInstalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_go32_dpmi_get_protected_mode_interrupt_vector(9, &sOldInt9);
|
||||||
|
|
||||||
|
sNewInt9.pm_offset = (unsigned long)int9Handler;
|
||||||
|
sNewInt9.pm_selector = _go32_my_cs();
|
||||||
|
|
||||||
|
// Chain: our handler runs first, then DJGPP automatically
|
||||||
|
// calls the original handler via an IRET wrapper.
|
||||||
|
_go32_dpmi_chain_protected_mode_interrupt_vector(9, &sNewInt9);
|
||||||
|
|
||||||
|
sKeyUpInstalled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void platformKeyUpShutdown(void) {
|
||||||
|
if (!sKeyUpInstalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_go32_dpmi_set_protected_mode_interrupt_vector(9, &sOldInt9);
|
||||||
|
sKeyUpInstalled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool platformKeyUpRead(PlatformKeyEventT *evt) {
|
||||||
|
if (sKeyUpTail == sKeyUpHead) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*evt = sKeyUpBuf[sKeyUpTail];
|
||||||
|
sKeyUpTail = (sKeyUpTail + 1) % KEYUP_BUF_SIZE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// platformLineEnding
|
// platformLineEnding
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -2364,6 +2445,9 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
||||||
DXE_EXPORT(platformInstallCrashHandler)
|
DXE_EXPORT(platformInstallCrashHandler)
|
||||||
DXE_EXPORT(platformKeyboardGetModifiers)
|
DXE_EXPORT(platformKeyboardGetModifiers)
|
||||||
DXE_EXPORT(platformKeyboardRead)
|
DXE_EXPORT(platformKeyboardRead)
|
||||||
|
DXE_EXPORT(platformKeyUpInit)
|
||||||
|
DXE_EXPORT(platformKeyUpRead)
|
||||||
|
DXE_EXPORT(platformKeyUpShutdown)
|
||||||
DXE_EXPORT(platformLineEnding)
|
DXE_EXPORT(platformLineEnding)
|
||||||
DXE_EXPORT(platformLogCrashDetail)
|
DXE_EXPORT(platformLogCrashDetail)
|
||||||
DXE_EXPORT(platformMkdirRecursive)
|
DXE_EXPORT(platformMkdirRecursive)
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,39 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetOnKeyUp
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetOnKeyUp(WindowT *win, int32_t scancode, int32_t mod) {
|
||||||
|
WidgetT *root = win->widgetRoot;
|
||||||
|
|
||||||
|
if (!root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
WidgetT *focus = sFocusedWidget;
|
||||||
|
|
||||||
|
if (!focus || !focus->focused || focus->window != win) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!focus->enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focus->onKeyUp) {
|
||||||
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
|
int32_t prevAppId = ctx->currentAppId;
|
||||||
|
ctx->currentAppId = win->appId;
|
||||||
|
|
||||||
|
focus->onKeyUp(focus, scancode, mod);
|
||||||
|
|
||||||
|
ctx->currentAppId = prevAppId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetOnMouse
|
// widgetOnMouse
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -307,6 +307,7 @@ WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
||||||
win->onPaint = widgetOnPaint;
|
win->onPaint = widgetOnPaint;
|
||||||
win->onMouse = widgetOnMouse;
|
win->onMouse = widgetOnMouse;
|
||||||
win->onKey = widgetOnKey;
|
win->onKey = widgetOnKey;
|
||||||
|
win->onKeyUp = widgetOnKeyUp;
|
||||||
win->onResize = widgetOnResize;
|
win->onResize = widgetOnResize;
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
|
|
|
||||||
|
|
@ -166,6 +166,15 @@ void wgtTimerStop(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void wgtTimerSetEnabled(WidgetT *w, bool enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
wgtTimerStart(w);
|
||||||
|
} else {
|
||||||
|
wgtTimerStop(w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void wgtUpdateTimers(void) {
|
void wgtUpdateTimers(void) {
|
||||||
clock_t now = clock();
|
clock_t now = clock();
|
||||||
|
|
||||||
|
|
@ -220,7 +229,7 @@ static const struct {
|
||||||
};
|
};
|
||||||
|
|
||||||
static const WgtPropDescT sProps[] = {
|
static const WgtPropDescT sProps[] = {
|
||||||
{ "Enabled", WGT_IFACE_BOOL, (void *)wgtTimerIsRunning, NULL },
|
{ "Enabled", WGT_IFACE_BOOL, (void *)wgtTimerIsRunning, (void *)wgtTimerSetEnabled },
|
||||||
{ "Interval", WGT_IFACE_INT, NULL, (void *)wgtTimerSetInterval }
|
{ "Interval", WGT_IFACE_INT, NULL, (void *)wgtTimerSetInterval }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue