sclass SimpleCRUD_v2 extends JConceptsTable { JPanel buttons, panel; Set unshownFields; // not shown in table or form Set excludeFieldsFromEditing; S modifiedField; // field to hold last-modified timestamp TableSearcher tableSearcher; Set multiLineFields; // string fields that should be shown as text areas Set dontDuplicateFields; int formFixer = 12; // stupid value to make submit button appear bool showBackRefs; int maxRefsToShow = 3; bool showClassNameSelectors; bool allowNewFields; int newFieldsToShow = 3; bool emptyStringsToNull; // convert all empty string values to nulls (also means dropping the field if it's dynamic) int formLabelsWidth = 100; // just another bug fixer settable bool showSearchBar = true; settable bool showAddButton = true; settable bool showEditButton = true; *(Class conceptClass) { super(conceptClass); } *(Concepts concepts, Class conceptClass) { super(concepts, conceptClass); } SimpleCRUD_v2 show(S frameTitle) { make(); showFrame(frameTitle, panel); this; } SimpleCRUD_v2 show() { ret show(plural(shortClassName(conceptClass))); } SimpleCRUD_v2 showMaximized() { show(); maximizeFrame(panel); ret this; } JPanel makePanel() { ret make(); } JPanel make() { db(); framesBot(); ret make_dontStartBots(); } swappable MapSO itemToMap_inner(A a) { ret super.itemToMap_base(a); } MapSO itemToMap_base(A a) { MapSO map = itemToMap_inner(a); if (map == null) null; ret putAll(map, moreSpecialFieldsForItem(a)); } // shown on the right (usually) swappable MapSO moreSpecialFieldsForItem(A a) { MapSO map = litorderedmap(); if (showBackRefs) { Cl refs = allBackRefs(a); if (nempty(refs)) { refs = sortedByConceptID(refs); int more = l(refs)-maxRefsToShow; map.put("Referenced by", joinWithComma(takeFirst(maxRefsToShow, refs)) + (more > 0 ? ", " + more + " more" : "")); } } ret map; } JPanel make_dontStartBots() { dropFields = asList(unshownFields); makeTable(); swing { buttons = jRightAlignedLine(flattenToList( !showAddButton ? null : jbutton("Add...", r { newConcept() }), !showEditButton ? null : tableDependButton(table, jbutton("Edit", r { editConcept(selectedConcept()) })), !showEditButton ? null : tableDependButton(table, jbutton("Duplicate...", r { duplicateConcept(selectedConcept()) })), tableDependButton(table, jbutton("Delete", r { final L l = selectedConcepts(); withDBLock(concepts, r { for (A c : l) c.delete() }); })), )); if (showSearchBar) { tableSearcher = tableWithSearcher2(table, withMargin := true); panel = centerAndSouthWithMargin(tableSearcher.panel, buttons); } else panel = centerAndSouthWithMargin(table, buttons); O fEdit = voidfunc(int row) { editConcept(getItem(row)) }; tablePopupMenuItem(table, "Edit...", fEdit); onDoubleClick(table, fEdit); tablePopupMenuFirst(table, (menu, row) -> { Concept c = getItem(row); if (c != null) addMenuItem(menu, "Delete " + quote(shorten(str(c))), rThread { deleteConcept(c); }); }); } // end of swing ret panel; } void newConcept { duplicateConcept(null); } void duplicateConcept(A oldConcept) { new EditWindow ew; ew.item = (A) unlisted(oldConcept == null ? conceptClass : _getClass(oldConcept)); ccopyFieldsExcept(oldConcept, ew.item, dontDuplicateFields); makeComponents(ew); // save action F0 r = func -> bool { try { concepts.register(ew.item); saveData(ew); setSelected(ew.item); } catch print e { infoBox(e); false; } true; }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); renameSubmitButton("Create", showAForm("New " + shortClassName(conceptClass), toObjectArray(listPlus(ew.matrix, r)))); } void editConcept(A c) { if (c == null) ret; new EditWindow ew; ew.item = c; makeComponents(ew); F0 r = func -> bool { try { // concept class was changed, replace object if (ew.item != c) { print("Replacing object: " + c + " => " + ew.item); replaceConceptAndUpdateRefs(c, ew.item); } saveData(ew); } catch print e { infoBox(e); false; } true; }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); renameSubmitButton("Save", showAForm("Edit " + shortClassName(conceptClass) + " #" + c.id, toObjectArray(listPlus(ew.matrix, r)))); } JComponent fieldComponent(A c, S field) { Class type = getFieldType(conceptClass, field); O value = cget(c, field); if (type == null) type = _getClass(value); print("Field type: " + field + " => " + type); if (type == bool.class) ret jCenteredCheckBox(isTrue(value)); else if (contains(multiLineFields, field) || containsNewLines(optCast S(value))) ret typeWriterTextArea((S) value); else if (isSubtype(type, Concept)) ret jcomboboxFromConcepts_str(concepts, type, (Concept) value); ifclass SecretValue else if (type == SecretValue.class) ret jpassword(strOrEmpty(getVar(value/SecretValue))); endif else if (isUneditableFieldType(type)) ret jlabel(structureOrText_crud(value)); else try { ret autoComboBox(structureOrText_crud(value), new TreeSet(map structureOrText_crud(collect(list(concepts, conceptClass), field)))); } catch e { printException(e); ret jTextField(structureOrText_crud(value)); } } void saveComponent(A c, S field, JComponent comp) { comp = unwrap(comp); ifdef SimpleCRUD_v2_debug printVars_str("saveComponent", +field, +comp); endifdef Class type = fieldType(c, field); ifclass SecretValue if (type == SecretValue.class) { S text = getTextTrim((JPasswordField) comp); cset(c, field, empty(text) ? null : SecretValue(text)); } else endif if (comp instanceof JTextComponent) { S text = trimIf(!comp instanceof JTextArea, getText((JTextComponent) comp)); O value = postProcessValue(text); O converted = convertToField(value, conceptClass, field); ifdef SimpleCRUD_v2_debug printVars_str("saveComponent", +field, +text, +value, +converted); endifdef cset(c, field, converted); } else if (comp cast JComboBox) { S text = getTextTrim(comp); if (isSubtype(type, Concept)) cset(c, field, getConcept(concepts, parseFirstLong(text))); else { O value = postProcessValue(text); cset(c, field, convertToField(value, conceptClass, field)); } } else if (comp instanceof JCheckBox) cset(c, field, isChecked((JCheckBox) comp)); ifclass ImageChooser else if (comp instanceof ImageChooser) cUpdatePNGFile(c, field, comp/ImageChooser.getImage(), false); endif } L fieldsForItem(A c) { if (excludeFieldsFromEditing != null && modifiedField != null) excludeFieldsFromEditing.add(modifiedField); ret listWithoutSet([S field : conceptFields(c) | fieldType(conceptClass, field) != Concept.Ref.class], joinSets(excludeFieldsFromEditing, unshownFields)); } void excludeFieldsFromEditing(S... fields) { excludeFieldsFromEditing = setPlus(excludeFieldsFromEditing, fields); } Cl> possibleClasses() { ret (Cl) moveItemFirst(conceptClass, dropTypeParameter(sortClassesByNameIC(myNonAbstractClassesImplementing(conceptClass)))); } JComboBox> classSelectorComponent(A c) { ret setComboBoxRenderer(jTypedComboBox(possibleClasses(), _getClass(c)), customToStringListCellRenderer shortClassName()); } class NewField { JTextField tfName = jtextfield(); JTextField tfValue = jtextfield(); JComboBox cbRef = jcomboboxFromConcepts_str(concepts, conceptClass); JComboBox cbType = jcombobox("String", "Reference"); SingleComponentPanel scpValue = singleComponentPanel(); JPanel panel() { onChangeAndNow(cbType, r updateSCP); ret jhgridWithSpacing( withToolTip("Name for new field", withLabel("Name", tfName)), withLabel("Value", westAndCenterWithMargin(cbType, scpValue))); } S typeStr() { ret getText(cbType); } void updateSCP { scpValue.setComponent(eqic(typeStr(), "Reference") ? cbRef : withToolTip("Contents of new field", tfValue)); } S field() { ret gtt(tfName); } O value() { ret eqic(typeStr(), "Reference") ? getConcept(concepts, parseFirstLong(getText(cbRef))) : gtt(tfValue); } } class EditWindow { A item; Map componentsByField = litorderedmap(); new L matrix; // label, component, label, component, ... JComboBox> classSelector; new L newFields; } // override the following two methods to customize edit window void makeComponents(EditWindow ew) { // class selector if (showClassNameSelectors) { addAll(ew.matrix, makeLabel("Java Class"), ew.classSelector = classSelectorComponent(ew.item)); onChange(ew.classSelector, r { Class oldClass = _getClass(ew.item); Class newClass = getSelectedItem_typed(ew.classSelector); if (oldClass == newClass) ret; A oldItem = ew.item; ew.item = unlisted(newClass); ccopyFields(oldItem, ew.item); }); } // regular fields for (S field : fieldsForItem(ew.item)) { JComponent c = fieldComponent(ew.item, field); ew.componentsByField.put(field, c); addAll(ew.matrix, makeLabel(field), c); } // new fields if (allowNewFields && newFieldsToShow > 0) { addAll(ew.matrix, " ", jlabel()); // spacing for i to newFieldsToShow: { new NewField nf; ew.newFields.add(nf); addAll(ew.matrix, makeLabel(""/*"New field"*/), nf.panel()); } } } void saveData(EditWindow ew) { // save regular fields for (S field, JComponent component : ew.componentsByField) if (isIdentifier(field)) saveComponent(ew.item, field, component); // save new fields for (NewField nf : ew.newFields) { S field = nf.field(); O value = nf.value(); if (nempty(field) && notNullOrEmptyString(value)) cset(ew.item, field, value); } if (modifiedField != null) cset(ew.item, modifiedField, now()); } O postProcessValue(O o) { if (emptyStringsToNull && eq(o, "")) null; ret o; } // labels on left hand side of form JComponent makeLabel(S label) { ret jMinWidthAtLeast(formLabelsWidth, jlabel(label)); } swappable JComponent showAForm(S title, O... parts) { ret showFormTitled2(title, parts); } bool isUneditableFieldType(Class type) { ret isSubclassOfAny(type, Map, L, Pair); } void hideFields(S... fields) { unshownFields = createOrAddToSet(unshownFields, fields); } } // end of SimpleCRUD_v2