WinComm/forms/dfm2form.c
Scott Duensing da21cc71f6 Add StringGrid control type
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:35:09 -06:00

1555 lines
57 KiB
C

// dfm2form.c - Convert Delphi 1.0 binary DFM (TPF0) to .form protocol text
//
// Usage: dfm2form <input.dfm> [output.form]
//
// Reads a binary DFM file, extracts the form and control definitions,
// and outputs protocol commands (FORM.CREATE, CTRL.CREATE, EVENT.BIND,
// FORM.SHOW) suitable for the remote forms system.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <ctype.h>
#include <errno.h>
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
typedef enum {
ctUnknown = 0,
ctLabel,
ctEdit,
ctButton,
ctCheckBox,
ctListBox,
ctComboBox,
ctMemo,
ctImage,
ctGroupBox,
ctRadioButton,
ctPanel,
ctScrollBar,
ctMediaPlayer,
ctMainMenu,
ctPopupMenu,
ctMenuItem,
ctRadioGroup,
ctBitBtn,
ctSpeedButton,
ctTabSet,
ctNotebook,
ctTabbedNotebook,
ctMaskEdit,
ctOutline,
ctBevel,
ctHeader,
ctScrollBox,
ctStringGrid
} CtrlTypeE;
typedef struct {
char name[64];
CtrlTypeE type;
int32_t left;
int32_t top;
int32_t width;
int32_t height;
char caption[256];
char text[4096];
char items[4096];
int32_t checked;
int32_t enabled;
int32_t visible;
int32_t maxLength;
int32_t readOnly;
int32_t scrollBars;
int32_t tabOrder;
int32_t itemIndex;
bool hasCaption;
bool hasText;
bool hasItems;
bool hasChecked;
bool hasEnabled;
bool hasVisible;
bool hasMaxLength;
bool hasReadOnly;
bool hasScrollBars;
bool hasTabOrder;
bool hasItemIndex;
bool hasOnClick;
bool hasOnChange;
bool hasOnDblClick;
bool hasOnEnter;
bool hasOnExit;
bool hasOnKeyDown;
bool hasOnKeyUp;
bool hasOnMouseDown;
bool hasOnMouseUp;
int32_t stretch;
int32_t center;
int32_t transparent;
int32_t bevelOuter;
int32_t bevelInner;
int32_t borderStyle;
int32_t kind;
int32_t min;
int32_t max;
int32_t position;
int32_t largeChange;
int32_t smallChange;
int32_t autoOpen;
char fileName[256];
char deviceType[64];
bool hasStretch;
bool hasCenter;
bool hasTransparent;
bool hasBevelOuter;
bool hasBevelInner;
bool hasBorderStyle;
bool hasKind;
bool hasMin;
bool hasMax;
bool hasPosition;
bool hasLargeChange;
bool hasSmallChange;
bool hasAutoOpen;
bool hasFileName;
bool hasDeviceType;
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;
int32_t colCount;
int32_t rowCount;
int32_t fixedCols;
int32_t fixedRows;
int32_t defaultColWidth;
int32_t defaultRowHeight;
int32_t options;
bool hasColCount;
bool hasRowCount;
bool hasFixedCols;
bool hasFixedRows;
bool hasDefaultColWidth;
bool hasDefaultRowHeight;
bool hasOptions;
bool hasOnSetEditText;
} DfmCtrlT;
typedef struct {
char name[64];
int32_t width;
int32_t height;
char caption[256];
DfmCtrlT ctrls[256];
int32_t ctrlCount;
} DfmFormT;
// DFM value type tags
enum {
vaNull = 0x00,
vaList = 0x01,
vaInt8 = 0x02,
vaInt16 = 0x03,
vaInt32 = 0x04,
vaExtended = 0x05,
vaString = 0x06,
vaIdent = 0x07,
vaFalse = 0x08,
vaTrue = 0x09,
vaBinary = 0x0A,
vaSet = 0x0B,
vaLString = 0x0C,
vaNil = 0x0D,
vaCollection = 0x0E
};
// ---------------------------------------------------------------------------
// Prototypes
// ---------------------------------------------------------------------------
static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl);
static void emitForm(FILE *out, int32_t formId, DfmFormT *form);
static void escapeStr(const char *src, char *dst, int32_t dstSize);
static void initCtrl(DfmCtrlT *ctrl);
static void initForm(DfmFormT *form);
static CtrlTypeE mapClassName(const char *className);
static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, bool isRoot, int32_t parentIdx);
static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, DfmCtrlT *ctrl, bool isForm);
static int32_t readByte(const uint8_t *data, int32_t size, int32_t *pos);
static int32_t readInt16LE(const uint8_t *data, int32_t size, int32_t *pos);
static int32_t readInt32LE(const uint8_t *data, int32_t size, int32_t *pos);
static int32_t readIntValue(const uint8_t *data, int32_t size, int32_t *pos, uint8_t tag);
static bool readStr(const uint8_t *data, int32_t size, int32_t *pos, char *buf, int32_t bufSize);
static void skipValue(const uint8_t *data, int32_t size, int32_t *pos, uint8_t tag);
static int stricmp_local(const char *a, const char *b);
static void usage(const char *progName);
// ---------------------------------------------------------------------------
// Low-level DFM readers
// ---------------------------------------------------------------------------
static int32_t readByte(const uint8_t *data, int32_t size, int32_t *pos) {
if (*pos >= size) {
fprintf(stderr, "Error: unexpected end of file at offset %d\n", *pos);
exit(1);
}
return data[(*pos)++];
}
static int32_t readInt16LE(const uint8_t *data, int32_t size, int32_t *pos) {
if (*pos + 2 > size) {
fprintf(stderr, "Error: unexpected end of file at offset %d\n", *pos);
exit(1);
}
int16_t val = (int16_t)(data[*pos] | (data[*pos + 1] << 8));
*pos += 2;
return val;
}
static int32_t readInt32LE(const uint8_t *data, int32_t size, int32_t *pos) {
if (*pos + 4 > size) {
fprintf(stderr, "Error: unexpected end of file at offset %d\n", *pos);
exit(1);
}
int32_t val = (int32_t)(data[*pos] | (data[*pos + 1] << 8) |
(data[*pos + 2] << 16) | (data[*pos + 3] << 24));
*pos += 4;
return val;
}
static bool readStr(const uint8_t *data, int32_t size, int32_t *pos, char *buf, int32_t bufSize) {
int32_t len = readByte(data, size, pos);
if (*pos + len > size) {
fprintf(stderr, "Error: string overflows file at offset %d\n", *pos);
exit(1);
}
int32_t copyLen = (len < bufSize - 1) ? len : bufSize - 1;
memcpy(buf, data + *pos, copyLen);
buf[copyLen] = '\0';
*pos += len;
return true;
}
static int32_t readIntValue(const uint8_t *data, int32_t size, int32_t *pos, uint8_t tag) {
switch (tag) {
case vaInt8:
return (int8_t)readByte(data, size, pos);
case vaInt16:
return readInt16LE(data, size, pos);
case vaInt32:
return readInt32LE(data, size, pos);
default:
return 0;
}
}
// ---------------------------------------------------------------------------
// Skip unknown property values
// ---------------------------------------------------------------------------
static void skipValue(const uint8_t *data, int32_t size, int32_t *pos, uint8_t tag) {
int32_t len;
char buf[256];
switch (tag) {
case vaNull:
break;
case vaList:
// skip items until vaNull
while (*pos < size) {
uint8_t itemTag = readByte(data, size, pos);
if (itemTag == vaNull) {
break;
}
skipValue(data, size, pos, itemTag);
}
break;
case vaInt8:
*pos += 1;
break;
case vaInt16:
*pos += 2;
break;
case vaInt32:
*pos += 4;
break;
case vaExtended:
*pos += 10;
break;
case vaString:
len = readByte(data, size, pos);
*pos += len;
break;
case vaIdent:
len = readByte(data, size, pos);
*pos += len;
break;
case vaFalse:
case vaTrue:
case vaNil:
break;
case vaBinary:
len = readInt32LE(data, size, pos);
*pos += len;
break;
case vaSet:
// repeated strings, empty string terminates
while (*pos < size) {
readStr(data, size, pos, buf, sizeof(buf));
if (buf[0] == '\0') {
break;
}
}
break;
case vaLString:
len = readInt32LE(data, size, pos);
*pos += len;
break;
case vaCollection:
// items bracketed by vaList/vaNull
while (*pos < size) {
uint8_t itemTag = readByte(data, size, pos);
if (itemTag == vaNull) {
break;
}
skipValue(data, size, pos, itemTag);
}
break;
default:
fprintf(stderr, "Warning: unknown value tag 0x%02X at offset %d\n", tag, *pos);
break;
}
}
// ---------------------------------------------------------------------------
// Case-insensitive string compare
// ---------------------------------------------------------------------------
static int stricmp_local(const char *a, const char *b) {
while (*a && *b) {
int ca = tolower((unsigned char)*a);
int cb = tolower((unsigned char)*b);
if (ca != cb) {
return ca - cb;
}
a++;
b++;
}
return (unsigned char)*a - (unsigned char)*b;
}
// ---------------------------------------------------------------------------
// Map Delphi class name to control type
// ---------------------------------------------------------------------------
static CtrlTypeE mapClassName(const char *className) {
if (stricmp_local(className, "TLabel") == 0) {
return ctLabel;
}
if (stricmp_local(className, "TEdit") == 0) {
return ctEdit;
}
if (stricmp_local(className, "TButton") == 0) {
return ctButton;
}
if (stricmp_local(className, "TCheckBox") == 0) {
return ctCheckBox;
}
if (stricmp_local(className, "TListBox") == 0) {
return ctListBox;
}
if (stricmp_local(className, "TComboBox") == 0) {
return ctComboBox;
}
if (stricmp_local(className, "TMemo") == 0) {
return ctMemo;
}
if (stricmp_local(className, "TImage") == 0) {
return ctImage;
}
if (stricmp_local(className, "TGroupBox") == 0) {
return ctGroupBox;
}
if (stricmp_local(className, "TRadioButton") == 0) {
return ctRadioButton;
}
if (stricmp_local(className, "TPanel") == 0) {
return ctPanel;
}
if (stricmp_local(className, "TScrollBar") == 0) {
return ctScrollBar;
}
if (stricmp_local(className, "TMediaPlayer") == 0) {
return ctMediaPlayer;
}
if (stricmp_local(className, "TMainMenu") == 0) {
return ctMainMenu;
}
if (stricmp_local(className, "TPopupMenu") == 0) {
return ctPopupMenu;
}
if (stricmp_local(className, "TMenuItem") == 0) {
return ctMenuItem;
}
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;
}
if (stricmp_local(className, "TStringGrid") == 0) {
return ctStringGrid;
}
return ctUnknown;
}
// ---------------------------------------------------------------------------
// Init helpers
// ---------------------------------------------------------------------------
static void initCtrl(DfmCtrlT *ctrl) {
memset(ctrl, 0, sizeof(DfmCtrlT));
ctrl->enabled = 1;
ctrl->visible = 1;
ctrl->itemIndex = -1;
ctrl->tabOrder = -1;
ctrl->bevelOuter = 1; // bvRaised
ctrl->largeChange = 1;
ctrl->smallChange = 1;
ctrl->parentCtrlIdx = -1;
}
static void initForm(DfmFormT *form) {
memset(form, 0, sizeof(DfmFormT));
}
// ---------------------------------------------------------------------------
// Parse properties from DFM binary
// ---------------------------------------------------------------------------
static void parseProperties(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, DfmCtrlT *ctrl, bool isForm) {
char propName[64];
char strBuf[4096];
while (*pos < size) {
// Property name (empty = end of properties)
int32_t nameLen = readByte(data, size, pos);
if (nameLen == 0) {
break;
}
// Read the property name
(*pos)--; // back up, readStr reads the length byte
readStr(data, size, pos, propName, sizeof(propName));
// Read value tag
uint8_t tag = readByte(data, size, pos);
// Match known properties
if (isForm && stricmp_local(propName, "Caption") == 0) {
if (tag == vaString) {
readStr(data, size, pos, form->caption, sizeof(form->caption));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(form->caption) - 1) ? len : (int32_t)sizeof(form->caption) - 1;
memcpy(form->caption, data + *pos, copyLen);
form->caption[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
} else if (isForm && stricmp_local(propName, "ClientWidth") == 0) {
form->width = readIntValue(data, size, pos, tag);
} else if (isForm && stricmp_local(propName, "ClientHeight") == 0) {
form->height = readIntValue(data, size, pos, tag);
} else if (isForm && stricmp_local(propName, "Width") == 0 && form->width == 0) {
form->width = readIntValue(data, size, pos, tag);
} else if (isForm && stricmp_local(propName, "Height") == 0 && form->height == 0) {
form->height = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Left") == 0) {
ctrl->left = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Top") == 0) {
ctrl->top = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Width") == 0) {
ctrl->width = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Height") == 0) {
ctrl->height = readIntValue(data, size, pos, tag);
} else if (!isForm && stricmp_local(propName, "Caption") == 0) {
if (tag == vaString) {
readStr(data, size, pos, ctrl->caption, sizeof(ctrl->caption));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->caption) - 1) ? len : (int32_t)sizeof(ctrl->caption) - 1;
memcpy(ctrl->caption, data + *pos, copyLen);
ctrl->caption[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasCaption = true;
} else if (!isForm && stricmp_local(propName, "Text") == 0) {
if (tag == vaString) {
readStr(data, size, pos, ctrl->text, sizeof(ctrl->text));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->text) - 1) ? len : (int32_t)sizeof(ctrl->text) - 1;
memcpy(ctrl->text, data + *pos, copyLen);
ctrl->text[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasText = true;
} 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;
while (*pos < size) {
uint8_t itemTag = readByte(data, size, pos);
if (itemTag == vaNull) {
break;
}
if (itemTag == vaString) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else if (itemTag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(strBuf) - 1) ? len : (int32_t)sizeof(strBuf) - 1;
memcpy(strBuf, data + *pos, copyLen);
strBuf[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, itemTag);
continue;
}
int32_t slen = (int32_t)strlen(strBuf);
if (itemsLen + slen + 2 < (int32_t)sizeof(ctrl->items)) {
if (itemsLen > 0) {
ctrl->items[itemsLen++] = '\n';
}
memcpy(ctrl->items + itemsLen, strBuf, slen);
itemsLen += slen;
ctrl->items[itemsLen] = '\0';
}
}
ctrl->hasItems = true;
} else if (!isForm && stricmp_local(propName, "Checked") == 0) {
if (tag == vaTrue) {
ctrl->checked = 1;
} else if (tag == vaFalse) {
ctrl->checked = 0;
} else {
ctrl->checked = readIntValue(data, size, pos, tag);
}
ctrl->hasChecked = true;
} else if (!isForm && stricmp_local(propName, "State") == 0) {
// TCheckBox.State: cbUnchecked=0, cbChecked=1, cbGrayed=2
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "cbChecked") == 0) {
ctrl->checked = 1;
} else {
ctrl->checked = 0;
}
} else {
ctrl->checked = readIntValue(data, size, pos, tag);
}
ctrl->hasChecked = true;
} else if (!isForm && stricmp_local(propName, "Enabled") == 0) {
if (tag == vaTrue) {
ctrl->enabled = 1;
} else if (tag == vaFalse) {
ctrl->enabled = 0;
} else {
ctrl->enabled = readIntValue(data, size, pos, tag);
}
ctrl->hasEnabled = true;
} else if (!isForm && stricmp_local(propName, "Visible") == 0) {
if (tag == vaTrue) {
ctrl->visible = 1;
} else if (tag == vaFalse) {
ctrl->visible = 0;
} else {
ctrl->visible = readIntValue(data, size, pos, tag);
}
ctrl->hasVisible = true;
} else if (!isForm && stricmp_local(propName, "MaxLength") == 0) {
ctrl->maxLength = readIntValue(data, size, pos, tag);
ctrl->hasMaxLength = true;
} else if (!isForm && stricmp_local(propName, "ReadOnly") == 0) {
if (tag == vaTrue) {
ctrl->readOnly = 1;
} else if (tag == vaFalse) {
ctrl->readOnly = 0;
} else {
ctrl->readOnly = readIntValue(data, size, pos, tag);
}
ctrl->hasReadOnly = true;
} else if (!isForm && stricmp_local(propName, "ScrollBars") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "ssNone") == 0) {
ctrl->scrollBars = 0;
} else if (stricmp_local(strBuf, "ssHorizontal") == 0) {
ctrl->scrollBars = 1;
} else if (stricmp_local(strBuf, "ssVertical") == 0) {
ctrl->scrollBars = 2;
} else if (stricmp_local(strBuf, "ssBoth") == 0) {
ctrl->scrollBars = 3;
} else {
ctrl->scrollBars = 0;
}
} else {
ctrl->scrollBars = readIntValue(data, size, pos, tag);
}
ctrl->hasScrollBars = true;
} 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 || 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) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnClick = true;
}
} else if (stricmp_local(propName, "OnChange") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnChange = true;
}
} else if (stricmp_local(propName, "OnDblClick") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnDblClick = true;
}
} else if (stricmp_local(propName, "OnEnter") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnEnter = true;
}
} else if (stricmp_local(propName, "OnExit") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnExit = true;
}
} else if (stricmp_local(propName, "OnKeyDown") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnKeyDown = true;
}
} else if (stricmp_local(propName, "OnKeyUp") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnKeyUp = true;
}
} else if (stricmp_local(propName, "OnMouseDown") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnMouseDown = true;
}
} else if (stricmp_local(propName, "OnMouseUp") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnMouseUp = true;
}
} else if (!isForm && stricmp_local(propName, "Stretch") == 0) {
if (tag == vaTrue) {
ctrl->stretch = 1;
} else if (tag == vaFalse) {
ctrl->stretch = 0;
} else {
ctrl->stretch = readIntValue(data, size, pos, tag);
}
ctrl->hasStretch = true;
} else if (!isForm && stricmp_local(propName, "Center") == 0) {
if (tag == vaTrue) {
ctrl->center = 1;
} else if (tag == vaFalse) {
ctrl->center = 0;
} else {
ctrl->center = readIntValue(data, size, pos, tag);
}
ctrl->hasCenter = true;
} else if (!isForm && stricmp_local(propName, "Transparent") == 0) {
if (tag == vaTrue) {
ctrl->transparent = 1;
} else if (tag == vaFalse) {
ctrl->transparent = 0;
} else {
ctrl->transparent = readIntValue(data, size, pos, tag);
}
ctrl->hasTransparent = true;
} else if (!isForm && stricmp_local(propName, "BevelOuter") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "bvNone") == 0) {
ctrl->bevelOuter = 0;
} else if (stricmp_local(strBuf, "bvLowered") == 0) {
ctrl->bevelOuter = 1;
} else if (stricmp_local(strBuf, "bvRaised") == 0) {
ctrl->bevelOuter = 2;
} else {
ctrl->bevelOuter = 0;
}
} else {
ctrl->bevelOuter = readIntValue(data, size, pos, tag);
}
ctrl->hasBevelOuter = true;
} else if (!isForm && stricmp_local(propName, "BevelInner") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "bvNone") == 0) {
ctrl->bevelInner = 0;
} else if (stricmp_local(strBuf, "bvLowered") == 0) {
ctrl->bevelInner = 1;
} else if (stricmp_local(strBuf, "bvRaised") == 0) {
ctrl->bevelInner = 2;
} else {
ctrl->bevelInner = 0;
}
} else {
ctrl->bevelInner = readIntValue(data, size, pos, tag);
}
ctrl->hasBevelInner = true;
} else if (!isForm && stricmp_local(propName, "BorderStyle") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "bsNone") == 0) {
ctrl->borderStyle = 0;
} else if (stricmp_local(strBuf, "bsSingle") == 0) {
ctrl->borderStyle = 1;
} else {
ctrl->borderStyle = 0;
}
} else {
ctrl->borderStyle = readIntValue(data, size, pos, tag);
}
ctrl->hasBorderStyle = true;
} else if (!isForm && stricmp_local(propName, "Kind") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (stricmp_local(strBuf, "sbHorizontal") == 0) {
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;
}
} else {
ctrl->kind = readIntValue(data, size, pos, tag);
}
ctrl->hasKind = true;
} else if (!isForm && stricmp_local(propName, "Min") == 0) {
ctrl->min = readIntValue(data, size, pos, tag);
ctrl->hasMin = true;
} else if (!isForm && stricmp_local(propName, "Max") == 0) {
ctrl->max = readIntValue(data, size, pos, tag);
ctrl->hasMax = true;
} else if (!isForm && stricmp_local(propName, "Position") == 0) {
ctrl->position = readIntValue(data, size, pos, tag);
ctrl->hasPosition = true;
} else if (!isForm && stricmp_local(propName, "LargeChange") == 0) {
ctrl->largeChange = readIntValue(data, size, pos, tag);
ctrl->hasLargeChange = true;
} else if (!isForm && stricmp_local(propName, "SmallChange") == 0) {
ctrl->smallChange = readIntValue(data, size, pos, tag);
ctrl->hasSmallChange = true;
} else if (!isForm && stricmp_local(propName, "FileName") == 0) {
if (tag == vaString) {
readStr(data, size, pos, ctrl->fileName, sizeof(ctrl->fileName));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->fileName) - 1) ? len : (int32_t)sizeof(ctrl->fileName) - 1;
memcpy(ctrl->fileName, data + *pos, copyLen);
ctrl->fileName[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasFileName = true;
} else if (!isForm && stricmp_local(propName, "DeviceType") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, ctrl->deviceType, sizeof(ctrl->deviceType));
} else if (tag == vaString) {
readStr(data, size, pos, ctrl->deviceType, sizeof(ctrl->deviceType));
} else if (tag == vaLString) {
int32_t len = readInt32LE(data, size, pos);
int32_t copyLen = (len < (int32_t)sizeof(ctrl->deviceType) - 1) ? len : (int32_t)sizeof(ctrl->deviceType) - 1;
memcpy(ctrl->deviceType, data + *pos, copyLen);
ctrl->deviceType[copyLen] = '\0';
*pos += len;
} else {
skipValue(data, size, pos, tag);
}
ctrl->hasDeviceType = true;
} else if (!isForm && stricmp_local(propName, "AutoOpen") == 0) {
if (tag == vaTrue) {
ctrl->autoOpen = 1;
} else if (tag == vaFalse) {
ctrl->autoOpen = 0;
} else {
ctrl->autoOpen = readIntValue(data, size, pos, tag);
}
ctrl->hasAutoOpen = true;
} else if (!isForm && stricmp_local(propName, "Columns") == 0) {
ctrl->columns = readIntValue(data, size, pos, tag);
ctrl->hasColumns = true;
} 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 if (!isForm && stricmp_local(propName, "ColCount") == 0) {
ctrl->colCount = readIntValue(data, size, pos, tag);
ctrl->hasColCount = true;
} else if (!isForm && stricmp_local(propName, "RowCount") == 0) {
ctrl->rowCount = readIntValue(data, size, pos, tag);
ctrl->hasRowCount = true;
} else if (!isForm && stricmp_local(propName, "FixedCols") == 0) {
ctrl->fixedCols = readIntValue(data, size, pos, tag);
ctrl->hasFixedCols = true;
} else if (!isForm && stricmp_local(propName, "FixedRows") == 0) {
ctrl->fixedRows = readIntValue(data, size, pos, tag);
ctrl->hasFixedRows = true;
} else if (!isForm && stricmp_local(propName, "DefaultColWidth") == 0) {
ctrl->defaultColWidth = readIntValue(data, size, pos, tag);
ctrl->hasDefaultColWidth = true;
} else if (!isForm && stricmp_local(propName, "DefaultRowHeight") == 0) {
ctrl->defaultRowHeight = readIntValue(data, size, pos, tag);
ctrl->hasDefaultRowHeight = true;
} else if (!isForm && stricmp_local(propName, "Options") == 0) {
if (tag == vaSet) {
ctrl->options = 0;
while (*pos < size) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
if (strBuf[0] == '\0') {
break;
}
if (stricmp_local(strBuf, "goFixedVertLine") == 0) { ctrl->options |= 0x0001; }
else if (stricmp_local(strBuf, "goFixedHorzLine") == 0) { ctrl->options |= 0x0002; }
else if (stricmp_local(strBuf, "goVertLine") == 0) { ctrl->options |= 0x0004; }
else if (stricmp_local(strBuf, "goHorzLine") == 0) { ctrl->options |= 0x0008; }
else if (stricmp_local(strBuf, "goRangeSelect") == 0) { ctrl->options |= 0x0010; }
else if (stricmp_local(strBuf, "goDrawFocusSelected") == 0) { ctrl->options |= 0x0020; }
else if (stricmp_local(strBuf, "goRowSizing") == 0) { ctrl->options |= 0x0040; }
else if (stricmp_local(strBuf, "goColSizing") == 0) { ctrl->options |= 0x0080; }
else if (stricmp_local(strBuf, "goRowMoving") == 0) { ctrl->options |= 0x0100; }
else if (stricmp_local(strBuf, "goColMoving") == 0) { ctrl->options |= 0x0200; }
else if (stricmp_local(strBuf, "goEditing") == 0) { ctrl->options |= 0x0400; }
else if (stricmp_local(strBuf, "goTabs") == 0) { ctrl->options |= 0x0800; }
else if (stricmp_local(strBuf, "goThumbTracking") == 0) { ctrl->options |= 0x1000; }
}
} else {
ctrl->options = readIntValue(data, size, pos, tag);
}
ctrl->hasOptions = true;
} else if (stricmp_local(propName, "OnSetEditText") == 0) {
if (tag == vaIdent) {
readStr(data, size, pos, strBuf, sizeof(strBuf));
} else {
skipValue(data, size, pos, tag);
}
if (!isForm) {
ctrl->hasOnSetEditText = true;
}
} else {
skipValue(data, size, pos, tag);
}
}
}
// ---------------------------------------------------------------------------
// Parse a component (form or child control) recursively
// ---------------------------------------------------------------------------
static bool parseComponent(const uint8_t *data, int32_t size, int32_t *pos, DfmFormT *form, bool isRoot, int32_t parentIdx) {
// Check for flags byte (Delphi 1.0 rarely uses this but handle it)
uint8_t peek = data[*pos];
if ((peek & 0xF0) == 0xF0) {
uint8_t flags = readByte(data, size, pos);
if (flags & 0x02) {
// ffChildPos: skip tagged integer
uint8_t childTag = readByte(data, size, pos);
readIntValue(data, size, pos, childTag);
}
}
// Class name
char className[64];
readStr(data, size, pos, className, sizeof(className));
// Instance name
char instName[64];
readStr(data, size, pos, instName, sizeof(instName));
if (isRoot) {
// This is the form itself
snprintf(form->name, sizeof(form->name), "%s", instName);
parseProperties(data, size, pos, form, NULL, true);
// Parse child components
while (*pos < size) {
peek = data[*pos];
if (peek == 0x00) {
(*pos)++; // consume terminator
break;
}
parseComponent(data, size, pos, form, false, -1);
}
} else {
// Child control
CtrlTypeE type = mapClassName(className);
if (type == ctUnknown) {
fprintf(stderr, "Warning: unknown control class '%s' (%s), skipping\n", className, instName);
// Still need to parse properties and children to advance pos
DfmCtrlT dummy;
initCtrl(&dummy);
parseProperties(data, size, pos, form, &dummy, false);
// Skip nested children
while (*pos < size) {
peek = data[*pos];
if (peek == 0x00) {
(*pos)++;
break;
}
parseComponent(data, size, pos, form, false, -1);
}
return true;
}
if (form->ctrlCount >= 256) {
fprintf(stderr, "Error: too many controls (max 256)\n");
exit(1);
}
int32_t myIdx = form->ctrlCount;
DfmCtrlT *ctrl = &form->ctrls[myIdx];
initCtrl(ctrl);
ctrl->type = type;
ctrl->parentCtrlIdx = parentIdx;
snprintf(ctrl->name, sizeof(ctrl->name), "%s", instName);
parseProperties(data, size, pos, form, ctrl, false);
form->ctrlCount++;
// Recurse into children (menu items, panels, etc.)
while (*pos < size) {
peek = data[*pos];
if (peek == 0x00) {
(*pos)++;
break;
}
parseComponent(data, size, pos, form, false, myIdx);
}
}
return true;
}
// ---------------------------------------------------------------------------
// Escape a string for protocol output
// ---------------------------------------------------------------------------
static void escapeStr(const char *src, char *dst, int32_t dstSize) {
int32_t di = 0;
for (int32_t si = 0; src[si] != '\0' && di < dstSize - 2; si++) {
char c = src[si];
if (c == '"') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = '"';
} else if (c == '\\') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = '\\';
} else if (c == '\n') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = 'n';
} else if (c == '\r') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = 'r';
} else if (c == '\t') {
if (di + 2 >= dstSize - 1) { break; }
dst[di++] = '\\';
dst[di++] = 't';
} else {
dst[di++] = c;
}
}
dst[di] = '\0';
}
// ---------------------------------------------------------------------------
// Emit protocol commands for a single control
// ---------------------------------------------------------------------------
static void emitCtrl(FILE *out, int32_t formId, int32_t ctrlId, DfmCtrlT *ctrl) {
static const char *typeNames[] = {
"Unknown", "Label", "Edit", "Button",
"CheckBox", "ListBox", "ComboBox", "Memo",
"Image", "GroupBox", "RadioButton", "Panel",
"ScrollBar", "MediaPlayer",
"MainMenu", "PopupMenu", "MenuItem", "RadioGroup",
"BitBtn", "SpeedButton", "TabSet", "Notebook",
"TabbedNotebook", "MaskEdit", "Outline", "Bevel",
"Header", "ScrollBox", "StringGrid"
};
char escaped[8192];
if (ctrl->type == ctMainMenu || ctrl->type == ctPopupMenu || ctrl->type == ctMenuItem) {
fprintf(out, "CTRL.CREATE %d %d %s 0 0 0 0", formId, ctrlId, typeNames[ctrl->type]);
} else {
fprintf(out, "CTRL.CREATE %d %d %s %d %d %d %d", formId, ctrlId, typeNames[ctrl->type], ctrl->left, ctrl->top, ctrl->width, ctrl->height);
}
// Inline properties
if (ctrl->hasCaption) {
escapeStr(ctrl->caption, escaped, sizeof(escaped));
fprintf(out, " Caption=\"%s\"", escaped);
}
if (ctrl->hasText) {
escapeStr(ctrl->text, escaped, sizeof(escaped));
fprintf(out, " Text=\"%s\"", escaped);
}
if (ctrl->hasItems) {
escapeStr(ctrl->items, escaped, sizeof(escaped));
fprintf(out, " Items=\"%s\"", escaped);
}
if (ctrl->hasChecked) {
fprintf(out, " Checked=%d", ctrl->checked);
}
if (ctrl->hasEnabled && ctrl->enabled == 0) {
fprintf(out, " Enabled=0");
}
if (ctrl->hasVisible && ctrl->visible == 0) {
fprintf(out, " Visible=0");
}
if (ctrl->hasMaxLength && ctrl->maxLength > 0) {
fprintf(out, " MaxLength=%d", ctrl->maxLength);
}
if (ctrl->hasReadOnly && ctrl->readOnly) {
fprintf(out, " ReadOnly=1");
}
if (ctrl->hasScrollBars && ctrl->scrollBars != 0) {
fprintf(out, " ScrollBars=%d", ctrl->scrollBars);
}
if (ctrl->hasTabOrder && ctrl->tabOrder >= 0) {
fprintf(out, " TabOrder=%d", ctrl->tabOrder);
}
if (ctrl->hasItemIndex && ctrl->itemIndex >= 0) {
fprintf(out, " ItemIndex=%d", ctrl->itemIndex);
}
if (ctrl->hasStretch && ctrl->stretch) {
fprintf(out, " Stretch=1");
}
if (ctrl->hasCenter && ctrl->center) {
fprintf(out, " Center=1");
}
if (ctrl->hasTransparent && ctrl->transparent) {
fprintf(out, " Transparent=1");
}
if (ctrl->hasBevelOuter) {
fprintf(out, " BevelOuter=%d", ctrl->bevelOuter);
}
if (ctrl->hasBevelInner && ctrl->bevelInner != 0) {
fprintf(out, " BevelInner=%d", ctrl->bevelInner);
}
if (ctrl->hasBorderStyle && ctrl->borderStyle != 0) {
fprintf(out, " BorderStyle=%d", ctrl->borderStyle);
}
if (ctrl->hasKind && ctrl->kind != 0) {
fprintf(out, " Kind=%d", ctrl->kind);
}
if (ctrl->hasMin) {
fprintf(out, " Min=%d", ctrl->min);
}
if (ctrl->hasMax) {
fprintf(out, " Max=%d", ctrl->max);
}
if (ctrl->hasPosition && ctrl->position != 0) {
fprintf(out, " Position=%d", ctrl->position);
}
if (ctrl->hasLargeChange && ctrl->largeChange != 1) {
fprintf(out, " LargeChange=%d", ctrl->largeChange);
}
if (ctrl->hasSmallChange && ctrl->smallChange != 1) {
fprintf(out, " SmallChange=%d", ctrl->smallChange);
}
if (ctrl->hasFileName) {
escapeStr(ctrl->fileName, escaped, sizeof(escaped));
fprintf(out, " FileName=\"%s\"", escaped);
}
if (ctrl->hasDeviceType) {
escapeStr(ctrl->deviceType, escaped, sizeof(escaped));
fprintf(out, " DeviceType=\"%s\"", escaped);
}
if (ctrl->hasAutoOpen && ctrl->autoOpen) {
fprintf(out, " AutoOpen=1");
}
if (ctrl->type == ctMenuItem && ctrl->parentCtrlIdx >= 0) {
fprintf(out, " Parent=%d", ctrl->parentCtrlIdx + 1);
}
if (ctrl->hasColumns && ctrl->columns != 0) {
fprintf(out, " Columns=%d", ctrl->columns);
}
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);
}
if (ctrl->hasColCount) {
fprintf(out, " ColCount=%d", ctrl->colCount);
}
if (ctrl->hasRowCount) {
fprintf(out, " RowCount=%d", ctrl->rowCount);
}
if (ctrl->hasFixedCols) {
fprintf(out, " FixedCols=%d", ctrl->fixedCols);
}
if (ctrl->hasFixedRows) {
fprintf(out, " FixedRows=%d", ctrl->fixedRows);
}
if (ctrl->hasDefaultColWidth) {
fprintf(out, " DefaultColWidth=%d", ctrl->defaultColWidth);
}
if (ctrl->hasDefaultRowHeight) {
fprintf(out, " DefaultRowHeight=%d", ctrl->defaultRowHeight);
}
if (ctrl->hasOptions) {
fprintf(out, " Options=%d", ctrl->options);
}
fprintf(out, "\n");
// Emit EVENT.BIND for non-auto-wired events
// 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 || 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) {
fprintf(out, "EVENT.BIND %d %d Click\n", formId, ctrlId);
}
if (ctrl->hasOnChange && !autoChange) {
fprintf(out, "EVENT.BIND %d %d Change\n", formId, ctrlId);
}
if (ctrl->hasOnDblClick) {
fprintf(out, "EVENT.BIND %d %d DblClick\n", formId, ctrlId);
}
if (ctrl->hasOnEnter) {
fprintf(out, "EVENT.BIND %d %d Enter\n", formId, ctrlId);
}
if (ctrl->hasOnExit) {
fprintf(out, "EVENT.BIND %d %d Exit\n", formId, ctrlId);
}
if (ctrl->hasOnKeyDown) {
fprintf(out, "EVENT.BIND %d %d KeyDown\n", formId, ctrlId);
}
if (ctrl->hasOnKeyUp) {
fprintf(out, "EVENT.BIND %d %d KeyUp\n", formId, ctrlId);
}
if (ctrl->hasOnMouseDown) {
fprintf(out, "EVENT.BIND %d %d MouseDown\n", formId, ctrlId);
}
if (ctrl->hasOnMouseUp) {
fprintf(out, "EVENT.BIND %d %d MouseUp\n", formId, ctrlId);
}
if (ctrl->hasOnSetEditText) {
fprintf(out, "EVENT.BIND %d %d SetEditText\n", formId, ctrlId);
}
// Suppress unused variable warning for autoSelect
(void)autoSelect;
}
// ---------------------------------------------------------------------------
// Emit the complete form
// ---------------------------------------------------------------------------
static void emitForm(FILE *out, int32_t formId, DfmFormT *form) {
char escaped[512];
escapeStr(form->caption, escaped, sizeof(escaped));
fprintf(out, "FORM.CREATE %d %d %d \"%s\"\n",
formId, form->width, form->height, escaped);
for (int32_t i = 0; i < form->ctrlCount; i++) {
emitCtrl(out, formId, i + 1, &form->ctrls[i]);
}
fprintf(out, "FORM.SHOW %d\n", formId);
}
// ---------------------------------------------------------------------------
// Usage
// ---------------------------------------------------------------------------
static void usage(const char *progName) {
fprintf(stderr, "Usage: %s <input.dfm> [output.form]\n", progName);
exit(1);
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
int main(int argc, char *argv[]) {
const char *inputPath = NULL;
const char *outputPath = NULL;
// Parse arguments
int32_t i = 1;
while (i < argc) {
if (argv[i][0] == '-') {
usage(argv[0]);
} else if (inputPath == NULL) {
inputPath = argv[i];
} else if (outputPath == NULL) {
outputPath = argv[i];
} else {
usage(argv[0]);
}
i++;
}
if (inputPath == NULL) {
usage(argv[0]);
}
// Read input file
FILE *fin = fopen(inputPath, "rb");
if (fin == NULL) {
fprintf(stderr, "Error: cannot open '%s': %s\n", inputPath, strerror(errno));
exit(1);
}
fseek(fin, 0, SEEK_END);
long fileSize = ftell(fin);
fseek(fin, 0, SEEK_SET);
uint8_t *data = (uint8_t *)malloc(fileSize);
if (data == NULL) {
fprintf(stderr, "Error: out of memory\n");
fclose(fin);
exit(1);
}
if ((long)fread(data, 1, fileSize, fin) != fileSize) {
fprintf(stderr, "Error: failed to read '%s'\n", inputPath);
free(data);
fclose(fin);
exit(1);
}
fclose(fin);
// Verify TPF0 signature
if (fileSize < 4 || memcmp(data, "TPF0", 4) != 0) {
fprintf(stderr, "Error: '%s' is not a Delphi binary DFM (missing TPF0 signature)\n", inputPath);
free(data);
exit(1);
}
// Parse
DfmFormT form;
initForm(&form);
int32_t pos = 4; // skip TPF0
parseComponent(data, (int32_t)fileSize, &pos, &form, true, -1);
// Default caption if empty
if (form.caption[0] == '\0') {
snprintf(form.caption, sizeof(form.caption), "%s", form.name);
}
// Output
FILE *fout;
if (outputPath != NULL) {
fout = fopen(outputPath, "w");
if (fout == NULL) {
fprintf(stderr, "Error: cannot create '%s': %s\n", outputPath, strerror(errno));
free(data);
exit(1);
}
} else {
fout = stdout;
}
emitForm(fout, 0, &form);
if (fout != stdout) {
fclose(fout);
}
free(data);
return 0;
}