package de.tu_darmstadt.tk.SmartHomeNetworkSim.view; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.util.Collection; import java.util.HashMap; import java.util.Observable; import java.util.Observer; import javax.swing.JPanel; import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.LinkColorController; import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.SettingsController; import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.Controller; import de.tu_darmstadt.tk.SmartHomeNetworkSim.control.NetworkController; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Connection; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Link; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.Port; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.SmartDevice; import de.tu_darmstadt.tk.SmartHomeNetworkSim.core.util.Pair; import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.VisualizationInteractor; import de.tu_darmstadt.tk.SmartHomeNetworkSim.view.util.LinkToolTip; /** * Panel which visualizes the SmartHome Network, it's Devices and Connections * * * @author Andreas T. Meyer-Berg */ @SuppressWarnings("serial") public class VisualizationPanel extends JPanel implements Observer { /** * Controller to notify when the model changes */ private Controller control; /** * Settings Controller */ private SettingsController config; /** * Network Controller */ private NetworkController network; /** * Listener which processes the GUI Interactions */ private VisualizationInteractor interactor; /** * Initializes the Visualization Panel * * @param control * Control, which changes the model */ public VisualizationPanel(Controller control) { super(); this.control = control; this.config = control.getSettingsController(); this.network = control.getNetworkController(); this.interactor = new VisualizationInteractor(control, this); this.addMouseMotionListener(interactor); this.addMouseListener(interactor); this.addKeyListener(interactor); } /** * Performs further initializations, which have to be performed, after the * model was made visible */ public void delayedInit() { this.addComponentListener(new ComponentListener() { @Override public void componentShown(ComponentEvent e) { } @Override public void componentResized(ComponentEvent e) { config.setDimension(getWidth(), getHeight(), control.getSettingsController().getDepth(), true); repaint(); } @Override public void componentMoved(ComponentEvent e) { } @Override public void componentHidden(ComponentEvent e) { } }); config.setDimension(getWidth(), getHeight(), control.getSettingsController().getDepth(), true); repaint(); } @Override public void paint(Graphics g) { setRenderOptions(g); // paint white background g.setColor(Color.white); g.fillRect(0, 0, this.getWidth(), this.getHeight()); if(config.isShowConnections()) paintConnections(g); if(config.isShowSmartDevices()) paintDevices(g); paintDrag(g); paintToolTip(g); paintDebugInformation(g); paintLinkLabels(g); } /** * Paints debug information like the current interaction mode * @param g Graphics which should be used */ private void paintDebugInformation(Graphics g) { if(!config.isDebugModus()) return; /** * Visual representation of the interaction modus */ String modus; switch (interactor.mode) { case VisualizationInteractor.DRAG_CONNECTION: modus="Drag Connection"; break; case VisualizationInteractor.SELECTED_DRAG: modus="Drag Device"; break; case VisualizationInteractor.DRAG_SELECT: modus="Drag Select"; break; case VisualizationInteractor.NOTHING: modus="Nothing"; break; case VisualizationInteractor.RIGHTCLICK_MENU: modus="RightClick Menu"; break; case VisualizationInteractor.SELECTED: modus="Selected"; break; default: modus="Unknown"; break; } g.setColor(Color.RED); g.drawString("Debug(Modus:"+modus+")", 0, 10); if(interactor.controlDown) g.drawString("Control down", 0, 20); } /** * Paints a list of all Link names on the left hand side * @param g Graphics to be used */ private void paintLinkLabels(Graphics g) { if(!config.isShowLinks()||network.getLinks().isEmpty()||!config.isShowLinkNameList()) return; g.setColor(Color.BLACK); /** * y-position of the next text Link line */ int yPositionLink = 10; if(config.isDebugModus()) yPositionLink+=25; g.drawString("Links:", 0, yPositionLink); yPositionLink += 10; for(Link l: network.getLinks()){ if(!config.getNetworkTreeSettingsController().isVisible(l))continue; g.setColor(config.getLinkColors().getColorOfLink(l).getRight()); g.drawString(l.getName(), 0, yPositionLink); yPositionLink+=10; } } /** * Sets the different rending options if available * @param g Graphics to be improved */ private void setRenderOptions(Graphics g) { // More beautiful Graphics if(g instanceof Graphics2D){ /** * Graphics2D options, if enabled on the machine */ Graphics2D graphics = (Graphics2D) g; /** * Rendering Options for the visualization */ RenderingHints renders = new RenderingHints(new HashMap<>()); /** * Add AntiAliasing */ renders.add(new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON)); /** * Add Text AntiAliasing */ renders.add(new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON)); /** * Add Improved Color Rendering */ renders.add(new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY)); /** * Add general quality Rendering */ renders.add(new RenderingHints(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY)); /** * Add improved interpolation for images */ renders.add(new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC)); graphics.setRenderingHints(renders); } } /** * Paints the smart devices of the Model * * @param g * the Graphics object to visualize on */ protected void paintDevices(Graphics g) { /** * Radius of devices */ int deviceRadius = config.getDeviceVisualizationRadius(); /* * Visualization calculations for all devices */ /** * Number of Links in the Model */ int numberOfLinks = network.getVisibleLinks().size(); /** * Angle per Link in "linkSlice degrees" */ double linkSlice = Math.min(360.0 / numberOfLinks,90.0); /** * Map of links to Colors and position of the Color */ LinkColorController linkColors = config.getLinkColors(); if(config.isShowLinks()){ int i = 0; for(Link l: network.getVisibleLinks()){ /** * Index & Color */ Pair p = config.getLinkColors().getColorOfLink(l); p.setLeft(i); i++; } } /** * Original Stroke - if it was changed */ Stroke f = null; /** * Set bigger brush size if possible */ if(g instanceof Graphics2D){ Graphics2D g2 = (Graphics2D) g; f = g2.getStroke(); g2.setStroke(new BasicStroke(Math.max(deviceRadius/20.0f,1f))); } /** * Paint Devices */ //Values for dragging of multiple Devices /** * Pixel offset of dragged devices on the x axis */ int x_offset = 0; /** * Pixel offset of dragged devices on the y axis */ int y_offset = 0; if(interactor.mode == VisualizationInteractor.SELECTED_DRAG && !control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevices.isEmpty()){ x_offset = interactor.dragged_x-interactor.dragged.getX(); y_offset = interactor.dragged_y-interactor.dragged.getY(); } //Paint Devices for (SmartDevice s: network.getVisibleSmartDevices()) { /** * x Position of the device */ int x = s.getX(); /** * y Position of the device */ int y = s.getY(); if(interactor.mode==VisualizationInteractor.SELECTED_DRAG && control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevices.contains(s)) { // Update visualization of dragged object x += x_offset; y += y_offset; } //Paint Links of Device V0.0 if(config.isShowLinks()){ for(Link l:s.getLinks()){ /* * Split circle around the object into NumberOfLinks Sections, color the sections, connected with the device */ /** * Number and color of the current link */ Pair linkPos = linkColors.getColorOfLink(l); if(linkPos==null||!config.getNetworkTreeSettingsController().isVisible(l))continue;//Skip Links, which are not in the model or hidden g.setColor(linkPos.getRight()); g.fillArc(x - deviceRadius - config.getLinkRadius(), y - deviceRadius - config.getLinkRadius(), 2 * deviceRadius - 1 + 2 * config.getLinkRadius(), 2 * deviceRadius - 1 + 2*config.getLinkRadius(), (int)Math.round(linkPos.getLeft()*linkSlice), (int)Math.ceil(linkSlice)); } } if(//(interactor.mode==VisualisationInteractor.SELECTED||interactor.mode==VisualisationInteractor.DRAG_SELECT ||interactor.mode == VisualisationInteractor.SELECTED_DRAG || interactor.mode == VisualisationInteractor.RIGHTCLICK_MENU)&& (control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevices.contains(s)^control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevicesDrag.contains(s))){ //HighlightSelected Devices g.setColor(new Color(135,206,235)); }else{ g.setColor(Color.WHITE); } fillCircle(g, x, y, deviceRadius); g.setColor(Color.BLACK); drawCircle(g,x,y,deviceRadius); g.setColor(new Color(91, 162, 229)); /** * Distance in pixels between the two circles at the border */ int innerDistance = Math.max(2, Math.round(deviceRadius/13f)); drawCircle(g, x, y, deviceRadius-innerDistance); g.setColor(Color.BLACK); if(config.isShowSmartDeviceNames()) g.drawString(s.getName(), x - g.getFontMetrics().stringWidth(s.getName()) / 2, y + deviceRadius + 11 + (config.isShowLinks()?config.getLinkRadius():0)); } if(f!=null) ((Graphics2D) g).setStroke(f); } /** * Paints the Connections * * @param g * the Graphics object to visualize on */ protected void paintConnections(Graphics g) { //Values for dragging of multiple Devices int x_offset = 0; int y_offset = 0; if(interactor.mode == VisualizationInteractor.SELECTED_DRAG && !control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevices.isEmpty()){ x_offset = interactor.dragged_x-interactor.dragged.getX(); y_offset = interactor.dragged_y-interactor.dragged.getY(); } // For all Connections for (Connection c : network.getVisibleConnections()) { Color connectionState; switch (c.getStatus()) { case Connection.ACTIVE: connectionState = Color.GREEN; break; case Connection.HALTED: connectionState = Color.ORANGE; break; case Connection.DONE: connectionState = Color.GRAY; break; case Connection.TERMINATED: case Connection.FINISHED: connectionState = Color.RED; break; default: connectionState = Color.BLUE; break; } g.setColor(connectionState); /** * All Devices that are part of the connection */ Collection d = c.getParticipants(); if(d.size()==0){ System.out.println("WARNING: Empty, undeleted Connection: "+c.toString()); continue; } if(c.getProtocol()==null){ System.out.println("WARNING: Protocol is null: "+c.toString()); continue; } for(Pair p: c.getProtocol().getTopology() ){ if(p.getLeft() != null && p.getLeft().getOwner() != null && p.getRight() != null && p.getRight().getOwner() != null){ if(control.getSettingsController().getConfigurationManager().getSelectionModel().clickedConnection.contains(new Pair>(c, p))) g.setColor(Color.BLUE); else g.setColor(connectionState); SmartDevice from = p.getLeft().getOwner(); SmartDevice to = p.getRight().getOwner(); if(!config.getNetworkTreeSettingsController().isVisible(from)||!config.getNetworkTreeSettingsController().isVisible(to))continue; int xFrom = from.getX(); int yFrom = from.getY(); int xTo = to.getX(); int yTo = to.getY(); if(control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevices.contains(from)^control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevicesDrag.contains(from)){ xFrom+=x_offset; yFrom+=y_offset; } if(control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevices.contains(to)^control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevicesDrag.contains(to)){ xTo+=x_offset; yTo+=y_offset; } g.drawLine(xFrom, yFrom, xTo, yTo); } } for(Pair p: c.getProtocol().getDeletedTopology() ){ if(control.getSettingsController().getConfigurationManager().getSelectionModel().clickedConnection.contains(new Pair>(c, p))) g.setColor(Color.BLUE); else{ //Just paint terminated connection, if config says so if(config.isShowTerminatedConnections()) g.setColor(Color.RED); else continue; } if(p.getLeft() != null && p.getLeft().getOwner() != null && p.getRight() != null && p.getRight().getOwner() != null){ SmartDevice from = p.getLeft().getOwner(); SmartDevice to = p.getRight().getOwner(); int xFrom = from.getX(); int yFrom = from.getY(); int xTo = to.getX(); int yTo = to.getY(); if(control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevices.contains(from)^control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevicesDrag.contains(from)){ xFrom+=x_offset; yFrom+=y_offset; } if(control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevices.contains(to)^control.getSettingsController().getConfigurationManager().getSelectionModel().selectedDevicesDrag.contains(to)){ xTo+=x_offset; yTo+=y_offset; } g.drawLine(xFrom, yFrom, xTo, yTo); } } } // paint new in progress connection, if a connection or link is in // creation if (interactor.mode == VisualizationInteractor.DRAG_CONNECTION && interactor.connectionFrom != null){ g.setColor(Color.ORANGE); g.drawLine(interactor.connectionFrom.getX(), interactor.connectionFrom.getY(), interactor.dragged_x, interactor.dragged_y); } } /** * Paints the new dragged Connection * @param g graphics of this panel */ protected void paintDrag(Graphics g){ if(interactor.mode!=VisualizationInteractor.DRAG_SELECT) return; int low_x = Math.min(interactor.dragged_x, interactor.dragged_x_start); int low_y = Math.min(interactor.dragged_y, interactor.dragged_y_start); int high_x = Math.max(interactor.dragged_x, interactor.dragged_x_start); int high_y = Math.max(interactor.dragged_y, interactor.dragged_y_start); g.setColor(Color.BLUE); g.drawRect(low_x, low_y, high_x-low_x, high_y-low_y); } /** * Paints the link ToolTip * @param g graphics of this panel */ protected void paintToolTip(Graphics g) { /** * ToolTip which is visualized */ LinkToolTip toolTip = interactor.getToolTip(); if (toolTip.isEnabled() && config.isShowLinkToolTips()) { /** * free space around the ToolTipText */ int border = 2; /** * Width of the text (multiLine later ?) */ int textWidth = g.getFontMetrics().stringWidth(toolTip.getText()) + 2 * border; /** * Height of the Text */ int textHeigth = 12 + 2 * border; /** * top left x position of the ToolTip */ int fixXPos = toolTip.getX(); /** * top left y position of the ToolTip */ int fixYPos = toolTip.getY(); //Check position that toolTip stays in bound if (fixXPos < 0) { fixXPos = 0; } else if (fixXPos + textWidth >= this.getWidth()) { fixXPos -= (fixXPos + textWidth + 1) - this.getWidth(); } if (fixYPos < 0){ fixYPos = 0; } else if(fixYPos + textHeigth >= this.getHeight()) { fixYPos -= (fixYPos + textHeigth + 1) - this.getHeight(); } //Paint background (toolTip Color (light yellow/brown)) g.setColor(new Color(255, 255, 225)); g.fillRect(fixXPos, fixYPos, textWidth, textHeigth); //Paint black surrounding rectangle g.setColor(Color.BLACK); g.drawRect(fixXPos, fixYPos, textWidth, textHeigth); //Draw the ToolTip text g.drawString(toolTip.getText(), fixXPos + 2, fixYPos + 12); } } @Override public void update(Observable o, Object arg) { repaint(); } /** * Fills a circle of radius radius around the given point (Radius includes middle), So width may be 2*radius-1 * @param g Graphics to be used * @param x x Position of the middle * @param y y Position of the middle * @param radius radius of the circle */ public void fillCircle(Graphics g, int x, int y, int radius){ g.fillOval(x-radius, y-radius, 2*radius+1, 2*radius+1); } /** * Draws a circle of radius radius around the given point (Radius includes middle) * @param g Graphics to be used * @param x x Position of the middle * @param y y Position of the middle * @param radius radius of the circle */ public void drawCircle(Graphics g, int x, int y, int radius){ g.drawOval(x-radius, y-radius, 2*radius-1, 2*radius-1); } }