sbool SimpleCRUD_searcher = true;
sclass SimpleCRUD {
Concepts concepts;
Class cc;
JTable table;
JPanel buttons, panel;
O renderer; // optional, func(A) -> Map
O sorter; // optional, func(Cl) -> Cl
S hID = "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;
bool latestFirst;
int formFixer = 12; // stupid value to make submit button appear
bool editOnDoubleClick = true;
bool ensureIndexed;
bool showValuesAsStruct; // BREAKING CHANGE - used to be true
*(Class cc) { this(db_mainConcepts(), cc); }
*(Concepts *concepts, Class *cc) {}
SimpleCRUD show(S frameTitle) {
make();
showFrame(frameTitle, panel);
this;
}
SimpleCRUD show() {
ret show(plural(shortClassName(cc)));
}
SimpleCRUD showMaximized() { show(); maximizeFrame(panel); ret this; }
JPanel makePanel() { ret make(); }
JPanel make() {
db();
framesBot();
ret make_dontStartBots();
}
JPanel make_dontStartBots() {
printVars("SimpleCRUD.make_dontStartBots", +concepts, +cc, count := countConcepts(cc));
if (ensureIndexed)
indexConceptClass(concepts, cc);
// next line not in swing part to allow passing arguments
// from outside like showConceptsTable_afterUpdate
temp tempSetTL(showConceptsTable_concepts, concepts);
temp tempSetTL(showConceptsTable_latestFirst, latestFirst);
temp tempSetTL(showConceptsTable_sorter, sorter);
temp tempSetTL(showConceptsTable_dropFields, asList(unshownFields));
temp tempSetTL(dataToTable_useStruct, showValuesAsStruct);
table = makeConceptsTable(cc, wrapRenderer(renderer));
swing {
buttons = jRightAlignedLine(
jbutton("Add...", r { newConcept() }),
tableDependButton(table, jbutton("Edit", r {
editConcept(selectedConcept())
})),
tableDependButton(table, jbutton("Delete", r {
final L l = selectedConcepts();
thread "Delete concepts" {
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(conceptForRow(row)) };
tablePopupMenuItem(table, "Edit...", fEdit);
if (editOnDoubleClick)
onDoubleClick(table, fEdit);
}
ret panel;
}
O wrapRenderer(fO renderer) {
ret renderer == null ? null : func(A a) {
putAll(litorderedmap(hID, str(a.id)), (Map) callF(renderer, a))
};
}
void newConcept {
duplicateConcept(null);
}
void duplicateConcept(A oldConcept) {
final A c = unlisted(cc);
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(cc), 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(cc) + " #" + c.id, arrayPlus(mapToObjectArray(map), r));
}
A selected aka selectedConcept() {
ret conceptForRow(selectedRow(table));
}
A conceptForRow(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() {
ret swing(() -> {
int[] rows = table?.getSelectedRows();
new L l;
fOr (int row : rows)
l.add(conceptForRow(row));
ret l;
});
}
Map makeComponents(A c) {
Map map = litorderedmap();
makeComponents(c, map);
ret map;
}
JComponent fieldComponent(A c, S field) {
Class type = getFieldType(cc, 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(valueToString(value), new TreeSet(map valueToString(collect(list(concepts, cc), field))));
} catch e {
printException(e);
ret jTextField(valueToString(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)), cc, 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, cc, 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(cc) | fieldType(cc, 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());
}
JTable table() { ret table; }
swappable S valueToString(O o) {
ret showValuesAsStruct
? structureOrText_crud(o)
: strOrNull(o);
}
} // end of SimpleCRUD