!7 sclass Entry { S type, text; } cmodule DialogEditor { transient JPanel stack; transient JTextArea taRaw; L entries = synchroList(); start { onChangeAndNow(r { if (taRaw != null) setText(taRaw, toRaw()); if (empty(entries) || nempty(last(entries).text)) { new Entry e; entries.add(e); change(); if (stack != null) addAndValidate(stack, makeLine(e)); } }); } visualize { stack = dynamicVStack2(); for (Entry e : cloneList(entries)) addAndValidate(stack, makeLine(e)); taRaw = jTypeWriterTextArea(toRaw()); ret jtabs( "Dialog" := jscroll_trackWidth(withRightMargin(stack)), "Raw" := withMargin(taRaw)); } JPanel makeLine(final Entry e) { final JComboBox cb = jcombobox(ai_lineTypes(), e.type); final JTextField tf = jtextfield(e.text); main.onChange(cb, r { e.type = selectedItem(cb); change(); }); main.onChange(tf, r { e.text = gtt(tf); change(); }); ret westAndCenterWithMargin(cb, tf); } S toRaw() { ret lines(nempties(map(entries, func(Entry e) -> S { bothEmpty(e.text, e.type) ? "" : or2(e.type, "?") + ": " + unnull(e.text) }))); } }