From dd115d37272cdbdcdd3837a01c2d6832671c08e1 Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Thu, 5 Mar 2026 15:13:59 -0600 Subject: [PATCH] Add BitBtn, SpeedButton, TabSet, Notebook, TabbedNotebook, MaskEdit, Outline, Bevel, Header, and ScrollBox control types Completes the Delphi 1.0 Standard, Additional, and Win31 component palettes. DFM parser maps Tabs/Lines/Pages/Sections.Strings to Items and TabIndex/PageIndex to ItemIndex. Kind extended with bk* idents for BitBtn. Co-Authored-By: Claude Opus 4.6 --- forms/README.md | 122 ++++++++++++++++++-- forms/dfm2form.c | 238 +++++++++++++++++++++++++++++++++++++- forms/formcli.pas | 287 ++++++++++++++++++++++++++++++++++++++++++++-- forms/protocol.md | 33 ++++-- 4 files changed, 651 insertions(+), 29 deletions(-) diff --git a/forms/README.md b/forms/README.md index 0240bf1..933b022 100644 --- a/forms/README.md +++ b/forms/README.md @@ -326,6 +326,16 @@ messages are available, dispatching each command as it arrives. | `PopupMenu` | TPopupMenu | Context (right-click) menu | | `MenuItem` | TMenuItem | Menu item (child of menu) | | `RadioGroup` | TRadioGroup | Grouped radio buttons | +| `BitBtn` | TBitBtn | Button with bitmap glyph | +| `SpeedButton` | TSpeedButton | Flat toolbar-style button | +| `TabSet` | TTabSet | Tab strip (no pages) | +| `Notebook` | TNotebook | Multi-page container | +| `TabbedNotebook` | TTabbedNotebook | Tabbed multi-page container | +| `MaskEdit` | TMaskEdit | Masked text input | +| `Outline` | TOutline | Hierarchical tree list | +| `Bevel` | TBevel | Cosmetic beveled line/box | +| `Header` | THeader | Column header bar | +| `ScrollBox` | TScrollBox | Scrollable container | ### Creating Controls @@ -363,16 +373,16 @@ CTRL.SET 1 3 Text="world" Enabled=0 ### Caption -- **Applies to:** Label, Button, CheckBox, GroupBox, RadioButton, Panel, MenuItem, RadioGroup +- **Applies to:** Label, Button, CheckBox, GroupBox, RadioButton, Panel, MenuItem, RadioGroup, BitBtn, SpeedButton - **Format:** Quoted string - **Example:** `Caption="Submit"` The display text for labels, buttons, check boxes, group boxes, radio -buttons, panels, menu items, and radio groups. +buttons, panels, menu items, radio groups, and bitmap/speed buttons. ### Text -- **Applies to:** Edit, ComboBox, Memo +- **Applies to:** Edit, ComboBox, Memo, MaskEdit - **Format:** Quoted string - **Example:** `Text="Hello world"` @@ -385,7 +395,7 @@ CTRL.SET 1 5 Text="Line one\nLine two\nLine three" ### Items -- **Applies to:** ListBox, ComboBox, RadioGroup +- **Applies to:** ListBox, ComboBox, RadioGroup, TabSet, Notebook, TabbedNotebook, Outline, Header - **Format:** Quoted string, items separated by `\n` - **Example:** `Items="Red\nGreen\nBlue"` @@ -412,7 +422,7 @@ new items are added. ### MaxLength -- **Applies to:** Edit +- **Applies to:** Edit, MaskEdit - **Format:** Integer (0 = no limit) - **Example:** `MaxLength=50` @@ -437,7 +447,7 @@ Maximum number of characters the user can type. ### ItemIndex -- **Applies to:** ListBox, ComboBox, RadioGroup +- **Applies to:** ListBox, ComboBox, RadioGroup, TabSet, Notebook, TabbedNotebook - **Format:** Integer (-1 = no selection) - **Example:** `ItemIndex=2` @@ -514,8 +524,12 @@ runtime via `CTRL.SET` or by manually editing the `.form` file. ### Kind -- **Applies to:** ScrollBar -- **Format:** `0` (sbHorizontal) or `1` (sbVertical) +- **Applies to:** ScrollBar, BitBtn +- **Format:** Integer +- **Values (ScrollBar):** `0` (sbHorizontal), `1` (sbVertical) +- **Values (BitBtn):** `0` (bkCustom), `1` (bkOK), `2` (bkCancel), + `3` (bkHelp), `4` (bkYes), `5` (bkNo), `6` (bkClose), `7` (bkAbort), + `8` (bkRetry), `9` (bkIgnore), `10` (bkAll) - **Example:** `Kind=1` ### Min @@ -627,6 +641,93 @@ encoding (virtual key + modifier flags). Associates a PopupMenu with a control. When the user right-clicks the control, the popup menu is displayed. +### Layout + +- **Applies to:** BitBtn, SpeedButton +- **Format:** Integer 0-3 +- **Values:** + - `0` — blGlyphLeft + - `1` — blGlyphRight + - `2` — blGlyphTop + - `3` — blGlyphBottom +- **Example:** `Layout=2` + +Position of the glyph relative to the caption text. + +### NumGlyphs + +- **Applies to:** BitBtn, SpeedButton +- **Format:** Integer (1-4) +- **Example:** `NumGlyphs=2` + +Number of glyph images in the bitmap (up, disabled, clicked, down). + +### GroupIndex + +- **Applies to:** SpeedButton +- **Format:** Integer (0 = no group) +- **Example:** `GroupIndex=1` + +Speed buttons with the same non-zero GroupIndex act as a radio group. + +### Down + +- **Applies to:** SpeedButton +- **Format:** `0` (up) or `1` (down) +- **Example:** `Down=1` + +Whether the speed button is pressed. Only meaningful when +GroupIndex is non-zero. + +### AllowAllUp + +- **Applies to:** SpeedButton +- **Format:** `0` (off) or `1` (on) +- **Example:** `AllowAllUp=1` + +When enabled, all speed buttons in a group can be unpressed. + +### EditMask + +- **Applies to:** MaskEdit +- **Format:** Quoted string +- **Example:** `EditMask="(999) 000-0000;1;_"` + +Input mask string (mask;save literals;blank char). + +### OutlineStyle + +- **Applies to:** Outline +- **Format:** Integer 0-6 +- **Values:** + - `0` — osText + - `1` — osPlusMinusText + - `2` — osPlusMinus + - `3` — osPictureText + - `4` — osPicturePlusMinusText + - `5` — osTreeText + - `6` — osTreePictureText +- **Example:** `OutlineStyle=5` + +### Shape + +- **Applies to:** Bevel +- **Format:** Integer 0-5 +- **Values:** + - `0` — bsBox + - `1` — bsFrame + - `2` — bsTopLine + - `3` — bsBottomLine + - `4` — bsLeftLine + - `5` — bsRightLine +- **Example:** `Shape=2` + +### Style (Bevel) + +- **Applies to:** Bevel +- **Format:** `0` (bsLowered) or `1` (bsRaised) +- **Example:** `Style=1` + ### BasePath `BasePath` is a property on `TFormClient` (not a protocol property). @@ -660,6 +761,11 @@ No `EVENT.BIND` command is needed. | ComboBox | Change | `"new text"` | | MenuItem | Click | (none) | | RadioGroup | Click | `` | +| BitBtn | Click | (none) | +| SpeedButton | Click | (none) | +| TabSet | Change | `` | +| TabbedNotebook | Change | `` | +| MaskEdit | Change | `"new text"` | ### Opt-in Events diff --git a/forms/dfm2form.c b/forms/dfm2form.c index d2570c5..67b212f 100644 --- a/forms/dfm2form.c +++ b/forms/dfm2form.c @@ -36,7 +36,17 @@ typedef enum { ctMainMenu, ctPopupMenu, ctMenuItem, - ctRadioGroup + ctRadioGroup, + ctBitBtn, + ctSpeedButton, + ctTabSet, + ctNotebook, + ctTabbedNotebook, + ctMaskEdit, + ctOutline, + ctBevel, + ctHeader, + ctScrollBox } CtrlTypeE; typedef struct { @@ -110,8 +120,26 @@ typedef struct { int32_t parentCtrlIdx; int32_t columns; int32_t shortCut; + int32_t layout; + int32_t numGlyphs; + int32_t groupIndex; + int32_t down; + int32_t allowAllUp; + int32_t outlineStyle; + int32_t shape; + int32_t style; + char editMask[256]; bool hasColumns; bool hasShortCut; + bool hasLayout; + bool hasNumGlyphs; + bool hasGroupIndex; + bool hasDown; + bool hasAllowAllUp; + bool hasOutlineStyle; + bool hasShape; + bool hasStyle; + bool hasEditMask; } DfmCtrlT; typedef struct { @@ -381,6 +409,36 @@ static CtrlTypeE mapClassName(const char *className) { if (stricmp_local(className, "TRadioGroup") == 0) { return ctRadioGroup; } + if (stricmp_local(className, "TBitBtn") == 0) { + return ctBitBtn; + } + if (stricmp_local(className, "TSpeedButton") == 0) { + return ctSpeedButton; + } + if (stricmp_local(className, "TTabSet") == 0) { + return ctTabSet; + } + if (stricmp_local(className, "TNotebook") == 0) { + return ctNotebook; + } + if (stricmp_local(className, "TTabbedNotebook") == 0) { + return ctTabbedNotebook; + } + if (stricmp_local(className, "TMaskEdit") == 0) { + return ctMaskEdit; + } + if (stricmp_local(className, "TOutline") == 0) { + return ctOutline; + } + if (stricmp_local(className, "TBevel") == 0) { + return ctBevel; + } + if (stricmp_local(className, "THeader") == 0) { + return ctHeader; + } + if (stricmp_local(className, "TScrollBox") == 0) { + return ctScrollBox; + } return ctUnknown; } @@ -484,7 +542,7 @@ static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, Dfm skipValue(data, size, pos, tag); } ctrl->hasText = true; - } else if (!isForm && stricmp_local(propName, "Items.Strings") == 0 && tag == vaList) { + } else if (!isForm && (stricmp_local(propName, "Items.Strings") == 0 || stricmp_local(propName, "Tabs.Strings") == 0 || stricmp_local(propName, "Lines.Strings") == 0 || stricmp_local(propName, "Pages.Strings") == 0 || stricmp_local(propName, "Sections.Strings") == 0) && tag == vaList) { // List of strings for ListBox/ComboBox ctrl->items[0] = '\0'; int32_t itemsLen = 0; @@ -589,7 +647,7 @@ static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, Dfm } else if (!isForm && stricmp_local(propName, "TabOrder") == 0) { ctrl->tabOrder = readIntValue(data, size, pos, tag); ctrl->hasTabOrder = true; - } else if (!isForm && stricmp_local(propName, "ItemIndex") == 0) { + } else if (!isForm && (stricmp_local(propName, "ItemIndex") == 0 || stricmp_local(propName, "TabIndex") == 0 || stricmp_local(propName, "PageIndex") == 0)) { ctrl->itemIndex = readIntValue(data, size, pos, tag); ctrl->hasItemIndex = true; } else if (stricmp_local(propName, "OnClick") == 0) { @@ -753,6 +811,28 @@ static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, Dfm ctrl->kind = 0; } else if (stricmp_local(strBuf, "sbVertical") == 0) { ctrl->kind = 1; + } else if (stricmp_local(strBuf, "bkCustom") == 0) { + ctrl->kind = 0; + } else if (stricmp_local(strBuf, "bkOK") == 0) { + ctrl->kind = 1; + } else if (stricmp_local(strBuf, "bkCancel") == 0) { + ctrl->kind = 2; + } else if (stricmp_local(strBuf, "bkHelp") == 0) { + ctrl->kind = 3; + } else if (stricmp_local(strBuf, "bkYes") == 0) { + ctrl->kind = 4; + } else if (stricmp_local(strBuf, "bkNo") == 0) { + ctrl->kind = 5; + } else if (stricmp_local(strBuf, "bkClose") == 0) { + ctrl->kind = 6; + } else if (stricmp_local(strBuf, "bkAbort") == 0) { + ctrl->kind = 7; + } else if (stricmp_local(strBuf, "bkRetry") == 0) { + ctrl->kind = 8; + } else if (stricmp_local(strBuf, "bkIgnore") == 0) { + ctrl->kind = 9; + } else if (stricmp_local(strBuf, "bkAll") == 0) { + ctrl->kind = 10; } else { ctrl->kind = 0; } @@ -818,6 +898,121 @@ static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, Dfm } else if (!isForm && stricmp_local(propName, "ShortCut") == 0) { ctrl->shortCut = readIntValue(data, size, pos, tag); ctrl->hasShortCut = true; + } else if (!isForm && stricmp_local(propName, "Layout") == 0) { + if (tag == vaIdent) { + readStr(data, size, pos, strBuf, sizeof(strBuf)); + if (stricmp_local(strBuf, "blGlyphLeft") == 0) { + ctrl->layout = 0; + } else if (stricmp_local(strBuf, "blGlyphRight") == 0) { + ctrl->layout = 1; + } else if (stricmp_local(strBuf, "blGlyphTop") == 0) { + ctrl->layout = 2; + } else if (stricmp_local(strBuf, "blGlyphBottom") == 0) { + ctrl->layout = 3; + } else { + ctrl->layout = 0; + } + } else { + ctrl->layout = readIntValue(data, size, pos, tag); + } + ctrl->hasLayout = true; + } else if (!isForm && stricmp_local(propName, "NumGlyphs") == 0) { + ctrl->numGlyphs = readIntValue(data, size, pos, tag); + ctrl->hasNumGlyphs = true; + } else if (!isForm && stricmp_local(propName, "GroupIndex") == 0) { + ctrl->groupIndex = readIntValue(data, size, pos, tag); + ctrl->hasGroupIndex = true; + } else if (!isForm && stricmp_local(propName, "Down") == 0) { + if (tag == vaTrue) { + ctrl->down = 1; + } else if (tag == vaFalse) { + ctrl->down = 0; + } else { + ctrl->down = readIntValue(data, size, pos, tag); + } + ctrl->hasDown = true; + } else if (!isForm && stricmp_local(propName, "AllowAllUp") == 0) { + if (tag == vaTrue) { + ctrl->allowAllUp = 1; + } else if (tag == vaFalse) { + ctrl->allowAllUp = 0; + } else { + ctrl->allowAllUp = readIntValue(data, size, pos, tag); + } + ctrl->hasAllowAllUp = true; + } else if (!isForm && stricmp_local(propName, "EditMask") == 0) { + if (tag == vaString) { + readStr(data, size, pos, ctrl->editMask, sizeof(ctrl->editMask)); + } else if (tag == vaLString) { + int32_t len = readInt32LE(data, size, pos); + int32_t copyLen = (len < (int32_t)sizeof(ctrl->editMask) - 1) ? len : (int32_t)sizeof(ctrl->editMask) - 1; + memcpy(ctrl->editMask, data + *pos, copyLen); + ctrl->editMask[copyLen] = '\0'; + *pos += len; + } else { + skipValue(data, size, pos, tag); + } + ctrl->hasEditMask = true; + } else if (!isForm && stricmp_local(propName, "OutlineStyle") == 0) { + if (tag == vaIdent) { + readStr(data, size, pos, strBuf, sizeof(strBuf)); + if (stricmp_local(strBuf, "osText") == 0) { + ctrl->outlineStyle = 0; + } else if (stricmp_local(strBuf, "osPlusMinusText") == 0) { + ctrl->outlineStyle = 1; + } else if (stricmp_local(strBuf, "osPlusMinus") == 0) { + ctrl->outlineStyle = 2; + } else if (stricmp_local(strBuf, "osPictureText") == 0) { + ctrl->outlineStyle = 3; + } else if (stricmp_local(strBuf, "osPicturePlusMinusText") == 0) { + ctrl->outlineStyle = 4; + } else if (stricmp_local(strBuf, "osTreeText") == 0) { + ctrl->outlineStyle = 5; + } else if (stricmp_local(strBuf, "osTreePictureText") == 0) { + ctrl->outlineStyle = 6; + } else { + ctrl->outlineStyle = 0; + } + } else { + ctrl->outlineStyle = readIntValue(data, size, pos, tag); + } + ctrl->hasOutlineStyle = true; + } else if (!isForm && stricmp_local(propName, "Shape") == 0) { + if (tag == vaIdent) { + readStr(data, size, pos, strBuf, sizeof(strBuf)); + if (stricmp_local(strBuf, "bsBox") == 0) { + ctrl->shape = 0; + } else if (stricmp_local(strBuf, "bsFrame") == 0) { + ctrl->shape = 1; + } else if (stricmp_local(strBuf, "bsTopLine") == 0) { + ctrl->shape = 2; + } else if (stricmp_local(strBuf, "bsBottomLine") == 0) { + ctrl->shape = 3; + } else if (stricmp_local(strBuf, "bsLeftLine") == 0) { + ctrl->shape = 4; + } else if (stricmp_local(strBuf, "bsRightLine") == 0) { + ctrl->shape = 5; + } else { + ctrl->shape = 0; + } + } else { + ctrl->shape = readIntValue(data, size, pos, tag); + } + ctrl->hasShape = true; + } else if (!isForm && stricmp_local(propName, "Style") == 0) { + if (tag == vaIdent) { + readStr(data, size, pos, strBuf, sizeof(strBuf)); + if (stricmp_local(strBuf, "bsLowered") == 0) { + ctrl->style = 0; + } else if (stricmp_local(strBuf, "bsRaised") == 0) { + ctrl->style = 1; + } else { + ctrl->style = 0; + } + } else { + ctrl->style = readIntValue(data, size, pos, tag); + } + ctrl->hasStyle = true; } else { skipValue(data, size, pos, tag); } @@ -962,7 +1157,10 @@ static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl) "CheckBox", "ListBox", "ComboBox", "Memo", "Image", "GroupBox", "RadioButton", "Panel", "ScrollBar", "MediaPlayer", - "MainMenu", "PopupMenu", "MenuItem", "RadioGroup" + "MainMenu", "PopupMenu", "MenuItem", "RadioGroup", + "BitBtn", "SpeedButton", "TabSet", "Notebook", + "TabbedNotebook", "MaskEdit", "Outline", "Bevel", + "Header", "ScrollBox" }; char escaped[8192]; @@ -1065,6 +1263,34 @@ static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl) if (ctrl->hasShortCut && ctrl->shortCut != 0) { fprintf(out, " ShortCut=%d", ctrl->shortCut); } + if (ctrl->hasLayout && ctrl->layout != 0) { + fprintf(out, " Layout=%d", ctrl->layout); + } + if (ctrl->hasNumGlyphs && ctrl->numGlyphs > 1) { + fprintf(out, " NumGlyphs=%d", ctrl->numGlyphs); + } + if (ctrl->hasGroupIndex && ctrl->groupIndex != 0) { + fprintf(out, " GroupIndex=%d", ctrl->groupIndex); + } + if (ctrl->hasDown && ctrl->down) { + fprintf(out, " Down=1"); + } + if (ctrl->hasAllowAllUp && ctrl->allowAllUp) { + fprintf(out, " AllowAllUp=1"); + } + if (ctrl->hasEditMask && ctrl->editMask[0] != '\0') { + escapeStr(ctrl->editMask, escaped, sizeof(escaped)); + fprintf(out, " EditMask=\"%s\"", escaped); + } + if (ctrl->hasOutlineStyle && ctrl->outlineStyle != 0) { + fprintf(out, " OutlineStyle=%d", ctrl->outlineStyle); + } + if (ctrl->hasShape) { + fprintf(out, " Shape=%d", ctrl->shape); + } + if (ctrl->hasStyle && ctrl->style != 0) { + fprintf(out, " Style=%d", ctrl->style); + } fprintf(out, "\n"); @@ -1072,8 +1298,8 @@ static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl) // Auto-wired: Button/CheckBox→Click, Edit→Change, ListBox→Select, // ComboBox→Select+Change, Memo→Change - bool autoClick = (ctrl->type == ctButton || ctrl->type == ctCheckBox || ctrl->type == ctRadioButton || ctrl->type == ctMenuItem || ctrl->type == ctRadioGroup); - bool autoChange = (ctrl->type == ctEdit || ctrl->type == ctComboBox || ctrl->type == ctMemo || ctrl->type == ctScrollBar); + bool autoClick = (ctrl->type == ctButton || ctrl->type == ctCheckBox || ctrl->type == ctRadioButton || ctrl->type == ctMenuItem || ctrl->type == ctRadioGroup || ctrl->type == ctBitBtn || ctrl->type == ctSpeedButton); + bool autoChange = (ctrl->type == ctEdit || ctrl->type == ctComboBox || ctrl->type == ctMemo || ctrl->type == ctScrollBar || ctrl->type == ctTabSet || ctrl->type == ctTabbedNotebook || ctrl->type == ctMaskEdit); bool autoSelect = (ctrl->type == ctListBox || ctrl->type == ctComboBox); if (ctrl->hasOnClick && !autoClick) { diff --git a/forms/formcli.pas b/forms/formcli.pas index 5289703..231160a 100644 --- a/forms/formcli.pas +++ b/forms/formcli.pas @@ -13,7 +13,8 @@ unit FormCli; interface uses - SysUtils, Classes, Controls, Forms, StdCtrls, ExtCtrls, Menus, MPlayer, WinTypes, WinProcs; + SysUtils, Classes, Controls, Forms, StdCtrls, ExtCtrls, Menus, Buttons, Tabs, + TabNotBk, Mask, Outline, MPlayer, WinTypes, WinProcs; const MaxMsgLen = 4096; @@ -30,7 +31,10 @@ type ctCheckBox, ctListBox, ctComboBox, ctMemo, ctImage, ctGroupBox, ctRadioButton, ctPanel, ctScrollBar, ctMediaPlayer, - ctMainMenu, ctPopupMenu, ctMenuItem, ctRadioGroup); + ctMainMenu, ctPopupMenu, ctMenuItem, ctRadioGroup, + ctBitBtn, ctSpeedButton, ctTabSet, ctNotebook, + ctTabbedNotebook, ctMaskEdit, ctOutline, ctBevel, + ctHeader, ctScrollBox); { Bound event flags } TBoundEvent = (beDblClick, beKeyDown, beKeyUp, @@ -111,8 +115,11 @@ type procedure HandleRadioButtonClick(Sender: TObject); procedure HandleScrollBarChange(Sender: TObject); procedure HandleMenuItemClick(Sender: TObject); + procedure HandleMaskEditChange(Sender: TObject); procedure HandleMediaPlayerNotify(Sender: TObject); procedure HandleRadioGroupClick(Sender: TObject); + procedure HandleTabSetChange(Sender: TObject); + procedure HandleTabbedNotebookChange(Sender: TObject); { Outgoing event helpers } procedure SendEvent(FormId, CtrlId: Integer; const EventName: string; const Data: string); @@ -726,6 +733,50 @@ begin end; +procedure TFormClient.HandleTabSetChange(Sender: TObject); +var + Tag: Longint; + FormId: Integer; + CtrlId: Integer; +begin + Tag := (Sender as TComponent).Tag; + FormId := Tag shr 16; + CtrlId := Tag and $FFFF; + SendEvent(FormId, CtrlId, 'Change', IntToStr((Sender as TTabSet).TabIndex)); +end; + + +procedure TFormClient.HandleTabbedNotebookChange(Sender: TObject); +var + Tag: Longint; + FormId: Integer; + CtrlId: Integer; +begin + Tag := (Sender as TComponent).Tag; + FormId := Tag shr 16; + CtrlId := Tag and $FFFF; + SendEvent(FormId, CtrlId, 'Change', IntToStr((Sender as TTabbedNotebook).PageIndex)); +end; + + +procedure TFormClient.HandleMaskEditChange(Sender: TObject); +var + Tag: Longint; + FormId: Integer; + CtrlId: Integer; + Escaped: array[0..4095] of Char; + Txt: string; +begin + Tag := (Sender as TComponent).Tag; + FormId := Tag shr 16; + CtrlId := Tag and $FFFF; + Txt := (Sender as TMaskEdit).Text; + StrPCopy(FTmpBuf, Txt); + EscapeString(FTmpBuf, Escaped, SizeOf(Escaped)); + SendEvent(FormId, CtrlId, 'Change', '"' + StrPas(Escaped) + '"'); +end; + + procedure TFormClient.HandleFormClose(Sender: TObject; var Action: TCloseAction); var @@ -767,6 +818,16 @@ begin (CR^.Control as TMenuItem).OnClick := HandleMenuItemClick; ctRadioGroup: (CR^.Control as TRadioGroup).OnClick := HandleRadioGroupClick; + ctBitBtn: + (CR^.Control as TBitBtn).OnClick := HandleButtonClick; + ctSpeedButton: + (CR^.Control as TSpeedButton).OnClick := HandleButtonClick; + ctTabSet: + (CR^.Control as TTabSet).OnChange := HandleTabSetChange; + ctTabbedNotebook: + (CR^.Control as TTabbedNotebook).OnChange := HandleTabbedNotebookChange; + ctMaskEdit: + (CR^.Control as TMaskEdit).OnChange := HandleMaskEditChange; end; end; @@ -935,14 +996,17 @@ begin ctPanel: (CR^.Control as TPanel).Caption := StrPas(Unesc); ctMenuItem: (CR^.Control as TMenuItem).Caption := StrPas(Unesc); ctRadioGroup: (CR^.Control as TRadioGroup).Caption := StrPas(Unesc); + ctBitBtn: (CR^.Control as TBitBtn).Caption := StrPas(Unesc); + ctSpeedButton: (CR^.Control as TSpeedButton).Caption := StrPas(Unesc); end; end else if S = 'Text' then begin UnescapeString(Value, Unesc, SizeOf(Unesc)); case CR^.CtrlType of - ctEdit: (CR^.Control as TEdit).Text := StrPas(Unesc); + ctEdit: (CR^.Control as TEdit).Text := StrPas(Unesc); ctComboBox: (CR^.Control as TComboBox).Text := StrPas(Unesc); + ctMaskEdit: (CR^.Control as TMaskEdit).Text := StrPas(Unesc); ctMemo: begin { Convert \n to Lines } @@ -1043,6 +1107,106 @@ begin if Start^ <> #0 then (CR^.Control as TRadioGroup).Items.Add(StrPas(Start)); end; + ctTabSet: + begin + (CR^.Control as TTabSet).Tabs.Clear; + P := Unesc; + Start := P; + while P^ <> #0 do + begin + if P^ = #10 then + begin + P^ := #0; + (CR^.Control as TTabSet).Tabs.Add(StrPas(Start)); + Inc(P); + Start := P; + end + else + Inc(P); + end; + if Start^ <> #0 then + (CR^.Control as TTabSet).Tabs.Add(StrPas(Start)); + end; + ctNotebook: + begin + (CR^.Control as TNotebook).Pages.Clear; + P := Unesc; + Start := P; + while P^ <> #0 do + begin + if P^ = #10 then + begin + P^ := #0; + (CR^.Control as TNotebook).Pages.Add(StrPas(Start)); + Inc(P); + Start := P; + end + else + Inc(P); + end; + if Start^ <> #0 then + (CR^.Control as TNotebook).Pages.Add(StrPas(Start)); + end; + ctTabbedNotebook: + begin + (CR^.Control as TTabbedNotebook).Pages.Clear; + P := Unesc; + Start := P; + while P^ <> #0 do + begin + if P^ = #10 then + begin + P^ := #0; + (CR^.Control as TTabbedNotebook).Pages.Add(StrPas(Start)); + Inc(P); + Start := P; + end + else + Inc(P); + end; + if Start^ <> #0 then + (CR^.Control as TTabbedNotebook).Pages.Add(StrPas(Start)); + end; + ctOutline: + begin + (CR^.Control as TOutline).Lines.Clear; + P := Unesc; + Start := P; + while P^ <> #0 do + begin + if P^ = #10 then + begin + P^ := #0; + (CR^.Control as TOutline).Lines.Add(StrPas(Start)); + Inc(P); + Start := P; + end + else + Inc(P); + end; + if Start^ <> #0 then + (CR^.Control as TOutline).Lines.Add(StrPas(Start)); + end; + ctHeader: + begin + (CR^.Control as THeader).Sections.Clear; + P := Unesc; + Start := P; + while P^ <> #0 do + begin + if P^ = #10 then + begin + P^ := #0; + (CR^.Control as THeader).Sections.Add(StrPas(Start)); + Inc(P); + Start := P; + end + else + Inc(P); + end; + if Start^ <> #0 then + (CR^.Control as THeader).Sections.Add(StrPas(Start)); + end; end; end else if S = 'Checked' then @@ -1075,7 +1239,9 @@ begin begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctEdit then - (CR^.Control as TEdit).MaxLength := N; + (CR^.Control as TEdit).MaxLength := N + else if CR^.CtrlType = ctMaskEdit then + (CR^.Control as TMaskEdit).MaxLength := N; end else if S = 'ReadOnly' then begin @@ -1101,9 +1267,12 @@ begin begin N := StrToIntDef(StrPas(Value), -1); case CR^.CtrlType of - ctListBox: (CR^.Control as TListBox).ItemIndex := N; - ctComboBox: (CR^.Control as TComboBox).ItemIndex := N; - ctRadioGroup: (CR^.Control as TRadioGroup).ItemIndex := N; + ctListBox: (CR^.Control as TListBox).ItemIndex := N; + ctComboBox: (CR^.Control as TComboBox).ItemIndex := N; + ctRadioGroup: (CR^.Control as TRadioGroup).ItemIndex := N; + ctTabSet: (CR^.Control as TTabSet).TabIndex := N; + ctNotebook: (CR^.Control as TNotebook).PageIndex := N; + ctTabbedNotebook: (CR^.Control as TTabbedNotebook).PageIndex := N; end; end else if S = 'Stretch' then @@ -1160,7 +1329,9 @@ begin begin N := StrToIntDef(StrPas(Value), 0); if CR^.CtrlType = ctScrollBar then - (CR^.Control as TScrollBar).Kind := TScrollBarKind(N); + (CR^.Control as TScrollBar).Kind := TScrollBarKind(N) + else if CR^.CtrlType = ctBitBtn then + (CR^.Control as TBitBtn).Kind := TBitBtnKind(N); end else if S = 'Min' then begin @@ -1307,6 +1478,66 @@ begin PCR := FindCtrl(FR, N); if (PCR <> nil) and (PCR^.Control is TPopupMenu) and (CR^.Control is TControl) then (CR^.Control as TControl).PopupMenu := PCR^.Control as TPopupMenu; + end + else if S = 'Layout' then + begin + N := StrToIntDef(StrPas(Value), 0); + if CR^.CtrlType = ctBitBtn then + (CR^.Control as TBitBtn).Layout := TButtonLayout(N) + else if CR^.CtrlType = ctSpeedButton then + (CR^.Control as TSpeedButton).Layout := TButtonLayout(N); + end + else if S = 'NumGlyphs' then + begin + N := StrToIntDef(StrPas(Value), 1); + if CR^.CtrlType = ctBitBtn then + (CR^.Control as TBitBtn).NumGlyphs := N + else if CR^.CtrlType = ctSpeedButton then + (CR^.Control as TSpeedButton).NumGlyphs := N; + end + else if S = 'GroupIndex' then + begin + N := StrToIntDef(StrPas(Value), 0); + if CR^.CtrlType = ctSpeedButton then + (CR^.Control as TSpeedButton).GroupIndex := N; + end + else if S = 'Down' then + begin + N := StrToIntDef(StrPas(Value), 0); + if CR^.CtrlType = ctSpeedButton then + (CR^.Control as TSpeedButton).Down := (N <> 0); + end + else if S = 'AllowAllUp' then + begin + N := StrToIntDef(StrPas(Value), 0); + if CR^.CtrlType = ctSpeedButton then + (CR^.Control as TSpeedButton).AllowAllUp := (N <> 0); + end + else if S = 'EditMask' then + begin + if CR^.CtrlType = ctMaskEdit then + begin + UnescapeString(Value, Unesc, SizeOf(Unesc)); + (CR^.Control as TMaskEdit).EditMask := StrPas(Unesc); + end; + end + else if S = 'OutlineStyle' then + begin + N := StrToIntDef(StrPas(Value), 0); + if CR^.CtrlType = ctOutline then + (CR^.Control as TOutline).OutlineStyle := TOutlineStyle(N); + end + else if S = 'Shape' then + begin + N := StrToIntDef(StrPas(Value), 0); + if CR^.CtrlType = ctBevel then + (CR^.Control as TBevel).Shape := TBevelShape(N); + end + else if S = 'Style' then + begin + N := StrToIntDef(StrPas(Value), 0); + if CR^.CtrlType = ctBevel then + (CR^.Control as TBevel).Style := TBevelStyle(N); end; end; @@ -1405,6 +1636,26 @@ begin Result := ctMenuItem else if S = 'RadioGroup' then Result := ctRadioGroup + else if S = 'BitBtn' then + Result := ctBitBtn + else if S = 'SpeedButton' then + Result := ctSpeedButton + else if S = 'TabSet' then + Result := ctTabSet + else if S = 'Notebook' then + Result := ctNotebook + else if S = 'TabbedNotebook' then + Result := ctTabbedNotebook + else if S = 'MaskEdit' then + Result := ctMaskEdit + else if S = 'Outline' then + Result := ctOutline + else if S = 'Bevel' then + Result := ctBevel + else if S = 'Header' then + Result := ctHeader + else if S = 'ScrollBox' then + Result := ctScrollBox else Result := ctUnknown; end; @@ -1562,6 +1813,26 @@ begin Comp := TMenuItem.Create(FR^.Form); ctRadioGroup: Comp := TRadioGroup.Create(FR^.Form); + ctBitBtn: + Comp := TBitBtn.Create(FR^.Form); + ctSpeedButton: + Comp := TSpeedButton.Create(FR^.Form); + ctTabSet: + Comp := TTabSet.Create(FR^.Form); + ctNotebook: + Comp := TNotebook.Create(FR^.Form); + ctTabbedNotebook: + Comp := TTabbedNotebook.Create(FR^.Form); + ctMaskEdit: + Comp := TMaskEdit.Create(FR^.Form); + ctOutline: + Comp := TOutline.Create(FR^.Form); + ctBevel: + Comp := TBevel.Create(FR^.Form); + ctHeader: + Comp := THeader.Create(FR^.Form); + ctScrollBox: + Comp := TScrollBox.Create(FR^.Form); end; if Comp = nil then diff --git a/forms/protocol.md b/forms/protocol.md index dcec27d..1bbe950 100644 --- a/forms/protocol.md +++ b/forms/protocol.md @@ -72,7 +72,7 @@ Event data varies by event type: |-----------|-------------------------------| | Click | (none), or `` (RadioGroup) | | DblClick | (none) | -| Change | `"new text"` or `` (ScrollBar) | +| Change | `"new text"`, `` (ScrollBar), or `` (TabSet, TabbedNotebook) | | Select | ` "selected text"` | | KeyDown | `` | | KeyUp | `` | @@ -105,6 +105,16 @@ Event data varies by event type: | PopupMenu | TPopupMenu | (none) | | MenuItem | TMenuItem | Click | | RadioGroup | TRadioGroup | Click | +| BitBtn | TBitBtn | Click | +| SpeedButton | TSpeedButton | Click | +| TabSet | TTabSet | Change | +| Notebook | TNotebook | (none) | +| TabbedNotebook | TTabbedNotebook | Change | +| MaskEdit | TMaskEdit | Change | +| Outline | TOutline | (none) | +| Bevel | TBevel | (none) | +| Header | THeader | (none) | +| ScrollBox | TScrollBox | (none) | MainMenu and PopupMenu use `0 0 0 0` geometry (non-visual). MenuItem uses `0 0 0 0` geometry and requires a `Parent` property to specify its @@ -123,16 +133,16 @@ not support opt-in events. | Property | Applies To | Value Format | |-------------|---------------------------------------------|-------------------------------------------| -| Caption | Label, Button, CheckBox, GroupBox, RadioButton, Panel, MenuItem, RadioGroup | Quoted string | -| Text | Edit, ComboBox, Memo | Quoted string (`\n` for line breaks) | -| Items | ListBox, ComboBox, RadioGroup | Quoted string (`\n`-delimited) | +| Caption | Label, Button, CheckBox, GroupBox, RadioButton, Panel, MenuItem, RadioGroup, BitBtn, SpeedButton | Quoted string | +| Text | Edit, ComboBox, Memo, MaskEdit | Quoted string (`\n` for line breaks) | +| Items | ListBox, ComboBox, RadioGroup, TabSet, Notebook, TabbedNotebook, Outline, Header | Quoted string (`\n`-delimited) | | Checked | CheckBox, RadioButton, MenuItem | 0 or 1 | | Enabled | All | 0 or 1 | | Visible | All | 0 or 1 | -| MaxLength | Edit | Integer | +| MaxLength | Edit, MaskEdit | Integer | | ReadOnly | Edit, Memo | 0 or 1 | | ScrollBars | Memo | 0-3 (ssNone..ssBoth) | -| ItemIndex | ListBox, ComboBox, RadioGroup | Integer (-1 = none) | +| ItemIndex | ListBox, ComboBox, RadioGroup, TabSet, Notebook, TabbedNotebook | Integer (-1 = none) | | TabOrder | All windowed controls | Integer | | Stretch | Image | 0 or 1 | | Center | Image | 0 or 1 | @@ -141,7 +151,7 @@ not support opt-in events. | BevelOuter | Panel | 0-2 (bvNone, bvLowered, bvRaised) | | BevelInner | Panel | 0-2 (bvNone, bvLowered, bvRaised) | | BorderStyle | Panel | 0-1 (bsNone, bsSingle) | -| Kind | ScrollBar | 0-1 (sbHorizontal, sbVertical) | +| Kind | ScrollBar, BitBtn | Integer (see below) | | Min | ScrollBar | Integer | | Max | ScrollBar | Integer | | Position | ScrollBar | Integer | @@ -155,6 +165,15 @@ not support opt-in events. | Columns | RadioGroup | Integer (number of columns) | | ShortCut | MenuItem | Integer (Delphi ShortCut value) | | PopupMenu | Any TControl | Integer (ctrlId of PopupMenu) | +| Layout | BitBtn, SpeedButton | 0-3 (blGlyphLeft..blGlyphBottom) | +| NumGlyphs | BitBtn, SpeedButton | Integer (1-4) | +| GroupIndex | SpeedButton | Integer (0 = no group) | +| Down | SpeedButton | 0 or 1 | +| AllowAllUp | SpeedButton | 0 or 1 | +| EditMask | MaskEdit | Quoted string | +| OutlineStyle| Outline | 0-6 (osText..osTreePictureText) | +| Shape | Bevel | 0-5 (bsBox..bsRightLine) | +| Style | Bevel | 0-1 (bsLowered, bsRaised) | File path properties (Picture, FileName) are resolved relative to the client's `BasePath` setting. Subdirectories are allowed (e.g.,