/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of PeerfactSim.KOM.
*
* PeerfactSim.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see .
*
*/
package de.tud.kom.p2psim.impl.topology.views;
import java.awt.AWTEvent;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JScrollPane;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.Timer;
import com.google.common.collect.Maps;
import de.tud.kom.p2psim.api.linklayer.mac.Link;
import de.tud.kom.p2psim.api.linklayer.mac.MacAddress;
import de.tud.kom.p2psim.api.linklayer.mac.MacLayer;
import de.tud.kom.p2psim.api.linklayer.mac.PhyType;
import de.tud.kom.p2psim.api.topology.Topology;
import de.tud.kom.p2psim.api.topology.TopologyComponent;
import de.tud.kom.p2psim.api.topology.movement.MovementSupported;
import de.tud.kom.p2psim.api.topology.obstacles.ObstacleModel;
import de.tud.kom.p2psim.api.topology.views.TopologyView;
import de.tud.kom.p2psim.api.topology.waypoints.WaypointModel;
import de.tud.kom.p2psim.impl.topology.PositionVector;
import de.tud.kom.p2psim.impl.topology.views.visualization.ComponentVisManager;
import de.tud.kom.p2psim.impl.topology.views.visualization.ComponentVisManager.VisInfo;
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.ComponentToggler;
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.PlottingView;
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.SimControlPanel;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.NodeVisInteractionListener;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.ObstacleComponentVis;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.StrongWaypointComponentVis;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.TopologyComponentVis;
import de.tud.kom.p2psim.impl.topology.views.visualization.world.WeakWaypointComponentVis;
import de.tud.kom.p2psim.impl.util.NotSupportedException;
import de.tudarmstadt.maki.simonstrator.api.Binder;
import de.tudarmstadt.maki.simonstrator.api.common.graph.INodeID;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
/**
* A very basic visualization of a Topology (ie. positioning and movement), just
* to ease further debugging and development of movement and obstacle-related
* classes.
*
* It is added to the Topology as a TopologyView with no PHY-Type (and therefore
* it is never used inside a MAC). Lateron an API will be provided that allows
* other components (analyzers) to display information using this basic
* Visualization.
*
* @author Bjoern Richerzhagen
* @version 1.0, 19.03.2012
*/
public class VisualizationTopologyView extends JFrame implements TopologyView,
Runnable {
protected final int WORLD_X;
protected final int WORLD_Y;
protected HashMap compVisPerHost = new HashMap();
protected final WorldPanel worldPanel;
protected SimControlPanel simControls;
public static final Stroke STROKE_BASIC = new BasicStroke(0.8f);
public static final Stroke STROKE_THICK = new BasicStroke(2f);
public static final Font FONT_TINY = new Font("SansSerif", Font.PLAIN, 7);
public static final Font FONT_MEDIUM = new Font("SansSerif", Font.PLAIN, 9);
private ComponentVisManager visManager;
private WeakWaypointComponentVis weakWaypointComponentVis;
private ObstacleComponentVis obstacleComponentVis;
private StrongWaypointComponentVis strongWaypointComponentVis;
/**
*
*/
public VisualizationTopologyView() {
WORLD_X = (int) Binder.getComponentOrNull(Topology.class)
.getWorldDimensions().getX();
WORLD_Y = (int) Binder.getComponentOrNull(Topology.class)
.getWorldDimensions().getY();
worldPanel = new WorldPanel();
visManager = new ComponentVisManager(worldPanel);
VisualizationInjector.setVariables(this, visManager, worldPanel,
WORLD_X, WORLD_Y);
this.setPreferredSize(new Dimension(800, 600));
Thread t = new Thread(this);
t.start();
}
@Override
public void run() {
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(new BorderLayout());
this.setTitle("Topology Visualization");
/*
* World-view
*/
JScrollPane mapScrollPanel = new JScrollPane(worldPanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
mapScrollPanel.getVerticalScrollBar().setUnitIncrement(50);
mapScrollPanel.getHorizontalScrollBar().setUnitIncrement(50);
mapScrollPanel.setBackground(Color.DARK_GRAY);
getContentPane().add(BorderLayout.CENTER, mapScrollPanel);
buildComponentToggler();
buildSimControls();
getRootPane().setDoubleBuffered(true);
/*
* Finally, lights!
*/
this.pack();
this.setVisible(true);
// for the movement of the line
final Timer timer = new Timer(250, null);
timer.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
worldPanel.invalidate();
timer.setDelay(250);
}
});
timer.start();
}
private void buildComponentToggler() {
ComponentToggler toggler = new ComponentToggler(this.visManager);
JScrollPane scrollPane = new JScrollPane(toggler,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
getContentPane().add(BorderLayout.WEST, scrollPane);
}
private void buildSimControls() {
simControls = new SimControlPanel();
getContentPane().add(BorderLayout.NORTH, simControls);
getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_P, 0), "pause");
Action pauseAction = new AbstractAction() {
private static final long serialVersionUID = 1L;
public void actionPerformed(ActionEvent e) {
getSimControls().togglePlayPause();
}
};
getRootPane().getActionMap().put("pause", pauseAction);
}
public SimControlPanel getSimControls() {
return simControls;
}
@Override
public void addedComponent(TopologyComponent comp) {
if (!compVisPerHost.containsKey(comp.getHost().getId())) {
TopologyComponentVis tVis = new TopologyComponentVis(this, comp);
compVisPerHost.put(comp.getHost().getId(), tVis);
// this.visManager.addComponent("Topology", 0, new
// TopologyComponentVis(this, comp));
worldPanel.add(new TopologyComponentVis(this, comp));
}
}
@Override
public void changedWaypointModel(WaypointModel model) {
if (weakWaypointComponentVis != null) {
this.visManager.removeComponent(weakWaypointComponentVis);
this.visManager.removeComponent(strongWaypointComponentVis);
// worldPanel.remove(waypointComponentVis);
}
if (model == null)
return;
weakWaypointComponentVis = new WeakWaypointComponentVis(model);
strongWaypointComponentVis = new StrongWaypointComponentVis(model);
this.visManager.addComponent("Streets", 0, weakWaypointComponentVis);
this.visManager
.addComponent("Waypoints", 0, strongWaypointComponentVis);
// worldPanel.add(waypointComponentVis);
}
@Override
public void changedObstacleModel(ObstacleModel model) {
if (obstacleComponentVis != null)
this.visManager.removeComponent(obstacleComponentVis);
// worldPanel.remove(obstacleComponentVis);
if (model == null)
return;
obstacleComponentVis = new ObstacleComponentVis(model);
this.visManager.addComponent("Buildings", 0, obstacleComponentVis);
// worldPanel.add(obstacleComponentVis);
}
/*
* @Override public void addedObstacle(Obstacle obstacle) { if (obstacle
* instanceof PolygonObstacle) { ObstacleComponentVis obsVis = new
* ObstacleComponentVis( ((PolygonObstacle) obstacle).getAwtPolygon(),
* obstacle); worldPanel.add(obsVis); } }
*/
@Override
public void afterComponentsMoved() {
worldPanel.invalidate();
}
@Override
public void afterComponentMoved(MovementSupported comp) {
// don't care
}
@Override
public Link getBestNextLink(MacAddress source, MacAddress lastHop,
MacAddress currentHop, MacAddress destination) {
throw new UnsupportedOperationException();
}
@Override
public Link getLinkBetween(MacAddress source, MacAddress destination) {
throw new UnsupportedOperationException();
}
@Override
public MacLayer getMac(MacAddress address) {
throw new UnsupportedOperationException();
}
@Override
public Collection getAllMacs() {
throw new UnsupportedOperationException();
}
@Override
public List getNeighbors(MacAddress address) {
throw new UnsupportedOperationException();
}
@Override
public PhyType getPhyType() {
return null;
}
/**
* The world
*
* @author Bjoern Richerzhagen
* @version 1.0, 19.03.2012
*/
protected class WorldPanel extends JLayeredPane {
private static final long serialVersionUID = -3023020559483652110L;
public WorldPanel() {
this.setLayout(null);
this.setPreferredSize(
new Dimension(VisualizationInjector.getWorldX(),
VisualizationInjector.getWorldY()));
}
@Override
public void invalidate() {
super.invalidate();
repaint();
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
paintPlayingFieldBorder(g2);
}
private void paintPlayingFieldBorder(Graphics2D g2) {
g2.setColor(Color.DARK_GRAY);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, VisualizationInjector.getWorldX(),
VisualizationInjector.getWorldY());
g2.setPaint(Color.BLACK);
g2.drawString("100 meters", 10, 15);
g2.drawLine(10, 30, 10 + VisualizationInjector.scaleValue(100), 30);
}
}
public void notifyInteractionListenersOnClick(long hostid, boolean isActive) {
VisualizationInjector.notifyInteractionListenersOnClick(hostid,
isActive);
}
@Deprecated
public void setPhy(String phy) {
// legacy
System.err
.println("The VisualizationTopologyView does no longer require PHY to be configured!");
}
/**
* This component allows the injection of JComponents into the visualization
* from anywhere in the simulator. It is marked deprecated due to its hacky
* nature. Integrate the visualization into the topology view instead.
*
* TODO: Add checks for every component or plotting view if a graphical
* environment is present. This will allow calls to the
* VisualizationInjector on servers without failing. e.g. supply dummy a
* PlottingView or add additional checks to the plotting view for such
* cases.
*
* @author Fabio Zöllner
* @version 1.0, 03.04.2012
*/
public static class VisualizationInjector {
protected static WorldPanel worldPanel;
public static ComponentVisManager visManager;
protected static ConcurrentLinkedQueue components = new ConcurrentLinkedQueue();
protected static Map nameToVisInfoMap = Maps
.newConcurrentMap();
protected static ConcurrentLinkedQueue displayStrings = new ConcurrentLinkedQueue();
protected static List mouseListeners = new CopyOnWriteArrayList();
protected static VisualizationTopologyView view;
protected static ConcurrentLinkedQueue interactionListeners = new ConcurrentLinkedQueue();
protected static long eventMask = AWTEvent.MOUSE_MOTION_EVENT_MASK
+ AWTEvent.MOUSE_EVENT_MASK;
private static int WORLD_X = 0;
private static int WORLD_Y = 0;
private static double SCALE = 1;
protected static Map plottingViews = Maps
.newHashMap();
public static PlottingView createPlottingView(String name) {
PlottingView view = new PlottingView(name);
plottingViews.put(name, view);
return view;
}
public static PlottingView createPlottingView(String name, int x, int y) {
PlottingView view = new PlottingView(name);
plottingViews.put(name, view);
view.setBounds(x, y, view.getWidth(), view.getHeight());
view.setVisible(true);
return view;
}
public static PlottingView getPlottingView(String name) {
return plottingViews.get(name);
}
protected static void notifyInteractionListenersOnClick(long hostid,
boolean isActive) {
for (NodeVisInteractionListener listener : VisualizationInjector.interactionListeners) {
listener.onHostClick(hostid, isActive);
}
}
protected static void setVariables(VisualizationTopologyView view,
ComponentVisManager visManager, WorldPanel worldPanel,
int WORLD_X, int WORLD_Y) {
VisualizationInjector.view = view;
VisualizationInjector.worldPanel = worldPanel;
VisualizationInjector.visManager = visManager;
VisualizationInjector.WORLD_X = WORLD_X;
VisualizationInjector.WORLD_Y = WORLD_Y;
for (VisInfo visInfo : components) {
visManager.addComponent(visInfo);
// worldPanel.add(comp);
}
worldPanel.add(new DrawDisplayStrings(WORLD_X, WORLD_Y));
setupAWTEventListener();
}
public static VisualizationTopologyView getTopologyView() {
return view;
}
/**
* Returns the {@link TopologyComponentVis} corresponding to the
* provided host ID
*
* @param nodeID
* id of the host.
* @return
*/
public static TopologyComponentVis getTopologyComponentVis(
INodeID nodeID) {
return view.compVisPerHost.get(nodeID);
}
private static void setupAWTEventListener() {
Toolkit tk = Toolkit.getDefaultToolkit();
tk.addAWTEventListener(new AWTEventListener() {
@Override
public void eventDispatched(AWTEvent e) {
if (e instanceof MouseEvent) {
MouseEvent me = (MouseEvent) e;
if (me.getID() == MouseEvent.MOUSE_CLICKED) {
// Another dirty hack until we have another graphic
// drawing system
if (((JFrame) worldPanel.getTopLevelAncestor())
.isActive()) {
int x = me.getXOnScreen();
int y = me.getYOnScreen();
if (x > (int) worldPanel.getLocationOnScreen()
.getX()
&& y > (int) worldPanel
.getLocationOnScreen().getY()
&& x < (int) worldPanel
.getLocationOnScreen().getX()
+ worldPanel.getWidth()
&& y < (int) worldPanel
.getLocationOnScreen().getX()
+ worldPanel.getHeight()) {
x -= (int) worldPanel.getLocationOnScreen()
.getX();
y -= (int) worldPanel.getLocationOnScreen()
.getY();
for (MouseClickListener l : mouseListeners) {
l.mouseClicked(x, y);
}
}
}
}
}
}
}, eventMask);
}
public static interface MouseClickListener {
public void mouseClicked(int x, int y);
}
public static JComponent getWorldPanel() {
return worldPanel;
}
public static void addMouseListener(MouseClickListener listener) {
mouseListeners.add(listener);
}
public static void removeMouseListener(MouseClickListener listener) {
mouseListeners.remove(listener);
}
/**
* Listener is informed, whenever the user clicks on a node
*
* @param listener
*/
public static void addInteractionListener(
NodeVisInteractionListener listener) {
interactionListeners.add(listener);
}
public static void removeInteractionListener(
NodeVisInteractionListener listener) {
interactionListeners.remove(listener);
}
protected static class DrawDisplayStrings extends JComponent {
public DrawDisplayStrings(int WORLD_X, int WORLD_Y) {
super();
setBounds(0, 0, WORLD_X, WORLD_Y);
setOpaque(false);
setVisible(true);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
g2.setFont(new Font("SansSerif", Font.PLAIN, 9));
int height = 10;
for (DisplayString str : displayStrings) {
g2.drawString(str.getDisplayString(), 0, height);
height += 10;
}
}
}
/**
* Returns a SCALED x-length of the world size, only to be used by
* visualizations.
*
* @return
*/
public static int getWorldX() {
// return WORLD_X;
return (int) (WORLD_X * getScale());
}
/**
* Returns a SCALED y-length of the world size, only to be used by
* visualizations.
*
* @return
*/
public static int getWorldY() {
// return WORLD_Y;
return (int) (WORLD_Y * getScale());
}
/**
* Still needed by OSM-loader (Fabio), however: should not be used
* anymore. The coordinates should be final.
*
* @param x
* @param y
*/
@Deprecated
public static void setWorldCoordinates(int x, int y) {
WORLD_X = x;
WORLD_Y = y;
}
public static double getScale() {
return SCALE;
}
public static void setScale(double scale) {
SCALE = scale;
}
public static int scaleValue(double value) {
return (int) (value * getScale());
}
public static void injectComponent(String name, int priority,
JComponent component) {
injectComponent(name, priority, component, true);
}
public static void injectComponent(String name, int priority,
JComponent component, boolean active) {
injectComponent(name, priority, component, active, true);
}
public static void injectComponent(String name, int priority,
JComponent component, boolean active, boolean showInList) {
VisInfo visInfo = new VisInfo(name, priority, component);
visInfo.setActiveByDefault(active);
visInfo.setShowInList(showInList);
components.add(visInfo);
nameToVisInfoMap.put(name, visInfo);
if (visManager != null) {
visManager.addComponent(visInfo);
}
}
public static void removeInjectedComponent(String name) {
if (nameToVisInfoMap.containsKey(name)) {
VisInfo visInfo = nameToVisInfoMap.get(name);
nameToVisInfoMap.remove(name);
components.remove(visInfo);
visManager.removeComponent(name);
}
}
public static void injectControl(JComponent component) {
view.getSimControls().add(component);
view.getSimControls().validate();
}
public static void invalidate() {
worldPanel.invalidate();
}
public static void addDisplayString(DisplayString str) {
displayStrings.add(str);
}
public static void removeDisplayString(DisplayString str) {
displayStrings.remove(str);
}
public static interface DisplayString {
public String getDisplayString();
}
}
public PositionVector getWorldDimensions() {
return new PositionVector(WORLD_X, WORLD_Y);
}
@Override
public Location getPosition(MacAddress address) {
throw new NotSupportedException();
}
@Override
public double getDistance(MacAddress addressA, MacAddress addressB) {
throw new NotSupportedException();
}
}