/* * 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(); } }