abstract sclass DynObjectTable extends DynModule { new L data; transient JTable table; transient F1 itemToMap; transient VF1 defaultAction; transient bool debug, fieldsInOrder = true, withSearcher; transient TableSearcher searcher; transient L onListChanged; transient Set hideFields; start { itemToMap = func(A a) -> Map { a instanceof S ? litorderedmap("" := (S) a) : humanizeKeys(fieldsInOrder ? objectToMap_inOrder_withoutFields(a, hideFields) : objectToMap(a)) }; onChange(r updateTable); } visualize { table = dataToTable_uneditable(sexyTable(), map(itemToMap, data)); onDoubleClickOrEnter(table, voidfunc(int row) { temp enter(); A a = _get(data, row); if (a != null) onDoubleClick(a); }); if (withSearcher) ret (searcher = tableWithSearcher2(table)).panel; ret table; } void unvisualize() { super.unvisualize(); searcher = null; } void onDoubleClick(A line) { callF(defaultAction, line); } void updateTable() { temp enter(); if (table != null) { dataToTable_uneditable(table, map(itemToMap, data)); if (searcher != null) searcher.rowIndices = null; if (debug) print("dataToTable done"); pcallFAll(onListChanged); } } void dontPersist() { _persistenceInfo = mapPlus(_persistenceInfo, data := false); } void clear() { syncClear(data); fireDataChanged(); } void add(A a) { syncAdd(data, a); fireDataChanged(); } void add(int idx, A a) { syncAdd(data, idx, a); fireDataChanged(); } void addAll(Collection l) { if (empty(l)) ret; syncAddAll(data, l); fireDataChanged(); } void addAndScrollDown(A a) { add(a); scrollTableDownNow(table); } void remove(A a) { syncRemove(data, a); fireDataChanged(); } void setList(Collection data) { setData(data); } void setData(Collection data) { int selection = selectedIndex(); if (isFalse(mapGet(_persistenceInfo, 'data))) { if (neq(this.data, data)) { this.data = cloneList(data); updateTable(); } } else setField(data := cloneList(data)); selectRow(table, selection); } int count() { ret syncL(data); } void setData_force(Collection data) { this.data = null; setData(data); } // tries to keep selection void fireDataChanged() { setData_force(data); } int rowFromSearcher(int i) { ret searcher == null || searcher.rowIndices == null ? i : or(get(searcher.rowIndices, i), -1); } A selected() { ret syncGet(data, rowFromSearcher(selectedTableRowInModel(table))); } int selectedIndex() { ret selectedTableRow(table); } bool selectItem(A a) { int i = indexOf(data, a); selectRow(table, i); ret i >= 0; } // true if item exists void doubleClickItem(A a) { if (selectItem(a)) onDoubleClick(a); } // r : Runnable or VF1 // run only in visualize()! void popupMenuItem(S text, O r) { tablePopupMenuItemsThreaded(table, text, _convertRunnable(r)); } void popupMenuItem_top(S text, O r) { tablePopupMenuItemsThreaded_top(table, text, _convertRunnable(r)); } // row index => element O _convertRunnable(fO r) { if (r == null || r instanceof Runnable) ret r; ret voidfunc(int idx) { callF(r, syncGet(data, idx)) }; } JTable table() { ret table; } void hideFields(S... fields) { if (hideFields == null) hideFields = new Set; _addAll(hideFields, fields); } // API L getData() { ret data; } // data is usually immutable L data() { ret getData(); } L getList() { ret getData(); } }