/*
* 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.Frame;
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.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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.JSplitPane;
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.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.views.visualization.ComponentVisManager;
import de.tud.kom.p2psim.impl.topology.views.visualization.ComponentVisManager.VisInfo;
import de.tud.kom.p2psim.impl.topology.views.visualization.ComponentVisManager.VisualizationListener;
import de.tud.kom.p2psim.impl.topology.views.visualization.ui.InteractiveVisualizationComponent;
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.ui.VisualizationComponent;
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.WeakWaypointComponentVis;
import de.tud.kom.p2psim.impl.util.NotSupportedException;
import de.tudarmstadt.maki.simonstrator.api.Binder;
import de.tudarmstadt.maki.simonstrator.api.Host;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.common.graph.INodeID;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.LocationListener;
/**
* 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, VisualizationListener {
protected final int WORLD_X;
protected final int WORLD_Y;
protected final WorldPanel worldPanel;
protected SimControlPanel simControls;
protected JSplitPane splitPane;
protected boolean showNodes = true;
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);
public static int REDRAW_INTERVAL_MS = 250;
private ComponentVisManager visManager;
private WeakWaypointComponentVis weakWaypointComponentVis;
private ObstacleComponentVis obstacleComponentVis;
private StrongWaypointComponentVis strongWaypointComponentVis;
/**
*
*/
public VisualizationTopologyView() {
Binder.registerComponent(this);
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);
splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
splitPane.setOneTouchExpandable(true);
splitPane.setDividerLocation(1.0);
splitPane.setResizeWeight(1.0);
worldPanel.setPreferredSize(
new Dimension(VisualizationInjector.getWorldX(),
VisualizationInjector.getWorldY()));
this.setPreferredSize(new Dimension(800, 600));
this.setExtendedState(Frame.MAXIMIZED_BOTH);
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");
visManager.addVisualizationListener(this);
/*
* World-view
*/
JScrollPane mapScrollPanel = new JScrollPane(worldPanel,
ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
mapScrollPanel.getVerticalScrollBar().setUnitIncrement(50);
mapScrollPanel.getHorizontalScrollBar().setUnitIncrement(50);
mapScrollPanel.setBackground(Color.DARK_GRAY);
splitPane.setLeftComponent(mapScrollPanel);
getContentPane().add(BorderLayout.CENTER, splitPane);
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(REDRAW_INTERVAL_MS);
}
});
timer.start();
}
@Override
public void visualizationAdded(VisInfo visInfo) {
/*
* Need demo controls?
*/
VisualizationComponent visComp = visInfo.getVisualizationComponent();
if (visComp != null
&& visComp instanceof InteractiveVisualizationComponent) {
JComponent sidebar = ((InteractiveVisualizationComponent) visComp)
.getSidebarComponent();
if (sidebar != null) {
splitPane.setRightComponent(sidebar);
splitPane.setDividerLocation(0.8);
splitPane.validate();
}
}
}
@Override
public void visualizationRemoved(VisInfo visInfo) {
// do not care
}
private void buildSimControls() {
simControls = new SimControlPanel(visManager);
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) {
worldPanel.addTopologyComponent(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); } }
*/
/**
* Sets a node to clicked or unclicked state
* @param id The node which should be affected
* @param clicked true if the node should be set as clicked, false otherwise
*/
public void toggleNodeClicked(INodeID id, boolean clicked)
{
worldPanel.nodeInformation.get(id).clicked = clicked;
}
@Override
public void onLocationChanged(Host host, Location location) {
// The NodeInformation objects are registered as listener.
}
@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;
}
/**
* Set to false to disable default drawing of nodes.
*
* @param showNodes
*/
public void setShowNodes(boolean showNodes) {
this.showNodes = showNodes;
}
/**
* Mini-Container for per-node information
*
* @author bjoern
* @version 1.0, Jul 5, 2016
*/
public static class VisNodeInformation implements LocationListener {
public Point2D position;
public boolean clicked = false;
/**
* No longer react to mouse clicks etc.
*/
public boolean disableClickListener = false;
public final long hostId;
public VisNodeInformation(TopologyComponent comp) {
this.hostId = comp.getHost().getId().value();
this.position = comp.getRealPosition().asPoint();
}
@Override
public void onLocationChanged(Host host, Location location) {
this.position.setLocation(
VisualizationInjector.scaleValue(location.getLongitudeOrX()),
VisualizationInjector.scaleValue(location.getLatitudeOrY()));
}
}
/**
* The world
*
* @author Bjoern Richerzhagen
* @version 1.0, 19.03.2012
*/
protected class WorldPanel extends JLayeredPane {
protected ConcurrentHashMap nodeInformation = new ConcurrentHashMap();
protected final static int PADDING = 16;
protected final static int NODE_PAD = 2;
private static final long serialVersionUID = -3023020559483652110L;
public WorldPanel() {
this.setLayout(null);
this.setDoubleBuffered(true);
this.addMouseListener(new MouseAdapter() {
/**
* Stores the mouse position, if the mouse button is pressed
*/
@Override
public void mousePressed(MouseEvent e) {
boolean turnedSomeoneOff = false;
for (VisNodeInformation node : nodeInformation.values()) {
// Make it easier to turn things off.
if (node.clicked && node.position
.distance(e.getPoint()) < PADDING + 2) {
node.clicked = !node.clicked;
VisualizationInjector
.notifyInteractionListenersOnClick(
node.hostId, node.clicked);
turnedSomeoneOff = true;
}
}
if (!turnedSomeoneOff) {
// Turn sth on (limit to one node)
for (VisNodeInformation node : nodeInformation.values()) {
if (node.disableClickListener) {
continue;
}
if (!node.clicked && node.position
.distance(e.getPoint()) < PADDING) {
node.clicked = !node.clicked;
VisualizationInjector
.notifyInteractionListenersOnClick(
node.hostId, node.clicked);
break;
}
}
}
}
});
}
public void addTopologyComponent(TopologyComponent comp) {
if (!nodeInformation.containsKey(comp.getHost().getId())) {
VisNodeInformation tVis = new VisNodeInformation(comp);
comp.requestLocationUpdates(null, tVis);
nodeInformation.put(comp.getHost().getId(), tVis);
}
}
@Override
public void invalidate() {
super.invalidate();
repaint();
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
paintNodes(g2);
super.paintComponent(g);
}
private void paintNodes(Graphics2D g2) {
for (VisNodeInformation node : nodeInformation.values()) {
if (node.clicked) {
g2.setColor(Color.MAGENTA);
g2.fillOval((int) node.position.getX() - PADDING,
(int) node.position.getY() - PADDING,
PADDING * 2 + 1, PADDING * 2 + 1);
} else {
if (showNodes) {
// Draw nodes
g2.setColor(Color.DARK_GRAY);
g2.fillOval((int) node.position.getX() - NODE_PAD,
(int) node.position.getY() - NODE_PAD,
NODE_PAD * 2 + 1, NODE_PAD * 2 + 1);
}
}
}
}
}
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!");
}
/**
* Interval between redraw-operations of the visualization.
* Lower interval = smoother visualizations but higher resource demands.
* @param redrawInterval
*/
public void setRedrawInterval(long redrawInterval) {
VisualizationTopologyView.REDRAW_INTERVAL_MS = (int)(redrawInterval/Time.MILLISECOND);
}
/**
* 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 {
private static boolean isActive = false;
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);
}
}
public static VisNodeInformation getNodeInformation(INodeID hostId) {
return worldPanel.nodeInformation.get(hostId);
}
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;
VisualizationInjector.isActive = true;
for (VisInfo visInfo : components) {
visManager.addComponent(visInfo);
// worldPanel.add(comp);
}
worldPanel.add(new DrawDisplayStrings(WORLD_X, WORLD_Y));
setupAWTEventListener();
}
/**
* True if this component is active at all (can be used)
* @return
*/
public static boolean isActive() {
return isActive;
}
public static VisualizationTopologyView getTopologyView() {
return view;
}
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
|| me.getID() == MouseEvent.MOUSE_DRAGGED
|| me.getID() == MouseEvent.MOUSE_PRESSED
|| me.getID() == MouseEvent.MOUSE_RELEASED) {
// 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) {
if (me.getID() == MouseEvent.MOUSE_CLICKED) {
l.mouseClicked(x, y);
l.mouseClicked(x, y, me.getModifiers());
} else {
if (l instanceof MouseListener) {
switch (me.getID()) {
case MouseEvent.MOUSE_DRAGGED:
((MouseListener) l)
.mouseDragged(x, y);
break;
case MouseEvent.MOUSE_PRESSED:
((MouseListener) l)
.mousePressed(x, y);
break;
case MouseEvent.MOUSE_RELEASED:
((MouseListener) l)
.mouseReleased(x,
y);
break;
default:
break;
}
}
}
}
}
}
}
}
}
}, eventMask);
}
public static interface MouseClickListener {
@Deprecated
/**
* Method is deprecated. Uses mouseClicked(int x, int y, int modifier) instead.
*/
default void mouseClicked(int x, int y){}
default void mouseClicked(int x, int y, int modifier) {}
}
public interface MouseListener extends MouseClickListener {
void mouseDragged(int x, int y);
void mousePressed(int x, int y);
void mouseReleased(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;
}
/**
* Returns the SCALED value of the given value. This is to be used by
* visualizations!
*
* @param value
* @return
*/
public static int scaleValue(double value) {
return (int) (value * getScale());
}
/**
* Returns the original value (in meters) of the given scaled value.
* This should be used if you need map values in meters from a scaled
* value. E.g. to find segments in a to meters mapped vector.
*
* @param value
* @return
*/
public static double originalValue(int value) {
return value * (1 / getScale());
}
/**
* Recommended way to add a custom visualization layer.
*
* @param comp
*/
public static void injectComponent(VisualizationComponent comp) {
if (!isActive()) {
return;
}
visManager.addComponent(comp);
}
/**
* @deprecated use injectComponent with a {@link VisualizationComponent}
* instead
*/
@Deprecated
public static void injectComponent(String name, int priority,
JComponent component) {
injectComponent(name, priority, component, true);
}
/**
* @deprecated use injectComponent with a {@link VisualizationComponent}
* instead
*/
@Deprecated
public static void injectComponent(String name, int priority,
JComponent component, boolean active) {
injectComponent(name, priority, component, active, true);
}
/**
* @deprecated use injectComponent with a {@link VisualizationComponent}
* instead
*/
@Deprecated
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);
}
}
/**
* @deprecated use removeInjectedComponent with a
* {@link VisualizationComponent} instead
*/
@Deprecated
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 invalidate() {
worldPanel.invalidate();
view.splitPane.revalidate();
}
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();
}
}
@Override
public Location getPosition(MacAddress address) {
throw new NotSupportedException();
}
@Override
public double getDistance(MacAddress addressA, MacAddress addressB) {
throw new NotSupportedException();
}
@Override
public boolean hasRealLinkLayer() {
throw new NotSupportedException();
}
}