package org.lemsml.jlems.viz.datadisplay; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.Image; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.WindowEvent; import java.io.File; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JToolBar; import javax.swing.KeyStroke; import javax.swing.SwingConstants; import org.lemsml.jlems.core.expression.ParseError; import org.lemsml.jlems.core.logging.E; import org.lemsml.jlems.core.run.ConnectionError; import org.lemsml.jlems.core.run.RunConfig; import org.lemsml.jlems.core.run.RuntimeError; import org.lemsml.jlems.core.sim.ContentError; import org.lemsml.jlems.core.sim.LEMSException; import org.lemsml.jlems.core.sim.Sim; public abstract class ControlPanel implements ActionListener { JFrame frame; JPanel pmain = new JPanel(); JLabel statusLabel = new JLabel(""); JMenuItem menuItemReloadAndRun; JMenuItem menuItemDump; JButton buttonReloadAndRun; File workingFile; File prevWorkingFile; Sim curentSimulation; Map<Integer, RunConfig> runConfigs = new HashMap<Integer, RunConfig>(); Map<String, Rectangle> viewerRects = new HashMap<String, Rectangle>(); Dimension windowDimension = new Dimension(200, 120); ExecutorService multiThreadService = Executors.newFixedThreadPool(1); boolean showGui; private static final String OPEN = "Open LEMS file"; private static final String EXIT = "Exit"; public final static String DEFAULT_NAME = "jLEMS"; public ControlPanel(String name, boolean showGui) { this.showGui = showGui; if (showGui) { frame = new JFrame(name+" Control Panel"); frame.setPreferredSize(windowDimension); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container ctr = frame.getContentPane(); // Set up the menu items JMenuBar jmb = new JMenuBar(); JMenu jm = new JMenu("File"); String[] actions = {OPEN, EXIT}; addToMenu(actions, jm); jmb.add(jm); JMenu jvm = new JMenu("View"); String[] viewActions = {"Bring to Front"}; addToMenu(viewActions, jvm); jmb.add(jvm); JMenu jmsimulation = new JMenu("Simulation"); menuItemReloadAndRun = addToMenuWithShortcut("Reload and Run", jmsimulation, KeyEvent.VK_F6, 0 ); menuItemDump = addToMenuWithShortcut("Dump simulation info", jmsimulation, KeyEvent.VK_F7, 0 ); jmb.add(jmsimulation); frame.setJMenuBar(jmb); // Set up the status bar at the bottom of the window statusLabel.setHorizontalAlignment(SwingConstants.LEFT); statusLabel.setVerticalAlignment(SwingConstants.TOP); statusLabel.setFont(new Font(statusLabel.getFont().getFontName(), 10, 10)); statusLabel.setHorizontalAlignment(SwingConstants.LEFT); JPanel statusPanel = new JPanel(); statusPanel.setBorder(BorderFactory.createLineBorder(Color.GRAY)); statusPanel.setLayout(new BoxLayout(statusPanel, BoxLayout.X_AXIS)); statusPanel.add(statusLabel); ctr.add(pmain, BorderLayout.SOUTH); ctr.add(statusPanel, BorderLayout.SOUTH); createToolbar(); //Initially the simulation buttons are disabled as we don't have a workingFile yet so have nothing to "reload and run" setRunSimulationEnabled(false); show(); } } public void setTitle(String title) { frame.setTitle(title); } /* * The initialise method calls importFile (has to have been defined in a child class) and registers the simulation object * so that it can be manipulated from the Control Panel. * Note it is expected that importFile has called sim.build() before this method */ public Sim initialise(File file) throws LEMSException { E.info("Loading LEMS file from: "+file.getAbsolutePath()); Sim sim = importFile(file); registerSimulation(sim, file); return sim; } /** * The control panel handles one simulation at a time, this should be "registered" using this method. * Load all the windows, one per display * @param sim - simulation object * @param simFile - new file to load (can be null) * @throws ConnectionError * @throws ContentError * @throws RuntimeError * @throws ParseError */ protected void registerSimulation(Sim sim, File simFile) { curentSimulation = sim; if(sim == null) return; // if we're reloading the same file, we would expect simFile to be null if(simFile != null) { setNewWorkingFile(simFile); } loadRunConfigsFromSimulation(); if (showGui) positionViewers(); } /* * importFile should handle creating a built (Sim.build() must be called) sim instance. * This will be called each time the Control Panel tries to open a new file or reload the existing simulation * It is called in the initialise method. */ protected abstract Sim importFile(File sourceFile) throws LEMSException; /** * The toolbar for the control panel - open, layer and run * The buttons have matching menu items performing the same actions */ protected final void createToolbar() { int iconSize = 20; JToolBar toolbar = new JToolBar(); toolbar.setFloatable(false); toolbar.setRollover(true); toolbar.setPreferredSize(new Dimension(0,iconSize+20)); URL imgURL = getClass().getResource("/org/lemsml/jlems/viz/datadisplay/open.png"); ImageIcon iconOpen = new ImageIcon(imgURL); Image img = iconOpen.getImage().getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH); iconOpen.setImage(img); JButton buttonOpen = new JButton(iconOpen); buttonOpen.setSize(iconSize,iconSize); buttonOpen.setToolTipText(OPEN); buttonOpen.setActionCommand(OPEN); buttonOpen.addActionListener(this); toolbar.add(buttonOpen); imgURL = getClass().getResource("/org/lemsml/jlems/viz/datadisplay/layer.png"); ImageIcon iconBringToFront = new ImageIcon(imgURL); img = iconBringToFront.getImage().getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH); iconBringToFront.setImage(img); JButton buttonBringToFront = new JButton(iconBringToFront); buttonBringToFront.setSize(iconSize,iconSize); buttonBringToFront.setToolTipText("Bring Windows to Front"); buttonBringToFront.setActionCommand("bring to front"); buttonBringToFront.addActionListener(this); toolbar.add(buttonBringToFront); imgURL = getClass().getResource("/org/lemsml/jlems/viz/datadisplay/run.png"); ImageIcon iconReloadAndRun = new ImageIcon(imgURL); img = iconReloadAndRun.getImage().getScaledInstance(iconSize, iconSize, Image.SCALE_SMOOTH); iconReloadAndRun.setImage(img); buttonReloadAndRun = new JButton(iconReloadAndRun); //initially this is disabled as we don't have anything to "reload and run" buttonReloadAndRun.setEnabled(false); buttonReloadAndRun.setSize(iconSize,iconSize); buttonReloadAndRun.setToolTipText("Reload and Run"); buttonReloadAndRun.setActionCommand("reload and run"); buttonReloadAndRun.addActionListener(this); toolbar.add(buttonReloadAndRun); frame.add(toolbar, BorderLayout.NORTH); } /** * load the runConfigs from the simulation into the runConfigs map. * The runConfigs map is indexed for easy referencing */ protected void loadRunConfigsFromSimulation() { int index = -1; for(RunConfig conf : curentSimulation.getRunConfigs()) { runConfigs.put(index++, conf); } } /** * Lay out the StandaloneViewer windows in a */ protected void positionViewers() { int borderWidth = 10; int layerWidth = 30; int start_cursor_x = (int)windowDimension.getWidth() + borderWidth; int start_cursor_y = 0; frame.setLocation(0, 0); int screenWidth = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().width; int screenHeight = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().height; int cursor_x = start_cursor_x; int cursor_y = start_cursor_y; for(String key : curentSimulation.getDvHM().keySet()) { if(curentSimulation.getDvHM().get(key) instanceof StandaloneViewer) { StandaloneViewer sViewer = ((StandaloneViewer)curentSimulation.getDvHM().get(key)); // If there's a viewer Rect with this key (we're reloading an existing file) let's use it // instead of putting it back in the default position if(viewerRects.containsKey(key)) { sViewer.setViewerRectangle(viewerRects.get(key)); // use show() here to repaint the window and bring it to the foreground // to get a nice cascading effect of the windows sViewer.showWithoutPack(); continue; } // sViewer.setViewerRectangle(new Rectangle(cursor_x, cursor_y, (int)sViewer.getDimensions().getWidth(), (int)sViewer.getDimensions().getHeight())); // use show() here to repaint the window and bring it to the foreground // to get a nice cascading effect of the windows sViewer.show(); cursor_y += sViewer.getDimensions().getHeight() + borderWidth; // first try to place a window below the last one, if that'll go over the bottom edge, // then place it to the right. If to the right goes over the right edge then // begin again with a slight offset if((cursor_y + sViewer.getDimensions().getHeight()) > screenHeight - start_cursor_y) { cursor_y = start_cursor_y; if((cursor_x + sViewer.getDimensions().getWidth()) > screenWidth - start_cursor_x) { start_cursor_y += layerWidth; start_cursor_x += layerWidth; cursor_y = start_cursor_y; cursor_x = start_cursor_x; } else { cursor_x += sViewer.getDimensions().getWidth() + borderWidth; } } } } //give focus to control panel for easy spamming of the F6 key! frame.setVisible(true); } protected final void setRunSimulationEnabled(boolean enabled) { menuItemReloadAndRun.setEnabled(enabled); buttonReloadAndRun.setEnabled(enabled); } protected final void addToMenu(String[] actions, JMenu jm) { for (String s : actions) { JMenuItem jmi = new JMenuItem(s); jmi.setActionCommand(s.toLowerCase()); jmi.addActionListener(this); jm.add(jmi); } } /** * * @param action - The name of the action item * @param jm - the menu for this item to be added to * @param key - int representing the ID of KeyEvent (eg KeyEvent.VK_F6) * @param modifier - int representing the ID of ActionEvent (eg ActionEvent.ALT_MASK , 0 for no modifier) * @return JMenuItem */ protected final JMenuItem addToMenuWithShortcut(String action, JMenu jm, int key, int modifier) { JMenuItem jmi = new JMenuItem(action); jmi.setActionCommand(action.toLowerCase()); jmi.addActionListener(this); jmi.setAccelerator(KeyStroke.getKeyStroke(key, modifier)); jm.add(jmi); return jmi; } public final void show() { frame.pack(); frame.setVisible(true); } /** * When simulation.run() is called from the actionPerformed method below, it holds up the * Java Swing display thread and we don't get the nice animation, so call run() in its own thread here. */ protected void runSimulationInNewThread() { for(final Entry<Integer, RunConfig> conf : runConfigs.entrySet()) { multiThreadService.execute(new Runnable() { @Override public void run() { try { curentSimulation.run(conf.getValue(), false); } catch (Exception ex) { JOptionPane.showMessageDialog(new JFrame(), String.format("Failed to run simulation : %s", ex.getMessage()), "Error", JOptionPane.ERROR_MESSAGE); } } }); } } @Override public void actionPerformed(ActionEvent e) { String sev = e.getActionCommand(); if (sev.equalsIgnoreCase(OPEN)) { //importing new file so reset the windows File newfile = SwingDialogs.getInstance().getFileToRead(); //probably we cancelled the dialog box if(newfile == null) return; clearAll(); try { setNewWorkingFile(newfile); Sim sim = importFile(newfile); registerSimulation(sim, workingFile); } catch (Exception ex) { setPrevWorkingFile(); restoreViewerWindows(); JOptionPane.showMessageDialog(new JFrame(), String.format("Unexpected error while opening and building simulation with message : %s", ex.getMessage()), "Error", JOptionPane.ERROR_MESSAGE); } runSimulationInNewThread(); } else if (sev.equalsIgnoreCase(EXIT)) { frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING)); } else if(sev.equals("bring to front")) { restoreViewerWindows(); } else if (sev.equals("reload and run")) { if(curentSimulation != null) { clearCurrentSimulation(); try { Sim sim = importFile(workingFile); registerSimulation(sim, null); } catch (Exception ex) { //if we failed to load the new sim, bring back our old windows. restoreViewerWindows(); JOptionPane.showMessageDialog(new JFrame(), String.format("Unexpected error while opening and building simulation with message : %s", ex.getMessage()), "Error", JOptionPane.ERROR_MESSAGE); } runSimulationInNewThread(); } } else if (sev.equals("dump simulation info")) { System.out.println("========================================\n\nPrinting simulation information\n State Types:\n\n"); System.out.println(curentSimulation.getTargetBehavior().getSummary(" ", "| ")+"\n\n----------------------------------\n\n State Instances:\n\n"); System.out.println(curentSimulation.getCurrentRootState().getSummary(" ", "| ")+"\n"); System.out.println("\n========================================\n"); } } /* * For each StandaloneViewer window, store the size and location in viewerRects to be remembered * when reloading. Then close the window. */ protected void clearCurrentSimulation() { for(String key : curentSimulation.getDvHM().keySet()) { if(curentSimulation.getDvHM().get(key) instanceof StandaloneViewer) { StandaloneViewer viewer = ((StandaloneViewer)curentSimulation.getDvHM().get(key)); viewerRects.put(key, viewer.getViewerRectangle()); viewer.close(); } } } /* * Bring all the StandaloneViewer windows to the foreground */ protected void restoreViewerWindows() { for(String key : curentSimulation.getDvHM().keySet()) { if(curentSimulation.getDvHM().get(key) instanceof StandaloneViewer) { StandaloneViewer viewer = ((StandaloneViewer)curentSimulation.getDvHM().get(key)); viewer.show(); } } //give focus to control panel frame.setVisible(true); } protected void clearAll() { clearCurrentSimulation(); viewerRects.clear(); runConfigs.clear(); } protected void setNewWorkingFile(File newfile) { if(newfile == null) return; prevWorkingFile = workingFile; workingFile = newfile; if (showGui) { setRunSimulationEnabled(true); statusLabel.setText(workingFile.getName()); } } protected void setPrevWorkingFile() { workingFile = prevWorkingFile; if(prevWorkingFile != null) { setRunSimulationEnabled(true); statusLabel.setText(prevWorkingFile.getName()); } else { setRunSimulationEnabled(false); statusLabel.setText(""); } } }