/* * AP(r) Computer Science GridWorld Case Study: * Copyright(c) 2002-2006 College Entrance Examination Board * (http://www.collegeboard.com). * * This code 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. * * This code 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. * * @author Julie Zelenski * @author Cay Horstmann */ package info.gridworld.gui; import info.gridworld.grid.*; import info.gridworld.world.World; import java.awt.Dimension; import java.awt.Point; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.lang.reflect.Modifier; import java.util.Comparator; import java.util.ResourceBundle; import java.util.Set; import java.util.TreeSet; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.*; /** * The GUIController controls the behavior in a WorldFrame. <br /> * This code is not tested on the AP CS A and AB exams. It contains GUI * implementation details that are not intended to be understood by AP CS * students. */ public class GUIController<T> { public static final int INDEFINITE = 0, FIXED_STEPS = 1, PROMPT_STEPS = 2; private static final int MIN_DELAY_MSECS = 10, MAX_DELAY_MSECS = 1000; private static final int INITIAL_DELAY = MIN_DELAY_MSECS + (MAX_DELAY_MSECS - MIN_DELAY_MSECS) / 2; private Timer timer; private JButton stepButton, runButton, stopButton; private JComponent controlPanel; private GridPanel display; private WorldFrame<T> parentFrame; private int numStepsToRun, numStepsSoFar; private ResourceBundle resources; private DisplayMap displayMap; private boolean running; private Set<Class> occupantClasses; /** * Creates a new controller tied to the specified display and gui * frame. * @param parent the frame for the world window * @param disp the panel that displays the grid * @param displayMap the map for occupant displays * @param res the resource bundle for message display */ public GUIController(WorldFrame<T> parent, GridPanel disp, DisplayMap displayMap, ResourceBundle res) { resources = res; display = disp; parentFrame = parent; this.displayMap = displayMap; makeControls(); occupantClasses = new TreeSet<Class>(new Comparator<Class>() { public int compare(Class a, Class b) { return a.getName().compareTo(b.getName()); } }); World<T> world = parentFrame.getWorld(); Grid<T> gr = world.getGrid(); for (Location loc : gr.getOccupiedLocations()) addOccupant(gr.get(loc)); for (String name : world.getOccupantClasses()) try { occupantClasses.add(Class.forName(name)); } catch (Exception ex) { ex.printStackTrace(); } timer = new Timer(INITIAL_DELAY, new ActionListener() { public void actionPerformed(ActionEvent evt) { step(); } }); display.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent evt) { Grid<T> gr = parentFrame.getWorld().getGrid(); Location loc = display.locationForPoint(evt.getPoint()); if (loc != null && gr.isValid(loc) && !isRunning()) { display.setCurrentLocation(loc); locationClicked(); } } }); stop(); } /** * Advances the world one step. */ public void step() { parentFrame.getWorld().step(); parentFrame.repaint(); if (++numStepsSoFar == numStepsToRun) stop(); Grid<T> gr = parentFrame.getWorld().getGrid(); for (Location loc : gr.getOccupiedLocations()) addOccupant(gr.get(loc)); } private void addOccupant(T occupant) { Class cl = occupant.getClass(); do { if ((cl.getModifiers() & Modifier.ABSTRACT) == 0) occupantClasses.add(cl); cl = cl.getSuperclass(); } while (cl != Object.class); } /** * Starts a timer to repeatedly carry out steps at the speed currently * indicated by the speed slider up Depending on the run option, it will * either carry out steps for some fixed number or indefinitely * until stopped. */ public void run() { display.setToolTipsEnabled(false); // hide tool tips while running parentFrame.setRunMenuItemsEnabled(false); stopButton.setEnabled(true); stepButton.setEnabled(false); runButton.setEnabled(false); numStepsSoFar = 0; timer.start(); running = true; } /** * Stops any existing timer currently carrying out steps. */ public void stop() { display.setToolTipsEnabled(true); parentFrame.setRunMenuItemsEnabled(true); timer.stop(); stopButton.setEnabled(false); runButton.setEnabled(true); stepButton.setEnabled(true); running = false; } public boolean isRunning() { return running; } /** * Builds the panel with the various controls (buttons and * slider). */ private void makeControls() { controlPanel = new JPanel(); stepButton = new JButton(resources.getString("button.gui.step")); runButton = new JButton(resources.getString("button.gui.run")); stopButton = new JButton(resources.getString("button.gui.stop")); controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.X_AXIS)); controlPanel.setBorder(BorderFactory.createEtchedBorder()); Dimension spacer = new Dimension(5, stepButton.getPreferredSize().height + 10); controlPanel.add(Box.createRigidArea(spacer)); controlPanel.add(stepButton); controlPanel.add(Box.createRigidArea(spacer)); controlPanel.add(runButton); controlPanel.add(Box.createRigidArea(spacer)); controlPanel.add(stopButton); runButton.setEnabled(false); stepButton.setEnabled(false); stopButton.setEnabled(false); controlPanel.add(Box.createRigidArea(spacer)); controlPanel.add(new JLabel(resources.getString("slider.gui.slow"))); JSlider speedSlider = new JSlider(MIN_DELAY_MSECS, MAX_DELAY_MSECS, INITIAL_DELAY); speedSlider.setInverted(true); speedSlider.setPreferredSize(new Dimension(100, speedSlider .getPreferredSize().height)); speedSlider.setMaximumSize(speedSlider.getPreferredSize()); // remove control PAGE_UP, PAGE_DOWN from slider--they should be used // for zoom InputMap map = speedSlider.getInputMap(); while (map != null) { map.remove(KeyStroke.getKeyStroke("control PAGE_UP")); map.remove(KeyStroke.getKeyStroke("control PAGE_DOWN")); map = map.getParent(); } controlPanel.add(speedSlider); controlPanel.add(new JLabel(resources.getString("slider.gui.fast"))); controlPanel.add(Box.createRigidArea(new Dimension(5, 0))); stepButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { step(); } }); runButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { run(); } }); stopButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { stop(); } }); speedSlider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent evt) { timer.setDelay(((JSlider) evt.getSource()).getValue()); } }); } /** * Returns the panel containing the controls. * @return the control panel */ public JComponent controlPanel() { return controlPanel; } /** * Callback on mousePressed when editing a grid. */ private void locationClicked() { World<T> world = parentFrame.getWorld(); Location loc = display.getCurrentLocation(); if (loc != null && !world.locationClicked(loc)) editLocation(); parentFrame.repaint(); } /** * Edits the contents of the current location, by displaying the constructor * or method menu. */ public void editLocation() { World<T> world = parentFrame.getWorld(); Location loc = display.getCurrentLocation(); if (loc != null) { T occupant = world.getGrid().get(loc); if (occupant == null) { MenuMaker<T> maker = new MenuMaker<T>(parentFrame, resources, displayMap); JPopupMenu popup = maker.makeConstructorMenu(occupantClasses, loc); Point p = display.pointForLocation(loc); popup.show(display, p.x, p.y); } else { MenuMaker<T> maker = new MenuMaker<T>(parentFrame, resources, displayMap); JPopupMenu popup = maker.makeMethodMenu(occupant, loc); Point p = display.pointForLocation(loc); popup.show(display, p.x, p.y); } } parentFrame.repaint(); } /** * Edits the contents of the current location, by displaying the constructor * or method menu. */ public void deleteLocation() { World<T> world = parentFrame.getWorld(); Location loc = display.getCurrentLocation(); if (loc != null) { world.remove(loc); parentFrame.repaint(); } } }