diff --git a/apps/dvxbasic/compiler/opcodes.h b/apps/dvxbasic/compiler/opcodes.h index eb16230..78b4c8e 100644 --- a/apps/dvxbasic/compiler/opcodes.h +++ b/apps/dvxbasic/compiler/opcodes.h @@ -193,6 +193,7 @@ #define OP_CREATE_CTRL 0x8B // pop name, pop typeName, pop formRef, push controlRef #define OP_FIND_CTRL 0x8C // pop ctrlName, pop formRef, push controlRef #define OP_CTRL_REF 0x8D // [uint16 nameConstIdx] push named control on current form +#define OP_FIND_CTRL_IDX 0x8E // pop index, pop ctrlName, pop formRef, push ctrlRef // ============================================================ // Array / misc diff --git a/apps/dvxbasic/compiler/parser.c b/apps/dvxbasic/compiler/parser.c index 7046aad..9c5633b 100644 --- a/apps/dvxbasic/compiler/parser.c +++ b/apps/dvxbasic/compiler/parser.c @@ -97,6 +97,7 @@ static const BuiltinFuncT builtinFuncs[] = { static void addPredefConst(BasParserT *p, const char *name, int32_t val); static void addPredefConsts(BasParserT *p); static void advance(BasParserT *p); +static bool checkCtrlArrayAccess(BasParserT *p); static bool check(BasParserT *p, BasTokenTypeE type); static bool checkKeyword(BasParserT *p, const char *kw); static bool checkKeywordText(const char *text, const char *kw); @@ -247,6 +248,45 @@ static void addPredefConsts(BasParserT *p) { } +// Check if current token '(' is followed by a matching ')' then '.'. +// This disambiguates control array access Name(idx).Property from +// function calls Name(args). Saves and restores lexer state. +// Must be called when current token is TOK_LPAREN. +static bool checkCtrlArrayAccess(BasParserT *p) { + BasLexerT savedLex = p->lex; + bool savedErr = p->hasError; + + basLexerNext(&p->lex); // consume ( + + int32_t depth = 1; + + while (depth > 0 && p->lex.token.type != TOK_EOF && !p->hasError) { + if (p->lex.token.type == TOK_LPAREN) { + depth++; + } else if (p->lex.token.type == TOK_RPAREN) { + depth--; + if (depth == 0) { + break; + } + } + basLexerNext(&p->lex); + } + + // Advance past the closing ) + if (p->lex.token.type == TOK_RPAREN) { + basLexerNext(&p->lex); + } + + bool dotFollows = (p->lex.token.type == TOK_DOT); + + // Restore lexer state + p->lex = savedLex; + p->hasError = savedErr; + + return dotFollows; +} + + static void advance(BasParserT *p) { if (p->hasError) { return; @@ -1361,8 +1401,41 @@ static void parsePrimary(BasParserT *p) { } return; } - // Unknown function -- forward reference, assume it's a function + // Unknown identifier + '(' -- could be forward-ref function or + // control array access: Name(index).Property if (sym == NULL) { + if (checkCtrlArrayAccess(p)) { + // Control array read: Name(idx).Property + expect(p, TOK_LPAREN); + + basEmit8(&p->cg, OP_PUSH_INT16); + basEmit16(&p->cg, 0); // NULL form ref = current form + uint16_t ctrlNameIdx = basAddConstant(&p->cg, name, (int32_t)strlen(name)); + basEmit8(&p->cg, OP_PUSH_STR); + basEmitU16(&p->cg, ctrlNameIdx); + + parseExpression(p); // index expression + expect(p, TOK_RPAREN); + basEmit8(&p->cg, OP_FIND_CTRL_IDX); + + expect(p, TOK_DOT); + if (!check(p, TOK_IDENT)) { + errorExpected(p, "property name"); + return; + } + char memberName[BAS_MAX_TOKEN_LEN]; + strncpy(memberName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1); + memberName[BAS_MAX_TOKEN_LEN - 1] = '\0'; + advance(p); + + uint16_t propNameIdx = basAddConstant(&p->cg, memberName, (int32_t)strlen(memberName)); + basEmit8(&p->cg, OP_PUSH_STR); + basEmitU16(&p->cg, propNameIdx); + basEmit8(&p->cg, OP_LOAD_PROP); + return; + } + + // Not a control array -- forward-ref function call sym = basSymTabAdd(&p->sym, name, SYM_FUNCTION, suffixToType(name)); if (sym == NULL) { error(p, "Symbol table full"); @@ -1673,7 +1746,7 @@ static void parseAssignOrCall(BasParserT *p) { return; } - // Array assignment: var(index) = expr + // Array assignment, sub/function call, or control array access: var(index) if (check(p, TOK_LPAREN)) { // Could be a function call as a statement (discard result) // or array assignment @@ -1685,6 +1758,60 @@ static void parseAssignOrCall(BasParserT *p) { return; } + // Control array property/method: Name(idx).Prop = expr OR Name(idx).Method args + if (sym == NULL && checkCtrlArrayAccess(p)) { + expect(p, TOK_LPAREN); + + basEmit8(&p->cg, OP_PUSH_INT16); + basEmit16(&p->cg, 0); // NULL form ref = current form + uint16_t ctrlNameIdx = basAddConstant(&p->cg, name, (int32_t)strlen(name)); + basEmit8(&p->cg, OP_PUSH_STR); + basEmitU16(&p->cg, ctrlNameIdx); + + parseExpression(p); // index expression + expect(p, TOK_RPAREN); + basEmit8(&p->cg, OP_FIND_CTRL_IDX); + + expect(p, TOK_DOT); + if (!check(p, TOK_IDENT) && !check(p, TOK_SHOW) && !check(p, TOK_HIDE)) { + errorExpected(p, "property or method name"); + return; + } + char memberName[BAS_MAX_TOKEN_LEN]; + strncpy(memberName, p->lex.token.text, BAS_MAX_TOKEN_LEN - 1); + memberName[BAS_MAX_TOKEN_LEN - 1] = '\0'; + advance(p); + + if (check(p, TOK_EQ)) { + // Property assignment: Name(idx).Prop = expr + advance(p); + uint16_t propNameIdx = basAddConstant(&p->cg, memberName, (int32_t)strlen(memberName)); + basEmit8(&p->cg, OP_PUSH_STR); + basEmitU16(&p->cg, propNameIdx); + parseExpression(p); + basEmit8(&p->cg, OP_STORE_PROP); + } else { + // Method call: Name(idx).Method args + uint16_t methodNameIdx = basAddConstant(&p->cg, memberName, (int32_t)strlen(memberName)); + basEmit8(&p->cg, OP_PUSH_STR); + basEmitU16(&p->cg, methodNameIdx); + + int32_t argc = 0; + while (!check(p, TOK_NEWLINE) && !check(p, TOK_COLON) && !check(p, TOK_EOF) && !check(p, TOK_ELSE)) { + if (argc > 0 && check(p, TOK_COMMA)) { + advance(p); + } + parseExpression(p); + argc++; + } + + basEmit8(&p->cg, OP_CALL_METHOD); + basEmit8(&p->cg, (uint8_t)argc); + basEmit8(&p->cg, OP_POP); // discard return value + } + return; + } + // Array element assignment if (sym == NULL) { sym = ensureVariable(p, name); @@ -4509,9 +4636,27 @@ static void parseStatement(BasParserT *p) { // Me.Hide basEmit8(&p->cg, OP_ME_REF); basEmit8(&p->cg, OP_HIDE_FORM); - } else if (check(p, TOK_DOT)) { - // Me.CtrlName.Property = expr (control access via Me) - advance(p); // consume second DOT + } else if (check(p, TOK_LPAREN) || check(p, TOK_DOT)) { + // Me.CtrlName(idx).Property OR Me.CtrlName.Property + bool hasIndex = check(p, TOK_LPAREN); + + // Push form ref (Me), ctrl name + basEmit8(&p->cg, OP_ME_REF); + uint16_t ctrlIdx = basAddConstant(&p->cg, meMember, (int32_t)strlen(meMember)); + basEmit8(&p->cg, OP_PUSH_STR); + basEmitU16(&p->cg, ctrlIdx); + + if (hasIndex) { + // Me.CtrlName(idx) -- parse index, use FIND_CTRL_IDX + expect(p, TOK_LPAREN); + parseExpression(p); + expect(p, TOK_RPAREN); + basEmit8(&p->cg, OP_FIND_CTRL_IDX); + } else { + basEmit8(&p->cg, OP_FIND_CTRL); + } + + expect(p, TOK_DOT); if (!check(p, TOK_IDENT)) { errorExpected(p, "property name"); break; @@ -4521,13 +4666,6 @@ static void parseStatement(BasParserT *p) { propName[BAS_MAX_TOKEN_LEN - 1] = '\0'; advance(p); - // Push form ref (Me), ctrl name, FIND_CTRL - basEmit8(&p->cg, OP_ME_REF); - uint16_t ctrlIdx = basAddConstant(&p->cg, meMember, (int32_t)strlen(meMember)); - basEmit8(&p->cg, OP_PUSH_STR); - basEmitU16(&p->cg, ctrlIdx); - basEmit8(&p->cg, OP_FIND_CTRL); - if (check(p, TOK_EQ)) { // Property assignment advance(p); diff --git a/apps/dvxbasic/formrt/formrt.c b/apps/dvxbasic/formrt/formrt.c index dd07baa..19028c1 100644 --- a/apps/dvxbasic/formrt/formrt.c +++ b/apps/dvxbasic/formrt/formrt.c @@ -101,8 +101,9 @@ void basFormRtBindVm(BasFormRtT *rt) { ui.setProp = basFormRtSetProp; ui.callMethod = basFormRtCallMethod; ui.createCtrl = basFormRtCreateCtrl; - ui.findCtrl = basFormRtFindCtrl; - ui.loadForm = basFormRtLoadForm; + ui.findCtrl = basFormRtFindCtrl; + ui.findCtrlIdx = basFormRtFindCtrlIdx; + ui.loadForm = basFormRtLoadForm; ui.unloadForm = basFormRtUnloadForm; ui.showForm = basFormRtShowForm; ui.hideForm = basFormRtHideForm; @@ -269,6 +270,7 @@ void *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const // Initialize control entry BasControlT entry; memset(&entry, 0, sizeof(entry)); + entry.index = -1; snprintf(entry.name, BAS_MAX_CTRL_NAME, "%s", ctrlName); arrput(form->controls, entry); form->controlCount = (int32_t)arrlen(form->controls); @@ -345,6 +347,28 @@ void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName) { } +// ============================================================ +// basFormRtFindCtrlIdx +// ============================================================ + +void *basFormRtFindCtrlIdx(void *ctx, void *formRef, const char *ctrlName, int32_t index) { + (void)ctx; + BasFormT *form = (BasFormT *)formRef; + + if (!form) { + return NULL; + } + + for (int32_t i = 0; i < form->controlCount; i++) { + if (form->controls[i].index == index && strcasecmp(form->controls[i].name, ctrlName) == 0) { + return &form->controls[i]; + } + } + + return NULL; +} + + // ============================================================ // basFormRtFireEvent // ============================================================ @@ -719,6 +743,7 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen memset(&ctrlEntry, 0, sizeof(ctrlEntry)); snprintf(ctrlEntry.name, BAS_MAX_CTRL_NAME, "%s", ctrlName); snprintf(ctrlEntry.typeName, BAS_MAX_CTRL_NAME, "%s", typeName); + ctrlEntry.index = -1; ctrlEntry.widget = widget; ctrlEntry.form = form; ctrlEntry.iface = wgtGetIface(wgtTypeName); @@ -787,6 +812,12 @@ BasFormT *basFormRtLoadFrm(BasFormRtT *rt, const char *source, int32_t sourceLen } if (current) { + // Control array index is stored on the struct, not as a widget property + if (strcasecmp(key, "Index") == 0) { + current->index = atoi(value); + continue; + } + BasValueT val; if (value[0] == '"') { @@ -1463,126 +1494,111 @@ static void onFormDeactivate(WindowT *win) { // ============================================================ -// onWidgetBlur +// fireCtrlEvent -- fire event, prepending Index for array members +// ============================================================ + +#define MAX_FIRE_ARGS 8 + +static void fireCtrlEvent(BasFormRtT *rt, BasControlT *ctrl, const char *eventName, const BasValueT *args, int32_t argCount) { + if (ctrl->index >= 0) { + // Control array element: prepend Index as first argument + BasValueT allArgs[MAX_FIRE_ARGS]; + allArgs[0] = basValLong(ctrl->index); + + for (int32_t i = 0; i < argCount && i < MAX_FIRE_ARGS - 1; i++) { + allArgs[i + 1] = args[i]; + } + + basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, eventName, allArgs, argCount + 1); + } else if (argCount > 0 && args) { + basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, eventName, args, argCount); + } else { + basFormRtFireEvent(rt, ctrl->form, ctrl->name, eventName); + } +} + + +// ============================================================ +// Widget event callbacks // ============================================================ static void onWidgetBlur(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; - if (!ctrl || !ctrl->form) { + if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } - BasFormRtT *rt = NULL; - - if (ctrl->form->vm) { - rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; - } + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { - basFormRtFireEvent(rt, ctrl->form, ctrl->name, "LostFocus"); + fireCtrlEvent(rt, ctrl, "LostFocus", NULL, 0); } } -// ============================================================ -// onWidgetChange -// ============================================================ - static void onWidgetChange(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; - if (!ctrl || !ctrl->form) { + if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } - BasFormRtT *rt = NULL; - - if (ctrl->form->vm) { - rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; - } + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { // 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); + fireCtrlEvent(rt, ctrl, evtName, NULL, 0); } } -// ============================================================ -// onWidgetClick -// ============================================================ - static void onWidgetClick(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; - if (!ctrl || !ctrl->form) { + if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } - BasFormRtT *rt = NULL; - - if (ctrl->form->vm) { - rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; - } + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { - basFormRtFireEvent(rt, ctrl->form, ctrl->name, "Click"); + fireCtrlEvent(rt, ctrl, "Click", NULL, 0); } } -// ============================================================ -// onWidgetDblClick -// ============================================================ - static void onWidgetDblClick(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; - if (!ctrl || !ctrl->form) { + if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } - BasFormRtT *rt = NULL; - - if (ctrl->form->vm) { - rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; - } + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { - basFormRtFireEvent(rt, ctrl->form, ctrl->name, "DblClick"); + fireCtrlEvent(rt, ctrl, "DblClick", NULL, 0); } } -// ============================================================ -// onWidgetFocus -// ============================================================ - static void onWidgetFocus(WidgetT *w) { BasControlT *ctrl = (BasControlT *)w->userData; - if (!ctrl || !ctrl->form) { + if (!ctrl || !ctrl->form || !ctrl->form->vm) { return; } - BasFormRtT *rt = NULL; - - if (ctrl->form->vm) { - rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; - } + BasFormRtT *rt = (BasFormRtT *)ctrl->form->vm->ui.ctx; if (rt) { - basFormRtFireEvent(rt, ctrl->form, ctrl->name, "GotFocus"); + fireCtrlEvent(rt, ctrl, "GotFocus", NULL, 0); } } -// ============================================================ -// onWidgetKeyPress / onWidgetKeyDown -// ============================================================ - static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii) { BasControlT *ctrl = (BasControlT *)w->userData; @@ -1595,7 +1611,7 @@ static void onWidgetKeyPress(WidgetT *w, int32_t keyAscii) { if (rt) { BasValueT args[1]; args[0] = basValLong(keyAscii); - basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "KeyPress", args, 1); + fireCtrlEvent(rt, ctrl, "KeyPress", args, 1); } } @@ -1613,15 +1629,11 @@ static void onWidgetKeyDown(WidgetT *w, int32_t keyCode, int32_t shift) { BasValueT args[2]; args[0] = basValLong(keyCode); args[1] = basValLong(shift); - basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "KeyDown", args, 2); + fireCtrlEvent(rt, ctrl, "KeyDown", args, 2); } } -// ============================================================ -// onWidgetKeyUp -// ============================================================ - static void onWidgetKeyUp(WidgetT *w, int32_t keyCode, int32_t shift) { BasControlT *ctrl = (BasControlT *)w->userData; @@ -1635,15 +1647,11 @@ static void onWidgetKeyUp(WidgetT *w, int32_t keyCode, int32_t shift) { BasValueT args[2]; args[0] = basValLong(keyCode); args[1] = basValLong(shift); - basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "KeyUp", args, 2); + fireCtrlEvent(rt, ctrl, "KeyUp", args, 2); } } -// ============================================================ -// onWidgetMouseDown / onWidgetMouseUp / onWidgetMouseMove -// ============================================================ - static void onWidgetMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y) { BasControlT *ctrl = (BasControlT *)w->userData; @@ -1658,7 +1666,7 @@ static void onWidgetMouseDown(WidgetT *w, int32_t button, int32_t x, int32_t y) args[0] = basValLong(button); args[1] = basValLong(x); args[2] = basValLong(y); - basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "MouseDown", args, 3); + fireCtrlEvent(rt, ctrl, "MouseDown", args, 3); } } @@ -1677,7 +1685,7 @@ static void onWidgetMouseUp(WidgetT *w, int32_t button, int32_t x, int32_t y) { args[0] = basValLong(button); args[1] = basValLong(x); args[2] = basValLong(y); - basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "MouseUp", args, 3); + fireCtrlEvent(rt, ctrl, "MouseUp", args, 3); } } @@ -1696,15 +1704,11 @@ static void onWidgetMouseMove(WidgetT *w, int32_t button, int32_t x, int32_t y) args[0] = basValLong(button); args[1] = basValLong(x); args[2] = basValLong(y); - basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "MouseMove", args, 3); + fireCtrlEvent(rt, ctrl, "MouseMove", args, 3); } } -// ============================================================ -// onWidgetScroll -// ============================================================ - static void onWidgetScroll(WidgetT *w, int32_t delta) { BasControlT *ctrl = (BasControlT *)w->userData; @@ -1717,7 +1721,7 @@ static void onWidgetScroll(WidgetT *w, int32_t delta) { if (rt) { BasValueT args[1]; args[0] = basValLong(delta); - basFormRtFireEventArgs(rt, ctrl->form, ctrl->name, "Scroll", args, 1); + fireCtrlEvent(rt, ctrl, "Scroll", args, 1); } } diff --git a/apps/dvxbasic/formrt/formrt.h b/apps/dvxbasic/formrt/formrt.h index d4a94b8..1417f6d 100644 --- a/apps/dvxbasic/formrt/formrt.h +++ b/apps/dvxbasic/formrt/formrt.h @@ -37,6 +37,7 @@ typedef struct BasControlT BasControlT; typedef struct BasControlT { char name[BAS_MAX_CTRL_NAME]; // VB control name (e.g. "Command1") char typeName[BAS_MAX_CTRL_NAME]; // VB type name (e.g. "CommandButton") + int32_t index; // control array index (-1 = not in array) WidgetT *widget; // the DVX widget BasFormT *form; // owning form const WgtIfaceT *iface; // interface descriptor (from .wgt) @@ -102,6 +103,7 @@ void basFormRtSetProp(void *ctx, void *ctrlRef, const char *propName, BasVa BasValueT basFormRtCallMethod(void *ctx, void *ctrlRef, const char *methodName, BasValueT *args, int32_t argc); void *basFormRtCreateCtrl(void *ctx, void *formRef, const char *typeName, const char *ctrlName); void *basFormRtFindCtrl(void *ctx, void *formRef, const char *ctrlName); +void *basFormRtFindCtrlIdx(void *ctx, void *formRef, const char *ctrlName, int32_t index); void *basFormRtLoadForm(void *ctx, const char *formName); void basFormRtUnloadForm(void *ctx, void *formRef); void basFormRtShowForm(void *ctx, void *formRef, bool modal); diff --git a/apps/dvxbasic/ide/ideDesigner.c b/apps/dvxbasic/ide/ideDesigner.c index 70c6bfc..d558a69 100644 --- a/apps/dvxbasic/ide/ideDesigner.c +++ b/apps/dvxbasic/ide/ideDesigner.c @@ -436,6 +436,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) { } else if (inForm) { DsgnControlT ctrl; memset(&ctrl, 0, sizeof(ctrl)); + ctrl.index = -1; snprintf(ctrl.name, DSGN_MAX_NAME, "%s", ctrlName); snprintf(ctrl.typeName, DSGN_MAX_NAME, "%s", typeName); @@ -545,6 +546,7 @@ bool dsgnLoadFrm(DsgnStateT *ds, const char *source, int32_t sourceLen) { else if (strcasecmp(key, "MaxWidth") == 0) { curCtrl->maxWidth = atoi(val); } else if (strcasecmp(key, "MaxHeight") == 0) { curCtrl->maxHeight = atoi(val); } else if (strcasecmp(key, "Weight") == 0) { curCtrl->weight = atoi(val); } + else if (strcasecmp(key, "Index") == 0) { curCtrl->index = atoi(val); } else if (strcasecmp(key, "TabIndex") == 0) { /* ignored -- DVX has no tab order */ } else { setPropValue(curCtrl, key, val); } } else { @@ -747,6 +749,7 @@ void dsgnOnMouse(DsgnStateT *ds, int32_t x, int32_t y, bool drag) { const char *typeName = ds->activeTool; DsgnControlT ctrl; memset(&ctrl, 0, sizeof(ctrl)); + ctrl.index = -1; dsgnAutoName(ds, typeName, ctrl.name, DSGN_MAX_NAME); snprintf(ctrl.typeName, DSGN_MAX_NAME, "%s", typeName); ctrl.width = DEFAULT_CTRL_W; @@ -906,6 +909,10 @@ static int32_t saveControls(const DsgnFormT *form, char *buf, int32_t bufSize, i 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"); diff --git a/apps/dvxbasic/ide/ideDesigner.h b/apps/dvxbasic/ide/ideDesigner.h index b2f8245..2b9250f 100644 --- a/apps/dvxbasic/ide/ideDesigner.h +++ b/apps/dvxbasic/ide/ideDesigner.h @@ -43,6 +43,7 @@ typedef struct { char name[DSGN_MAX_NAME]; char typeName[DSGN_MAX_NAME]; char parentName[DSGN_MAX_NAME]; // empty = top-level (child of form) + int32_t index; // control array index (-1 = not in array) int32_t left; int32_t top; int32_t width; diff --git a/apps/dvxbasic/ide/ideMain.c b/apps/dvxbasic/ide/ideMain.c index d714ee8..65f112e 100644 --- a/apps/dvxbasic/ide/ideMain.c +++ b/apps/dvxbasic/ide/ideMain.c @@ -3105,6 +3105,45 @@ static void onMenu(WindowT *win, int32_t menuId) { } } +// ============================================================ +// isCtrlArrayInDesigner -- check if a control name is a control +// array member in the current designer form. +// ============================================================ + +static bool isCtrlArrayInDesigner(const char *ctrlName) { + if (!sDesigner.form) { + return false; + } + + int32_t count = (int32_t)arrlen(sDesigner.form->controls); + + for (int32_t i = 0; i < count; i++) { + if (strcasecmp(sDesigner.form->controls[i].name, ctrlName) == 0 && sDesigner.form->controls[i].index >= 0) { + return true; + } + } + + return false; +} + + +// ============================================================ +// getEventExtraParams -- return the extra parameters for a +// known event type (the part after "Index As Integer"). +// ============================================================ + +static const char *getEventExtraParams(const char *evtName) { + if (strcasecmp(evtName, "KeyPress") == 0) { return ", KeyAscii As Integer"; } + if (strcasecmp(evtName, "KeyDown") == 0) { return ", KeyCode As Integer, Shift As Integer"; } + if (strcasecmp(evtName, "KeyUp") == 0) { return ", KeyCode As Integer, Shift As Integer"; } + if (strcasecmp(evtName, "MouseDown") == 0) { return ", Button As Integer, X As Integer, Y As Integer"; } + if (strcasecmp(evtName, "MouseUp") == 0) { return ", Button As Integer, X As Integer, Y As Integer"; } + if (strcasecmp(evtName, "MouseMove") == 0) { return ", Button As Integer, X As Integer, Y As Integer"; } + if (strcasecmp(evtName, "Scroll") == 0) { return ", Delta As Integer"; } + return ""; +} + + // ============================================================ // onEvtDropdownChange // ============================================================ @@ -3169,8 +3208,13 @@ static void onEvtDropdownChange(WidgetT *w) { char subName[128]; snprintf(subName, sizeof(subName), "%s_%s", selObj, evtName); - char skeleton[256]; - snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName); + char skeleton[512]; + + if (isCtrlArrayInDesigner(selObj)) { + snprintf(skeleton, sizeof(skeleton), "Sub %s (Index As Integer%s)\n\nEnd Sub\n", subName, getEventExtraParams(evtName)); + } else { + snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName); + } arrput(sProcBufs, strdup(skeleton)); showProc((int32_t)arrlen(sProcBufs) - 1); @@ -3552,6 +3596,11 @@ static void dsgnCopySelected(void) { DsgnControlT *ctrl = &sDesigner.form->controls[sDesigner.selectedIdx]; pos += snprintf(buf + pos, sizeof(buf) - pos, "Begin %s %s\n", ctrl->typeName, ctrl->name); + + if (ctrl->index >= 0) { + pos += snprintf(buf + pos, sizeof(buf) - pos, " Index = %d\n", (int)ctrl->index); + } + pos += snprintf(buf + pos, sizeof(buf) - pos, " Caption = \"%s\"\n", wgtGetText(ctrl->widget) ? wgtGetText(ctrl->widget) : ""); if (ctrl->width > 0) { @@ -3673,13 +3722,58 @@ static void dsgnPasteControl(void) { ctrlName[ci] = '\0'; - // Auto-generate a unique name to avoid duplicates + // Check if a control with the same name exists -- create control array char newName[DSGN_MAX_NAME]; - dsgnAutoName(&sDesigner, typeName, newName, DSGN_MAX_NAME); + int32_t newIndex = -1; + bool nameExists = false; + int32_t highIdx = -1; + int32_t existCount = (int32_t)arrlen(sDesigner.form->controls); + + for (int32_t i = 0; i < existCount; i++) { + if (strcasecmp(sDesigner.form->controls[i].name, ctrlName) == 0) { + nameExists = true; + if (sDesigner.form->controls[i].index > highIdx) { + highIdx = sDesigner.form->controls[i].index; + } + } + } + + if (nameExists) { + // Already a control array -- just add the next element + if (highIdx >= 0) { + snprintf(newName, DSGN_MAX_NAME, "%s", ctrlName); + newIndex = highIdx + 1; + } else { + // Not yet an array -- ask the user + int32_t result = dvxMessageBox(sAc, "Paste", + "A control with this name already exists.\n" + "Create a control array?", + MB_YESNO | MB_ICONQUESTION); + + if (result == ID_YES) { + // Convert existing control to index 0 + for (int32_t i = 0; i < existCount; i++) { + if (strcasecmp(sDesigner.form->controls[i].name, ctrlName) == 0) { + sDesigner.form->controls[i].index = 0; + break; + } + } + + snprintf(newName, DSGN_MAX_NAME, "%s", ctrlName); + newIndex = 1; + } else { + // Rename to a unique name + dsgnAutoName(&sDesigner, typeName, newName, DSGN_MAX_NAME); + } + } + } else { + dsgnAutoName(&sDesigner, typeName, newName, DSGN_MAX_NAME); + } // Create the control DsgnControlT ctrl; memset(&ctrl, 0, sizeof(ctrl)); + ctrl.index = newIndex; snprintf(ctrl.name, DSGN_MAX_NAME, "%s", newName); snprintf(ctrl.typeName, DSGN_MAX_NAME, "%s", typeName); @@ -4065,8 +4159,13 @@ static void navigateToEventSub(void) { // Not found -- create a new sub skeleton for editing. // Don't mark dirty yet; saveCurProc will discard it if the // user doesn't add any code. - char skeleton[256]; - snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName); + char skeleton[512]; + + if (isCtrlArrayInDesigner(ctrlName)) { + snprintf(skeleton, sizeof(skeleton), "Sub %s (Index As Integer%s)\n\nEnd Sub\n", subName, getEventExtraParams(eventName)); + } else { + snprintf(skeleton, sizeof(skeleton), "Sub %s ()\n\nEnd Sub\n", subName); + } arrput(sProcBufs, strdup(skeleton)); diff --git a/apps/dvxbasic/ide/ideProperties.c b/apps/dvxbasic/ide/ideProperties.c index 26ce5da..5327e5d 100644 --- a/apps/dvxbasic/ide/ideProperties.c +++ b/apps/dvxbasic/ide/ideProperties.c @@ -87,7 +87,7 @@ static void onTreeReorder(WidgetT *w); // ============================================================ static void onPrpClose(WindowT *win) { - (void)win; + win->visible = false; } @@ -107,7 +107,8 @@ static void onPrpClose(WindowT *win) { static uint8_t getPropType(const char *propName, const char *typeName) { // Read-only properties - if (strcasecmp(propName, "Type") == 0) { return PROP_TYPE_READONLY; } + if (strcasecmp(propName, "Type") == 0) { return PROP_TYPE_READONLY; } + if (strcasecmp(propName, "Index") == 0) { return PROP_TYPE_READONLY; } // Known built-in types if (strcasecmp(propName, "Name") == 0) { return PROP_TYPE_STRING; } @@ -343,13 +344,21 @@ static void onPropDblClick(WidgetT *w) { if (strcasecmp(propName, "Name") == 0) { char oldName[DSGN_MAX_NAME]; snprintf(oldName, sizeof(oldName), "%s", ctrl->name); - snprintf(ctrl->name, DSGN_MAX_NAME, "%.31s", newValue); - if (ctrl->widget) { - wgtSetName(ctrl->widget, ctrl->name); + // Rename all members of a control array, not just the selected one + for (int32_t i = 0; i < count; i++) { + DsgnControlT *c = &sDs->form->controls[i]; + + if (strcasecmp(c->name, oldName) == 0) { + snprintf(c->name, DSGN_MAX_NAME, "%.31s", newValue); + + if (c->widget) { + wgtSetName(c->widget, c->name); + } + } } - ideRenameInCode(oldName, ctrl->name); + ideRenameInCode(oldName, newValue); prpRebuildTree(sDs); } else if (strcasecmp(propName, "MinWidth") == 0) { ctrl->width = atoi(newValue); @@ -834,7 +843,12 @@ void prpRebuildTree(DsgnStateT *ds) { for (int32_t i = 0; i < count; i++) { DsgnControlT *ctrl = &ds->form->controls[i]; char buf[128]; - snprintf(buf, sizeof(buf), "%s (%s)", ctrl->name, ctrl->typeName); + + if (ctrl->index >= 0) { + snprintf(buf, sizeof(buf), "%s(%d) (%s)", ctrl->name, (int)ctrl->index, ctrl->typeName); + } else { + snprintf(buf, sizeof(buf), "%s (%s)", ctrl->name, ctrl->typeName); + } char *label = strdup(buf); arrput(sTreeLabels, label); @@ -897,6 +911,12 @@ void prpRefresh(DsgnStateT *ds) { char buf[32]; addPropRow("Name", ctrl->name); + + if (ctrl->index >= 0) { + snprintf(buf, sizeof(buf), "%d", (int)ctrl->index); + addPropRow("Index", buf); + } + addPropRow("Type", ctrl->typeName); snprintf(buf, sizeof(buf), "%d", (int)ctrl->width); diff --git a/apps/dvxbasic/ide/ideToolbox.c b/apps/dvxbasic/ide/ideToolbox.c index 92d9941..1966be9 100644 --- a/apps/dvxbasic/ide/ideToolbox.c +++ b/apps/dvxbasic/ide/ideToolbox.c @@ -68,7 +68,7 @@ static void onToolClick(WidgetT *w) { static void onTbxClose(WindowT *win) { - (void)win; + win->visible = false; } @@ -89,7 +89,7 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) { win->onClose = onTbxClose; WidgetT *root = wgtInitWindow(ctx, win); - root->spacing = wgtPixels(1); + root->spacing = 0; // Enumerate all registered widget interfaces with a basName int32_t ifaceCount = wgtIfaceCount(); @@ -145,7 +145,7 @@ WindowT *tbxCreate(AppContextT *ctx, DsgnStateT *ds) { // Start a new row every TBX_COLS buttons if (col == 0 || !row) { row = wgtHBox(root); - row->spacing = wgtPixels(1); + row->spacing = 0; } // Create button in the current row diff --git a/apps/dvxbasic/runtime/vm.c b/apps/dvxbasic/runtime/vm.c index 436e4b6..71e30f4 100644 --- a/apps/dvxbasic/runtime/vm.c +++ b/apps/dvxbasic/runtime/vm.c @@ -2878,6 +2878,33 @@ BasVmResultE basVmStep(BasVmT *vm) { break; } + case OP_FIND_CTRL_IDX: { + // Stack: [formRef, ctrlName, index] -- index on top + BasValueT idxVal; + BasValueT nameVal; + BasValueT formVal; + + if (!pop(vm, &idxVal) || !pop(vm, &nameVal) || !pop(vm, &formVal)) { + return BAS_VM_STACK_UNDERFLOW; + } + + void *ctrlRef = NULL; + + if (vm->ui.findCtrlIdx) { + void *formRef = (formVal.type == BAS_TYPE_OBJECT) ? formVal.objVal : vm->currentForm; + int32_t index = (int32_t)basValToNumber(idxVal); + BasValueT sv = basValToString(nameVal); + ctrlRef = vm->ui.findCtrlIdx(vm->ui.ctx, formRef, sv.strVal->data, index); + basValRelease(&sv); + } + + basValRelease(&idxVal); + basValRelease(&nameVal); + basValRelease(&formVal); + push(vm, basValObject(ctrlRef)); + break; + } + // ============================================================ // External library calls // ============================================================ diff --git a/apps/dvxbasic/runtime/vm.h b/apps/dvxbasic/runtime/vm.h index 94e99a6..4386331 100644 --- a/apps/dvxbasic/runtime/vm.h +++ b/apps/dvxbasic/runtime/vm.h @@ -100,6 +100,9 @@ typedef void *(*BasUiCreateCtrlFnT)(void *ctx, void *formRef, const char *typeNa // Find an existing control by name on a form. Returns NULL if not found. typedef void *(*BasUiFindCtrlFnT)(void *ctx, void *formRef, const char *ctrlName); +// Find a control array element by name and index. Returns NULL if not found. +typedef void *(*BasUiFindCtrlIdxFnT)(void *ctx, void *formRef, const char *ctrlName, int32_t index); + // Load a form by name. Returns an opaque form reference. typedef void *(*BasUiLoadFormFnT)(void *ctx, const char *formName); @@ -125,6 +128,7 @@ typedef struct { BasUiCallMethodFnT callMethod; BasUiCreateCtrlFnT createCtrl; BasUiFindCtrlFnT findCtrl; + BasUiFindCtrlIdxFnT findCtrlIdx; BasUiLoadFormFnT loadForm; BasUiUnloadFormFnT unloadForm; BasUiShowFormFnT showForm; diff --git a/apps/dvxbasic/test_compiler.c b/apps/dvxbasic/test_compiler.c index 8f190c0..cfa3081 100644 --- a/apps/dvxbasic/test_compiler.c +++ b/apps/dvxbasic/test_compiler.c @@ -2501,6 +2501,135 @@ int main(void) { "End If\n" ); + // ============================================================ + // Coverage: Control array read -- Name(0).Caption + // ============================================================ + + { + printf("=== Control array read ===\n"); + BasParserT parser; + basParserInit(&parser, + "DIM x AS STRING\n" + "x = Command1(0).Caption\n" + "PRINT x\n", + -1); + + if (!basParse(&parser)) { + printf("COMPILE ERROR: %s\n", parser.error); + } else { + printf("OK\n"); + } + + basParserFree(&parser); + printf("\n"); + } + + // ============================================================ + // Coverage: Control array write -- Name(idx).Caption = "text" + // ============================================================ + + { + printf("=== Control array write ===\n"); + BasParserT parser; + basParserInit(&parser, + "DIM idx AS INTEGER\n" + "idx = 1\n" + "Command1(idx).Caption = \"Hello\"\n", + -1); + + if (!basParse(&parser)) { + printf("COMPILE ERROR: %s\n", parser.error); + } else { + printf("OK\n"); + } + + basParserFree(&parser); + printf("\n"); + } + + // ============================================================ + // Coverage: Control array complex index -- Name(X + 1).Caption + // ============================================================ + + { + printf("=== Control array complex index ===\n"); + BasParserT parser; + basParserInit(&parser, + "DIM i AS INTEGER\n" + "DIM s AS STRING\n" + "i = 2\n" + "s = Btn(i + 1).Text\n" + "PRINT s\n", + -1); + + if (!basParse(&parser)) { + printf("COMPILE ERROR: %s\n", parser.error); + } else { + printf("OK\n"); + } + + basParserFree(&parser); + printf("\n"); + } + + // ============================================================ + // Coverage: Forward-ref function still works (no dot after paren) + // ============================================================ + + runProgram("Forward-ref function still works", + "Function GetValue () As Integer\n" + " GetValue = 42\n" + "End Function\n" + "\n" + "PRINT GetValue()\n" + ); + + // ============================================================ + // Coverage: Control array in event handler + // ============================================================ + + { + printf("=== Control array event handler ===\n"); + BasParserT parser; + basParserInit(&parser, + "Sub Cmd_Click (Index As Integer)\n" + " Cmd(Index).Caption = \"Clicked\"\n" + "End Sub\n", + -1); + + if (!basParse(&parser)) { + printf("COMPILE ERROR: %s\n", parser.error); + } else { + printf("OK\n"); + } + + basParserFree(&parser); + printf("\n"); + } + + // ============================================================ + // Coverage: Me.Control(idx).Property + // ============================================================ + + { + printf("=== Me.Control(idx).Property ===\n"); + BasParserT parser; + basParserInit(&parser, + "Sub Cmd_Click (Index As Integer)\n" + " Me.Cmd(Index).Caption = \"OK\"\n" + "End Sub\n", + -1); + + if (!basParse(&parser)) { + printf("COMPILE ERROR: %s\n", parser.error); + } else { + printf("OK\n"); + } + + basParserFree(&parser); + printf("\n"); + } + printf("All tests complete.\n"); return 0; } diff --git a/apps/progman/progman.c b/apps/progman/progman.c index 061e6a9..4f33033 100644 --- a/apps/progman/progman.c +++ b/apps/progman/progman.c @@ -112,7 +112,7 @@ static int32_t sAppCount = 0; int32_t appMain(DxeAppContextT *ctx); static void buildPmWindow(void); static void desktopUpdate(void); -static void onAppButtonDblClick(WidgetT *w); +static void onAppButtonClick(WidgetT *w); static void onPmClose(WindowT *win); static void onPmMenu(WindowT *win, int32_t menuId); static void scanAppsDir(void); @@ -233,7 +233,7 @@ static void buildPmWindow(void) { } btn->userData = &sAppFiles[i]; - btn->onDblClick = onAppButtonDblClick; + btn->onClick = onAppButtonClick; if (sAppFiles[i].tooltip[0]) { wgtSetTooltip(btn, sAppFiles[i].tooltip); @@ -276,7 +276,7 @@ static void desktopUpdate(void) { // Widget click handler for app grid buttons. userData was set to the // AppEntryT pointer during window construction, giving us the .app path. -static void onAppButtonDblClick(WidgetT *w) { +static void onAppButtonClick(WidgetT *w) { AppEntryT *entry = (AppEntryT *)w->userData; if (!entry) { diff --git a/tools/mkwgticon.c b/tools/mkwgticon.c new file mode 100644 index 0000000..ece9bcf --- /dev/null +++ b/tools/mkwgticon.c @@ -0,0 +1,566 @@ +// mkwgticon.c -- Generate 24x24 BMP widget icons for the DVX BASIC toolbox +// +// Usage: mkwgticon +// Types: button, label, textbox, checkbox, radio, dropdown, combobox, +// listbox, listview, treeview, image, imgbtn, canvas, slider, +// spinner, progress, timer, frame, hbox, vbox, splitter, +// scrollpane, tabctrl, toolbar, statusbar, separator, spacer, +// terminal + +#include +#include +#include + +#define W 24 +#define H 24 + +static uint8_t pixels[H][W][3]; // BGR + +static void clear(uint8_t r, uint8_t g, uint8_t b) { + for (int y = 0; y < H; y++) { + for (int x = 0; x < W; x++) { + pixels[y][x][0] = b; + pixels[y][x][1] = g; + pixels[y][x][2] = r; + } + } +} + + +static void px(int x, int y, uint8_t r, uint8_t g, uint8_t b) { + if (x >= 0 && x < W && y >= 0 && y < H) { + pixels[y][x][0] = b; + pixels[y][x][1] = g; + pixels[y][x][2] = r; + } +} + + +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++) { + px(x, y, r, g, b); + } + } +} + + +static void hline(int x, int y, int len, uint8_t r, uint8_t g, uint8_t b) { + for (int i = 0; i < len; i++) { + px(x + i, y, r, g, b); + } +} + + +static void vline(int x, int y, int len, uint8_t r, uint8_t g, uint8_t b) { + for (int i = 0; i < len; i++) { + px(x, y + i, r, g, b); + } +} + + +static void box(int x0, int y0, int w, int h, uint8_t r, uint8_t g, uint8_t b) { + hline(x0, y0, w, r, g, b); + hline(x0, y0 + h - 1, w, r, g, b); + vline(x0, y0, h, r, g, b); + vline(x0 + w - 1, y0, h, r, g, b); +} + + +// 3x5 mini font for labeling icons +static void miniChar(int x, int y, char ch, uint8_t r, uint8_t g, uint8_t b) { + // Simplified: just draw recognizable glyphs for A-Z, 0-9 + static const uint16_t font[128] = { + ['A'] = 0x7D6F, ['B'] = 0xFD7F, ['C'] = 0x7C9F, ['D'] = 0xF56F, + ['E'] = 0xFC9F, ['F'] = 0xFC90, ['G'] = 0x7CBF, ['H'] = 0xB7ED, + ['I'] = 0xE92E, ['K'] = 0xB6AD, ['L'] = 0x924F, ['M'] = 0xBFED, + ['N'] = 0xBDED, ['O'] = 0x756E, ['P'] = 0xFD20, ['R'] = 0xFD6D, + ['S'] = 0x7C1F, ['T'] = 0xE924, ['U'] = 0xB6DE, ['V'] = 0xB6A4, + ['W'] = 0xB7FA, ['X'] = 0xB52D, ['Y'] = 0xB524, ['Z'] = 0xE54F, + }; + + uint16_t bits = (ch >= 'A' && ch <= 'Z') ? font[(int)ch] : 0; + + for (int row = 0; row < 5; row++) { + for (int col = 0; col < 3; col++) { + if (bits & (1 << (14 - row * 3 - col))) { + px(x + col, y + row, r, g, b); + } + } + } +} + + +static void miniText(int x, int y, const char *text, uint8_t r, uint8_t g, uint8_t b) { + while (*text) { + char ch = *text; + + if (ch >= 'a' && ch <= 'z') { + ch -= 32; + } + + miniChar(x, y, ch, r, g, b); + x += 4; + text++; + } +} + + +static void bevel(int x0, int y0, int w, int h, int bw) { + // Raised bevel: white top-left, dark bottom-right + for (int i = 0; i < bw; i++) { + hline(x0 + i, y0 + i, w - i * 2, 255, 255, 255); + vline(x0 + i, y0 + i, h - i * 2, 255, 255, 255); + hline(x0 + i, y0 + h - 1 - i, w - i * 2, 128, 128, 128); + vline(x0 + w - 1 - i, y0 + i, h - i * 2, 128, 128, 128); + } +} + + +static void drawButton(void) { + clear(192, 192, 192); + rect(3, 5, 18, 14, 192, 192, 192); + bevel(3, 5, 18, 14, 2); + miniText(7, 9, "OK", 0, 0, 0); +} + + +static void drawLabel(void) { + clear(192, 192, 192); + miniText(3, 9, "LABEL", 0, 0, 128); +} + + +static void drawTextbox(void) { + clear(192, 192, 192); + rect(2, 6, 20, 12, 255, 255, 255); + box(2, 6, 20, 12, 128, 128, 128); + miniText(4, 9, "TEXT", 0, 0, 0); +} + + +static void drawCheckbox(void) { + clear(192, 192, 192); + rect(4, 7, 10, 10, 255, 255, 255); + box(4, 7, 10, 10, 128, 128, 128); + // Checkmark + px(6, 12, 0, 0, 0); px(7, 13, 0, 0, 0); px(8, 14, 0, 0, 0); + px(9, 13, 0, 0, 0); px(10, 12, 0, 0, 0); px(11, 11, 0, 0, 0); + px(12, 10, 0, 0, 0); + miniText(16, 9, "AB", 0, 0, 0); +} + + +static void drawRadio(void) { + clear(192, 192, 192); + // Circle outline + for (int i = 5; i <= 9; i++) { px(i, 6, 128, 128, 128); px(i, 16, 128, 128, 128); } + for (int i = 7; i <= 15; i++) { px(4, i, 128, 128, 128); px(10, i, 128, 128, 128); } + // Filled dot + rect(6, 9, 3, 3, 0, 0, 0); + miniText(14, 9, "AB", 0, 0, 0); +} + + +static void drawDropdown(void) { + clear(192, 192, 192); + rect(2, 7, 20, 11, 255, 255, 255); + box(2, 7, 20, 11, 128, 128, 128); + // Down arrow button + rect(16, 8, 5, 9, 192, 192, 192); + bevel(16, 8, 5, 9, 1); + px(17, 11, 0, 0, 0); px(18, 12, 0, 0, 0); px(19, 11, 0, 0, 0); +} + + +static void drawCombobox(void) { + clear(192, 192, 192); + drawDropdown(); + // Add text to distinguish from dropdown + miniText(4, 9, "AB", 0, 0, 0); +} + + +static void drawListbox(void) { + clear(192, 192, 192); + rect(3, 3, 18, 18, 255, 255, 255); + box(3, 3, 18, 18, 128, 128, 128); + // List items + hline(5, 6, 10, 0, 0, 0); + rect(5, 9, 14, 3, 0, 0, 128); // selected + hline(5, 10, 10, 255, 255, 255); + hline(5, 14, 10, 0, 0, 0); + hline(5, 17, 10, 0, 0, 0); +} + + +static void drawListview(void) { + clear(192, 192, 192); + rect(2, 2, 20, 20, 255, 255, 255); + box(2, 2, 20, 20, 128, 128, 128); + // Column headers + rect(3, 3, 18, 4, 192, 192, 192); + hline(3, 6, 18, 128, 128, 128); + vline(10, 3, 18, 128, 128, 128); + // Grid lines + hline(3, 10, 18, 192, 192, 192); + hline(3, 14, 18, 192, 192, 192); +} + + +static void drawTreeview(void) { + clear(192, 192, 192); + rect(2, 2, 20, 20, 255, 255, 255); + box(2, 2, 20, 20, 128, 128, 128); + // Tree lines + vline(6, 5, 14, 128, 128, 128); + hline(6, 7, 5, 128, 128, 128); + hline(6, 12, 5, 128, 128, 128); + vline(10, 12, 5, 128, 128, 128); + hline(10, 16, 5, 128, 128, 128); + // Expand box + rect(4, 4, 5, 5, 255, 255, 255); + box(4, 4, 5, 5, 0, 0, 0); + hline(5, 6, 3, 0, 0, 0); // minus + px(6, 5, 0, 0, 0); // plus vertical + px(6, 7, 0, 0, 0); +} + + +static void drawImage(void) { + clear(192, 192, 192); + rect(3, 3, 18, 18, 255, 255, 255); + box(3, 3, 18, 18, 128, 128, 128); + // Mountain landscape + px(8, 8, 255, 255, 0); // sun + px(9, 8, 255, 255, 0); + px(8, 7, 255, 255, 0); + px(9, 7, 255, 255, 0); + // Mountain + for (int i = 0; i < 7; i++) { + hline(10 - i, 17 - i, 1 + i * 2, 0, 128, 0); + } +} + + +static void drawImgbtn(void) { + clear(192, 192, 192); + bevel(2, 3, 20, 18, 2); + // Small image inside + rect(7, 7, 10, 10, 200, 200, 255); + box(7, 7, 10, 10, 0, 0, 128); +} + + +static void drawCanvas(void) { + clear(192, 192, 192); + rect(3, 3, 18, 18, 255, 255, 255); + box(3, 3, 18, 18, 128, 128, 128); + // Diagonal line + for (int i = 0; i < 14; i++) { + px(4 + i, 4 + i, 255, 0, 0); + } + // Circle + for (int i = 0; i < 8; i++) { + px(14 + i/2, 6, 0, 0, 255); + px(14 + i/2, 12, 0, 0, 255); + } +} + + +static void drawSlider(void) { + clear(192, 192, 192); + // Track + hline(3, 12, 18, 128, 128, 128); + hline(3, 13, 18, 255, 255, 255); + // Thumb + rect(10, 8, 4, 8, 192, 192, 192); + bevel(10, 8, 4, 8, 1); +} + + +static void drawSpinner(void) { + clear(192, 192, 192); + rect(3, 6, 14, 12, 255, 255, 255); + box(3, 6, 14, 12, 128, 128, 128); + miniText(5, 9, "42", 0, 0, 0); + // Up/down buttons + rect(17, 6, 5, 6, 192, 192, 192); + bevel(17, 6, 5, 6, 1); + rect(17, 12, 5, 6, 192, 192, 192); + bevel(17, 12, 5, 6, 1); + // Arrows + px(19, 8, 0, 0, 0); px(18, 9, 0, 0, 0); px(20, 9, 0, 0, 0); + px(18, 14, 0, 0, 0); px(20, 14, 0, 0, 0); px(19, 15, 0, 0, 0); +} + + +static void drawProgress(void) { + clear(192, 192, 192); + rect(2, 8, 20, 8, 255, 255, 255); + box(2, 8, 20, 8, 128, 128, 128); + // Fill + rect(3, 9, 12, 6, 0, 0, 128); +} + + +static void drawTimer(void) { + clear(192, 192, 192); + // Clock face + for (int a = 0; a < 12; a++) { + int cx = 12, cy = 12, r = 8; + // Approximate circle points + static const int dx[] = {0, 4, 7, 8, 7, 4, 0, -4, -7, -8, -7, -4}; + static const int dy[] = {-8, -7, -4, 0, 4, 7, 8, 7, 4, 0, -4, -7}; + px(cx + dx[a], cy + dy[a], 0, 0, 0); + } + // Hands + vline(12, 5, 7, 0, 0, 0); + hline(12, 12, 5, 0, 0, 0); +} + + +static void drawFrame(void) { + clear(192, 192, 192); + box(3, 6, 18, 15, 128, 128, 128); + rect(5, 4, 14, 5, 192, 192, 192); + miniText(6, 4, "GRP", 0, 0, 0); +} + + +static void drawHbox(void) { + clear(192, 192, 192); + box(2, 4, 20, 16, 0, 0, 128); + vline(8, 4, 16, 0, 0, 128); + vline(15, 4, 16, 0, 0, 128); + // Arrow + hline(4, 12, 16, 0, 0, 128); + px(18, 11, 0, 0, 128); px(18, 13, 0, 0, 128); +} + + +static void drawVbox(void) { + clear(192, 192, 192); + box(2, 2, 20, 20, 0, 128, 0); + hline(2, 8, 20, 0, 128, 0); + hline(2, 15, 20, 0, 128, 0); + // Arrow + vline(12, 4, 16, 0, 128, 0); + px(11, 18, 0, 128, 0); px(13, 18, 0, 128, 0); +} + + +static void drawSplitter(void) { + clear(192, 192, 192); + rect(2, 2, 9, 20, 200, 200, 200); + box(2, 2, 9, 20, 128, 128, 128); + rect(13, 2, 9, 20, 200, 200, 200); + box(13, 2, 9, 20, 128, 128, 128); + // Grip dots + for (int i = 0; i < 3; i++) { + px(11, 9 + i * 3, 128, 128, 128); + px(12, 9 + i * 3, 255, 255, 255); + } +} + + +static void drawScrollpane(void) { + clear(192, 192, 192); + rect(2, 2, 18, 18, 255, 255, 255); + box(2, 2, 20, 20, 128, 128, 128); + // Scrollbar + rect(19, 2, 3, 20, 192, 192, 192); + rect(19, 5, 3, 6, 160, 160, 160); + bevel(19, 5, 3, 6, 1); +} + + +static void drawTabctrl(void) { + clear(192, 192, 192); + // Tab buttons + rect(3, 4, 8, 5, 192, 192, 192); + hline(3, 4, 8, 255, 255, 255); + vline(3, 4, 5, 255, 255, 255); + vline(10, 4, 5, 128, 128, 128); + rect(11, 5, 8, 4, 180, 180, 180); + hline(11, 5, 8, 255, 255, 255); + vline(18, 5, 4, 128, 128, 128); + // Body + rect(2, 9, 20, 13, 192, 192, 192); + box(2, 9, 20, 13, 128, 128, 128); +} + + +static void drawToolbar(void) { + clear(192, 192, 192); + rect(1, 7, 22, 10, 192, 192, 192); + bevel(1, 7, 22, 10, 1); + // Buttons + rect(3, 8, 5, 8, 192, 192, 192); + bevel(3, 8, 5, 8, 1); + rect(9, 8, 5, 8, 192, 192, 192); + bevel(9, 8, 5, 8, 1); + rect(15, 8, 5, 8, 192, 192, 192); + bevel(15, 8, 5, 8, 1); +} + + +static void drawStatusbar(void) { + clear(192, 192, 192); + rect(1, 14, 22, 8, 192, 192, 192); + // Sunken panels + hline(2, 15, 12, 128, 128, 128); + hline(2, 20, 12, 255, 255, 255); + vline(2, 15, 6, 128, 128, 128); + vline(13, 15, 6, 255, 255, 255); + hline(15, 15, 7, 128, 128, 128); + hline(15, 20, 7, 255, 255, 255); + vline(15, 15, 6, 128, 128, 128); + vline(21, 15, 6, 255, 255, 255); +} + + +static void drawSeparator(void) { + clear(192, 192, 192); + // Horizontal separator line + hline(3, 11, 18, 128, 128, 128); + hline(3, 12, 18, 255, 255, 255); + // Vertical separator line + vline(12, 3, 18, 128, 128, 128); + vline(13, 3, 18, 255, 255, 255); +} + + +static void drawSpacer(void) { + clear(192, 192, 192); + // Dotted rectangle outline + for (int i = 0; i < 20; i += 2) { + px(2 + i, 4, 128, 128, 128); + px(2 + i, 19, 128, 128, 128); + } + for (int i = 0; i < 16; i += 2) { + px(2, 4 + i, 128, 128, 128); + px(21, 4 + i, 128, 128, 128); + } + // Double-headed arrow + hline(5, 12, 14, 128, 128, 128); + px(6, 11, 128, 128, 128); px(6, 13, 128, 128, 128); + px(18, 11, 128, 128, 128); px(18, 13, 128, 128, 128); +} + + +static void drawTerminal(void) { + clear(192, 192, 192); + rect(2, 2, 20, 20, 0, 0, 0); + box(2, 2, 20, 20, 128, 128, 128); + // Green text on black + miniText(4, 5, "C", 0, 255, 0); + hline(4, 11, 4, 0, 255, 0); + miniText(4, 14, "A", 0, 255, 0); + // Cursor + rect(8, 14, 2, 5, 0, 255, 0); +} + + +static void writeBmp(const char *path) { + // 24-bit BMP, bottom-up + int rowBytes = W * 3; + int padding = (4 - (rowBytes % 4)) % 4; + int stride = rowBytes + padding; + int dataSize = stride * H; + int fileSize = 54 + dataSize; + + FILE *f = fopen(path, "wb"); + + if (!f) { + fprintf(stderr, "Cannot open %s\n", path); + return; + } + + // BMP header + uint8_t hdr[54]; + memset(hdr, 0, sizeof(hdr)); + hdr[0] = 'B'; hdr[1] = 'M'; + hdr[2] = fileSize; hdr[3] = fileSize >> 8; hdr[4] = fileSize >> 16; hdr[5] = fileSize >> 24; + hdr[10] = 54; + hdr[14] = 40; + hdr[18] = W; hdr[19] = W >> 8; + hdr[22] = H; hdr[23] = H >> 8; + hdr[26] = 1; + hdr[28] = 24; + + fwrite(hdr, 1, 54, f); + + uint8_t pad[3] = {0, 0, 0}; + + for (int y = H - 1; y >= 0; y--) { + fwrite(pixels[y], 1, rowBytes, f); + + if (padding) { + fwrite(pad, 1, padding, f); + } + } + + fclose(f); +} + + +int main(int argc, char **argv) { + if (argc != 3) { + fprintf(stderr, "Usage: mkwgticon \n"); + return 1; + } + + const char *path = argv[1]; + const char *type = argv[2]; + + typedef struct { const char *name; void (*fn)(void); } IconT; + IconT icons[] = { + {"button", drawButton}, + {"label", drawLabel}, + {"textbox", drawTextbox}, + {"checkbox", drawCheckbox}, + {"radio", drawRadio}, + {"dropdown", drawDropdown}, + {"combobox", drawCombobox}, + {"listbox", drawListbox}, + {"listview", drawListview}, + {"treeview", drawTreeview}, + {"image", drawImage}, + {"imgbtn", drawImgbtn}, + {"canvas", drawCanvas}, + {"slider", drawSlider}, + {"spinner", drawSpinner}, + {"progress", drawProgress}, + {"timer", drawTimer}, + {"frame", drawFrame}, + {"hbox", drawHbox}, + {"vbox", drawVbox}, + {"splitter", drawSplitter}, + {"scrollpane", drawScrollpane}, + {"tabctrl", drawTabctrl}, + {"toolbar", drawToolbar}, + {"statusbar", drawStatusbar}, + {"separator", drawSeparator}, + {"spacer", drawSpacer}, + {"terminal", drawTerminal}, + {NULL, NULL} + }; + + for (int i = 0; icons[i].name; i++) { + if (strcmp(type, icons[i].name) == 0) { + icons[i].fn(); + writeBmp(path); + return 0; + } + } + + fprintf(stderr, "Unknown type: %s\nAvailable:", type); + + for (int i = 0; icons[i].name; i++) { + fprintf(stderr, " %s", icons[i].name); + } + + fprintf(stderr, "\n"); + return 1; +}