/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package com.bearsoft.gui.grid.data; import com.bearsoft.gui.grid.events.data.ElementsAddedEvent; import com.bearsoft.gui.grid.events.data.ElementsDataChangedEvent; import com.bearsoft.gui.grid.events.data.ElementsRemovedEvent; import com.bearsoft.gui.grid.events.data.TreedModelListener; import com.eas.util.ListenerRegistration; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableModel; /** * This class in the table front to a treed data. * * @author mg * @param <T> */ public class TableFront2TreedModel<T> implements TableModel { protected Set<TableModelListener> listeners = new HashSet<>(); protected Set<CollapseExpandListener<T>> collapseExpandListeners = new HashSet<>(); protected TreedModel<T> treedModel; protected Set<T> expanded = new HashSet<>(); protected List<T> front; protected Map<T, Integer> frontIndexes = new HashMap<>(); protected class TreedDataListener implements TreedModelListener<T> { @Override public void elementsDataChanged(ElementsDataChangedEvent<T> anEvent) { if (anEvent.getElements() == null && anEvent.getColIndex() == -1) { fireAllDataChanged(); } else if (!anEvent.getElements().isEmpty() && !anEvent.isAjusting()) { Map<Integer, Integer> indicies = convertElements2Indicies(anEvent.getElements()); for (Integer rIndex : indicies.keySet()) { fireRowChanged(anEvent.getColIndex(), rIndex); } } } @Override public void elementsAdded(ElementsAddedEvent<T> anEvent) { if (!anEvent.isAjusting()) { List<T> elementsInExpandedSpace = removeCollapsed(anEvent.getElements()); if (!elementsInExpandedSpace.isEmpty()) { invalidateFront(); Map<Integer, Integer> indicies = convertElements2Indicies(elementsInExpandedSpace); generateIntervaleInserts(indicies); } } } @Override public void elementsRemoved(ElementsRemovedEvent<T> anEvent) { if (!anEvent.isAjusting()) { List<T> elementsInExpandedSpace = removeCollapsed(anEvent.getElements()); if (!elementsInExpandedSpace.isEmpty()) { Map<Integer, Integer> indicies = convertElements2Indicies(elementsInExpandedSpace); invalidateFront(); generateIntervaledDeletes(indicies); } } } private Map<Integer, Integer> convertElements2Indicies(List<T> aElements) { TreeMap<Integer, Integer> indicies = new TreeMap<>(); for (int i = 0; i < aElements.size(); i++) { Integer rIndex = getIndexOf(aElements.get(i)); indicies.put(rIndex, rIndex); } return indicies; } private List<T> removeCollapsed(List<T> aOffer) { List<T> filtered = new ArrayList<>(); for (T el : aOffer) { if (el != null) { T elParent = treedModel.getParentOf(el); if (elParent == null || expanded.contains(elParent)) { filtered.add(el); } filtered.addAll(treedModel.getChildrenOf(el)); } } return filtered; } private void generateIntervaleInserts(Map<Integer, Integer> aIndiciesChanged) { generateIntervaledEvents(aIndiciesChanged, true); } private void generateIntervaledDeletes(Map<Integer, Integer> aIndiciesChanged) { generateIntervaledEvents(aIndiciesChanged, false); } private void generateIntervaledEvents(Map<Integer, Integer> aIndiciesChanged, boolean isInserts) { // generate intervaled events Integer rOldIndex = null; Integer rIndex1 = null; Integer rIndex2 = null; for (Integer rIndex : aIndiciesChanged.keySet()) { if (rOldIndex == null || (rIndex - rOldIndex) > 1) { if (rIndex1 != null) { if (isInserts) { fireRowsInserted(rIndex1, rIndex2); } else { fireRowsDeleted(rIndex1, rIndex2); } } rIndex1 = rIndex; } rIndex2 = rIndex; rOldIndex = rIndex; } if (isInserts) { fireRowsInserted(rIndex1, rIndex2); } else { fireRowsDeleted(rIndex1, rIndex2); } } @Override public void elementsStructureChanged() { invalidateFront(); fireAllDataChanged(); } } /** * Table front constructor. Constructs a lazy tree front. * * @param aTreedModel - Deep treed model, containing data. */ public TableFront2TreedModel(TreedModel<T> aTreedModel) { super(); treedModel = aTreedModel; treedModel.addTreedModelListener(new TreedDataListener()); } /** * {@inheritDoc} */ @Override public int getColumnCount() { return treedModel.getColumnCount(); } /** * {@inheritDoc} */ @Override public String getColumnName(int columnIndex) { return treedModel.getColumnName(columnIndex); } /** * {@inheritDoc} */ @Override public Class<?> getColumnClass(int columnIndex) { return treedModel.getColumnClass(columnIndex); } /** * {@inheritDoc} */ @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return true; } /** * {@inheritDoc} */ @Override public int getRowCount() { validateFront(); return front.size(); } /** * {@inheritDoc} */ @Override public Object getValueAt(int rowIndex, int columnIndex) { validateFront(); T el = front.get(rowIndex); return treedModel.getValue(el, columnIndex); } public T getElementAt(int rowIndex) { validateFront(); return (rowIndex >= 0 && rowIndex < front.size()) ? front.get(rowIndex) : null; } /** * {@inheritDoc} */ @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { validateFront(); T el = front.get(rowIndex); treedModel.setValue(el, columnIndex, aValue); } /** * {@inheritDoc} */ @Override public void addTableModelListener(TableModelListener l) { listeners.add(l); } /** * {@inheritDoc} */ @Override public void removeTableModelListener(TableModelListener l) { listeners.remove(l); } protected void validateFront() { assert treedModel != null; if (front == null) { List<T> children = treedModel.getChildrenOf(null); front = new ArrayList<>(); front.addAll(children); int i = 0; while (i < front.size()) { if (expanded.contains(front.get(i))) { List<T> children1 = treedModel.getChildrenOf(front.get(i)); front.addAll(i + 1, children1); } ++i; } frontIndexes.clear(); for (i = 0; i < front.size(); i++) { frontIndexes.put(front.get(i), i); } } } protected void invalidateFront() { front = null; frontIndexes.clear(); } /** * Builds path to specified element if the element belongs to the model. * * @param anElement Element to build path to. * @return ArrayList<T> of elements comprising the path, excluding * root null element. */ public List<T> buildPathTo(T anElement) { List<T> path = new ArrayList<>(); if (anElement != null) { T currentParent = anElement; path.add(currentParent); while (currentParent != null) { currentParent = getParentOf(currentParent); if (currentParent != null) { path.add(0, currentParent); } } } return path; } protected T getParentOf(T aElement) { assert treedModel != null; return treedModel.getParentOf(aElement); } /** * Converts element of the model to it's position in front. * * @param anElement Element of the model to return index of. * @return Index of model's element. It returns -1 if element is not in * expanded space, including case of an element doesn't belong to the model * at all. */ public int getIndexOf(T anElement) { validateFront(); Integer index = frontIndexes.get(anElement); return index != null ? index : -1; } protected void fireAllDataChanged() { TableModelEvent event = new TableModelEvent(this); for (TableModelListener l : listeners) { l.tableChanged(event); } } protected void fireRowsInserted(int aFirstRow, int aLastRow) { TableModelEvent event = new TableModelEvent(this, aFirstRow, aLastRow, TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT); for (TableModelListener l : listeners) { l.tableChanged(event); } } protected void fireRowsDeleted(int aFirstRow, int aLastRow) { TableModelEvent event = new TableModelEvent(this, aFirstRow, aLastRow, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE); for (TableModelListener l : listeners) { l.tableChanged(event); } } protected void fireRowChanged(int aModelColumnIndex, int aRowIndex) { TableModelEvent event = new TableModelEvent(this, aRowIndex, aRowIndex, aModelColumnIndex, TableModelEvent.UPDATE); for (TableModelListener l : listeners) { l.tableChanged(event); } } protected void fireRowsChanged(int aModelColumnIndex, int aFirstRow, int aLastRow) { TableModelEvent event = new TableModelEvent(this, aFirstRow, aLastRow, aModelColumnIndex, TableModelEvent.UPDATE); for (TableModelListener l : listeners) { l.tableChanged(event); } } public ListenerRegistration addCollapseExpandListener(CollapseExpandListener l) { collapseExpandListeners.add(l); return () -> { collapseExpandListeners.remove(l); }; } public void removeCollapseExpandListener(CollapseExpandListener l) { collapseExpandListeners.remove(l); } protected void fireCollapsed(T aItem) { for (CollapseExpandListener l : collapseExpandListeners.toArray(new CollapseExpandListener[]{})) { l.collapsed(aItem); } } protected void fireExpanded(T aItem) { for (CollapseExpandListener l : collapseExpandListeners.toArray(new CollapseExpandListener[]{})) { l.expanded(aItem); } } public boolean isExpanded(T anElement) { return expanded.contains(anElement); } public void expand(final T anElement, boolean aAsynchronous) { List<T> children = treedModel.getChildrenOf(anElement); if (!expanded.contains(anElement)) { if (!children.isEmpty()) { invalidateFront(); expanded.add(anElement); int firstRow = getIndexOf(children.get(0)); int lastRow = getIndexOf(children.get(children.size() - 1)); fireRowsInserted(firstRow, lastRow); } fireExpanded(anElement); } } public void collapse(T anElement) { List<T> children = treedModel.getChildrenOf(anElement); if (expanded.contains(anElement)) { if (!children.isEmpty()) { int firstRow = getIndexOf(children.get(0)); int lastRow = getIndexOf(children.get(children.size() - 1)); invalidateFront(); expanded.remove(anElement); fireRowsDeleted(firstRow, lastRow); } fireCollapsed(anElement); } } public TreedModel<T> unwrap() { return treedModel; } }