sbool SimpleCRUD_searcher = true; sclass SimpleCRUD_v2 extends JConceptsTable { JPanel buttons, panel; S hID = "ID"; // Column header for concept ID 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) *(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(putAll(specialFieldsForItem(a), map), moreSpecialFieldsForItem(a)); } // shown on the left (usually) swappable MapSO specialFieldsForItem(A a) { MapSO map = litorderedmap(hID, str(a.id)); mapPut(map, "Java Class", javaClassDescForItem(a)); ret map; } // 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; } S javaClassDescForItem(A a) { S className = dynShortClassName(a); if (neq(className, shortClassName(conceptClass))) { S text = className; S realClass = shortClassName(a); if (neq(className, realClass)) text += " as " + realClass; ret text; } null; } JPanel make_dontStartBots() { dropFields = asList(unshownFields); makeTable(); swing { buttons = jRightAlignedLine( jbutton("Add...", r { newConcept() }), tableDependButton(table, jbutton("Edit", r { editConcept(selectedConcept()) })), tableDependButton(table, jbutton("Delete", r { final L l = selectedConcepts(); withDBLock(concepts, r { for (A c : l) c.delete() }); })), tableDependButton(table, jbutton("Duplicate...", r { duplicateConcept(selectedConcept()) }))); if (SimpleCRUD_searcher) { 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); Runnable r = r { concepts.register(ew.item); saveData(ew); }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); showFormTitled2("New " + shortClassName(conceptClass), arrayPlus(mapToObjectArray(ew.componentsByField), r)); } void editConcept(A c) { if (c == null) ret; new EditWindow ew; ew.item = c; makeComponents(ew); Runnable r = r { saveData(ew) }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); showFormTitled2("Edit " + shortClassName(conceptClass) + " #" + c.id, arrayPlus(mapToObjectArray(ew.componentsByField), r)); } A selectedConcept() { ret (A) concepts.getConcept(toLong(selectedTableCell(table, 0))); } A selected() { ret selectedConcept(); } A getItem(int row) { ret (A) concepts.getConcept(toLong(getTableCell(table, row, 0))); } int indexOfConcept(final A c) { if (c == null) ret -1; ret swing(func -> int { int n = tableRowCount(table); for row to n: if (toLong(getTableCell(table, row, 0)) == c.id) ret row; ret -1; }); } L selectedConcepts() { int[] rows = table.getSelectedRows(); new L l; for (int row : rows) l.add(getItem(row)); ret l; } JComponent fieldComponent(A c, S field) { Class type = getFieldType(conceptClass, field); O value = getOpt(c, field); //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 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); 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 cset(c, field, convertToField(text, 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(); JPanel panel() { ret jhgridWithSpacing(withLabel("Name", tfName), withLabel("Value", tfValue)); } S field() { ret gtt(tfName); } O value() { ret gtt(tfValue); } } class EditWindow { A item; Map componentsByField = litorderedmap(); JComboBox> classSelector; new L newFields; } // override the following two methods to customize edit window void makeComponents(EditWindow ew) { // class selector if (showClassNameSelectors) ew.componentsByField.put("Java Class", ew.classSelector = classSelectorComponent(ew.item)); // regular fields for (S field : fieldsForItem(ew.item)) ew.componentsByField.put(field, fieldComponent(ew.item, field)); // new fields if (allowNewFields) { for i to newFieldsToShow: { new NewField nf; ew.newFields.add(nf); ew.componentsByField.put("New field " + (i+1), 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; } }