gt:             count = (count + 1) % MicropolisDrawingArea.SHAKE_STEPS;
context: // This file is part of DiverCity
  // DiverCity is based on MicropolisJ
  // Copyright (C) 2013 Jason Long
  // Portions Copyright (C) 1989-2007 Electronic Arts Inc.
  //
  // MicropolisJ is free software; you can redistribute it and/or modify
  // it under the terms of the GNU GPLv3, with additional terms.
  // See the README file, included in this distribution, for details.
  
  package micropolisj.gui;
  
  import java.awt.*;
  import java.awt.event.*;
  import java.beans.PropertyChangeEvent;
  import java.beans.PropertyChangeListener;
  import java.io.*;
  import java.net.URL;
  import java.text.MessageFormat;
  import java.text.NumberFormat;
  import java.text.SimpleDateFormat;
  import java.util.*;
  import java.util.prefs.*;
  import javax.sound.sampled.*;
  import javax.swing.*;
  import javax.swing.Timer;
  import javax.swing.filechooser.FileNameExtensionFilter;
  import javax.swing.plaf.metal.MetalIconFactory;
  
  import micropolisj.engine.*;
  import micropolisj.engine.techno.GeneralTechnology;
  import micropolisj.util.TranslationTool;
  
  public class MainWindow extends JFrame
          implements Micropolis.Listener, EarthquakeListener {
      Micropolis engine;
      MicropolisDrawingArea drawingArea;
      JScrollPane drawingAreaScroll;
      DemandIndicator demandInd;
      MessagesPane messagesPane;
      JLabel mapLegendLbl;
      OverlayMapView mapView;
      public NotificationPane notificationPane;
      EvaluationPane evaluationPane;
      ScienceFrameA scienceFrameA;
      ScienceFrameB scienceFrameB;    
      GraphsPane graphsPane;
      JLabel dateLbl;
      JLabel fundsLbl;
      JLabel popLbl;
      JLabel currentToolLbl;
      JLabel currentToolCostLbl;
      Map<MicropolisTool, JToggleButton> toolBtns;
      EnumMap<MapState, JMenuItem> mapStateMenuItems = new EnumMap<MapState, JMenuItem>(MapState.class);
      MicropolisTool currentTool;
      File currentFile;
      boolean doSounds = true;
      boolean dirty1 = false;  //indicates if a tool was successfully applied since last save
      boolean dirty2 = false;  //indicates if simulator took a step since last save
      long lastSavedTime = 0;  //real-time clock of when file was last saved
      boolean autoBudgetPending;
  
  
      static ImageIcon appIcon;
  
      static {
          appIcon = new ImageIcon(MainWindow.class.getResource("/micropolism.png"));
      }
  
      static ResourceBundle strings = ResourceBundle.getBundle("micropolisj.GuiStrings");
      static final String PRODUCT_NAME = strings.getString("PRODUCT");
  
      public MainWindow() {
          this(new Micropolis());
      }
  
      public MainWindow(Micropolis engine) {
          setIconImage(appIcon.getImage());
  
  
          this.engine = engine;
  
          JPanel mainArea = new JPanel(new BorderLayout());
          add(mainArea, BorderLayout.CENTER);
  
          drawingArea = new MicropolisDrawingArea(engine);
          drawingAreaScroll = new JScrollPane(drawingArea);
          mainArea.add(drawingAreaScroll);
  
          makeMenu();
          JToolBar tb = makeToolbar();
          mainArea.add(tb, BorderLayout.WEST);
  
          Box evalGraphsBox = new Box(BoxLayout.Y_AXIS);
          mainArea.add(evalGraphsBox, BorderLayout.SOUTH);
  
          graphsPane = new GraphsPane(engine);
          graphsPane.setVisible(false);
          evalGraphsBox.add(graphsPane);
  
          evaluationPane = new EvaluationPane(engine);
          evaluationPane.setVisible(false);
          evalGraphsBox.add(evaluationPane, BorderLayout.SOUTH);
          
  
          JPanel leftPane = new JPanel(new GridBagLayout());
          add(leftPane, BorderLayout.WEST);
  
          GridBagConstraints c = new GridBagConstraints();
          c.gridx = c.gridy = 0;
          c.anchor = GridBagConstraints.SOUTHWEST;
          c.insets = new Insets(4, 4, 4, 4);
          c.weightx = 1.0;
  
          demandInd = new DemandIndicator();
          leftPane.add(demandInd, c);
  
          c.gridx = 1;
          c.weightx = 0.0;
          c.fill = GridBagConstraints.BOTH;
          c.insets = new Insets(4, 20, 4, 4);
  
          leftPane.add(makeDateFunds(), c);
  
          c.gridx = 0;
          c.gridy = 1;
          c.gridwidth = 2;
          c.weighty = 0.0;
          c.anchor = GridBagConstraints.NORTH;
          c.insets = new Insets(0, 0, 0, 0);
  
          JPanel mapViewContainer = new JPanel(new BorderLayout());
          mapViewContainer.setBorder(BorderFactory.createLineBorder(Color.BLACK));
          leftPane.add(mapViewContainer, c);
  
          JMenuBar mapMenu = new JMenuBar();
          mapViewContainer.add(mapMenu, BorderLayout.NORTH);
  
          JMenu zonesMenu = new JMenu(strings.getString("menu.zones"));
          setupKeys(zonesMenu, "menu.zones");
          mapMenu.add(zonesMenu);
  
          zonesMenu.add(makeMapStateMenuItem("menu.zones.ALL", MapState.ALL));
          zonesMenu.add(makeMapStateMenuItem("menu.zones.RESIDENTIAL", MapState.RESIDENTIAL));
          zonesMenu.add(makeMapStateMenuItem("menu.zones.COMMERCIAL", MapState.COMMERCIAL));
          zonesMenu.add(makeMapStateMenuItem("menu.zones.INDUSTRIAL", MapState.INDUSTRIAL));
          zonesMenu.add(makeMapStateMenuItem("menu.zones.TRANSPORT", MapState.TRANSPORT));
  
          JMenu overlaysMenu = new JMenu(strings.getString("menu.overlays"));
          setupKeys(overlaysMenu, "menu.overlays");
          mapMenu.add(overlaysMenu);
  
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.POPDEN_OVERLAY", MapState.POPDEN_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.GROWTHRATE_OVERLAY", MapState.GROWTHRATE_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.LANDVALUE_OVERLAY", MapState.LANDVALUE_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.CRIME_OVERLAY", MapState.CRIME_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.POLLUTE_OVERLAY", MapState.POLLUTE_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.TRAFFIC_OVERLAY", MapState.TRAFFIC_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.POWER_OVERLAY", MapState.POWER_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.FIRE_OVERLAY", MapState.FIRE_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.POLICE_OVERLAY", MapState.POLICE_OVERLAY));
          overlaysMenu.add(makeMapStateMenuItem("menu.overlays.VISIT_OVERLAY", MapState.VISIT_OVERLAY));
  
          mapMenu.add(Box.createHorizontalGlue());
          mapLegendLbl = new JLabel();
          mapMenu.add(mapLegendLbl);
  
          mapView = new OverlayMapView(engine);
          mapView.connectView(drawingArea, drawingAreaScroll);
          mapViewContainer.add(mapView, BorderLayout.CENTER);
  
          setMapState(MapState.ALL);
  
          c.gridx = 0;
          c.gridy = 2;
          c.gridwidth = 2;
          c.weighty = 1.0;
          c.fill = GridBagConstraints.BOTH;
          c.insets = new Insets(0, 0, 0, 0);
  
          messagesPane = new MessagesPane();
          JScrollPane scroll2 = new JScrollPane(messagesPane);
          scroll2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
          scroll2.setPreferredSize(new Dimension(0, 0));
          scroll2.setMinimumSize(new Dimension(0, 0));
          leftPane.add(scroll2, c);
  
          c.gridy = 3;
          c.weighty = 0.0;
          notificationPane = new NotificationPane(engine);
          leftPane.add(notificationPane, c);
  
          pack();
          setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
          setLocationRelativeTo(null);
  
          InputMap inputMap = ((JComponent) getContentPane()).getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
          inputMap.put(KeyStroke.getKeyStroke("ADD"), "zoomIn");
          inputMap.put(KeyStroke.getKeyStroke("shift EQUALS"), "zoomIn");
          inputMap.put(KeyStroke.getKeyStroke("SUBTRACT"), "zoomOut");
          inputMap.put(KeyStroke.getKeyStroke("MINUS"), "zoomOut");
          inputMap.put(KeyStroke.getKeyStroke("ESCAPE"), "escape");
          inputMap.put(KeyStroke.getKeyStroke("ENTER"), "openCheatBox");
          inputMap.put(KeyStroke.getKeyStroke("P"), "setPaused");
          inputMap.put(KeyStroke.getKeyStroke("1"), "setSlow");
          inputMap.put(KeyStroke.getKeyStroke("2"), "setNormal");
          inputMap.put(KeyStroke.getKeyStroke("3"), "setFast");
          inputMap.put(KeyStroke.getKeyStroke("4"), "setSuperFast");
  
  
  
  
          ActionMap actionMap = ((JComponent) getContentPane()).getActionMap();
          actionMap.put("zoomIn", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  doZoom(1);
              }
          });
          actionMap.put("zoomOut", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  doZoom(-1);
              }
          });
          actionMap.put("escape", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  onEscapePressed();
              }
          });
          actionMap.put("openCheatBox", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  openCheatBox();
              }
          });
          actionMap.put("setPaused", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  onPriorityClicked(Speed.PAUSED);
              }
          });
          actionMap.put("setSlow", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  onPriorityClicked(Speed.SLOW);
              }
          });
          actionMap.put("setNormal", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  onPriorityClicked(Speed.NORMAL);
              }
          });
          actionMap.put("setFast", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  onPriorityClicked(Speed.FAST);
              }
          });
          actionMap.put("setSuperFast", new AbstractAction() {
              public void actionPerformed(ActionEvent evt) {
                  onPriorityClicked(Speed.SUPER_FAST);
              }
          });
  
  
  
  
          MouseAdapter mouse = new MouseAdapter() {
              public void mousePressed(MouseEvent ev) {
                  try {
                      onToolDown(ev);
                  } catch (Throwable e) {
                      showErrorMessage(e);
                  }
              }
  
              public void mouseReleased(MouseEvent ev) {
                  try {
                      onToolUp(ev);
                  } catch (Throwable e) {
                      showErrorMessage(e);
                  }
              }
  
              public void mouseDragged(MouseEvent ev) {
                  try {
                      onToolDrag(ev);
                  } catch (Throwable e) {
                      showErrorMessage(e);
                  }
              }
  
              public void mouseMoved(MouseEvent ev) {
  
                  try {
                      onToolHover(ev);
                  } catch (Throwable e) {
                      showErrorMessage(e);
                  }
              }
  
              public void mouseExited(MouseEvent ev) {
                  try {
                      onToolExited(ev);
                  } catch (Throwable e) {
                      showErrorMessage(e);
                  }
              }
  
              public void mouseWheelMoved(MouseWheelEvent evt) {
                  try {
                      onMouseWheelMoved(evt);
                  } catch (Throwable e) {
                      showErrorMessage(e);
                  }
              }
          };
          drawingArea.addMouseListener(mouse);
          drawingArea.addMouseMotionListener(mouse);
          drawingArea.addMouseWheelListener(mouse);
  
          drawingArea.requestFocusInWindow();
  
          final KeyboardFocusManager focusManager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
          focusManager.addPropertyChangeListener(new PropertyChangeListener() {
              @Override
              public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
                  if (!(focusManager.getFocusOwner() == null || focusManager.getFocusOwner() == getRootPane()))
                      drawingArea.requestFocusInWindow();
              }
          });
  
  
  
  
          addWindowListener(new WindowAdapter() {
              public void windowClosing(WindowEvent ev) {
                  leaveGame();
              }
  
              public void windowClosed(WindowEvent ev) {
                  onWindowClosed(ev);
              }
          });
  
          Preferences prefs = Preferences.userNodeForPackage(MainWindow.class);
          doSounds = prefs.getBoolean(SOUNDS_PREF, true);
  
          // start things up
          mapView.setEngine(engine);
          engine.addListener(this);
          engine.addEarthquakeListener(this);
          reloadFunds();
          reloadOptions();
          startTimer();
          makeClean();
      }
  
      public void setEngine(Micropolis newEngine) {
          if (engine != null) { // old engine
              engine.removeListener(this);
              engine.removeEarthquakeListener(this);
          }
  
          engine = newEngine;
  
          if (engine != null) { // new engine
              engine.addListener(this);
              engine.addEarthquakeListener(this);
          }
  
          boolean timerEnabled = isTimerActive();
          if (timerEnabled) {
              stopTimer();
          }
          stopEarthquake();
  
          drawingArea.setEngine(engine);
          mapView.setEngine(engine);   //must change mapView after drawingArea
          evaluationPane.setEngine(engine);
          demandInd.setEngine(engine);
          graphsPane.setEngine(engine);
          reloadFunds();
          reloadOptions();
          notificationPane.setVisible(false);
  
          if (timerEnabled) {
              startTimer();
          }
      }
  
      boolean needsSaved() {
          if (dirty1)    //player has built something since last save
              return true;
  
          if (!dirty2)   //no simulator ticks since last save
              return false;
  
          // simulation time has passed since last save, but the player
          // hasn't done anything. Whether we need to prompt for save
          // will depend on how much real time has elapsed.
          // The threshold is 30 seconds.
  
          return (System.currentTimeMillis() - lastSavedTime > 30000);
      }
  
      boolean maybeSaveCity() {
          if (needsSaved()) {
              boolean timerEnabled = isTimerActive();
              if (timerEnabled) {
                  stopTimer();
              }
  
              try {
                  int rv = JOptionPane.showConfirmDialog(
                          this,
                          strings.getString("main.save_query"),
                          PRODUCT_NAME,
                          JOptionPane.YES_NO_CANCEL_OPTION,
                          JOptionPane.WARNING_MESSAGE);
                  if (rv == JOptionPane.CANCEL_OPTION)
                      return false;
  
                  if (rv == JOptionPane.YES_OPTION) {
                      if (!onSaveCityClicked()) {
                          // canceled save dialog
                          return false;
                      }
                  }
              } finally {
                  if (timerEnabled) {
                      startTimer();
                  }
              }
          }
          return true;
      }
  
      void leaveGame() {
          if (maybeSaveCity()) {
              System.exit(0);
          }
      }
  
      JComponent makeDateFunds() {
          JPanel pane = new JPanel(new GridBagLayout());
          GridBagConstraints c0 = new GridBagConstraints();
          GridBagConstraints c1 = new GridBagConstraints();
  
          c0.gridx = 0;
          c1.gridx = 1;
          c0.gridy = c1.gridy = 0;
          c0.weightx = 1.0;
          c0.weighty = c1.weighty = 1.0;
          c0.anchor = GridBagConstraints.WEST;
          c1.anchor = GridBagConstraints.EAST;
  
          pane.add(new JLabel(strings.getString("main.date_label")), c0);
          dateLbl = new JLabel();
          pane.add(dateLbl, c1);
  
          c0.gridy = c1.gridy = 1;
  
          pane.add(new JLabel(strings.getString("main.funds_label")), c0);
          fundsLbl = new JLabel();
          pane.add(fundsLbl, c1);
  
          c0.gridy = c1.gridy = 2;
  
          pane.add(new JLabel(strings.getString("main.population_label")), c0);
          popLbl = new JLabel();
          pane.add(popLbl, c1);
  
          return pane;
      }
  
      private void setupKeys(JMenu menu, String prefix) {
          if (strings.containsKey(prefix + ".key")) {
              String mnemonic = strings.getString(prefix + ".key");
              menu.setMnemonic(
                      KeyStroke.getKeyStroke(mnemonic).getKeyCode()
              );
          }
      }
  
      private void setupKeys(JMenuItem menuItem, String prefix) {
          if (strings.containsKey(prefix + ".key")) {
              String mnemonic = strings.getString(prefix + ".key");
              menuItem.setMnemonic(
                      KeyStroke.getKeyStroke(mnemonic).getKeyCode()
              );
          }
          if (strings.containsKey(prefix + ".shortcut")) {
              String shortcut = strings.getString(prefix + ".shortcut");
              menuItem.setAccelerator(
                      KeyStroke.getKeyStroke(shortcut)
              );
          }
      }
  
      private void makeMenu() {
          JMenuBar menuBar = new JMenuBar();
  
          JMenu gameMenu = new JMenu(strings.getString("menu.game"));
          setupKeys(gameMenu, "menu.game");
          menuBar.add(gameMenu);
  
          JMenuItem menuItem;
          menuItem = new JMenuItem(strings.getString("menu.game.new"));
          setupKeys(menuItem, "menu.game.new");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onNewCityClicked();
                      }
                  }));
          gameMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.game.load"));
          setupKeys(menuItem, "menu.game.load");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onLoadGameClicked();
                      }
                  }));
          gameMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.game.save"));
          setupKeys(menuItem, "menu.game.save");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onSaveCityClicked();
                      }
                  }));
          gameMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.game.save_as"));
          setupKeys(menuItem, "menu.game.save_as");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onSaveCityAsClicked();
                      }
                  }));
          gameMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.game.exit"));
          setupKeys(menuItem, "menu.game.exit");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          leaveGame();
                      }
                  }));
          gameMenu.add(menuItem);
  
          JMenu optionsMenu = new JMenu(strings.getString("menu.options"));
          setupKeys(optionsMenu, "menu.options");
          menuBar.add(optionsMenu);
  
          JMenu levelMenu = new JMenu(strings.getString("menu.difficulty"));
          setupKeys(levelMenu, "menu.difficulty");
          optionsMenu.add(levelMenu);
  
          difficultyMenuItems = new HashMap<Integer, JMenuItem>();
          for (int i = GameLevel.MIN_LEVEL; i <= GameLevel.MAX_LEVEL; i++) {
              final int level = i;
              menuItem = new JRadioButtonMenuItem(strings.getString("menu.difficulty." + level));
              setupKeys(menuItem, "menu.difficulty." + level);
              menuItem.addActionListener(wrapActionListener(
                      new ActionListener() {
                          public void actionPerformed(ActionEvent evt) {
                              onDifficultyClicked(level);
                          }
                      }));
              levelMenu.add(menuItem);
              difficultyMenuItems.put(level, menuItem);
          }
  
          autoBudgetMenuItem = new JCheckBoxMenuItem(strings.getString("menu.options.auto_budget"));
          setupKeys(autoBudgetMenuItem, "menu.options.auto_budget");
          autoBudgetMenuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onAutoBudgetClicked();
                      }
                  }));
          optionsMenu.add(autoBudgetMenuItem);
  
          autoBulldozeMenuItem = new JCheckBoxMenuItem(strings.getString("menu.options.auto_bulldoze"));
          setupKeys(autoBulldozeMenuItem, "menu.options.auto_bulldoze");
          autoBulldozeMenuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onAutoBulldozeClicked();
                      }
                  }));
          optionsMenu.add(autoBulldozeMenuItem);
  
          disastersMenuItem = new JCheckBoxMenuItem(strings.getString("menu.options.disasters"));
          setupKeys(disastersMenuItem, "menu.options.disasters");
          disastersMenuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onDisastersClicked();
                      }
                  }
          ));
          optionsMenu.add(disastersMenuItem);
  
          soundsMenuItem = new JCheckBoxMenuItem(strings.getString("menu.options.sound"));
          setupKeys(soundsMenuItem, "menu.options.sound");
          soundsMenuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onSoundClicked();
                      }
                  }));
          optionsMenu.add(soundsMenuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.options.zoom_in"));
          setupKeys(menuItem, "menu.options.zoom_in");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          doZoom(1);
                      }
                  }));
          optionsMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.options.zoom_out"));
          setupKeys(menuItem, "menu.options.zoom_out");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          doZoom(-1);
                      }
                  }));
          optionsMenu.add(menuItem);
  
          JMenu disastersMenu = new JMenu(strings.getString("menu.disasters"));
          setupKeys(disastersMenu, "menu.disasters");
          menuBar.add(disastersMenu);
  
          menuItem = new JMenuItem(strings.getString("menu.disasters.MONSTER"));
          setupKeys(menuItem, "menu.disasters.MONSTER");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onInvokeDisasterClicked(Disaster.MONSTER);
                      }
                  }));
          disastersMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.disasters.FIRE"));
          setupKeys(menuItem, "menu.disasters.FIRE");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onInvokeDisasterClicked(Disaster.FIRE);
                      }
                  }));
          disastersMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.disasters.FLOOD"));
          setupKeys(menuItem, "menu.disasters.FLOOD");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onInvokeDisasterClicked(Disaster.FLOOD);
                      }
                  }));
          disastersMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.disasters.MELTDOWN"));
          setupKeys(menuItem, "menu.disasters.MELTDOWN");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onInvokeDisasterClicked(Disaster.MELTDOWN);
                      }
                  }));
          disastersMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.disasters.TORNADO"));
          setupKeys(menuItem, "menu.disasters.TORNADO");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onInvokeDisasterClicked(Disaster.TORNADO);
                      }
                  }));
          disastersMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.disasters.EARTHQUAKE"));
          setupKeys(menuItem, "menu.disasters.EARTHQUAKE");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onInvokeDisasterClicked(Disaster.EARTHQUAKE);
                      }
                  }));
          disastersMenu.add(menuItem);
  
          JMenu priorityMenu = new JMenu(strings.getString("menu.speed"));
          setupKeys(priorityMenu, "menu.speed");
          menuBar.add(priorityMenu);
  
          priorityMenuItems = new EnumMap<Speed, JMenuItem>(Speed.class);
          menuItem = new JMenuItem(strings.getString("menu.speed.SUPER_FAST"));
          setupKeys(menuItem, "menu.speed.SUPER_FAST");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onPriorityClicked(Speed.SUPER_FAST);
                      }
                  }));
          priorityMenu.add(menuItem);
          priorityMenuItems.put(Speed.SUPER_FAST, menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.speed.FAST"));
          setupKeys(menuItem, "menu.speed.FAST");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onPriorityClicked(Speed.FAST);
                      }
                  }));
          priorityMenu.add(menuItem);
          priorityMenuItems.put(Speed.FAST, menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.speed.NORMAL"));
          setupKeys(menuItem, "menu.speed.NORMAL");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onPriorityClicked(Speed.NORMAL);
                      }
                  }));
          priorityMenu.add(menuItem);
          priorityMenuItems.put(Speed.NORMAL, menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.speed.SLOW"));
          setupKeys(menuItem, "menu.speed.SLOW");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onPriorityClicked(Speed.SLOW);
                      }
                  }));
          priorityMenu.add(menuItem);
          priorityMenuItems.put(Speed.SLOW, menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.speed.PAUSED"));
          setupKeys(menuItem, "menu.speed.PAUSED");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onPriorityClicked(Speed.PAUSED);
                      }
                  }));
          priorityMenu.add(menuItem);
          priorityMenuItems.put(Speed.PAUSED, menuItem);
  
          JMenu windowsMenu = new JMenu(strings.getString("menu.windows"));
          setupKeys(windowsMenu, "menu.windows");
          menuBar.add(windowsMenu);
  
          menuItem = new JMenuItem(strings.getString("menu.windows.budget"));
          setupKeys(menuItem, "menu.windows.budget");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onViewBudgetClicked();
                      }
                  }));
          windowsMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.windows.evaluation"));
          setupKeys(menuItem, "menu.windows.evaluation");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onViewEvaluationClicked();
                      }
                  }));
          //windowsMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.windows.graph"));
          setupKeys(menuItem, "menu.windows.graph");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onViewGraphClicked();
                      }
                  }));
          windowsMenu.add(menuItem);
  
          JMenu helpMenu = new JMenu(strings.getString("menu.help"));
          setupKeys(helpMenu, "menu.help");
          menuBar.add(helpMenu);
  
          menuItem = new JMenuItem(strings.getString("menu.help.launch-translation-tool"));
          setupKeys(menuItem, "menu.help.launch-translation-tool");
          menuItem.addActionListener(new ActionListener() {
              public void actionPerformed(ActionEvent evt) {
                  onLaunchTranslationToolClicked();
              }
          });
          helpMenu.add(menuItem);
  
          menuItem = new JMenuItem(strings.getString("menu.help.about"));
          setupKeys(menuItem, "menu.help.about");
          menuItem.addActionListener(wrapActionListener(
                  new ActionListener() {
                      public void actionPerformed(ActionEvent ev) {
                          onAboutClicked();
                      }
                  }));
          helpMenu.add(menuItem);
  
          setJMenuBar(menuBar);
      }
  
      private Micropolis getEngine() {
          return engine;
      }
  
      JMenuItem autoBudgetMenuItem;
      JMenuItem autoBulldozeMenuItem;
      JMenuItem disastersMenuItem;
      JMenuItem soundsMenuItem;
      Map<Speed, JMenuItem> priorityMenuItems;
      Map<Integer, JMenuItem> difficultyMenuItems;
  
      private void onAutoBudgetClicked() {
          dirty1 = true;
          getEngine().toggleAutoBudget();
      }
  
      private void onAutoBulldozeClicked() {
          dirty1 = true;
          getEngine().toggleAutoBulldoze();
      }
  
      private void onDisastersClicked() {
          dirty1 = true;
          getEngine().toggleDisasters();
      }
  
      static final String SOUNDS_PREF = "enable_sounds";
  
      private void onSoundClicked() {
          doSounds = !doSounds;
          Preferences prefs = Preferences.userNodeForPackage(MainWindow.class);
          prefs.putBoolean(SOUNDS_PREF, doSounds);
          reloadOptions();
      }
  
  
      void makeClean() {
          dirty1 = false;
          dirty2 = false;
          lastSavedTime = System.currentTimeMillis();
          if (currentFile != null) {
              String fileName = currentFile.getName();
              if (fileName.endsWith("." + EXTENSION)) {
                  fileName = fileName.substring(0, fileName.length() - 1 - EXTENSION.length());
              }
              setTitle(MessageFormat.format(strings.getString("main.caption_named_city"), fileName));
          } else {
              setTitle(strings.getString("main.caption_unnamed_city"));
          }
      }
  
      private boolean onSaveCityClicked() {
          if (currentFile == null) {
              return onSaveCityAsClicked();
          }
  
          try {
              getEngine().save(currentFile);
              makeClean();
              return true;
          } catch (IOException e) {
              e.printStackTrace(System.err);
              JOptionPane.showMessageDialog(this, e, strings.getString("main.error_caption"),
                      JOptionPane.ERROR_MESSAGE);
              return false;
          }
      }
  
      static final String EXTENSION = "cty";
  
      private boolean onSaveCityAsClicked() {
          boolean timerEnabled = isTimerActive();
          if (timerEnabled) {
              stopTimer();
          }
          try {
              JFileChooser fc = new JFileChooser();
              FileNameExtensionFilter filter1 = new FileNameExtensionFilter(strings.getString("cty_file"), EXTENSION);
              fc.setFileFilter(filter1);
              int rv = fc.showSaveDialog(this);
              if (rv == JFileChooser.APPROVE_OPTION) {
                  currentFile = fc.getSelectedFile();
                  if (!currentFile.getName().endsWith("." + EXTENSION)) {
                      currentFile = new File(currentFile.getPath() + "." + EXTENSION);
                  }
                  getEngine().save(currentFile);
                  makeClean();
                  return true;
              }
          } catch (Exception e) {
              e.printStackTrace(System.err);
              JOptionPane.showMessageDialog(this, e, strings.getString("main.error_caption"),
                      JOptionPane.ERROR_MESSAGE);
          } finally {
              if (timerEnabled) {
                  startTimer();
              }
          }
          return false;
      }
  
      private void onLoadGameClicked() {
          // check if user wants to save their current city
          if (!maybeSaveCity()) {
              return;
          }
  
          boolean timerEnabled = isTimerActive();
          if (timerEnabled) {
              stopTimer();
          }
  
          try {
              JFileChooser fc = new JFileChooser();
              FileNameExtensionFilter filter1 = new FileNameExtensionFilter(strings.getString("cty_file"), EXTENSION);
              fc.setFileFilter(filter1);
  
              assert !isTimerActive();
  
              int rv = fc.showOpenDialog(this);
              if (rv == JFileChooser.APPROVE_OPTION) {
                  File file = fc.getSelectedFile();
                  Micropolis newEngine = new Micropolis();
                  newEngine.load(file);
                  setEngine(newEngine);
                  currentFile = file;
                  makeClean();
              }
          } catch (Exception e) {
              e.printStackTrace(System.err);
              JOptionPane.showMessageDialog(this, e, strings.getString("main.error_caption"),
                      JOptionPane.ERROR_MESSAGE);
          } finally {
              if (timerEnabled) {
                  startTimer();
              }
          }
      }
  
      private JToggleButton makeToolBtn(final MicropolisTool tool) {
          String iconName = strings.containsKey("tool." + tool.name() + ".icon") ?
                  strings.getString("tool." + tool.name() + ".icon") :
                  "/graphics/tools/" + tool.name().toLowerCase() + ".png";
          String iconSelectedName = strings.containsKey("tool." + tool.name() + ".selected_icon") ?
                  strings.getString("tool." + tool.name() + ".selected_icon") :
                  iconName;
          String tipText = strings.containsKey("tool." + tool.name() + ".tip") ?
                  strings.getString("tool." + tool.name() + ".tip") :
                  tool.name();
  
          JToggleButton btn = new JToggleButton();
          btn.setIcon(new ImageIcon(MainWindow.class.getResource(iconName)));
          btn.setSelectedIcon(new ImageIcon(MainWindow.class.getResource(iconSelectedName)));
          btn.setToolTipText(tipText);
          btn.setMargin(new Insets(0, 0, 0, 0));
          btn.setBorderPainted(false);
          btn.addActionListener(new ActionListener() {
              public void actionPerformed(ActionEvent ev) {
                  selectTool(tool);
              }
          });
          toolBtns.put(tool, btn);
          return btn;
      }
  
      private JToolBar makeToolbar() {
          toolBtns = new EnumMap<MicropolisTool, JToggleButton>(MicropolisTool.class);
  
          JToolBar toolBar = new JToolBar(strings.getString("main.tools_caption"), JToolBar.VERTICAL);
          toolBar.setFloatable(false);
          toolBar.setRollover(false);
  
          JPanel gridBox = new JPanel(new GridBagLayout());
          toolBar.add(gridBox);
  
          GridBagConstraints c = new GridBagConstraints();
          c.gridx = c.gridy = 0;
          c.anchor = GridBagConstraints.NORTH;
          c.insets = new Insets(8, 0, 0, 0);
          currentToolLbl = new JLabel(" ");
          gridBox.add(currentToolLbl, c);
  
          c.gridy = 1;
          c.insets = new Insets(0, 0, 12, 0);
          currentToolCostLbl = new JLabel(" ");
          gridBox.add(currentToolCostLbl, c);
  
          c.gridy++;
          c.fill = GridBagConstraints.NONE;
          c.weightx = 1.0;
          c.insets = new Insets(0, 0, 0, 0);
          Box b0 = new Box(BoxLayout.X_AXIS);
          gridBox.add(b0, c);
  
          b0.add(makeToolBtn(MicropolisTool.BULLDOZER));
          b0.add(makeToolBtn(MicropolisTool.WIRE));
          b0.add(makeToolBtn(MicropolisTool.PARK));
          b0.add(makeToolBtn(MicropolisTool.BIGPARK));
  
          c.gridy++;
          Box b1 = new Box(BoxLayout.X_AXIS);
          gridBox.add(b1, c);
  
          b1.add(makeToolBtn(MicropolisTool.ROADS));
          b1.add(makeToolBtn(MicropolisTool.BIGROADS));
          
          c.gridy++;
          Box b2 = new Box(BoxLayout.X_AXIS);
          gridBox.add(b2, c);
          
          b2.add(makeToolBtn(MicropolisTool.RAIL));
          b2.add(makeToolBtn(MicropolisTool.STATION));
  
          c.gridy++;
          Box b3 = new Box(BoxLayout.X_AXIS);
          gridBox.add(b3, c);
  
          b3.add(makeToolBtn(MicropolisTool.RESIDENTIAL));
          b3.add(makeToolBtn(MicropolisTool.COMMERCIAL));
          b3.add(makeToolBtn(MicropolisTool.INDUSTRIAL));
  
          c.gridy++;
          Box b4 = new Box(BoxLayout.X_AXIS);
          gridBox.add(b4, c);
  
          
          b4.add(makeToolBtn(MicropolisTool.CITYHALL));
          b4.add(makeToolBtn(MicropolisTool.FIRE));
          b4.add(makeToolBtn(MicropolisTool.POLICE));
          b4.add(makeToolBtn(MicropolisTool.QUERY));
  
          c.gridy++;
          Box b5 = new Box(BoxLayout.X_AXIS);
          gridBox.add(b5, c);
  
          b5.add(makeToolBtn(MicropolisTool.POWERPLANT));
          b5.add(makeToolBtn(MicropolisTool.NUCLEAR));
          b5.add(makeToolBtn(MicropolisTool.SOLAR));
          b5.add(makeToolBtn(MicropolisTool.WIND));
          
          c.gridy++;
          Box b6 = new Box(BoxLayout.X_AXIS);
          gridBox.add(b6, c);
  
      //    b5.add(makeToolBtn(MicropolisTool.STADIUM));
        //  b5.add(makeToolBtn(MicropolisTool.SEAPORT));
          b6.add(makeToolBtn(MicropolisTool.SCHOOL));
          b6.add(makeToolBtn(MicropolisTool.UNIA));
          b6.add(makeToolBtn(MicropolisTool.UNIB));
          
  
          c.gridy++;
          Box b7 = new Box(BoxLayout.X_AXIS);
          gridBox.add(b7, c);
  
          b7.add(makeToolBtn(MicropolisTool.OPENAIR));
          b7.add(makeToolBtn(MicropolisTool.STADIUM));
      //   b6.add(makeToolBtn(MicropolisTool.AIRPORT));
          b7.add(makeToolBtn(MicropolisTool.MUSEUM));
          
          c.gridy++;
          Box b8 = new Box(BoxLayout.X_AXIS);
  	    gridBox.add(b8, c);
  	    
  	//    b7.add(makeToolBtn(MicropolisTool.STATION));
  	    b8.add(makeToolBtn(MicropolisTool.SEAPORT));
  	    b8.add(makeToolBtn(MicropolisTool.AIRPORT));
  	    
  	    //c.gridy++;
         // Box b9 = new Box(BoxLayout.X_AXIS);
          //gridBox.add(b9, c);
          
          //b9.add(makeToolBtn(MicropolisTool.CITYHALL));
         
  
          // add glue to make all elements align toward top
          c.gridy++;
          c.weighty = 1.0;
          gridBox.add(new JLabel(), c);
  
          return toolBar;
      }
  
      private void selectTool(MicropolisTool newTool) {
          toolBtns.get(newTool).setSelected(true);
          if (newTool == currentTool) {
              return;
          }
  
          if (currentTool != null) {
              toolBtns.get(currentTool).setSelected(false);
          }
  
          currentTool = newTool;
  
          currentToolLbl.setText(
                  strings.containsKey("tool." + currentTool.name() + ".name") ?
                          strings.getString("tool." + currentTool.name() + ".name") :
                          currentTool.name()
          );
  
          int cost = currentTool.getToolCost();
          currentToolCostLbl.setText(cost != 0 ? formatFunds(cost) : " ");
      }
  
      private void onNewCityClicked() {
          if (maybeSaveCity()) {
              doNewCity(false);
          }
      }
  
      public void doNewCity(boolean firstTime) {
          boolean timerEnabled = isTimerActive();
          if (timerEnabled) {
              stopTimer();
          }
  
          new NewCityDialog(this, !firstTime).setVisible(true);
  
          if (timerEnabled) {
              startTimer();
          }
      }
  
      void doQueryTool(int xpos, int ypos) {
  
          if (!engine.testBounds(xpos, ypos))
              return;
          ZoneStatus z = engine.queryZoneStatus(xpos, ypos);
          notificationPane.showZoneStatus(engine, xpos, ypos, z);
          if (TileConstants.isCityHallBuilding((int) engine.getTile(xpos, ypos))) {
              evaluationPane.setVisible(true);
          }
          if (TileConstants.isUniversityA((int) engine.getTile(xpos, ypos))) {
  
  
  
          	scienceFrameA = new ScienceFrameA(this, engine);
              scienceFrameA.setVisible(true);
          }
          if (TileConstants.isUniversityB((int) engine.getTile(xpos, ypos))) {
          	scienceFrameB = new ScienceFrameB(this, engine);
              scienceFrameB.setVisible(true);
          }
      }
  
      private void doZoom(int dir, Point mousePt) {
          int oldZoom = drawingArea.getTileSize();
          int newZoom = dir < 0 ? (oldZoom / 2) : (oldZoom * 2);
          if (newZoom < 8) {
              newZoom = 8;
          }
          if (newZoom > 32) {
              newZoom = 32;
          }
  
          if (oldZoom != newZoom) {
              // preserve effective mouse position in viewport when changing zoom level
              double f = (double) newZoom / (double) oldZoom;
              Point pos = drawingAreaScroll.getViewport().getViewPosition();
              int newX = (int) Math.round(mousePt.x * f - (mousePt.x - pos.x));
              int newY = (int) Math.round(mousePt.y * f - (mousePt.y - pos.y));
              drawingArea.selectTileSize(newZoom);
              drawingAreaScroll.validate();
              drawingAreaScroll.getViewport().setViewPosition(new Point(newX, newY));
          }
      }
  
      private void openCheatBox() {
          String text = JOptionPane.showInputDialog(strings.getString("cheating.dialog"));
          if (text.equals(strings.getString("cheating.moneycheat"))) {
              engine.spend(-10000);
              engine.incNCheats();
          }
          if (text.equals(strings.getString("cheating.destructioncheat"))) {
              citySound(Sound.EXPLOSION_BOTH, new CityLocation(1, 1));
              engine.destroyEverything();
          }
          if (text.equals(strings.getString("cheating.populationcheat"))) {
              engine.incCityPopulation();
              engine.incNCheats();
          }    
          if (text.equals(strings.getString("cheating.twolaneroadcheat"))) {
              engine.twoLaneRoadTech.addResearchPoints(4000);
              engine.incNCheats();
          }
          if (text.equals(strings.getString("cheating.windcheat"))) {
              engine.windTech.addResearchPoints(4000);
              engine.incNCheats();
          }
          if (text.equals(strings.getString("cheating.solarcheat"))) {
              engine.solarTech.addResearchPoints(4000);
              engine.incNCheats();
          }
          if (text.equals(strings.getString("cheating.alltechscheat"))) {
              
          	for (GeneralTechnology t: engine.eetechs) {
                  if(t != null)
  
          		    t.isResearched = true;
          	}
  
          	for (GeneralTechnology t: engine.infraTechs) {
                  if(t != null)
          		    t.addResearchPoints(t.getPointsNeeded() + 1);
          	}
          	
              engine.incNCheats();
          }
      }
  
  
  
      private void doZoom(int dir) {
          Rectangle rect = drawingAreaScroll.getViewport().getViewRect();
          doZoom(dir,
                  new Point(rect.x + rect.width / 2,
                          rect.y + rect.height / 2
                  )
          );
      }
  
      private void onMouseWheelMoved(MouseWheelEvent evt) {
          if (evt.getWheelRotation() < 0) {
              doZoom(1, evt.getPoint());
          } else {
              doZoom(-1, evt.getPoint());
          }
      }
  
      // used when a tool is being pressed
      ToolStroke toolStroke;
  
      // where the tool was last applied during the current drag
      int lastX;
      int lastY;
  
      private void onToolDown(MouseEvent ev) {
          if (ev.getButton() == MouseEvent.BUTTON3) {
              CityLocation loc = drawingArea.getCityLocation(ev.getX(), ev.getY());
              doQueryTool(loc.x, loc.y);
              return;
          }
  
          if (ev.getButton() != MouseEvent.BUTTON1)
              return;
  
          if (currentTool == null)
              return;
  
          CityLocation loc = drawingArea.getCityLocation(ev.getX(), ev.getY());
          int x = loc.x;
          int y = loc.y;
  
          if (currentTool == MicropolisTool.QUERY) {
              doQueryTool(x, y);
              this.toolStroke = null;
          } else {
              this.toolStroke = currentTool.beginStroke(engine, x, y);
              previewTool();
          }
  
          this.lastX = x;
          this.lastY = y;
      }
  
      private void onEscapePressed() {
          // if currently dragging a tool...
          if (toolStroke != null) {
              // cancel the current mouse operation
              toolStroke = null;
              drawingArea.setToolPreview(null);
              drawingArea.setToolCursor(null);
          } else {
              // dismiss any alerts currently visible
              notificationPane.setVisible(false);
          }
      }
  
      private void onToolUp(MouseEvent ev) {
          if (toolStroke != null) {
              drawingArea.setToolPreview(null);
  
              CityLocation loc = toolStroke.getLocation();
              ToolResult tr = toolStroke.apply();
              showToolResult(loc, tr);
              toolStroke = null;
          }
  
          onToolHover(ev);
  
          if (autoBudgetPending) {
              autoBudgetPending = false;
              showBudgetWindow(true);
          }
      }
  
      void previewTool() {
          assert this.toolStroke != null;
          assert this.currentTool != null;
  
          drawingArea.setToolCursor(
                  toolStroke.getBounds(),
                  currentTool
          );
          drawingArea.setToolPreview(
                  toolStroke.getPreview()
          );
      }
  
      private void onToolDrag(MouseEvent ev) {
          if (currentTool == null)
              return;
          if ((ev.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
              return;
  
          CityLocation loc = drawingArea.getCityLocation(ev.getX(), ev.getY());
          int x = loc.x;
          int y = loc.y;
          if (x == lastX && y == lastY)
              return;
  
          if (toolStroke != null) {
              toolStroke.dragTo(x, y);
              previewTool();
          } else if (currentTool == MicropolisTool.QUERY) {
              doQueryTool(x, y);
          }
  
          lastX = x;
          lastY = y;
      }
  
      private void onToolHover(MouseEvent ev) {
          if (currentTool == null || currentTool == MicropolisTool.QUERY) {
              drawingArea.setToolCursor(null);
              return;
          }
  
          CityLocation loc = drawingArea.getCityLocation(ev.getX(), ev.getY());
          int x = loc.x;
          int y = loc.y;
          int w = currentTool.getWidth();
          int h = currentTool.getHeight();
  
          if (w >= 3)
              x--;
          if (h >= 3)
              y--;
  
          drawingArea.setToolCursor(new CityRect(x, y, w, h), currentTool);
      }
  
      private void onToolExited(MouseEvent ev) {
          drawingArea.setToolCursor(null);
      }
  
      private void showToolResult(CityLocation loc, ToolResult result) {
          switch (result) {
              case SUCCESS:
                  citySound(currentTool == MicropolisTool.BULLDOZER ? Sound.BULLDOZE : Sound.BUILD, loc);
                  dirty1 = true;
                  break;
  
              case NONE:
                  break;
              case UH_OH:
                  messagesPane.appendCityMessage(MicropolisMessage.BULLDOZE_FIRST);
                  citySound(Sound.UHUH, loc);
                  break;
  
              case INSUFFICIENT_FUNDS:
                  messagesPane.appendCityMessage(MicropolisMessage.INSUFFICIENT_FUNDS);
                  citySound(Sound.SORRY, loc);
                  break;
              case INSUFFICIENT_POPULATION:
                  messagesPane.appendCityMessage(MicropolisMessage.NEED_MORE_CITIZENS);
                  citySound(Sound.SORRY, loc);
                  break;
              case RESEARCH_REQUIRED:
                  messagesPane.appendCityMessage(MicropolisMessage.RESEARCH_REQUIRED);
                  citySound(Sound.SORRY, loc);
                  break;
              case NO_MORE_CITYHALLS:
                  messagesPane.appendCityMessage(MicropolisMessage.NO_MORE_CITYHALLS);
                  citySound(Sound.SORRY, loc);
                  break;
              case NEED_A_SCHOOL:
                  messagesPane.appendCityMessage(MicropolisMessage.NEED_A_SCHOOL);
                  citySound(Sound.SORRY, loc);
                  break;
  
  
              default:
                  assert false;
          }
      }
  
      public static String formatFunds(int funds) {
          return MessageFormat.format(
                  strings.getString("funds"), funds
          );
      }
  
      public static String formatGameDate(int cityTime) {
          Calendar c = Calendar.getInstance();
          c.set(1900 + cityTime / 48,
                  (cityTime % 48) / 4,
                  (cityTime % 4) * 7 + 1
          );
  
          return MessageFormat.format(
                  strings.getString("citytime"),
                  c.getTime()
          );
      }
  
      private void updateDateLabel() {
          dateLbl.setText(formatGameDate(engine.cityTime));
  
          NumberFormat nf = NumberFormat.getInstance();
          popLbl.setText(nf.format(getEngine().getCityPopulation()));
      }
  
      Timer simTimer;
      Timer shakeTimer;
  
      private void startTimer() {
          final Micropolis engine = getEngine();
          final int count = engine.simSpeed.simStepsPerUpdate;
  
          assert !isTimerActive();
  
          if (engine.simSpeed == Speed.PAUSED)
              return;
  
          if (currentEarthquake != null) {
              int interval = 3000 / MicropolisDrawingArea.SHAKE_STEPS;
              shakeTimer = new Timer(interval, new ActionListener() {
                  public void actionPerformed(ActionEvent evt) {
                      currentEarthquake.oneStep();
                      if (currentEarthquake.count == 0) {
                          stopTimer();
                          currentEarthquake = null;
                          startTimer();
                      }
                  }
              });
              shakeTimer.start();
              return;
          }
  
          ActionListener taskPerformer = new ActionListener() {
              public void actionPerformed(ActionEvent evt) {
                  for (int i = 0; i < count; i++) {
                      engine.animate();
                      if(scienceFrameA != null){
                          scienceFrameA.updateProgressBar();
                          scienceFrameA.updateButtons();
                      }
                      if(scienceFrameB != null){
                          scienceFrameB.updateProgressBar();
                          scienceFrameB.updateButtons();
                      }
  
  
  
                      if (!engine.autoBudget && engine.isBudgetTime()) {
                          showAutoBudget();
                          return;
                      }
                  }
                  updateDateLabel();
                  dirty2 = true;
              }
          };
          taskPerformer = wrapActionListener(taskPerformer);
  
          assert simTimer == null;
          simTimer = new Timer(engine.simSpeed.animationDelay, taskPerformer);
          simTimer.start();
      }
  
      ActionListener wrapActionListener(final ActionListener l) {
          return new ActionListener() {
              public void actionPerformed(ActionEvent evt) {
                  try {
                      l.actionPerformed(evt);
                  } catch (Throwable e) {
                      showErrorMessage(e);
                  }
              }
          };
      }
  
      private void showErrorMessage(Throwable e) {
          StringWriter w = new StringWriter();
          e.printStackTrace(new PrintWriter(w));
  
          JTextPane stackTracePane = new JTextPane();
          stackTracePane.setEditable(false);
          stackTracePane.setText(w.toString());
  
          final JScrollPane detailsPane = new JScrollPane(stackTracePane);
          detailsPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
          detailsPane.setPreferredSize(new Dimension(480, 240));
          detailsPane.setMinimumSize(new Dimension(0, 0));
  
          int rv = JOptionPane.showOptionDialog(this, e,
                  strings.getString("main.error_unexpected"),
                  JOptionPane.DEFAULT_OPTION,
                  JOptionPane.ERROR_MESSAGE,
                  null,
                  new String[]{
                          strings.getString("main.error_show_stacktrace"),
                          strings.getString("main.error_close"),
                          strings.getString("main.error_shutdown")
                  },
                  1
          );
          if (rv == 0) {
              JOptionPane.showMessageDialog(this, detailsPane,
                      strings.getString("main.error_unexpected"),
                      JOptionPane.ERROR_MESSAGE);
          }
          if (rv == 2) {
              rv = JOptionPane.showConfirmDialog(
                      this,
                      strings.getString("error.shutdown_query"),
                      strings.getString("main.error_unexpected"),
                      JOptionPane.OK_CANCEL_OPTION,
                      JOptionPane.WARNING_MESSAGE);
              if (rv == JOptionPane.OK_OPTION) {
                  System.exit(1);
              }
          }
      }
  
      class EarthquakeStepper {
          int count = 0;
  
          void oneStep() {
  