!7 sclass Entry { S type, text; } cmodule DialogEditor { L entries = synchroList(); transient JPanel stack; transient JTextArea taRaw; transient dontLoop; start { onChangeAndNow(r { if (taRaw != null) { temp dm_tempSetField(dontLoop := true); 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(); fillStack(); taRaw = jTypeWriterTextArea(toRaw()); onChange(taRaw, r { if (!dontLoop) fromRaw(getText(taRaw)) }); ret withMargin(jtabs( "Dialog" := jscroll_trackWidth(withRightMargin(stack)), "Raw" := withMargin(taRaw))); } void fillStack { removeAllComponents(stack); for (Entry e : cloneList(entries)) addAndValidate(stack, makeLine(e)); } 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) }))); } void fromRaw(S text) { entries.clear(); for (S type, text : parseColonProperties(text)) entries.add(nu Entry(+type, +text)); change(); fillStack(); } }