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; *(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, withBottomMargin(buttons)); } else panel = centerAndSouthWithMargin(table, withBottomMargin(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) { final A c = unlisted(conceptClass); ccopyFieldsExcept(oldConcept, c, dontDuplicateFields); final Map map = makeComponents(c); Runnable r = r { concepts.register(c); saveData(c, map); }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); showFormTitled2("New " + shortClassName(conceptClass), arrayPlus(mapToObjectArray(map), r)); } void editConcept(final A c) { if (c == null) ret; final Map map = makeComponents(c); Runnable r = r { saveData(c, map) }; temp tempSetMCOpt(formLayouter1_fixer2 := formFixer); showFormTitled2("Edit " + shortClassName(conceptClass) + " #" + c.id, arrayPlus(mapToObjectArray(map), 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; } Map makeComponents(A c) { Map map = litorderedmap(); makeComponents(c, map); ret map; } 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) cset(c, field, convertToField(trimIf(!comp instanceof JTextArea, getText((JTextComponent) comp)), conceptClass, field)); 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 fields() { if (excludeFieldsFromEditing != null && modifiedField != null) excludeFieldsFromEditing.add(modifiedField); ret listWithoutSet([S field : conceptFieldsInOrder(conceptClass) | fieldType(conceptClass, field) != Concept.Ref.class], joinSets(excludeFieldsFromEditing, unshownFields)); } void excludeFieldsFromEditing(S... fields) { excludeFieldsFromEditing = setPlus(excludeFieldsFromEditing, fields); } // override the following two methods to customize edit window void makeComponents(A c, Map map) { for (S field : fields()) map.put(field, fieldComponent(c, field)); } void saveData(A c, Map components) { for (S field : keys(components)) saveComponent(c, field, components.get(field)); if (modifiedField != null) cset(c, modifiedField, now()); } }