package org.opentrafficsim.swing.gui; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.rmi.RemoteException; import java.text.NumberFormat; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JToggleButton; import javax.swing.border.EmptyBorder; import org.djutils.event.EventInterface; import org.djutils.event.EventListenerInterface; import org.djutils.event.TimedEvent; import org.djutils.exceptions.Throw; import org.opentrafficsim.core.animation.gtu.colorer.GTUColorer; import org.opentrafficsim.core.dsol.OTSAnimator; import org.opentrafficsim.core.dsol.OTSModelInterface; import org.opentrafficsim.core.gtu.GTU; import org.opentrafficsim.core.network.Network; import org.opentrafficsim.core.network.OTSNetwork; import nl.javel.gisbeans.map.MapInterface; import nl.tudelft.simulation.dsol.animation.Locatable; import nl.tudelft.simulation.dsol.animation.D2.GisRenderable2D; import nl.tudelft.simulation.dsol.animation.D2.Renderable2DInterface; import nl.tudelft.simulation.dsol.experiment.Replication; import nl.tudelft.simulation.dsol.simulators.SimulatorInterface; import nl.tudelft.simulation.dsol.swing.animation.D2.AnimationPanel; import nl.tudelft.simulation.dsol.swing.animation.D2.GridPanel; import nl.tudelft.simulation.dsol.swing.animation.D2.mouse.InputListener; import nl.tudelft.simulation.language.DSOLException; import nl.tudelft.simulation.language.d3.DirectedPoint; /** * Animation panel with various controls. *

* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
* BSD-style license. See OpenTrafficSim License. *

* $LastChangedDate: 2018-10-16 12:57:02 +0200 (Tue, 16 Oct 2018) $, @version $Revision: 4703 $, by $Author: wjschakel $, * initial version Jun 18, 2015
* @author Alexander Verbraeck * @author Peter Knoppers */ public class OTSAnimationPanel extends OTSSimulationPanel implements ActionListener, WindowListener, EventListenerInterface { /** */ private static final long serialVersionUID = 20150617L; /** The animation panel on tab position 0. */ private final AutoAnimationPanel animationPanel; /** Border panel in which the animation is shown. */ private final JPanel borderPanel; /** Toggle panel with which animation features can be shown/hidden. */ private final JPanel togglePanel; /** Demo panel. */ private JPanel demoPanel = null; /** Map of toggle names to toggle animation classes. */ private Map> toggleLocatableMap = new LinkedHashMap<>(); /** Set of animation classes to toggle buttons. */ private Map, JToggleButton> toggleButtons = new LinkedHashMap<>(); /** Set of GIS layer names to toggle GIS layers . */ private Map toggleGISMap = new LinkedHashMap<>(); /** Set of GIS layer names to toggle buttons. */ private Map toggleGISButtons = new LinkedHashMap<>(); /** The switchableGTUColorer used to color the GTUs. */ private GTUColorer gtuColorer = null; /** The ColorControlPanel that allows the user to operate the SwitchableGTUColorer. */ private ColorControlPanel colorControlPanel = null; /** The coordinates of the cursor. */ private final JLabel coordinateField; /** The GTU count field. */ private final JLabel gtuCountField; /** The GTU count. */ private int gtuCount = 0; /** The animation buttons. */ private final ArrayList buttons = new ArrayList<>(); /** The formatter for the world coordinates. */ private static final NumberFormat FORMATTER = NumberFormat.getInstance(); /** Has the window close handler been registered? */ @SuppressWarnings("checkstyle:visibilitymodifier") protected boolean closeHandlerRegistered = false; /** Indicate the window has been closed and the timer thread can stop. */ @SuppressWarnings("checkstyle:visibilitymodifier") protected boolean windowExited = false; /** Id of object to auto pan to. */ private String autoPanId = null; /** Type of object to auto pan to. */ private OTSSearchPanel.ObjectKind autoPanKind = null; /** Track auto pan object continuously? */ private boolean autoPanTrack = false; /** Track auto on the next paintComponent operation; then copy state from autoPanTrack. */ private boolean autoPanOnNextPaintComponent = false; /** Initialize the formatter. */ static { FORMATTER.setMaximumFractionDigits(3); } /** * Construct a panel that looks like the DSOLPanel for quick building of OTS applications. * @param extent Rectangle2D; bottom left corner, length and width of the area (world) to animate. * @param size Dimension; the size to be used for the animation. * @param simulator OTSAnimator; the simulator or animator of the model. * @param otsModel OTSModelInterface; the builder and rebuilder of the simulation, based on properties. * @param gtuColorer GTUColorer; the colorer to use for the GTUs. * @param network OTSNetwork; network * @throws RemoteException when notification of the animation panel fails * @throws DSOLException when simulator does not implement AnimatorInterface */ public OTSAnimationPanel(final Rectangle2D extent, final Dimension size, final OTSAnimator simulator, final OTSModelInterface otsModel, final GTUColorer gtuColorer, final OTSNetwork network) throws RemoteException, DSOLException { super(simulator, otsModel); // Add the animation panel as a tab. this.animationPanel = new AutoAnimationPanel(extent, size, simulator, network); this.animationPanel.showGrid(false); this.borderPanel = new JPanel(new BorderLayout()); this.borderPanel.add(this.animationPanel, BorderLayout.CENTER); getTabbedPane().addTab(0, "animation", this.borderPanel); getTabbedPane().setSelectedIndex(0); // Show the animation panel as the default tab // Include the GTU colorer control panel NORTH of the animation. this.gtuColorer = gtuColorer; this.colorControlPanel = new ColorControlPanel(this.gtuColorer); JPanel buttonPanel = new JPanel(); buttonPanel.setLayout(new BoxLayout(buttonPanel, BoxLayout.X_AXIS)); this.borderPanel.add(buttonPanel, BorderLayout.NORTH); buttonPanel.add(this.colorControlPanel); // Include the TogglePanel WEST of the animation. this.togglePanel = new JPanel(); this.togglePanel.setLayout(new BoxLayout(this.togglePanel, BoxLayout.Y_AXIS)); this.borderPanel.add(this.togglePanel, BorderLayout.WEST); // add the buttons for home, zoom all, grid, and mouse coordinates buttonPanel.add(new JLabel(" ")); buttonPanel.add(makeButton("allButton", "/Expand.png", "ZoomAll", "Zoom whole network", true)); buttonPanel.add(makeButton("homeButton", "/Home.png", "Home", "Zoom to original extent", true)); buttonPanel.add(makeButton("gridButton", "/Grid.png", "Grid", "Toggle grid on/off", true)); buttonPanel.add(new JLabel(" ")); // add info labels next to buttons JPanel infoTextPanel = new JPanel(); buttonPanel.add(infoTextPanel); infoTextPanel.setMinimumSize(new Dimension(250, 20)); infoTextPanel.setPreferredSize(new Dimension(250, 20)); infoTextPanel.setLayout(new BoxLayout(infoTextPanel, BoxLayout.Y_AXIS)); this.coordinateField = new JLabel("Mouse: "); this.coordinateField.setMinimumSize(new Dimension(250, 10)); this.coordinateField.setPreferredSize(new Dimension(250, 10)); infoTextPanel.add(this.coordinateField); // gtu fields JPanel gtuPanel = new JPanel(); gtuPanel.setAlignmentX(0.0f); gtuPanel.setLayout(new BoxLayout(gtuPanel, BoxLayout.X_AXIS)); gtuPanel.setMinimumSize(new Dimension(250, 10)); gtuPanel.setPreferredSize(new Dimension(250, 10)); infoTextPanel.add(gtuPanel); if (null != network) { network.addListener(this, Network.GTU_ADD_EVENT); network.addListener(this, Network.GTU_REMOVE_EVENT); } // gtu counter this.gtuCountField = new JLabel("0 GTU's"); this.gtuCount = null == network ? 0 : network.getGTUs().size(); gtuPanel.add(this.gtuCountField); setGtuCountText(); // Tell the animation to build the list of animation objects. this.animationPanel.notify(new TimedEvent(Replication.START_REPLICATION_EVENT, simulator.getSourceId(), null, getSimulator().getSimulatorTime())); // switch off the X and Y coordinates in a tooltip. this.animationPanel.setShowToolTip(false); // run the update task for the mouse coordinate panel new UpdateTimer().start(); // make sure the thread gets killed when the window closes. installWindowCloseHandler(); } /** * Change auto pan target. * @param newAutoPanId String; id of object to track (or * @param newAutoPanKind String; kind of object to track * @param newAutoPanTrack boolean; if true; tracking is continuously; if false; tracking is once */ public void setAutoPan(final String newAutoPanId, final OTSSearchPanel.ObjectKind newAutoPanKind, final boolean newAutoPanTrack) { this.autoPanId = newAutoPanId; this.autoPanKind = newAutoPanKind; this.autoPanTrack = newAutoPanTrack; this.autoPanOnNextPaintComponent = true; // System.out.println("AutoPan id=" + newAutoPanId + ", kind=" + newAutoPanKind + ", track=" + newAutoPanTrack); if (null != this.autoPanId && this.autoPanId.length() > 0 && null != this.autoPanKind) { OTSAnimationPanel.this.animationPanel.repaint(); } } /** * Create a button. * @param name String; name of the button * @param iconPath String; path to the resource * @param actionCommand String; the action command * @param toolTipText String; the hint to show when the mouse hovers over the button * @param enabled boolean; true if the new button must initially be enable; false if it must initially be disabled * @return JButton */ private JButton makeButton(final String name, final String iconPath, final String actionCommand, final String toolTipText, final boolean enabled) { // JButton result = new JButton(new ImageIcon(this.getClass().getResource(iconPath))); JButton result = new JButton(OTSControlPanel.loadIcon(iconPath)); result.setPreferredSize(new Dimension(34, 32)); result.setName(name); result.setEnabled(enabled); result.setActionCommand(actionCommand); result.setToolTipText(toolTipText); result.addActionListener(this); this.buttons.add(result); return result; } /** * Add a button for toggling an animatable class on or off. Button icons for which 'idButton' is true will be placed to the * right of the previous button, which should be the corresponding button without the id. An example is an icon for * showing/hiding the class 'Lane' followed by the button to show/hide the Lane ids. * @param name String; the name of the button * @param locatableClass Class<? extends Locatable>; the class for which the button holds (e.g., GTU.class) * @param iconPath String; the path to the 24x24 icon to display * @param toolTipText String; the tool tip text to show when hovering over the button * @param initiallyVisible boolean; whether the class is initially shown or not * @param idButton boolean; id button that needs to be placed next to the previous button */ public final void addToggleAnimationButtonIcon(final String name, final Class locatableClass, final String iconPath, final String toolTipText, final boolean initiallyVisible, final boolean idButton) { JToggleButton button; Icon icon = OTSControlPanel.loadIcon(iconPath); Icon unIcon = OTSControlPanel.loadGrayscaleIcon(iconPath); button = new JCheckBox(); button.setSelectedIcon(icon); button.setIcon(unIcon); button.setPreferredSize(new Dimension(32, 28)); button.setName(name); button.setEnabled(true); button.setSelected(initiallyVisible); button.setActionCommand(name); button.setToolTipText(toolTipText); button.addActionListener(this); // place an Id button to the right of the corresponding content button if (idButton && this.togglePanel.getComponentCount() > 0) { JPanel lastToggleBox = (JPanel) this.togglePanel.getComponent(this.togglePanel.getComponentCount() - 1); lastToggleBox.add(button); } else { JPanel toggleBox = new JPanel(); toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS)); toggleBox.add(button); this.togglePanel.add(toggleBox); toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT); } if (initiallyVisible) { this.animationPanel.showClass(locatableClass); } else { this.animationPanel.hideClass(locatableClass); } this.toggleLocatableMap.put(name, locatableClass); this.toggleButtons.put(locatableClass, button); } /** * Add a button for toggling an animatable class on or off. * @param name String; the name of the button * @param locatableClass Class<? extends Locatable>; the class for which the button holds (e.g., GTU.class) * @param toolTipText String; the tool tip text to show when hovering over the button * @param initiallyVisible boolean; whether the class is initially shown or not */ public final void addToggleAnimationButtonText(final String name, final Class locatableClass, final String toolTipText, final boolean initiallyVisible) { JToggleButton button; button = new JCheckBox(name); button.setName(name); button.setEnabled(true); button.setSelected(initiallyVisible); button.setActionCommand(name); button.setToolTipText(toolTipText); button.addActionListener(this); JPanel toggleBox = new JPanel(); toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS)); toggleBox.add(button); this.togglePanel.add(toggleBox); toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT); if (initiallyVisible) { this.animationPanel.showClass(locatableClass); } else { this.animationPanel.hideClass(locatableClass); } this.toggleLocatableMap.put(name, locatableClass); this.toggleButtons.put(locatableClass, button); } /** * Add a text to explain animatable classes. * @param text String; the text to show */ public final void addToggleText(final String text) { JPanel textBox = new JPanel(); textBox.setLayout(new BoxLayout(textBox, BoxLayout.X_AXIS)); textBox.add(new JLabel(text)); this.togglePanel.add(textBox); textBox.setAlignmentX(Component.LEFT_ALIGNMENT); } /** * Add buttons for toggling all GIS layers on or off. * @param header String; the name of the group of layers * @param gisMap GisRenderable2D; the GIS map for which the toggles have to be added * @param toolTipText String; the tool tip text to show when hovering over the button */ public final void addAllToggleGISButtonText(final String header, final GisRenderable2D gisMap, final String toolTipText) { addToggleText(" "); addToggleText(header); try { for (String layerName : gisMap.getMap().getLayerMap().keySet()) { addToggleGISButtonText(layerName, layerName, gisMap, toolTipText); } } catch (RemoteException exception) { exception.printStackTrace(); } } /** * Add a button to toggle a GIS Layer on or off. * @param layerName String; the name of the layer * @param displayName String; the name to display next to the tick box * @param gisMap GisRenderable2D; the map * @param toolTipText String; the tool tip text */ public final void addToggleGISButtonText(final String layerName, final String displayName, final GisRenderable2D gisMap, final String toolTipText) { JToggleButton button; button = new JCheckBox(displayName); button.setName(layerName); button.setEnabled(true); button.setSelected(true); button.setActionCommand(layerName); button.setToolTipText(toolTipText); button.addActionListener(this); JPanel toggleBox = new JPanel(); toggleBox.setLayout(new BoxLayout(toggleBox, BoxLayout.X_AXIS)); toggleBox.add(button); this.togglePanel.add(toggleBox); toggleBox.setAlignmentX(Component.LEFT_ALIGNMENT); this.toggleGISMap.put(layerName, gisMap.getMap()); this.toggleGISButtons.put(layerName, button); } /** * Set a GIS layer to be shown in the animation to true. * @param layerName String; the name of the GIS-layer that has to be shown. */ public final void showGISLayer(final String layerName) { MapInterface gisMap = this.toggleGISMap.get(layerName); if (gisMap != null) { try { gisMap.showLayer(layerName); this.toggleGISButtons.get(layerName).setSelected(true); this.animationPanel.repaint(); } catch (RemoteException exception) { exception.printStackTrace(); } } } /** * Set a GIS layer to be hidden in the animation to true. * @param layerName String; the name of the GIS-layer that has to be hidden. */ public final void hideGISLayer(final String layerName) { MapInterface gisMap = this.toggleGISMap.get(layerName); if (gisMap != null) { try { gisMap.hideLayer(layerName); this.toggleGISButtons.get(layerName).setSelected(false); this.animationPanel.repaint(); } catch (RemoteException exception) { exception.printStackTrace(); } } } /** * Toggle a GIS layer to be displayed in the animation to its reverse value. * @param layerName String; the name of the GIS-layer that has to be turned off or vice versa. */ public final void toggleGISLayer(final String layerName) { MapInterface gisMap = this.toggleGISMap.get(layerName); if (gisMap != null) { try { if (gisMap.getVisibleLayers().contains(gisMap.getLayerMap().get(layerName))) { gisMap.hideLayer(layerName); this.toggleGISButtons.get(layerName).setSelected(false); } else { gisMap.showLayer(layerName); this.toggleGISButtons.get(layerName).setSelected(true); } this.animationPanel.repaint(); } catch (RemoteException exception) { exception.printStackTrace(); } } } /** {@inheritDoc} */ @Override public final void actionPerformed(final ActionEvent actionEvent) { String actionCommand = actionEvent.getActionCommand(); // System.out.println("Action command is " + actionCommand); try { if (actionCommand.equals("Home")) { this.animationPanel.home(); } if (actionCommand.equals("ZoomAll")) { this.animationPanel.zoomAll(); } if (actionCommand.equals("Grid")) { this.animationPanel.showGrid(!this.animationPanel.isShowGrid()); } if (this.toggleLocatableMap.containsKey(actionCommand)) { Class locatableClass = this.toggleLocatableMap.get(actionCommand); this.animationPanel.toggleClass(locatableClass); this.togglePanel.repaint(); } if (this.toggleGISMap.containsKey(actionCommand)) { this.toggleGISLayer(actionCommand); this.togglePanel.repaint(); } } catch (Exception exception) { exception.printStackTrace(); } } /** * Easy access to the AnimationPanel. * @return AnimationPanel */ public final AnimationPanel getAnimationPanel() { return this.animationPanel; } /** * Creates a demo panel within the animation area. * @param position String; any string from BorderLayout indicating the position of the demo panel, except CENTER. * @throws IllegalStateException if the panel was already created */ public void createDemoPanel(final DemoPanelPosition position) { Throw.when(this.demoPanel != null, IllegalStateException.class, "Attempt to create demo panel, but it's already created"); Throw.whenNull(position, "Position may not be null."); Container parent = this.animationPanel.getParent(); parent.remove(this.animationPanel); JPanel splitPanel = new JPanel(new BorderLayout()); parent.add(splitPanel); splitPanel.add(this.animationPanel, BorderLayout.CENTER); this.demoPanel = new JPanel(); this.demoPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); splitPanel.add(this.demoPanel, position.getBorderLayoutPosition()); } /** * Return a panel for on-screen demo controls. The panel is create on first call. * @return JPanel; panel */ public JPanel getDemoPanel() { if (this.demoPanel == null) { createDemoPanel(DemoPanelPosition.RIGHT); // this.demoPanel = new JPanel(); // this.demoPanel.setLayout(new BoxLayout(this.demoPanel, BoxLayout.Y_AXIS)); // this.demoPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); // this.demoPanel.setPreferredSize(new Dimension(300, 300)); // getAnimationPanel().getParent().add(this.demoPanel, BorderLayout.EAST); this.demoPanel.addContainerListener(new ContainerListener() { @Override public void componentAdded(final ContainerEvent e) { try { // setAppearance(getAppearance()); } catch (NullPointerException exception) { // } } @Override public void componentRemoved(final ContainerEvent e) { // } }); } return this.demoPanel; } /** * Update the checkmark related to a programmatically changed animation state. * @param locatableClass Class<? extends Locatable>; class to show the checkmark for */ public final void updateAnimationClassCheckBox(final Class locatableClass) { JToggleButton button = this.toggleButtons.get(locatableClass); if (button == null) { return; } button.setSelected(getAnimationPanel().isShowClass(locatableClass)); } /** * Display the latest world coordinate based on the mouse position on the screen. */ protected final void updateWorldCoordinate() { String worldPoint = "(x=" + FORMATTER.format(this.animationPanel.getWorldCoordinate().getX()) + " ; y=" + FORMATTER .format(this.animationPanel.getWorldCoordinate().getY()) + ")"; this.coordinateField.setText("Mouse: " + worldPoint); int requiredWidth = this.coordinateField.getGraphics().getFontMetrics().stringWidth(this.coordinateField.getText()); if (this.coordinateField.getPreferredSize().width < requiredWidth) { Dimension requiredSize = new Dimension(requiredWidth, this.coordinateField.getPreferredSize().height); this.coordinateField.setPreferredSize(requiredSize); this.coordinateField.setMinimumSize(requiredSize); Container parent = this.coordinateField.getParent(); parent.setPreferredSize(requiredSize); parent.setMinimumSize(requiredSize); // System.out.println("Increased minimum width to " + requiredSize.width); parent.revalidate(); } this.coordinateField.repaint(); } /** * Access the GTUColorer of this animation ControlPanel. * @return GTUColorer the colorer used. If it is a SwitchableGTUColorer, the wrapper with the list will be returned, not the * actual colorer in use. */ public final GTUColorer getGTUColorer() { return this.gtuColorer; } /** * Access the ColorControlPanel of this ControlPanel. If the simulator is not a SimpleAnimator, no ColorControlPanel was * constructed and this method will return null. * @return ColorControlPanel */ public final ColorControlPanel getColorControlPanel() { return this.colorControlPanel; } /** * Install a handler for the window closed event that stops the simulator (if it is running). */ public final void installWindowCloseHandler() { if (this.closeHandlerRegistered) { return; } // make sure the root frame gets disposed of when the closing X icon is pressed. new DisposeOnCloseThread(this).start(); } /** Install the dispose on close when the OTSControlPanel is registered as part of a frame. */ protected class DisposeOnCloseThread extends Thread { /** The current container. */ private OTSAnimationPanel panel; /** * @param panel OTSAnimationPanel; the OTSControlpanel container. */ public DisposeOnCloseThread(final OTSAnimationPanel panel) { this.panel = panel; } /** {@inheritDoc} */ @Override public final void run() { Container root = this.panel; while (!(root instanceof JFrame)) { try { Thread.sleep(10); } catch (InterruptedException exception) { // nothing to do } // Search towards the root of the Swing components until we find a JFrame root = this.panel; while (null != root.getParent() && !(root instanceof JFrame)) { root = root.getParent(); } } JFrame frame = (JFrame) root; frame.addWindowListener(this.panel); this.panel.closeHandlerRegistered = true; } /** {@inheritDoc} */ @Override public final String toString() { return "DisposeOnCloseThread of OTSAnimationPanel [panel=" + this.panel + "]"; } } /** {@inheritDoc} */ @Override public void windowOpened(final WindowEvent e) { // No action } /** {@inheritDoc} */ @Override public final void windowClosing(final WindowEvent e) { // No action } /** {@inheritDoc} */ @Override public final void windowClosed(final WindowEvent e) { this.windowExited = true; } /** {@inheritDoc} */ @Override public final void windowIconified(final WindowEvent e) { // No action } /** {@inheritDoc} */ @Override public final void windowDeiconified(final WindowEvent e) { // No action } /** {@inheritDoc} */ @Override public final void windowActivated(final WindowEvent e) { // No action } /** {@inheritDoc} */ @Override public final void windowDeactivated(final WindowEvent e) { // No action } /** {@inheritDoc} */ @Override public void notify(final EventInterface event) throws RemoteException { if (event.getType().equals(Network.GTU_ADD_EVENT)) { this.gtuCount++; setGtuCountText(); } else if (event.getType().equals(Network.GTU_REMOVE_EVENT)) { this.gtuCount--; setGtuCountText(); } } /** * Updates the text of the GTU counter. */ private void setGtuCountText() { this.gtuCountField.setText(this.gtuCount + " GTU's"); } /** * UpdateTimer class to update the coordinate on the screen. */ protected class UpdateTimer extends Thread { /** {@inheritDoc} */ @Override public final void run() { while (!OTSAnimationPanel.this.windowExited) { if (OTSAnimationPanel.this.isShowing()) { OTSAnimationPanel.this.updateWorldCoordinate(); } try { Thread.sleep(50); // 20 times per second } catch (InterruptedException exception) { // do nothing } } } /** {@inheritDoc} */ @Override public final String toString() { return "UpdateTimer thread for OTSAnimationPanel"; } } /** * Animation panel that adds autopan functionality. *

* Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. *
* BSD-style license. See OpenTrafficSim License. *

* @version $Revision: 4703 $, $LastChangedDate: 2018-10-16 12:57:02 +0200 (Tue, 16 Oct 2018) $, by $Author: wjschakel $, * initial version 30 apr. 2018
* @author Alexander Verbraeck * @author Peter Knoppers * @author Wouter Schakel */ private class AutoAnimationPanel extends AnimationPanel { /** */ private static final long serialVersionUID = 20180430L; /** Network. */ private final OTSNetwork network; /** Last GTU that was followed. */ private GTU lastGtu; /** * Constructor. * @param extent Rectangle2D; home extent * @param size Dimension; size * @param simulator SimulatorInterface<?, ?, ?>; simulator * @param network OTSNetwork; network * @throws RemoteException on remote animation error * @throws DSOLException when simulator does not implement AnimatorInterface */ AutoAnimationPanel(final Rectangle2D extent, final Dimension size, final SimulatorInterface simulator, final OTSNetwork network) throws RemoteException, DSOLException { super(extent, size, simulator); this.network = network; MouseListener[] listeners = getMouseListeners(); for (MouseListener listener : listeners) { removeMouseListener(listener); } this.addMouseListener(new MouseAdapter() { /** {@inheritDoc} */ @SuppressWarnings("synthetic-access") @Override public void mouseClicked(final MouseEvent e) { if (e.isControlDown()) { GTU gtu = getSelectedGTU(e.getPoint()); if (gtu != null) { getOtsControlPanel().getOtsSearchPanel().selectAndTrackObject("GTU", gtu.getId(), true); e.consume(); // sadly doesn't work to prevent a pop up } } e.consume(); } }); for (MouseListener listener : listeners) { addMouseListener(listener); } // mouse wheel MouseWheelListener[] wheelListeners = getMouseWheelListeners(); for (MouseWheelListener wheelListener : wheelListeners) { removeMouseWheelListener(wheelListener); } this.addMouseWheelListener(new InputListener(this) { /** {@inheritDoc} */ @Override public void mouseWheelMoved(final MouseWheelEvent e) { if (e.isShiftDown()) { int amount = e.getUnitsToScroll(); if (amount > 0) { zoomVertical(GridPanel.ZOOMFACTOR, e.getX(), e.getY()); } else { zoomVertical(1.0 / GridPanel.ZOOMFACTOR, e.getX(), e.getY()); } } else if (e.isAltDown()) { int amount = e.getUnitsToScroll(); if (amount > 0) { zoomHorizontal(GridPanel.ZOOMFACTOR, e.getX(), e.getY()); } else { zoomHorizontal(1.0 / GridPanel.ZOOMFACTOR, e.getX(), e.getY()); } } else { super.mouseWheelMoved(e); } } }); } /** * Zoom vertical. * @param factor double; The zoom factor * @param mouseX int; x-position of the mouse around which we zoom * @param mouseY int; y-position of the mouse around which we zoom */ final synchronized void zoomVertical(final double factor, final int mouseX, final int mouseY) { // TODO allow vertical and horizontal zoom when DSOL supports it in getScreenCoordinates() and getWorldCoordinates() this.zoom(factor, mouseX, mouseY); // double minX = this.extent.getMinX(); // Point2D mwc = Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(mouseX, mouseY), this.extent, // this.getSize()); // double minY = mwc.getY() - (mwc.getY() - this.extent.getMinY()) * factor; // double w = this.extent.getWidth(); // double h = this.extent.getHeight() * factor; // // this.extent.setRect(minX, minY, w, h); // this.repaint(); } /** * Zoom horizontal. * @param factor double; The zoom factor * @param mouseX int; x-position of the mouse around which we zoom * @param mouseY int; y-position of the mouse around which we zoom */ final synchronized void zoomHorizontal(final double factor, final int mouseX, final int mouseY) { this.zoom(factor, mouseX, mouseY); // double minY = this.extent.getMinY(); // Point2D mwc = Renderable2DInterface.Util.getWorldCoordinates(new Point2D.Double(mouseX, mouseY), this.extent, // this.getSize()); // double minX = mwc.getX() - (mwc.getX() - this.extent.getMinX()) * factor; // double w = this.extent.getWidth() * factor; // double h = this.extent.getHeight(); // // this.extent.setRect(minX, minY, w, h); // this.repaint(); } /** * returns the list of selected objects at a certain mousePoint. * @param mousePoint Point2D; the mousePoint * @return the selected objects */ @SuppressWarnings("synthetic-access") protected GTU getSelectedGTU(final Point2D mousePoint) { List targets = new ArrayList<>(); Point2D point = Renderable2DInterface.Util.getWorldCoordinates(mousePoint, OTSAnimationPanel.this.animationPanel .getExtent(), OTSAnimationPanel.this.animationPanel.getSize()); for (Renderable2DInterface renderable : OTSAnimationPanel.this.animationPanel.getElements()) { if (OTSAnimationPanel.this.animationPanel.isShowElement(renderable) && renderable.contains(point, OTSAnimationPanel.this.animationPanel.getExtent(), OTSAnimationPanel.this.animationPanel.getSize())) { if (renderable.getSource() instanceof GTU) { targets.add((GTU) renderable.getSource()); } } } if (targets.size() == 1) { return targets.get(0); } return null; } /** {@inheritDoc} */ @SuppressWarnings("synthetic-access") @Override public void paintComponent(final Graphics g) { final OTSSearchPanel.ObjectKind panKind = OTSAnimationPanel.this.autoPanKind; final String panId = OTSAnimationPanel.this.autoPanId; final boolean doPan = OTSAnimationPanel.this.autoPanOnNextPaintComponent; OTSAnimationPanel.this.autoPanOnNextPaintComponent = OTSAnimationPanel.this.autoPanTrack; if (doPan && panKind != null && panId != null) { Locatable locatable = panKind.searchNetwork(this.network, panId); if (null != locatable) { try { DirectedPoint point = locatable.getLocation(); if (point != null) // Center extent around point { double w = this.extent.getWidth(); double h = this.extent.getHeight(); this.extent = new Rectangle2D.Double(point.getX() - w / 2, point.getY() - h / 2, w, h); } } catch (RemoteException exception) { getSimulator().getLogger().always().warn( "Caught RemoteException trying to locate {} with id {} in network {}.", panKind, panId, network .getId()); return; } } } super.paintComponent(g); } /** {@inheritDoc} */ @Override public String toString() { return "AutoAnimationPanel [network=" + this.network + ", lastGtu=" + this.lastGtu + "]"; } } /** * Enum for demo panel position. Each value contains a field representing the position correlating to the * {@code BorderLayout} class. */ public enum DemoPanelPosition { /** Top. */ TOP("First"), /** Bottom. */ BOTTOM("Last"), /** Left. */ LEFT("Before"), /** Right. */ RIGHT("After"); /** Value used in {@code BorderLayout}. */ private final String direction; /** * @param direction String; value used in {@code BorderLayout} */ DemoPanelPosition(final String direction) { this.direction = direction; } /** * @return direction String; value used in {@code BorderLayout} */ public String getBorderLayoutPosition() { return this.direction; } } }