746 lines
20 KiB
C
746 lines
20 KiB
C
// ideMenuEditor.c -- DVX BASIC menu editor dialog
|
|
//
|
|
// VB3-style modal dialog for designing form menu bars. Works on
|
|
// a cloned copy of the menu item array; changes are only applied
|
|
// when the user clicks OK.
|
|
|
|
#include "ideMenuEditor.h"
|
|
#include "dvxDlg.h"
|
|
#include "dvxWm.h"
|
|
#include "box/box.h"
|
|
#include "button/button.h"
|
|
#include "checkbox/checkbox.h"
|
|
#include "label/label.h"
|
|
#include "listBox/listBox.h"
|
|
#include "textInput/textInpt.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
|
|
// ============================================================
|
|
// Constants
|
|
// ============================================================
|
|
|
|
#define MAX_MENU_ITEMS 128
|
|
#define MAX_MENU_LEVEL 5
|
|
#define ARROW_STR "-> "
|
|
|
|
// ============================================================
|
|
// Dialog state
|
|
// ============================================================
|
|
|
|
typedef struct {
|
|
bool done;
|
|
bool accepted;
|
|
AppContextT *ctx;
|
|
DsgnFormT *form;
|
|
|
|
// Working copy of menu items
|
|
DsgnMenuItemT *items; // stb_ds array
|
|
int32_t selectedIdx;
|
|
|
|
bool nameAutoGen; // true = name was auto-generated, update on caption change
|
|
|
|
// Widgets
|
|
WidgetT *captionInput;
|
|
WidgetT *nameInput;
|
|
WidgetT *checkedCb;
|
|
WidgetT *radioCheckCb;
|
|
WidgetT *enabledCb;
|
|
WidgetT *listBox;
|
|
} MnuEdStateT;
|
|
|
|
static MnuEdStateT sMed;
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
static void applyFields(void);
|
|
static int32_t findSubtreeEnd(int32_t idx);
|
|
static void loadFields(void);
|
|
static void onCancel(WidgetT *w);
|
|
static void onCaptionChange(WidgetT *w);
|
|
static void onDelete(WidgetT *w);
|
|
static void onNameChange(WidgetT *w);
|
|
static void onIndent(WidgetT *w);
|
|
static void onInsert(WidgetT *w);
|
|
static void onListClick(WidgetT *w);
|
|
static void onMoveDown(WidgetT *w);
|
|
static void onMoveUp(WidgetT *w);
|
|
static void onNext(WidgetT *w);
|
|
static void onOk(WidgetT *w);
|
|
static void onOutdent(WidgetT *w);
|
|
static void rebuildList(void);
|
|
|
|
|
|
// ============================================================
|
|
// applyFields -- save current widget values to selected item
|
|
// ============================================================
|
|
|
|
static void applyFields(void) {
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
|
|
if (sMed.selectedIdx < 0 || sMed.selectedIdx >= count) {
|
|
return;
|
|
}
|
|
|
|
DsgnMenuItemT *mi = &sMed.items[sMed.selectedIdx];
|
|
const char *cap = wgtGetText(sMed.captionInput);
|
|
const char *nam = wgtGetText(sMed.nameInput);
|
|
|
|
if (cap) {
|
|
snprintf(mi->caption, DSGN_MAX_TEXT, "%s", cap);
|
|
}
|
|
|
|
if (nam) {
|
|
snprintf(mi->name, DSGN_MAX_NAME, "%s", nam);
|
|
}
|
|
|
|
// Auto-generate name from caption if name is empty or was auto-generated
|
|
if ((mi->name[0] == '\0' || sMed.nameAutoGen) && mi->caption[0] != '\0') {
|
|
char autoName[DSGN_MAX_NAME];
|
|
|
|
if (mi->caption[0] == '-') {
|
|
// Separator: generate mnuSep1, mnuSep2, etc.
|
|
int32_t sepNum = 1;
|
|
int32_t itemCount = (int32_t)arrlen(sMed.items);
|
|
|
|
for (int32_t i = 0; i < itemCount; i++) {
|
|
if (i != sMed.selectedIdx && strncasecmp(sMed.items[i].name, "mnuSep", 6) == 0) {
|
|
int32_t n = atoi(sMed.items[i].name + 6);
|
|
|
|
if (n >= sepNum) {
|
|
sepNum = n + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
snprintf(autoName, DSGN_MAX_NAME, "mnuSep%d", (int)sepNum);
|
|
} else {
|
|
// Normal item: strip & and non-alphanumeric, prefix "mnu"
|
|
int32_t p = 0;
|
|
autoName[p++] = 'm';
|
|
autoName[p++] = 'n';
|
|
autoName[p++] = 'u';
|
|
|
|
for (const char *c = mi->caption; *c && p < DSGN_MAX_NAME - 1; c++) {
|
|
if (*c == '&') {
|
|
continue;
|
|
}
|
|
|
|
if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c >= '0' && *c <= '9')) {
|
|
autoName[p++] = *c;
|
|
}
|
|
}
|
|
|
|
autoName[p] = '\0';
|
|
}
|
|
|
|
snprintf(mi->name, DSGN_MAX_NAME, "%s", autoName);
|
|
wgtSetText(sMed.nameInput, mi->name);
|
|
sMed.nameAutoGen = true;
|
|
}
|
|
|
|
mi->checked = wgtCheckboxIsChecked(sMed.checkedCb);
|
|
mi->radioCheck = wgtCheckboxIsChecked(sMed.radioCheckCb);
|
|
mi->enabled = wgtCheckboxIsChecked(sMed.enabledCb);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// findSubtreeEnd -- index past the last child of items[idx]
|
|
// ============================================================
|
|
|
|
static int32_t findSubtreeEnd(int32_t idx) {
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
int32_t level = sMed.items[idx].level;
|
|
int32_t end = idx + 1;
|
|
|
|
while (end < count && sMed.items[end].level > level) {
|
|
end++;
|
|
}
|
|
|
|
return end;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// loadFields -- populate widgets from selected item
|
|
// ============================================================
|
|
|
|
static void loadFields(void) {
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
|
|
// Reset auto-gen flag — if the loaded item has an empty name, it's
|
|
// eligible for auto-generation; if it has a name, the user set it.
|
|
sMed.nameAutoGen = false;
|
|
|
|
if (sMed.selectedIdx < 0 || sMed.selectedIdx >= count) {
|
|
wgtSetText(sMed.captionInput, "");
|
|
wgtSetText(sMed.nameInput, "");
|
|
wgtCheckboxSetChecked(sMed.checkedCb, false);
|
|
wgtCheckboxSetChecked(sMed.radioCheckCb, false);
|
|
wgtCheckboxSetChecked(sMed.enabledCb, true);
|
|
sMed.nameAutoGen = true; // new blank item -- auto-gen eligible
|
|
return;
|
|
}
|
|
|
|
DsgnMenuItemT *mi = &sMed.items[sMed.selectedIdx];
|
|
sMed.nameAutoGen = (mi->name[0] == '\0');
|
|
wgtSetText(sMed.captionInput, mi->caption);
|
|
wgtSetText(sMed.nameInput, mi->name);
|
|
wgtCheckboxSetChecked(sMed.checkedCb, mi->checked);
|
|
wgtCheckboxSetChecked(sMed.radioCheckCb, mi->radioCheck);
|
|
wgtCheckboxSetChecked(sMed.enabledCb, mi->enabled);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onCancel
|
|
// ============================================================
|
|
|
|
static void onCancel(WidgetT *w) {
|
|
(void)w;
|
|
sMed.accepted = false;
|
|
sMed.done = true;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onCaptionChange -- caption field changed, auto-update name
|
|
// ============================================================
|
|
|
|
static void onCaptionChange(WidgetT *w) {
|
|
(void)w;
|
|
applyFields();
|
|
rebuildList();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onNameChange -- user manually edited the name field
|
|
// ============================================================
|
|
|
|
static void onNameChange(WidgetT *w) {
|
|
(void)w;
|
|
sMed.nameAutoGen = false; // user took over
|
|
applyFields();
|
|
rebuildList();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onDelete
|
|
// ============================================================
|
|
|
|
static void onDelete(WidgetT *w) {
|
|
(void)w;
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
|
|
if (sMed.selectedIdx < 0 || sMed.selectedIdx >= count) {
|
|
return;
|
|
}
|
|
|
|
applyFields();
|
|
|
|
// Delete selected item and its children
|
|
int32_t subEnd = findSubtreeEnd(sMed.selectedIdx);
|
|
|
|
for (int32_t i = subEnd - 1; i >= sMed.selectedIdx; i--) {
|
|
arrdel(sMed.items, i);
|
|
}
|
|
|
|
count = (int32_t)arrlen(sMed.items);
|
|
|
|
if (sMed.selectedIdx >= count) {
|
|
sMed.selectedIdx = count - 1;
|
|
}
|
|
|
|
rebuildList();
|
|
loadFields();
|
|
wgtSetFocused(sMed.captionInput);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onIndent -- increase level (make child of previous item)
|
|
// ============================================================
|
|
|
|
static void onIndent(WidgetT *w) {
|
|
(void)w;
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
int32_t idx = sMed.selectedIdx;
|
|
|
|
if (idx <= 0 || idx >= count) {
|
|
return;
|
|
}
|
|
|
|
applyFields();
|
|
|
|
// Can only indent if previous item is at same or higher level
|
|
int32_t prevLevel = sMed.items[idx - 1].level;
|
|
|
|
if (sMed.items[idx].level > prevLevel) {
|
|
return; // already deeper than prev
|
|
}
|
|
|
|
if (sMed.items[idx].level >= MAX_MENU_LEVEL) {
|
|
return;
|
|
}
|
|
|
|
// Indent this item and all its children
|
|
int32_t subEnd = findSubtreeEnd(idx);
|
|
|
|
for (int32_t i = idx; i < subEnd; i++) {
|
|
sMed.items[i].level++;
|
|
}
|
|
|
|
rebuildList();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onInsert
|
|
// ============================================================
|
|
|
|
static void onInsert(WidgetT *w) {
|
|
(void)w;
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
|
|
if (count >= MAX_MENU_ITEMS) {
|
|
return;
|
|
}
|
|
|
|
applyFields();
|
|
|
|
DsgnMenuItemT mi;
|
|
memset(&mi, 0, sizeof(mi));
|
|
mi.enabled = true;
|
|
|
|
// Insert after the current item's subtree, at the same level
|
|
int32_t insertAt;
|
|
|
|
if (sMed.selectedIdx >= 0 && sMed.selectedIdx < count) {
|
|
mi.level = sMed.items[sMed.selectedIdx].level;
|
|
insertAt = findSubtreeEnd(sMed.selectedIdx);
|
|
} else {
|
|
insertAt = count;
|
|
}
|
|
|
|
arrins(sMed.items, insertAt, mi);
|
|
sMed.selectedIdx = insertAt;
|
|
rebuildList();
|
|
loadFields();
|
|
wgtSetFocused(sMed.captionInput);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onListClick
|
|
// ============================================================
|
|
|
|
static void onListClick(WidgetT *w) {
|
|
int32_t prev = sMed.selectedIdx;
|
|
sMed.selectedIdx = wgtListBoxGetSelected(w);
|
|
|
|
if (prev != sMed.selectedIdx && prev >= 0 && prev < (int32_t)arrlen(sMed.items)) {
|
|
// Save fields from previously selected item
|
|
int32_t saveSel = sMed.selectedIdx;
|
|
sMed.selectedIdx = prev;
|
|
applyFields();
|
|
sMed.selectedIdx = saveSel;
|
|
}
|
|
|
|
loadFields();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onMoveDown
|
|
// ============================================================
|
|
|
|
static void onMoveDown(WidgetT *w) {
|
|
(void)w;
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
int32_t idx = sMed.selectedIdx;
|
|
|
|
if (idx < 0 || idx >= count) {
|
|
return;
|
|
}
|
|
|
|
applyFields();
|
|
|
|
int32_t subEnd = findSubtreeEnd(idx);
|
|
|
|
if (subEnd >= count) {
|
|
return; // already at bottom
|
|
}
|
|
|
|
// The item/subtree after us
|
|
int32_t nextEnd = findSubtreeEnd(subEnd);
|
|
|
|
// Rotate: move the next subtree before our subtree
|
|
// Simple approach: extract our subtree, delete it, insert after next subtree
|
|
int32_t subSize = subEnd - idx;
|
|
DsgnMenuItemT *tmp = (DsgnMenuItemT *)malloc(subSize * sizeof(DsgnMenuItemT));
|
|
memcpy(tmp, &sMed.items[idx], subSize * sizeof(DsgnMenuItemT));
|
|
|
|
// Delete our subtree
|
|
for (int32_t i = subEnd - 1; i >= idx; i--) {
|
|
arrdel(sMed.items, i);
|
|
}
|
|
|
|
// Insert after what was the next subtree (now shifted)
|
|
int32_t insertAt = nextEnd - subSize;
|
|
|
|
for (int32_t i = 0; i < subSize; i++) {
|
|
arrins(sMed.items, insertAt + i, tmp[i]);
|
|
}
|
|
|
|
free(tmp);
|
|
sMed.selectedIdx = insertAt;
|
|
rebuildList();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onMoveUp
|
|
// ============================================================
|
|
|
|
static void onMoveUp(WidgetT *w) {
|
|
(void)w;
|
|
int32_t idx = sMed.selectedIdx;
|
|
|
|
if (idx <= 0) {
|
|
return;
|
|
}
|
|
|
|
applyFields();
|
|
|
|
// Find the start of the previous subtree at the same or lower level
|
|
int32_t prevIdx = idx - 1;
|
|
|
|
while (prevIdx > 0 && sMed.items[prevIdx].level > sMed.items[idx].level) {
|
|
prevIdx--;
|
|
}
|
|
|
|
// Move our subtree before the previous item's subtree
|
|
int32_t subEnd = findSubtreeEnd(idx);
|
|
int32_t subSize = subEnd - idx;
|
|
DsgnMenuItemT *tmp = (DsgnMenuItemT *)malloc(subSize * sizeof(DsgnMenuItemT));
|
|
memcpy(tmp, &sMed.items[idx], subSize * sizeof(DsgnMenuItemT));
|
|
|
|
// Delete our subtree
|
|
for (int32_t i = subEnd - 1; i >= idx; i--) {
|
|
arrdel(sMed.items, i);
|
|
}
|
|
|
|
// Insert at prevIdx
|
|
for (int32_t i = 0; i < subSize; i++) {
|
|
arrins(sMed.items, prevIdx + i, tmp[i]);
|
|
}
|
|
|
|
free(tmp);
|
|
sMed.selectedIdx = prevIdx;
|
|
rebuildList();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onNext
|
|
// ============================================================
|
|
|
|
static void onNext(WidgetT *w) {
|
|
(void)w;
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
|
|
applyFields();
|
|
|
|
if (sMed.selectedIdx < count - 1) {
|
|
sMed.selectedIdx++;
|
|
} else {
|
|
// Append new item
|
|
if (count >= MAX_MENU_ITEMS) {
|
|
return;
|
|
}
|
|
|
|
DsgnMenuItemT mi;
|
|
memset(&mi, 0, sizeof(mi));
|
|
mi.enabled = true;
|
|
|
|
if (count > 0) {
|
|
mi.level = sMed.items[count - 1].level;
|
|
}
|
|
|
|
arrput(sMed.items, mi);
|
|
sMed.selectedIdx = (int32_t)arrlen(sMed.items) - 1;
|
|
}
|
|
|
|
rebuildList();
|
|
loadFields();
|
|
wgtSetFocused(sMed.captionInput);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onOk
|
|
// ============================================================
|
|
|
|
static void onOk(WidgetT *w) {
|
|
(void)w;
|
|
applyFields();
|
|
|
|
// Strip blank items (no caption and no name)
|
|
for (int32_t i = (int32_t)arrlen(sMed.items) - 1; i >= 0; i--) {
|
|
if (sMed.items[i].caption[0] == '\0' && sMed.items[i].name[0] == '\0') {
|
|
arrdel(sMed.items, i);
|
|
}
|
|
}
|
|
|
|
// Validate: check names are non-empty and unique
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
|
|
for (int32_t i = 0; i < count; i++) {
|
|
if (sMed.items[i].name[0] == '\0') {
|
|
dvxMessageBox(sMed.ctx, "Menu Editor", "All menu items must have a Name.", MB_OK | MB_ICONERROR);
|
|
sMed.selectedIdx = i;
|
|
rebuildList();
|
|
loadFields();
|
|
wgtSetFocused(sMed.captionInput);
|
|
return;
|
|
}
|
|
|
|
for (int32_t j = i + 1; j < count; j++) {
|
|
if (strcasecmp(sMed.items[i].name, sMed.items[j].name) == 0) {
|
|
char msg[128];
|
|
snprintf(msg, sizeof(msg), "Duplicate menu name: %s", sMed.items[i].name);
|
|
dvxMessageBox(sMed.ctx, "Menu Editor", msg, MB_OK | MB_ICONERROR);
|
|
sMed.selectedIdx = j;
|
|
rebuildList();
|
|
loadFields();
|
|
wgtSetFocused(sMed.captionInput);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Copy working items back to form
|
|
arrfree(sMed.form->menuItems);
|
|
sMed.form->menuItems = NULL;
|
|
|
|
for (int32_t i = 0; i < count; i++) {
|
|
arrput(sMed.form->menuItems, sMed.items[i]);
|
|
}
|
|
|
|
sMed.accepted = true;
|
|
sMed.done = true;
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// onOutdent -- decrease level
|
|
// ============================================================
|
|
|
|
static void onOutdent(WidgetT *w) {
|
|
(void)w;
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
int32_t idx = sMed.selectedIdx;
|
|
|
|
if (idx < 0 || idx >= count) {
|
|
return;
|
|
}
|
|
|
|
if (sMed.items[idx].level <= 0) {
|
|
return;
|
|
}
|
|
|
|
applyFields();
|
|
|
|
int32_t subEnd = findSubtreeEnd(idx);
|
|
|
|
for (int32_t i = idx; i < subEnd; i++) {
|
|
sMed.items[i].level--;
|
|
}
|
|
|
|
rebuildList();
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// rebuildList -- refresh the listbox display
|
|
// ============================================================
|
|
|
|
static void rebuildList(void) {
|
|
int32_t count = (int32_t)arrlen(sMed.items);
|
|
|
|
// Build display strings
|
|
static const char *sLabels[MAX_MENU_ITEMS];
|
|
static char sLabelBufs[MAX_MENU_ITEMS][DSGN_MAX_TEXT + 32];
|
|
|
|
for (int32_t i = 0; i < count && i < MAX_MENU_ITEMS; i++) {
|
|
int32_t pos = 0;
|
|
|
|
for (int32_t lv = 0; lv < sMed.items[i].level; lv++) {
|
|
pos += snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "%s", ARROW_STR);
|
|
}
|
|
|
|
const char *cap = sMed.items[i].caption;
|
|
|
|
if (cap[0] == '-') {
|
|
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "--------");
|
|
} else if (cap[0]) {
|
|
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "%s", cap);
|
|
} else {
|
|
snprintf(sLabelBufs[i] + pos, sizeof(sLabelBufs[i]) - pos, "(%s)", sMed.items[i].name[0] ? sMed.items[i].name : "untitled");
|
|
}
|
|
|
|
sLabels[i] = sLabelBufs[i];
|
|
}
|
|
|
|
wgtListBoxSetItems(sMed.listBox, sLabels, count < MAX_MENU_ITEMS ? count : MAX_MENU_ITEMS);
|
|
wgtListBoxSetSelected(sMed.listBox, sMed.selectedIdx);
|
|
}
|
|
|
|
|
|
// ============================================================
|
|
// mnuEditorDialog
|
|
// ============================================================
|
|
|
|
bool mnuEditorDialog(AppContextT *ctx, DsgnFormT *form) {
|
|
memset(&sMed, 0, sizeof(sMed));
|
|
sMed.ctx = ctx;
|
|
sMed.form = form;
|
|
sMed.selectedIdx = 0;
|
|
|
|
// Clone menu items as working copy
|
|
int32_t count = (int32_t)arrlen(form->menuItems);
|
|
|
|
for (int32_t i = 0; i < count; i++) {
|
|
arrput(sMed.items, form->menuItems[i]);
|
|
}
|
|
|
|
// If empty, start with one blank item so the user can type immediately
|
|
if (arrlen(sMed.items) == 0) {
|
|
DsgnMenuItemT mi;
|
|
memset(&mi, 0, sizeof(mi));
|
|
mi.enabled = true;
|
|
arrput(sMed.items, mi);
|
|
}
|
|
|
|
// Create modal dialog
|
|
WindowT *win = dvxCreateWindowCentered(ctx, "Menu Editor", 360, 420, false);
|
|
|
|
if (!win) {
|
|
arrfree(sMed.items);
|
|
return false;
|
|
}
|
|
|
|
win->maxW = win->w;
|
|
win->maxH = win->h;
|
|
|
|
WidgetT *root = wgtInitWindow(ctx, win);
|
|
root->spacing = wgtPixels(4);
|
|
|
|
// Caption row
|
|
WidgetT *capRow = wgtHBox(root);
|
|
capRow->spacing = wgtPixels(4);
|
|
WidgetT *capLbl = wgtLabel(capRow, "Caption:");
|
|
capLbl->minW = wgtPixels(60);
|
|
sMed.captionInput = wgtTextInput(capRow, DSGN_MAX_TEXT);
|
|
sMed.captionInput->weight = 100;
|
|
sMed.captionInput->onChange = onCaptionChange;
|
|
|
|
// Name row
|
|
WidgetT *namRow = wgtHBox(root);
|
|
namRow->spacing = wgtPixels(4);
|
|
WidgetT *namLbl = wgtLabel(namRow, "Name:");
|
|
namLbl->minW = wgtPixels(60);
|
|
sMed.nameInput = wgtTextInput(namRow, DSGN_MAX_NAME);
|
|
sMed.nameInput->weight = 100;
|
|
sMed.nameInput->onChange = onNameChange;
|
|
|
|
// Check row
|
|
WidgetT *chkRow = wgtHBox(root);
|
|
chkRow->spacing = wgtPixels(12);
|
|
sMed.checkedCb = wgtCheckbox(chkRow, "Checked");
|
|
sMed.radioCheckCb = wgtCheckbox(chkRow, "RadioCheck");
|
|
sMed.enabledCb = wgtCheckbox(chkRow, "Enabled");
|
|
wgtCheckboxSetChecked(sMed.enabledCb, true);
|
|
|
|
// Listbox
|
|
sMed.listBox = wgtListBox(root);
|
|
sMed.listBox->weight = 100;
|
|
sMed.listBox->onChange = onListClick;
|
|
|
|
// Arrow buttons
|
|
WidgetT *arrowRow = wgtHBox(root);
|
|
arrowRow->spacing = wgtPixels(4);
|
|
|
|
WidgetT *btnOut = wgtButton(arrowRow, "<-");
|
|
btnOut->onClick = onOutdent;
|
|
btnOut->minW = wgtPixels(32);
|
|
|
|
WidgetT *btnIn = wgtButton(arrowRow, "->");
|
|
btnIn->onClick = onIndent;
|
|
btnIn->minW = wgtPixels(32);
|
|
|
|
WidgetT *btnUp = wgtButton(arrowRow, "Up");
|
|
btnUp->onClick = onMoveUp;
|
|
btnUp->minW = wgtPixels(32);
|
|
|
|
WidgetT *btnDn = wgtButton(arrowRow, "Dn");
|
|
btnDn->onClick = onMoveDown;
|
|
btnDn->minW = wgtPixels(32);
|
|
|
|
// Action buttons
|
|
WidgetT *actRow = wgtHBox(root);
|
|
actRow->spacing = wgtPixels(4);
|
|
|
|
WidgetT *btnNext = wgtButton(actRow, "&Next");
|
|
btnNext->onClick = onNext;
|
|
|
|
WidgetT *btnIns = wgtButton(actRow, "&Insert");
|
|
btnIns->onClick = onInsert;
|
|
|
|
WidgetT *btnDel = wgtButton(actRow, "&Delete");
|
|
btnDel->onClick = onDelete;
|
|
|
|
// OK / Cancel
|
|
WidgetT *okRow = wgtHBox(root);
|
|
okRow->spacing = wgtPixels(8);
|
|
|
|
WidgetT *btnOk = wgtButton(okRow, "OK");
|
|
btnOk->onClick = onOk;
|
|
btnOk->minW = wgtPixels(60);
|
|
|
|
WidgetT *btnCancel = wgtButton(okRow, "Cancel");
|
|
btnCancel->onClick = onCancel;
|
|
btnCancel->minW = wgtPixels(60);
|
|
|
|
// Populate
|
|
rebuildList();
|
|
loadFields();
|
|
wgtSetFocused(sMed.captionInput);
|
|
|
|
dvxFitWindow(ctx, win);
|
|
|
|
// Modal loop
|
|
WindowT *prevModal = ctx->modalWindow;
|
|
ctx->modalWindow = win;
|
|
|
|
while (!sMed.done && ctx->running) {
|
|
dvxUpdate(ctx);
|
|
}
|
|
|
|
ctx->modalWindow = prevModal;
|
|
dvxDestroyWindow(ctx, win);
|
|
|
|
// Cleanup working copy
|
|
arrfree(sMed.items);
|
|
sMed.items = NULL;
|
|
|
|
return sMed.accepted;
|
|
}
|