package ch.rakudave.jnetmap.view.components;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.lang.reflect.Constructor;
import java.util.LinkedList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import ch.rakudave.jnetmap.controller.Controller;
import ch.rakudave.jnetmap.controller.command.Command;
import ch.rakudave.jnetmap.model.Connection;
import ch.rakudave.jnetmap.model.Map;
import ch.rakudave.jnetmap.model.MapEvent;
import ch.rakudave.jnetmap.model.MapListener;
import ch.rakudave.jnetmap.model.device.Device;
import ch.rakudave.jnetmap.model.factories.ConnectionFactory;
import ch.rakudave.jnetmap.model.factories.DeviceFactory;
import ch.rakudave.jnetmap.util.Icons;
import ch.rakudave.jnetmap.util.Lang;
import ch.rakudave.jnetmap.util.logging.Logger;
import ch.rakudave.jnetmap.view.jung.EdgeTransformers;
import ch.rakudave.jnetmap.view.jung.MapGraphMouse;
import ch.rakudave.jnetmap.view.jung.VertexTransformers;
import edu.uci.ics.jung.algorithms.layout.CircleLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.ISOMLayout;
import edu.uci.ics.jung.algorithms.layout.KKLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.SpringLayout2;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.visualization.GraphZoomScrollPane;
import edu.uci.ics.jung.visualization.RenderContext;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.CrossoverScalingControl;
import edu.uci.ics.jung.visualization.control.GraphMouseListener;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ScalingControl;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.layout.LayoutTransition;
import edu.uci.ics.jung.visualization.util.Animator;
/**
* Defines a tab-component that contains and draws a map.
* Tabs feature a close-button similar to Firefox.
* @author rakudave
*/
@SuppressWarnings("serial")
public class TabPanel extends JTabbedPane implements ChangeListener, GraphMouseListener<Device> {
private TabPanel _this = this; // parent-reference for inner classes
private Frame owner;
private List<GraphMouseListener<Device>> graphListeners;
private static TabComponent currentTab;
private JButton plusButton;
private static ScalingControl scaler;
public TabPanel(int tabPlacement, int tabLayoutPolicy, Frame owner) {
super(tabPlacement, tabLayoutPolicy);
this.owner = owner;
graphListeners = new LinkedList<GraphMouseListener<Device>>();
scaler = new CrossoverScalingControl();
addTab("+", null, new WelcomeScreen());
createPlusButton();
//GraphMouse
ToolBar.getInstance().add("map.pick", getPickingModeSetter());
ToolBar.getInstance().add("map.edit", getEditingModeSetter());
ToolBar.getInstance().add("map.transform", getTransformingModeSetter());
//Zoom
ToolBar.getInstance().add("map.zoomin", getZoomPlus());
ToolBar.getInstance().add("map.zoomout", getZoomMinus());
addChangeListener(this);
}
@Override
public void stateChanged(ChangeEvent e) {
Object o = getSelectedComponent();
if (o instanceof TabComponent) {
currentTab = (TabComponent) o;
Controller.setCurrentMap(currentTab.map);
}
}
private void closeTab(String tabTitle) {
int tabIndex = getTabIndex(tabTitle);
TabComponent tc = (TabComponent) getComponentAt(tabIndex);
if (!Controller.close(tc.map)) return;
removeTabAt(tabIndex);
}
private void createPlusButton() {
plusButton = new JButton(Icons.get("add"));
plusButton.setContentAreaFilled(false);
plusButton.setBorder(BorderFactory.createEmptyBorder());
plusButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
openTab(new Map());
}
});
setTabComponentAt(0, plusButton);
}
private int getTabIndex(String tabTitle) {
for (int i = 0; i < getTabCount()-1; i++) {
if (tabTitle.equals(getTabComponentAt(i).getName()))
return i;
}
return -1;
}
public void openTab(Map map) {
int tabCount = getTabCount()-1;
final VisualizationViewer<Device, Connection> vv = new VisualizationViewer<Device, Connection>(map.getGraphLayout());
map.addMapListener(new MapListener() {
@Override
public void mapChanged(MapEvent e) {
vv.repaint();
}
});
MapGraphMouse egm = new MapGraphMouse(owner, vv.getRenderContext(), new DeviceFactory(), new ConnectionFactory());
egm.setMode(ModalGraphMouse.Mode.PICKING);
vv.setGraphMouse(egm);
vv.setBackground(Color.white);
vv.addGraphMouseListener(this);
vv.getRenderer().getVertexLabelRenderer().setPosition(edu.uci.ics.jung.visualization.renderers.Renderer.VertexLabel.Position.S);
RenderContext<Device, Connection> rc = vv.getRenderContext();
rc.setVertexIconTransformer(VertexTransformers.iconTransformer(vv.getPickedVertexState()));
rc.setVertexShapeTransformer(VertexTransformers.shapeTransformer());
rc.setVertexLabelTransformer(new ToStringLabeller<Device>());
vv.setVertexToolTipTransformer(VertexTransformers.tooltipTransformer());
rc.setEdgeStrokeTransformer(EdgeTransformers.strokeTransformer());
rc.setEdgeDrawPaintTransformer(EdgeTransformers.paintTransformer(vv.getPickedEdgeState()));
insertTab(map.getFileName(), null, new TabComponent(map, egm, vv), map.getFilePath(), tabCount);
setTabComponentAt(tabCount, new ClosableTabHeader(map.getFileName(), map.getFilePath()));
setSelectedIndex(tabCount);
}
public void addGraphListener(GraphMouseListener<Device> l) {
graphListeners.add(l);
}
public void removeGraphListener(GraphMouseListener<Device> l) {
graphListeners.remove(l);
}
@Override
public void graphClicked(Device v, MouseEvent me) {
for (GraphMouseListener<Device> l : graphListeners) {
try {
l.graphClicked(v, me);
} catch (Exception e) {
Logger.error("Unable to notify graphListener", e);
}
}
}
@Override
public void graphPressed(Device v, MouseEvent me) {
for (GraphMouseListener<Device> l : graphListeners) {
try {
l.graphPressed(v, me);
} catch (Exception e) {
Logger.error("Unable to notify graphListener", e);
}
}
}
@Override
public void graphReleased(Device v, MouseEvent me) {
for (GraphMouseListener<Device> l : graphListeners) {
try {
l.graphReleased(v, me);
} catch (Exception e) {
Logger.error("Unable to notify graphListener", e);
}
}
}
public static Action getPickingModeSetter() {
return new AbstractAction(Lang.getNoHTML("map.pick"), Icons.get("cursor_pick")) {
@Override
public void actionPerformed(ActionEvent e) {
if (currentTab != null)
currentTab.mouse.setMode(ModalGraphMouse.Mode.PICKING);
}
};
}
public static Action getEditingModeSetter() {
return new AbstractAction(Lang.getNoHTML("map.edit"), Icons.get("cursor_edit")) {
@Override
public void actionPerformed(ActionEvent e) {
if (currentTab != null)
currentTab.mouse.setMode(ModalGraphMouse.Mode.EDITING);
}
};
}
public static Action getTransformingModeSetter() {
return new AbstractAction(Lang.getNoHTML("map.transform"), Icons.get("cursor_move")) {
@Override
public void actionPerformed(ActionEvent e) {
if (currentTab != null)
currentTab.mouse.setMode(ModalGraphMouse.Mode.TRANSFORMING);
}
};
}
public static Action getZoomPlus() {
return new AbstractAction(Lang.getNoHTML("map.zoomin"), Icons.get("zoom_in")) {
@Override
public void actionPerformed(ActionEvent e) {
if (currentTab != null)
scaler.scale(currentTab.vv, 1.5f, currentTab.vv.getCenter());
}
};
}
public static Action getZoomMinus() {
return new AbstractAction(Lang.getNoHTML("map.zoomout"), Icons.get("zoom_out")) {
@Override
public void actionPerformed(ActionEvent e) {
if (currentTab != null)
scaler.scale(currentTab.vv, 1/1.5f, currentTab.vv.getCenter());
}
};
}
// TODO how to set root of a tree???
/*public static Action getTest() {
return new AbstractAction(Lang.get("test"), Icons.get("add")) {
@Override
public void actionPerformed(ActionEvent e) {
MinimumSpanningForest2<Device, Connection> prim =
new MinimumSpanningForest2<Device, Connection>(currentTab.map,
new DelegateForest<Device, Connection>(),
DelegateTree.<Device, Connection>getFactory(), null);
currentTab.vv.setGraphLayout(new TreeLayout<Device, Connection>(prim.getForest()));
}
};
}*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static List<Action> getLayoutTransformers() {
final LinkedList<Class> layouts = new LinkedList<Class>() {{
add(CircleLayout.class);
add(FRLayout.class);
add(ISOMLayout.class);
add(KKLayout.class);
add(SpringLayout2.class);
add(StaticLayout.class);
}};
return new LinkedList<Action>() {{
for (final Class c : layouts) add(new AbstractAction(c.getSimpleName()) {
@Override
public void actionPerformed(ActionEvent e) {
setLayout(c);
}
});
}};
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static void setLayout(Class<Layout<Device, Connection>> layout) {
if (currentTab == null) return;
final Map map = currentTab.map;
final VisualizationViewer<Device, Connection> vv = currentTab.vv;
final Layout<Device, Connection> oldLayout = map.getGraphLayout();
try {
Class<? extends Layout<Device, Connection>> layoutC = (Class<? extends Layout<Device, Connection>>) layout;
Constructor constructor = layoutC.getConstructor(new Class[] {Graph.class});
Object o = constructor.newInstance(map);
final Layout<Device, Connection> newLayout = (Layout<Device, Connection>) o;
newLayout.setInitializer(vv.getGraphLayout());
newLayout.setSize(vv.getSize());
map.getHistory().execute(new Command() {
@Override
public Object undo() {
map.setLayout(oldLayout);
new Animator(new LayoutTransition<Device, Connection>(vv, newLayout, oldLayout)).start();
vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
vv.repaint();
return null;
}
@Override
public Object redo() {
map.setLayout(newLayout);
new Animator(new LayoutTransition<Device, Connection>(vv, oldLayout, newLayout)).start();
vv.getRenderContext().getMultiLayerTransformer().setToIdentity();
vv.repaint();
return null;
}
});
} catch (Exception e) {
Logger.error("Unable to set new layout "+layout.getSimpleName(), e);
}
}
/**
* "Struct" to keep track of and easily retrieve the Objects corresponding to the displayed Map
* @author rakudave
*/
public class TabComponent extends GraphZoomScrollPane {
Map map;
MapGraphMouse mouse;
VisualizationViewer<Device, Connection> vv;
public TabComponent(Map map, MapGraphMouse mouse, VisualizationViewer<Device, Connection> vv) {
super(vv);
this.map = map;
this.mouse = mouse;
this.vv = vv;
}
}
/**
* Tab-component that may be closed by clicking the "x" located at the right,
* similar to the way Firefox does it. It also sports an icon and the usual title and tooltip.
* @author rakudave
*/
private class ClosableTabHeader extends JPanel {
private static final long serialVersionUID = -8130758834224431426L;
private JButton label, close;
public ClosableTabHeader(String title, String path) {
super(new BorderLayout());
setName(path + "///" + Math.random()); // UID
setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
setBackground(new Color(0, 0, 0, 0));
label = new JButton(title, Icons.get("jnetmap_small")) {{
setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 10));
setContentAreaFilled(false);
setFocusable(false);
}};
label.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setSelectedIndex(getTabIndex(getName()));
}
});
close = new JButton(Icons.get("close")) {{
setPreferredSize(new Dimension(16, 16));
setContentAreaFilled(false);
setFocusable(false);
}};
close.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
_this.closeTab(getName());
}
});
add(label, BorderLayout.CENTER);
add(close, BorderLayout.EAST);
setToolTipText(path);
}
@SuppressWarnings("unused")
public void setLabel(String text) {
label.setText(text);
}
public void setToolTipText(String text) {
super.setToolTipText(text);
label.setToolTipText(text);
}
}
public static TabComponent getCurrentTab() {
return currentTab;
}
}