sclass JConceptsTable is Swingable { Class conceptClass; Concepts concepts; JTable table; // options MapSO filters; // fields to filter by/add to new objects S hID = "ID"; // Column header for concept ID settable LS dropFields; settable bool noSubclasses; IF1 postProcess; Runnable afterUpdate; bool latestFirst; IF1> sorter = lambda1 defaultSort; int idWidth = 50; settable int updateInterval = 100; int firstUpdateInterval = 100; bool humanizeFieldNames = true; Float tableFontSize; Int tableRowHeight; settable bool addCountToEnclosingTab; settable bool useNewChangeHandler; settable IVF1 defaultAction; bool pauseUpdates; int count; event selectionChanged; event singleSelectionChanged; // internal AWTOnConceptChanges changeHandler; AWTOnConceptChangesByClass newChangeHandler; bool updatingList; settable A selectAfterUpdate; A lastSelected; *() {} *(Class *conceptClass) {} *(Concepts *concepts, Class *conceptClass) {} swappable MapSO itemToMap(A a) { MapSO map = specialFieldsForItem(a); try { putAll(map, mapValues renderValue(itemToMap_inner2(a))); } catch print e { map.put("Error", str(e)); } ret map; } swappable O renderValue(O o) { ret renderForTable_noStruct(o); } swappable MapSO itemToMap_inner2(A a) { ret allConceptFieldsAsMapExcept(a, dropFields); } // 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; } 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; } S defaultTitle() { ret plural(shortClassName(conceptClass)); } void showAsFrame(S title default defaultTitle()) { makeTable(); showFrame(title, table); } void makeTable { if (table != null) ret; if (concepts == null) concepts = db_mainConcepts(); table = sexyTable(); if (tableFontSize != null) { setTableFontSizes(tableFontSize, table); if (tableRowHeight == null) tableRowHeight = iround(tableFontSize*1.5); } if (tableRowHeight != null) setRowHeight(table, tableRowHeight); if (useNewChangeHandler) { newChangeHandler = new AWTOnConceptChangesByClass(concepts, conceptClass, table, l0 _update) .delay(updateInterval) .firstDelay(firstUpdateInterval); newChangeHandler.install(); } else { changeHandler = new AWTOnConceptChanges(concepts, table, l0 _update) .delay(updateInterval) .firstDelay(firstUpdateInterval); changeHandler.install(); } onTableSelectionChanged(table, -> { if (updatingList) ret; selectionChanged(); }); onSelectionChanged(-> { var a = selected(); if (a != lastSelected) { lastSelected = a; singleSelectionChanged(); } }); onDoubleClickOrEnter(table, -> { A a = selected(); if (a != null && defaultAction != null) pcallF(defaultAction, a); }); } // e.g. to update enclosing tab when hidden void update { swing { _update(); } } // run in Swing thread void _update { if (table == null || pauseUpdates) ret; set updatingList; bool allRestored; A selectAfterUpdate = selectAfterUpdate(); try { new L data; Set selection; if (selectAfterUpdate != null) { selection = litset(selectAfterUpdate._conceptID()); selectAfterUpdate(null); } else selection = toSet(selectedConceptIDs()); Cl l = conceptsWhere(concepts, conceptClass, mapToParams(filters)); if (noSubclasses) l = filter(l, x -> x.getClass() == conceptClass); l = postProcess(sorter, l); for (A c : l) addIfNotNull(data, itemToMap(c)); if (latestFirst) reverseInPlace(data); data = (L) postProcess(postProcess, data); count = l(data); dataToTable_uneditable(data, table); if (humanizeFieldNames) humanizeTableColumns(); tableColumnMaxWidth(table, 0, idWidth); allRestored = restoreSelection(selection); if (addCountToEnclosingTab) updateEnclosingTabTitle(); } finally { updatingList = false; } pcallF(afterUpdate); if (!allRestored || selectAfterUpdate != null) selectionChanged(); } void updateEnclosingTabTitle { updateEnclosingTabTitleWithCount(table, count); } void humanizeTableColumns { int n = tableColumnCount(table); for i to n: setColumnName(table, i, humanizeFormLabel(getColumnName(table, i))); }; visual table(); JTable table() { makeTable(); ret table; } A selectedConcept() { ret (A) concepts.getConcept(toLong(selectedTableCell(table, 0))); } A selected() { ret selectedConcept(); } long getItemID(int row) { ret toLong(getTableCell(table, row, 0)); } L getList() swing { ret countIteratorAsList(size(), row -> getItem(row)); } A getItem(int row) { ret (A) concepts.getConcept(getItemID(row)); } int size() { ret tableRowCount(table); } int indexOfConcept(final A c) { if (c == null) ret -1; ret swing(func -> int { int n = size(); 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(getItem(row)); ret l; }); } L selectedConceptIDs() { ret swing(-> { int[] rows = table.getSelectedRows(); L l = emptyList(l(rows)); for (int row : rows) l.add(getItemID(row)); ret l; }); } // returns true if all selected items still exist bool restoreSelection(Set selection) { ret swing(-> { int n = size(); new IntBuffer toSelect; for row to n: if (selection.contains(getItemID(row))) toSelect.add(row); selectTableRows(table, toSelect.toIntArray()); ret toSelect.size() == selection.size(); }); } void setSelected(A a) { selectRow(table(), indexOfConcept(a)); } Cl defaultSort(Cl l) { ret sortByConceptID(l); } selfType addFilter(S field, O value) { filters = orderedMapPutOrCreate(filters, field, value); this; } void onSelectionChangedAndWhenShowing(Runnable r) { bindToComponent(table(), r); onSelectionChanged(r); } void onSelectionChangedAndNow(Runnable r) { if (r == null) ret; onSelectionChanged(r); r.run(); } selfType updateInterval(double seconds) { ret updateInterval(toMS_int(seconds)); } selfType pauseUpdates(bool b) { swing { if (pauseUpdates != b) { if (!(pauseUpdates = b)) _update(); } } this; } }