package ini.trakem2.display;
import ini.trakem2.utils.Bureaucrat;
import ini.trakem2.utils.IJError;
import ini.trakem2.utils.Utils;
import ini.trakem2.utils.Worker;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
/** List all connectors whose origins intersect with the given tree. */
public class TreeConnectorsView {
static private Map<Tree<?>,TreeConnectorsView> open = Collections.synchronizedMap(new HashMap<Tree<?>,TreeConnectorsView>());
private JFrame frame;
private TargetsTableModel outgoing_model = new TargetsTableModel(),
incoming_model = new TargetsTableModel();
private Tree<?> tree;
public TreeConnectorsView(final Tree<?> tree) {
this.tree = tree;
update();
createGUI();
open.put(tree,this);
}
static public Bureaucrat create(final Tree<?> tree) {
return Bureaucrat.createAndStart(new Worker.Task("Opening connectors table") {
public void exec() {
TreeConnectorsView tcv = open.get(tree);
if (null != tcv) {
tcv.update();
tcv.frame.setVisible(true);
tcv.frame.toFront();
} else {
// Create and store in the Map of 'open'
new TreeConnectorsView(tree);
}
}
}, tree.getProject());
}
static public void dispose(final Tree<?> tree) {
TreeConnectorsView tcv = open.remove(tree);
if (null == tcv) return;
tcv.frame.dispose();
}
static private final Comparator<Displayable> IDSorter = new Comparator<Displayable>() {
@Override
public int compare(Displayable o1, Displayable o2) {
if (o1.getId() < o1.getId()) return -1;
return 1;
}
};
private class Row {
final Connector connector;
final int i;
final ArrayList<Displayable> origins, targets;
String originids, targetids;
Row(final Connector c, final int i, final ArrayList<Displayable> origins, final ArrayList<Displayable> targets) {
this.connector = c;
this.i = i;
this.origins = origins;
this.targets = targets;
for (final Iterator<Displayable> it = this.targets.iterator(); it.hasNext(); ) {
if (it.next().getClass() == Connector.class) {
it.remove();
}
}
}
final Coordinate<Node<Float>> getCoordinate(int col) {
switch (col) {
case 0:
case 1:
return connector.getCoordinateAtOrigin();
case 2:
return connector.getCoordinate(i);
default:
Utils.log2("Can't deal with column " + col);
return null;
}
}
private final long getFirstId(final ArrayList<Displayable> c) {
if (c.isEmpty())
return 0;
else
return c.get(0).getId();
}
final long getColumn(final int col) {
switch (col) {
case 0:
return connector.getId();
case 1:
return getFirstId(origins);
case 2:
return getFirstId(targets);
default:
Utils.log2("Don't know how to deal with column " + col);
return 0;
}
}
private final String getIds(String ids, final ArrayList<Displayable> ds) {
if (null == ids) {
switch (ds.size()) {
case 0:
ids = "";
break;
case 1:
ids = ds.get(0).toString();
break;
default:
final StringBuilder sb = new StringBuilder();
for (final Displayable d : ds) {
sb.append(d).append(',').append(' ');
}
sb.setLength(sb.length() -2);
ids = sb.toString();
break;
}
}
return ids;
}
final String getTargetIds() {
targetids = getIds(targetids, targets);
return targetids;
}
final String getOriginIds() {
originids = getIds(originids, origins);
return originids;
}
}
public void update() {
// Find all Connector instances intersecting with the nodes of Tree
try {
final Collection<Connector>[] connectors = this.tree.findConnectors();
outgoing_model.setData(connectors[0]);
incoming_model.setData(connectors[1]);
} catch (Exception e) {
IJError.print(e);
}
}
private void addTab(JTabbedPane tabs, String title, TargetsTableModel model) {
JTable table = new Table();
table.setModel(model);
JScrollPane jsp = new JScrollPane(table);
jsp.setPreferredSize(new Dimension(500, 500));
tabs.addTab(title, jsp);
}
private void createGUI() {
this.frame = new JFrame("Connectors for Tree #" + this.tree.getId());
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent we) {
open.remove(tree);
}
});
JTabbedPane tabs = new JTabbedPane();
addTab(tabs, "Outgoing", outgoing_model);
addTab(tabs, "Incoming", incoming_model);
frame.getContentPane().add(tabs);
frame.pack();
frame.setVisible(true);
}
private class Table extends JTable {
private static final long serialVersionUID = 1L;
Table() {
super();
getTableHeader().addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent me) {
if (2 != me.getClickCount()) return;
int viewColumn = getColumnModel().getColumnIndexAtX(me.getX());
int column = convertColumnIndexToModel(viewColumn);
if (-1 == column) return;
((TargetsTableModel)getModel()).sortByColumn(column, me.isShiftDown());
}
});
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
final int row = Table.this.rowAtPoint(me.getPoint());
final int col = Table.this.columnAtPoint(me.getPoint());
if (2 == me.getClickCount()) {
go(col, row);
} else if (Utils.isPopupTrigger(me)) {
JPopupMenu popup = new JPopupMenu();
final JMenuItem go = new JMenuItem("Go"); popup.add(go);
final JMenuItem goandsel = new JMenuItem("Go and select"); popup.add(go);
final JMenuItem update = new JMenuItem("Update"); popup.add(update);
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
final Object src = ae.getSource();
if (src == go) go(col, row);
else if (src == goandsel) {
go(col, row);
if (0 != (ae.getModifiers() ^ ActionEvent.SHIFT_MASK)) Display.getFront().getSelection().clear();
TargetsTableModel ttm = (TargetsTableModel)getModel();
Display.getFront().getSelection().add(ttm.rows.get(row).connector);
} else if (src == update) {
Bureaucrat.createAndStart(new Worker.Task("Updating...") {
public void exec() {
TreeConnectorsView.this.update();
}
}, TreeConnectorsView.this.tree.getProject());
}
}
};
go.addActionListener(listener);
goandsel.addActionListener(listener);
update.addActionListener(listener);
popup.show(Table.this, me.getX(), me.getY());
}
}
});
}
void go(int col, int row) {
TargetsTableModel ttm = (TargetsTableModel)getModel();
Display.centerAt(ttm.rows.get(row).getCoordinate(col));
}
}
private class TargetsTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
List<Row> rows = null;
synchronized public void setData(final Collection<Connector> connectors) {
this.rows = new ArrayList<Row>(connectors.size());
for (final Connector c : connectors) {
int i = 0;
final ArrayList<Displayable> origins = new ArrayList<Displayable>(c.getOrigins(VectorData.class, true));
Collections.sort(origins, IDSorter);
for (final Set<Displayable> targets : c.getTargets(VectorData.class, true)) {
final ArrayList<Displayable> ts = new ArrayList<Displayable>(targets);
Collections.sort(ts, IDSorter);
this.rows.add(new Row(c, i++, origins, ts));
}
}
SwingUtilities.invokeLater(new Runnable() {public void run() {
fireTableDataChanged();
fireTableStructureChanged();
}});
}
public int getColumnCount() { return 3; }
public String getColumnName(final int col) {
switch (col) {
case 0: return "Connector id";
case 1: return "Origin id";
case 2: return "Target id";
default: return null;
}
}
public int getRowCount() { return rows.size(); }
public Object getValueAt(final int row, final int col) {
switch (col) {
case 0: return rows.get(row).connector.getId();
case 1: return rows.get(row).getOriginIds();
case 2: return rows.get(row).getTargetIds();
default: return null;
}
}
public boolean isCellEditable(int row, int col) { return false; }
public void setValueAt(Object value, int row, int col) {}
final void sortByColumn(final int col, final boolean descending) {
final ArrayList<Row> rows = new ArrayList<Row>(this.rows);
Collections.sort(rows, new Comparator<Row>() {
public int compare(final Row r1, final Row r2) {
final long op = r1.getColumn(col) - r2.getColumn(col);
if (descending) {
if (op > 0) return -1;
if (op < 0) return 1;
return 0;
}
if (op < 0) return -1;
if (op > 0) return 1;
return 0;
}
});
this.rows = rows; // swap
fireTableDataChanged();
fireTableStructureChanged();
}
}
}