DVX_GUI/dvxbasic/ide/ideProperties.c
2026-03-28 21:14:38 -05:00

276 lines
8 KiB
C

// ideProperties.c -- DVX BASIC form designer properties window
//
// A floating window with a TreeView listing all controls on the
// form (for selection and drag-reorder) and a read-only TextArea
// showing properties of the selected control.
#include "ideProperties.h"
#include "dvxWm.h"
#include "widgetBox.h"
#include "widgetLabel.h"
#include "widgetTextInput.h"
#include "widgetTreeView.h"
#include "widgetSplitter.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
// ============================================================
// Constants
// ============================================================
#define PRP_WIN_W 200
#define PRP_WIN_H 340
// ============================================================
// Module state
// ============================================================
static DsgnStateT *sDs = NULL;
static WindowT *sPrpWin = NULL;
static WidgetT *sTree = NULL;
static WidgetT *sPropsArea = NULL;
static AppContextT *sPrpCtx = NULL;
static bool sUpdating = false; // prevent feedback loops
// ============================================================
// Prototypes
// ============================================================
static void onPrpClose(WindowT *win);
static void onTreeChange(WidgetT *w);
// ============================================================
// onPrpClose
// ============================================================
static void onPrpClose(WindowT *win) {
(void)win;
}
// ============================================================
// onTreeChange
// ============================================================
//
// Called when the TreeView selection changes or when items are
// reordered by drag. Sync the designer state from the tree.
static void onTreeChange(WidgetT *w) {
(void)w;
if (!sDs || !sDs->form || !sTree || sUpdating) {
return;
}
// Find which tree item is selected and map to control index
int32_t count = (int32_t)arrlen(sDs->form->controls);
int32_t selIdx = -1;
// Iterate tree items (children of sTree) to find the selected one
int32_t idx = 0;
for (WidgetT *item = sTree->firstChild; item; item = item->nextSibling, idx++) {
if (wgtTreeItemIsSelected(item)) {
selIdx = idx;
break;
}
}
// Check if the tree order differs from the design array
// (drag-reorder happened). If so, rebuild the array to match.
bool reordered = false;
idx = 0;
for (WidgetT *item = sTree->firstChild; item; item = item->nextSibling, idx++) {
if (idx >= count) {
break;
}
// Each tree item's userData stores the control name
const char *itemName = (const char *)item->userData;
if (itemName && strcmp(itemName, sDs->form->controls[idx].name) != 0) {
reordered = true;
break;
}
}
if (reordered) {
// Rebuild the controls array to match tree order
DsgnControlT *newArr = NULL;
idx = 0;
for (WidgetT *item = sTree->firstChild; item; item = item->nextSibling) {
const char *itemName = (const char *)item->userData;
if (!itemName) {
continue;
}
// Find this control in the original array
for (int32_t i = 0; i < count; i++) {
if (strcmp(sDs->form->controls[i].name, itemName) == 0) {
arrput(newArr, sDs->form->controls[i]);
break;
}
}
}
// Replace the controls array
arrfree(sDs->form->controls);
sDs->form->controls = newArr;
sDs->form->dirty = true;
// Rebuild live widgets in new order
if (sDs->form->contentBox) {
sDs->form->contentBox->firstChild = NULL;
sDs->form->contentBox->lastChild = NULL;
int32_t newCount = (int32_t)arrlen(sDs->form->controls);
for (int32_t i = 0; i < newCount; i++) {
sDs->form->controls[i].widget = NULL;
}
dsgnCreateWidgets(sDs, sDs->form->contentBox);
}
// Update selection to match tree
count = (int32_t)arrlen(sDs->form->controls);
}
// Update designer selection
if (selIdx != sDs->selectedIdx) {
sDs->selectedIdx = selIdx;
if (sDs->formWin) {
dvxInvalidateWindow(sPrpCtx, sDs->formWin);
}
}
// Refresh properties display
prpRefresh(sDs);
}
// ============================================================
// prpCreate
// ============================================================
WindowT *prpCreate(AppContextT *ctx, DsgnStateT *ds) {
sDs = ds;
sPrpCtx = ctx;
int32_t winX = ctx->display.width - PRP_WIN_W - 10;
WindowT *win = dvxCreateWindow(ctx, "Properties", winX, 30, PRP_WIN_W, PRP_WIN_H, false);
if (!win) {
return NULL;
}
win->onClose = onPrpClose;
sPrpWin = win;
WidgetT *root = wgtInitWindow(ctx, win);
// Splitter: tree on top, properties on bottom
WidgetT *splitter = wgtSplitter(root, false);
splitter->weight = 100;
// Control tree (top pane)
sTree = wgtTreeView(splitter);
sTree->onChange = onTreeChange;
wgtTreeViewSetReorderable(sTree, true);
// Properties text (bottom pane)
sPropsArea = wgtTextArea(splitter, 4096);
sPropsArea->readOnly = true;
prpRefresh(ds);
return win;
}
// ============================================================
// prpDestroy
// ============================================================
void prpDestroy(AppContextT *ctx, WindowT *win) {
if (win) {
dvxDestroyWindow(ctx, win);
}
sPrpWin = NULL;
sTree = NULL;
sPropsArea = NULL;
sDs = NULL;
}
// ============================================================
// prpRefresh
// ============================================================
void prpRefresh(DsgnStateT *ds) {
if (!ds || !ds->form) {
return;
}
// Rebuild tree items
if (sTree) {
sUpdating = true;
// Clear existing tree items
sTree->firstChild = NULL;
sTree->lastChild = NULL;
int32_t count = (int32_t)arrlen(ds->form->controls);
for (int32_t i = 0; i < count; i++) {
DsgnControlT *ctrl = &ds->form->controls[i];
char label[128];
snprintf(label, sizeof(label), "%s (%s)", ctrl->name, ctrl->typeName);
WidgetT *item = wgtTreeItem(sTree, label);
item->userData = ctrl->name; // store name for reorder tracking
if (i == ds->selectedIdx) {
wgtTreeItemSetSelected(item, true);
}
}
sUpdating = false;
}
// Update properties text
if (!sPropsArea) {
return;
}
char buf[4096];
int32_t pos = 0;
if (ds->selectedIdx >= 0 && ds->selectedIdx < (int32_t)arrlen(ds->form->controls)) {
DsgnControlT *ctrl = &ds->form->controls[ds->selectedIdx];
pos += snprintf(buf + pos, sizeof(buf) - pos, "Name = %s\n", ctrl->name);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Type = %s\n", ctrl->typeName);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Width = %d\n", (int)ctrl->width);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Height = %d\n", (int)ctrl->height);
pos += snprintf(buf + pos, sizeof(buf) - pos, "TabIndex = %d\n", (int)ctrl->tabIndex);
for (int32_t i = 0; i < ctrl->propCount; i++) {
pos += snprintf(buf + pos, sizeof(buf) - pos, "%-8s = %s\n", ctrl->props[i].name, ctrl->props[i].value);
}
} else {
pos += snprintf(buf + pos, sizeof(buf) - pos, "%s (Form)\n", ds->form->name);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Caption = %s\n", ds->form->caption);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Width = %d\n", (int)ds->form->width);
pos += snprintf(buf + pos, sizeof(buf) - pos, "Height = %d\n", (int)ds->form->height);
}
wgtSetText(sPropsArea, buf);
}