/* Copyright (C) 2002 J. M. Spivey */ import java.awt.*; import java.awt.event.*; import java.applet.*; import java.text.NumberFormat; import java.net.*; import java.util.*; import java.io.*; /** The main application class. * * This class is responsible for displaying all of the GUI, apart * from the map display. It also responds to button clicks and map * selection events. */ class AutoSnail extends DemoFrame implements SelectionListener, Observer { private Map map = null; // The map, loaded from a text file private MapViewer viewer = new MapViewer(); // A view of the map; we listen for selection events private PathFinder pathfinder = null; private SearchState state; // Thread for the search, if one is in progress private Map.Town from, to; // Source and destination towns, if selected private int selStage = 0; // Number of towns chosen so far; when it reaches 2, we're // ready to search. private int delay = 0; // The control panel (right-hand column) with various text fields // and check boxes. private Panel controls = new Panel(new ColumnLayout(5, 10, 2, ColumnLayout.LEFT)); private Font textFont = new Font("SansSerif", Font.PLAIN, 12); private Font labelFont = new Font("SansSerif", Font.BOLD, 12); private PassiveTextField fromText = new PassiveTextField(20); private PassiveTextField toText = new PassiveTextField(20); private PassiveTextField distText = new PassiveTextField(20); private Checkbox workCheck = new Checkbox("Show working"); private Checkbox heurCheck = new Checkbox("Use heuristic"); private Checkbox delayCheck = new Checkbox("Delay between steps"); // The scroll bar for delay between steps private Scrollbar delaySlider = new Scrollbar(Scrollbar.HORIZONTAL, 500, 100, 0, 1000) { // Make it quite small public Dimension getPreferredSize() { return new Dimension(100, 15); } }; // The buttons at the bottom private Panel buttons = new Panel(new FlowLayout()); /** Make a button, connect it to the listener, and put it in the panel */ private Button makeButton(String text, ActionListener listener) { Button button = new Button(text); button.addActionListener(listener); buttons.add(button); return button; } private Button searchButton = makeButton("Search", new ActionListener() { public void actionPerformed(ActionEvent e) { search(); } }); private Button resetButton = makeButton("Reset", new ActionListener() { public void actionPerformed(ActionEvent e) { reset(); } }); private Button quitButton = makeButton("Quit", new ActionListener() { public void actionPerformed(ActionEvent e) { die(); } }); /** Make a label with specified text and font */ private Label makeLabel(String text, Font font) { Label label = new Label(text); label.setFont(font); return label; } public AutoSnail() { super("AutoSnail Express"); this.setFont(textFont); // Basic layout: map with controls at right, buttons at bottom setLayout(new BorderLayout()); this.add(viewer, "Center"); this.add(controls, "East"); this.add(buttons, "South"); controls.setBackground(Color.lightGray); buttons.setBackground(Color.lightGray); viewer.setBackground(Color.white); viewer.setSelectionListener(this); controls.add(makeLabel("From:", labelFont)); controls.add(fromText); controls.add(makeLabel("To:", labelFont)); controls.add(toText); controls.add(makeLabel("Distance:", labelFont)); controls.add(distText); controls.add(workCheck); controls.add(heurCheck); controls.add(delayCheck); controls.add(delaySlider); // Checking or clearing the delay box calls our setDelay() method delayCheck.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { delaySlider.setVisible(delayCheck.getState()); setDelay(); } }); // Moving the delay slider also calls setDelay() delaySlider.addAdjustmentListener(new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent e) { setDelay(); } }); delaySlider.setBlockIncrement(100); delaySlider.setUnitIncrement(100); setLocation(250, 50); pack(); // The delay slider is invisible until the check box is selected delaySlider.setVisible(false); } /** Respond to selection events on towns */ public void select(Map.Town t) { switch (selStage) { case 0: // Select first town from = t; from.setColor(Color.green); viewer.repaint(); fromText.setText(from.getName()); selStage = 1; break; case 1: // Select second town if (from == t) break; to = t; to.setColor(Color.red); viewer.repaint(); toText.setText(to.getName()); searchButton.setEnabled(true); selStage = 2; break; default: // Ignore selections after first two break; } } /** Adjust the delay to match what is selected by the controls */ private void setDelay() { if (delayCheck.getState()) delay = delaySlider.getValue(); else delay = 0; } /** If all is ready, carry out a search */ public void search() { if (selStage < 2) return; searchButton.setEnabled(false); resetButton.setEnabled(false); resetSearch(); viewer.repaint(); boolean heur = heurCheck.getState(); boolean work = workCheck.getState(); pathfinder = new PathFinder(map, from, to, heur, work); state = pathfinder.getStateObject(); state.addObserver(viewer); state.addObserver(this); setDelay(); pathfinder.start(); } // Observer method private static NumberFormat fmt = NumberFormat.getInstance(); static { fmt.setMaximumFractionDigits(1); } public void update(Observable pf, Object arg) { float dist = state.getDistance(); distText.setText(dist < PathFinder.INF ? fmt.format(dist) : "infinity"); if (state.isDone()) { pathfinder = null; searchButton.setEnabled(true); resetButton.setEnabled(true); return; } try { Thread.sleep(delay); } catch (InterruptedException e) {} } /** Reset the selection of towns and the search */ private void reset() { resetSearch(); if (from != null) from.setColor(Color.black); if (to != null) to.setColor(Color.black); searchButton.setEnabled(false); selStage = 0; fromText.setText(""); toText.setText(""); controls.repaint(); viewer.repaint(); } /** Prepare for a new search with the same towns */ private void resetSearch() { map.reset(); if (from != null) from.setColor(Color.green); if (to != null) to.setColor(Color.red); distText.setText(""); } public void setMap(Map map) { this.map = map; viewer.setMap(map); reset(); } public void appletMain(DemoApplet applet) { ClassLoader loader = this.getClass().getClassLoader(); InputStream stream = loader.getResourceAsStream("mapinfo.txt"); setMap(Map.fromStream(stream)); } public void die() { if (pathfinder != null) pathfinder.kill(); super.die(); } public static void main(String args[]) { AutoSnail frame = new AutoSnail(); frame.setMap(Map.fromFile("mapinfo.txt")); frame.setVisible(true); } /** An uneditable text field with a white background */ class PassiveTextField extends TextField { public PassiveTextField(int width) { super(width); setEditable(false); setBackground(Color.white); } } }