276 lines
8 KiB
C
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);
|
|
}
|