// 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 #include #include #include // ============================================================ // 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); }