Methapolis  0.27
 All Classes Namespaces Files Functions Variables Enumerator
MainWindow.java
Go to the documentation of this file.
1 // This file is part of MicropolisJ.
2 // Copyright (C) 2013 Jason Long
3 // Portions Copyright (C) 1989-2007 Electronic Arts Inc.
4 //
5 // MicropolisJ is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU GPLv3, with additional terms.
7 // See the README file, included in this distribution, for details.
8 
9 package micropolisj.gui;
10 
11 import java.awt.BorderLayout;
12 import java.awt.Color;
13 import java.awt.Dimension;
14 import java.awt.GridBagConstraints;
15 import java.awt.GridBagLayout;
16 import java.awt.Insets;
17 import java.awt.KeyEventDispatcher;
18 import java.awt.KeyboardFocusManager;
19 import java.awt.Point;
20 import java.awt.Rectangle;
21 import java.awt.event.ActionEvent;
22 import java.awt.event.ActionListener;
23 import java.awt.event.KeyEvent;
24 import java.awt.event.MouseAdapter;
25 import java.awt.event.MouseEvent;
26 import java.awt.event.MouseWheelEvent;
27 import java.awt.event.WindowAdapter;
28 import java.awt.event.WindowEvent;
29 import java.io.File;
30 import java.io.IOException;
31 import java.io.PrintWriter;
32 import java.io.StringWriter;
33 import java.net.URL;
34 import java.text.MessageFormat;
35 import java.text.NumberFormat;
36 import java.util.Calendar;
37 import java.util.EnumMap;
38 import java.util.Formatter;
39 import java.util.HashMap;
40 import java.util.Map;
41 import java.util.ResourceBundle;
42 import java.util.prefs.Preferences;
43 
44 import javax.sound.sampled.AudioInputStream;
45 import javax.sound.sampled.AudioSystem;
46 import javax.sound.sampled.Clip;
47 import javax.swing.AbstractAction;
48 import javax.swing.ActionMap;
49 import javax.swing.BorderFactory;
50 import javax.swing.Box;
51 import javax.swing.BoxLayout;
52 import javax.swing.ImageIcon;
53 import javax.swing.InputMap;
54 import javax.swing.JCheckBoxMenuItem;
55 import javax.swing.JComponent;
56 import javax.swing.JFileChooser;
57 import javax.swing.JFrame;
58 import javax.swing.JLabel;
59 import javax.swing.JMenu;
60 import javax.swing.JMenuBar;
61 import javax.swing.JMenuItem;
62 import javax.swing.JOptionPane;
63 import javax.swing.JPanel;
64 import javax.swing.JRadioButtonMenuItem;
65 import javax.swing.JScrollPane;
66 import javax.swing.JTextPane;
67 import javax.swing.JToggleButton;
68 import javax.swing.JToolBar;
69 import javax.swing.KeyStroke;
70 import javax.swing.Timer;
71 import javax.swing.WindowConstants;
72 import javax.swing.filechooser.FileNameExtensionFilter;
73 
74 import micropolisj.engine.CityLocation;
75 import micropolisj.engine.CityRect;
76 import micropolisj.engine.Disaster;
77 import micropolisj.engine.EarthquakeListener;
78 import micropolisj.engine.GameLevel;
79 import micropolisj.engine.MapState;
80 import micropolisj.engine.Micropolis;
81 import micropolisj.engine.MicropolisMessage;
82 import micropolisj.engine.MicropolisTool;
83 import micropolisj.engine.Sound;
84 import micropolisj.engine.Speed;
85 import micropolisj.engine.TempelListener;
86 import micropolisj.engine.ToolResult;
87 import micropolisj.engine.ToolStroke;
88 import micropolisj.engine.ZoneStatus;
89 import micropolisj.network.ClientMicropolis;
90 import micropolisj.network.ServerMicropolis;
91 import micropolisj.util.MP3;
92 import micropolisj.util.TranslationTool;
93 import micropolisj.util.Utilities;
94 
95 public class MainWindow extends JFrame implements Micropolis.Listener, EarthquakeListener, KeyEventDispatcher, TempelListener {
96  Micropolis engine;
97  MicropolisDrawingArea drawingArea;
98  JScrollPane drawingAreaScroll;
99  DemandIndicator demandInd;
100  MessagesPane messagesPane;
101  JLabel mapLegendLbl;
102  OverlayMapView mapView;
103  NotificationPane notificationPane;
104  EvaluationPane evaluationPane;
105  GraphsPane graphsPane;
106  JLabel dateLbl;
107  JLabel fundsLbl;
108  JLabel popLbl;
109  JLabel currentToolLbl;
110  JLabel currentToolCostLbl;
111  public Map<MicropolisTool, JToggleButton> toolBtns;
112  EnumMap<MapState, JMenuItem> mapStateMenuItems = new EnumMap<MapState, JMenuItem>(MapState.class);
113  // CUSTOM
114  MP3 backgroundMusic;
115  MicropolisTool currentTool;
116  File currentFile;
117  boolean doSounds = true;
118  boolean dirty1 = false; // indicates if a tool was successfully applied
119  // since last save
120  boolean dirty2 = false; // indicates if simulator took a step since last
121  // save
122  long lastSavedTime = 0; // real-time clock of when file was last saved
123  boolean autoBudgetPending;
124 
125  private static final int SCROLLING_SPEED = 100;
126 
127  static ImageIcon appIcon;
128  static {
129  appIcon = new ImageIcon(MainWindow.class.getResource("/micropolism.png"));
130  }
131 
132  static ResourceBundle strings = ResourceBundle.getBundle("micropolisj.GuiStrings");
133  static final String PRODUCT_NAME = strings.getString("PRODUCT");
134 
135  public MainWindow() {
136  this(new Micropolis());
137  }
138 
139  public MainWindow(Micropolis engine) {
140  setIconImage(appIcon.getImage());
141 
142  backgroundMusic = new MP3(MainWindow.class.getResource("/sounds/" + Sound.BG_MUSIC.getWavName() + ".mp3"), true);
143  backgroundMusic.play();
144 
145  // TODO custom: necessary so sprites can access window size
146  engine.mainWindow = this;
147 
148  engine.getPlayerInfo().researchState.setLocationRelativeTo(this);
149 
150  engine.addTempelListener(this);
151  this.engine = engine;
152 
153 
154  JPanel mainArea = new JPanel(new BorderLayout());
155  add(mainArea, BorderLayout.CENTER);
156 
157  drawingArea = new MicropolisDrawingArea(engine);
158  drawingAreaScroll = new JScrollPane(drawingArea);
159  mainArea.add(drawingAreaScroll);
160 
161  makeMenu();
162  JToolBar tb = makeToolbar();
163  mainArea.add(tb, BorderLayout.WEST);
164 
165  toolBtns.get(MicropolisTool.ROCKET).setVisible(false);
167 
168  Box evalGraphsBox = new Box(BoxLayout.Y_AXIS);
169  mainArea.add(evalGraphsBox, BorderLayout.SOUTH);
170 
171  graphsPane = new GraphsPane(engine);
172  graphsPane.setVisible(false);
173  evalGraphsBox.add(graphsPane);
174 
175  evaluationPane = new EvaluationPane(engine);
176  evaluationPane.setVisible(false);
177  evalGraphsBox.add(evaluationPane, BorderLayout.SOUTH);
178 
179  JPanel leftPane = new JPanel(new GridBagLayout());
180  add(leftPane, BorderLayout.WEST);
181 
182  GridBagConstraints c = new GridBagConstraints();
183  c.gridx = c.gridy = 0;
184  c.anchor = GridBagConstraints.SOUTHWEST;
185  c.insets = new Insets(4, 4, 4, 4);
186  c.weightx = 1.0;
187 
188  demandInd = new DemandIndicator();
189  leftPane.add(demandInd, c);
190 
191  c.gridx = 1;
192  c.weightx = 0.0;
193  c.fill = GridBagConstraints.BOTH;
194  c.insets = new Insets(4, 20, 4, 4);
195 
196  leftPane.add(makeDateFunds(), c);
197 
198  c.gridx = 0;
199  c.gridy = 1;
200  c.gridwidth = 2;
201  c.weighty = 0.0;
202  c.anchor = GridBagConstraints.NORTH;
203  c.insets = new Insets(0, 0, 0, 0);
204 
205  JPanel mapViewContainer = new JPanel(new BorderLayout());
206  mapViewContainer.setBorder(BorderFactory.createLineBorder(Color.BLACK));
207  leftPane.add(mapViewContainer, c);
208 
209  JMenuBar mapMenu = new JMenuBar();
210  mapViewContainer.add(mapMenu, BorderLayout.NORTH);
211 
212  JMenu zonesMenu = new JMenu(strings.getString("menu.zones"));
213  setupKeys(zonesMenu, "menu.zones");
214  mapMenu.add(zonesMenu);
215 
216  zonesMenu.add(makeMapStateMenuItem("menu.zones.ALL", MapState.ALL));
217  zonesMenu.add(makeMapStateMenuItem("menu.zones.RESIDENTIAL", MapState.RESIDENTIAL));
218  zonesMenu.add(makeMapStateMenuItem("menu.zones.COMMERCIAL", MapState.COMMERCIAL));
219  zonesMenu.add(makeMapStateMenuItem("menu.zones.INDUSTRIAL", MapState.INDUSTRIAL));
220  zonesMenu.add(makeMapStateMenuItem("menu.zones.TRANSPORT", MapState.TRANSPORT));
221 
222  JMenu overlaysMenu = new JMenu(strings.getString("menu.overlays"));
223  setupKeys(overlaysMenu, "menu.overlays");
224  mapMenu.add(overlaysMenu);
225 
226  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.POPDEN_OVERLAY", MapState.POPDEN_OVERLAY));
227  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.GROWTHRATE_OVERLAY", MapState.GROWTHRATE_OVERLAY));
228  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.LANDVALUE_OVERLAY", MapState.LANDVALUE_OVERLAY));
229  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.CRIME_OVERLAY", MapState.CRIME_OVERLAY));
230  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.POLLUTE_OVERLAY", MapState.POLLUTE_OVERLAY));
231  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.TRAFFIC_OVERLAY", MapState.TRAFFIC_OVERLAY));
232  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.POWER_OVERLAY", MapState.POWER_OVERLAY));
233  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.FIRE_OVERLAY", MapState.FIRE_OVERLAY));
234  overlaysMenu.add(makeMapStateMenuItem("menu.overlays.POLICE_OVERLAY", MapState.POLICE_OVERLAY));
235 
236  mapMenu.add(Box.createHorizontalGlue());
237  mapLegendLbl = new JLabel();
238  mapMenu.add(mapLegendLbl);
239 
240  mapView = new OverlayMapView(engine);
241  mapView.connectView(drawingArea, drawingAreaScroll);
242  mapViewContainer.add(mapView, BorderLayout.CENTER);
243 
245 
246  c.gridx = 0;
247  c.gridy = 2;
248  c.gridwidth = 2;
249  c.weighty = 1.0;
250  c.fill = GridBagConstraints.BOTH;
251  c.insets = new Insets(0, 0, 0, 0);
252 
253  messagesPane = new MessagesPane();
254  JScrollPane scroll2 = new JScrollPane(messagesPane);
255  scroll2.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
256  scroll2.setPreferredSize(new Dimension(0, 0));
257  scroll2.setMinimumSize(new Dimension(0, 0));
258  leftPane.add(scroll2, c);
259 
260  c.gridy = 3;
261  c.weighty = 0.0;
262  notificationPane = new NotificationPane(engine);
263  leftPane.add(notificationPane, c);
264 
265  pack();
266  setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
267  setLocationRelativeTo(null);
268 
269  InputMap inputMap = ((JComponent) getContentPane()).getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
270  inputMap.put(KeyStroke.getKeyStroke("ADD"), "zoomIn");
271  inputMap.put(KeyStroke.getKeyStroke("shift EQUALS"), "zoomIn");
272  inputMap.put(KeyStroke.getKeyStroke("SUBTRACT"), "zoomOut");
273  inputMap.put(KeyStroke.getKeyStroke("MINUS"), "zoomOut");
274  inputMap.put(KeyStroke.getKeyStroke("ESCAPE"), "escape");
275 
276  ActionMap actionMap = ((JComponent) getContentPane()).getActionMap();
277  actionMap.put("zoomIn", new AbstractAction() {
278  public void actionPerformed(ActionEvent evt) {
279  doZoom(1);
280  }
281  });
282  actionMap.put("zoomOut", new AbstractAction() {
283  public void actionPerformed(ActionEvent evt) {
284  doZoom(-1);
285  }
286  });
287  actionMap.put("escape", new AbstractAction() {
288  public void actionPerformed(ActionEvent evt) {
289  onEscapePressed();
290  }
291  });
292 
293  MouseAdapter mouse = new MouseAdapter() {
294  public void mousePressed(MouseEvent ev) {
295  try {
296  onToolDown(ev);
297  }
298  catch(Throwable e) {
299  showErrorMessage(e);
300  }
301  }
302 
303  public void mouseReleased(MouseEvent ev) {
304  try {
305  onToolUp(ev);
306  }
307  catch(Throwable e) {
308  showErrorMessage(e);
309  }
310  }
311 
312  public void mouseDragged(MouseEvent ev) {
313  try {
314  onToolDrag(ev);
315  }
316  catch(Throwable e) {
317  showErrorMessage(e);
318  }
319  }
320 
321  public void mouseMoved(MouseEvent ev) {
322  try {
323  onToolHover(ev);
324  }
325  catch(Throwable e) {
326  showErrorMessage(e);
327  }
328  }
329 
330  public void mouseExited(MouseEvent ev) {
331  try {
332  onToolExited(ev);
333  }
334  catch(Throwable e) {
335  showErrorMessage(e);
336  }
337  }
338 
339  public void mouseWheelMoved(MouseWheelEvent evt) {
340  try {
341  onMouseWheelMoved(evt);
342  }
343  catch(Throwable e) {
344  showErrorMessage(e);
345  }
346  }
347  };
348  drawingArea.addMouseListener(mouse);
349  drawingArea.addMouseMotionListener(mouse);
350  drawingArea.addMouseWheelListener(mouse);
351 
352  addWindowListener(new WindowAdapter() {
353  public void windowClosing(WindowEvent ev) {
354  closeWindow();
355  }
356 
357  public void windowClosed(WindowEvent ev) {
358  onWindowClosed(ev);
359  }
360  });
361 
362  Preferences prefs = Preferences.userNodeForPackage(MainWindow.class);
363  doSounds = prefs.getBoolean(SOUNDS_PREF, true);
364 
365  // CUSTOM: add cheat listener
366  KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
367  manager.addKeyEventDispatcher(this);
368  // start things up
369  mapView.setEngine(engine);
370  engine.addListener(this);
371  engine.addEarthquakeListener(this);
372  reloadFunds();
373  reloadOptions();
374  startTimer();
375  makeClean();
376 
377  // TODO custom: just a test
378 // engine.getSprites().add(new SpaceshipSprite(engine, 350, 500));
379  }
380 
381  public void setEngine(Micropolis newEngine) {
382  if(engine != null) { // old engine
383  engine.removeListener(this);
384  engine.removeEarthquakeListener(this);
385  }
386 
387  engine = newEngine;
388 
389  if(engine != null) { // new engine
390  engine.addListener(this);
391  engine.addEarthquakeListener(this);
392  }
393 
394  boolean timerEnabled = isTimerActive();
395  if(timerEnabled) {
396  stopTimer();
397  }
398  stopEarthquake();
399 
400  drawingArea.setEngine(engine);
401  mapView.setEngine(engine); // must change mapView after drawingArea
402  evaluationPane.setEngine(engine);
403  demandInd.setEngine(engine);
404  graphsPane.setEngine(engine);
405  reloadFunds();
406  reloadOptions();
407  notificationPane.setVisible(false);
408 
409  if(timerEnabled) {
410  startTimer();
411  }
412  }
413 
414  boolean needsSaved() {
415  if(dirty1) // player has built something since last save
416  return true;
417 
418  if(!dirty2) // no simulator ticks since last save
419  return false;
420 
421  // simulation time has passed since last save, but the player
422  // hasn't done anything. Whether we need to prompt for save
423  // will depend on how much real time has elapsed.
424  // The threshold is 30 seconds.
425 
426  return (System.currentTimeMillis() - lastSavedTime > 30000);
427  }
428 
429  boolean maybeSaveCity() {
430  if(needsSaved()) {
431  boolean timerEnabled = isTimerActive();
432  if(timerEnabled) {
433  stopTimer();
434  }
435 
436  try {
437  int rv = JOptionPane.showConfirmDialog(this, strings.getString("main.save_query"), PRODUCT_NAME,
438  JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
439  if(rv == JOptionPane.CANCEL_OPTION)
440  return false;
441 
442  if(rv == JOptionPane.YES_OPTION) {
443  if(!onSaveCityClicked()) {
444  // canceled save dialog
445  return false;
446  }
447  }
448  }
449  finally {
450  if(timerEnabled) {
451  startTimer();
452  }
453  }
454  }
455  return true;
456  }
457 
458  void closeWindow() {
459  // TODO CUSTOM
460  if(maybeSaveCity()) {
461  backgroundMusic.close();
462  dispose();
463  }
464  }
465 
466  JComponent makeDateFunds() {
467  JPanel pane = new JPanel(new GridBagLayout());
468  GridBagConstraints c0 = new GridBagConstraints();
469  GridBagConstraints c1 = new GridBagConstraints();
470 
471  c0.gridx = 0;
472  c1.gridx = 1;
473  c0.gridy = c1.gridy = 0;
474  c0.weightx = 1.0;
475  c0.weighty = c1.weighty = 1.0;
476  c0.anchor = GridBagConstraints.WEST;
477  c1.anchor = GridBagConstraints.EAST;
478 
479  pane.add(new JLabel(strings.getString("main.date_label")), c0);
480  dateLbl = new JLabel();
481  pane.add(dateLbl, c1);
482 
483  c0.gridy = c1.gridy = 1;
484 
485  pane.add(new JLabel(strings.getString("main.funds_label")), c0);
486  fundsLbl = new JLabel();
487  pane.add(fundsLbl, c1);
488 
489  c0.gridy = c1.gridy = 2;
490 
491  pane.add(new JLabel(strings.getString("main.population_label")), c0);
492  popLbl = new JLabel();
493  pane.add(popLbl, c1);
494 
495  return pane;
496  }
497 
498  private void setupKeys(JMenu menu, String prefix) {
499  if(strings.containsKey(prefix + ".key")) {
500  String mnemonic = strings.getString(prefix + ".key");
501  menu.setMnemonic(KeyStroke.getKeyStroke(mnemonic).getKeyCode());
502  }
503  }
504 
505  private void setupKeys(JMenuItem menuItem, String prefix) {
506  if(strings.containsKey(prefix + ".key")) {
507  String mnemonic = strings.getString(prefix + ".key");
508  menuItem.setMnemonic(KeyStroke.getKeyStroke(mnemonic).getKeyCode());
509  }
510  if(strings.containsKey(prefix + ".shortcut")) {
511  String shortcut = strings.getString(prefix + ".shortcut");
512  menuItem.setAccelerator(KeyStroke.getKeyStroke(shortcut));
513  }
514  }
515 
516  private void makeMenu() {
517  JMenuBar menuBar = new JMenuBar();
518 
519  JMenu gameMenu = new JMenu(strings.getString("menu.game"));
520  setupKeys(gameMenu, "menu.game");
521  menuBar.add(gameMenu);
522 
523  JMenuItem menuItem;
524  menuItem = new JMenuItem(strings.getString("menu.game.new"));
525  setupKeys(menuItem, "menu.game.new");
526  menuItem.addActionListener(wrapActionListener(new ActionListener() {
527  public void actionPerformed(ActionEvent ev) {
529  }
530  }));
531  gameMenu.add(menuItem);
532 
533  menuItem = new JMenuItem(strings.getString("menu.game.load"));
534  setupKeys(menuItem, "menu.game.load");
535  menuItem.addActionListener(wrapActionListener(new ActionListener() {
536  public void actionPerformed(ActionEvent ev) {
538  }
539  }));
540  gameMenu.add(menuItem);
541 
542  menuItem = new JMenuItem(strings.getString("menu.game.save"));
543  setupKeys(menuItem, "menu.game.save");
544  menuItem.addActionListener(wrapActionListener(new ActionListener() {
545  public void actionPerformed(ActionEvent ev) {
547  }
548  }));
549  gameMenu.add(menuItem);
550 
551  menuItem = new JMenuItem(strings.getString("menu.game.save_as"));
552  setupKeys(menuItem, "menu.game.save_as");
553  menuItem.addActionListener(wrapActionListener(new ActionListener() {
554  public void actionPerformed(ActionEvent ev) {
556  }
557  }));
558  gameMenu.add(menuItem);
559 
560  menuItem = new JMenuItem(strings.getString("menu.game.exit"));
561  setupKeys(menuItem, "menu.game.exit");
562  menuItem.addActionListener(wrapActionListener(new ActionListener() {
563  public void actionPerformed(ActionEvent ev) {
564  closeWindow();
565  }
566  }));
567  gameMenu.add(menuItem);
568 
569  JMenu optionsMenu = new JMenu(strings.getString("menu.options"));
570  setupKeys(optionsMenu, "menu.options");
571  menuBar.add(optionsMenu);
572 
573  JMenu levelMenu = new JMenu(strings.getString("menu.difficulty"));
574  setupKeys(levelMenu, "menu.difficulty");
575  optionsMenu.add(levelMenu);
576 
577  difficultyMenuItems = new HashMap<Integer, JMenuItem>();
578  for(int i = GameLevel.MIN_LEVEL; i <= GameLevel.MAX_LEVEL; i++) {
579  final int level = i;
580  menuItem = new JRadioButtonMenuItem(strings.getString("menu.difficulty." + level));
581  setupKeys(menuItem, "menu.difficulty." + level);
582  menuItem.addActionListener(wrapActionListener(new ActionListener() {
583  public void actionPerformed(ActionEvent evt) {
584  onDifficultyClicked(level);
585  }
586  }));
587  levelMenu.add(menuItem);
588  difficultyMenuItems.put(level, menuItem);
589  }
590 
591  autoBudgetMenuItem = new JCheckBoxMenuItem(strings.getString("menu.options.auto_budget"));
592  setupKeys(autoBudgetMenuItem, "menu.options.auto_budget");
593  autoBudgetMenuItem.addActionListener(wrapActionListener(new ActionListener() {
594  public void actionPerformed(ActionEvent ev) {
596  }
597  }));
598  optionsMenu.add(autoBudgetMenuItem);
599 
600  autoBulldozeMenuItem = new JCheckBoxMenuItem(strings.getString("menu.options.auto_bulldoze"));
601  setupKeys(autoBulldozeMenuItem, "menu.options.auto_bulldoze");
602  autoBulldozeMenuItem.addActionListener(wrapActionListener(new ActionListener() {
603  public void actionPerformed(ActionEvent ev) {
605  }
606  }));
607  optionsMenu.add(autoBulldozeMenuItem);
608 
609  disastersMenuItem = new JCheckBoxMenuItem(strings.getString("menu.options.disasters"));
610  setupKeys(disastersMenuItem, "menu.options.disasters");
611  disastersMenuItem.addActionListener(wrapActionListener(new ActionListener() {
612  public void actionPerformed(ActionEvent ev) {
614  }
615  }));
616  optionsMenu.add(disastersMenuItem);
617 
618  soundsMenuItem = new JCheckBoxMenuItem(strings.getString("menu.options.sound"));
619  setupKeys(soundsMenuItem, "menu.options.sound");
620  soundsMenuItem.addActionListener(wrapActionListener(new ActionListener() {
621  public void actionPerformed(ActionEvent ev) {
622  onSoundClicked();
623  }
624  }));
625  optionsMenu.add(soundsMenuItem);
626 
627  menuItem = new JMenuItem(strings.getString("menu.options.zoom_in"));
628  setupKeys(menuItem, "menu.options.zoom_in");
629  menuItem.addActionListener(wrapActionListener(new ActionListener() {
630  public void actionPerformed(ActionEvent ev) {
631  doZoom(1);
632  }
633  }));
634  optionsMenu.add(menuItem);
635 
636  menuItem = new JMenuItem(strings.getString("menu.options.zoom_out"));
637  setupKeys(menuItem, "menu.options.zoom_out");
638  menuItem.addActionListener(wrapActionListener(new ActionListener() {
639  public void actionPerformed(ActionEvent ev) {
640  doZoom(-1);
641  }
642  }));
643  optionsMenu.add(menuItem);
644 
645  JMenu disastersMenu = new JMenu(strings.getString("menu.disasters"));
646  setupKeys(disastersMenu, "menu.disasters");
647  menuBar.add(disastersMenu);
648 
649  menuItem = new JMenuItem(strings.getString("menu.disasters.MONSTER"));
650  setupKeys(menuItem, "menu.disasters.MONSTER");
651  menuItem.addActionListener(wrapActionListener(new ActionListener() {
652  public void actionPerformed(ActionEvent ev) {
654  }
655  }));
656  disastersMenu.add(menuItem);
657 
658  menuItem = new JMenuItem(strings.getString("menu.disasters.FIRE"));
659  setupKeys(menuItem, "menu.disasters.FIRE");
660  menuItem.addActionListener(wrapActionListener(new ActionListener() {
661  public void actionPerformed(ActionEvent ev) {
663  }
664  }));
665  disastersMenu.add(menuItem);
666 
667  menuItem = new JMenuItem(strings.getString("menu.disasters.FLOOD"));
668  setupKeys(menuItem, "menu.disasters.FLOOD");
669  menuItem.addActionListener(wrapActionListener(new ActionListener() {
670  public void actionPerformed(ActionEvent ev) {
672  }
673  }));
674  disastersMenu.add(menuItem);
675 
676  menuItem = new JMenuItem(strings.getString("menu.disasters.MELTDOWN"));
677  setupKeys(menuItem, "menu.disasters.MELTDOWN");
678  menuItem.addActionListener(wrapActionListener(new ActionListener() {
679  public void actionPerformed(ActionEvent ev) {
681  }
682  }));
683  disastersMenu.add(menuItem);
684 
685  menuItem = new JMenuItem(strings.getString("menu.disasters.TORNADO"));
686  setupKeys(menuItem, "menu.disasters.TORNADO");
687  menuItem.addActionListener(wrapActionListener(new ActionListener() {
688  public void actionPerformed(ActionEvent ev) {
690  }
691  }));
692  disastersMenu.add(menuItem);
693 
694  menuItem = new JMenuItem(strings.getString("menu.disasters.EARTHQUAKE"));
695  setupKeys(menuItem, "menu.disasters.EARTHQUAKE");
696  menuItem.addActionListener(wrapActionListener(new ActionListener() {
697  public void actionPerformed(ActionEvent ev) {
699  }
700  }));
701  disastersMenu.add(menuItem);
702 
703  JMenu priorityMenu = new JMenu(strings.getString("menu.speed"));
704  setupKeys(priorityMenu, "menu.speed");
705  menuBar.add(priorityMenu);
706 
707  priorityMenuItems = new EnumMap<Speed, JMenuItem>(Speed.class);
708  menuItem = new JRadioButtonMenuItem(strings.getString("menu.speed.SUPER_FAST"));
709  setupKeys(menuItem, "menu.speed.SUPER_FAST");
710  menuItem.addActionListener(wrapActionListener(new ActionListener() {
711  public void actionPerformed(ActionEvent ev) {
713  }
714  }));
715  priorityMenu.add(menuItem);
716  priorityMenuItems.put(Speed.SUPER_FAST, menuItem);
717 
718  menuItem = new JRadioButtonMenuItem(strings.getString("menu.speed.FAST"));
719  setupKeys(menuItem, "menu.speed.FAST");
720  menuItem.addActionListener(wrapActionListener(new ActionListener() {
721  public void actionPerformed(ActionEvent ev) {
723  }
724  }));
725  priorityMenu.add(menuItem);
726  priorityMenuItems.put(Speed.FAST, menuItem);
727 
728  menuItem = new JRadioButtonMenuItem(strings.getString("menu.speed.NORMAL"));
729  setupKeys(menuItem, "menu.speed.NORMAL");
730  menuItem.addActionListener(wrapActionListener(new ActionListener() {
731  public void actionPerformed(ActionEvent ev) {
733  }
734  }));
735  priorityMenu.add(menuItem);
736  priorityMenuItems.put(Speed.NORMAL, menuItem);
737 
738  menuItem = new JRadioButtonMenuItem(strings.getString("menu.speed.SLOW"));
739  setupKeys(menuItem, "menu.speed.SLOW");
740  menuItem.addActionListener(wrapActionListener(new ActionListener() {
741  public void actionPerformed(ActionEvent ev) {
743  }
744  }));
745  priorityMenu.add(menuItem);
746  priorityMenuItems.put(Speed.SLOW, menuItem);
747 
748  menuItem = new JRadioButtonMenuItem(strings.getString("menu.speed.PAUSED"));
749  setupKeys(menuItem, "menu.speed.PAUSED");
750  menuItem.addActionListener(wrapActionListener(new ActionListener() {
751  public void actionPerformed(ActionEvent ev) {
753  }
754  }));
755  priorityMenu.add(menuItem);
756  priorityMenuItems.put(Speed.PAUSED, menuItem);
757 
758  JMenu windowsMenu = new JMenu(strings.getString("menu.windows"));
759  setupKeys(windowsMenu, "menu.windows");
760  menuBar.add(windowsMenu);
761 
762  menuItem = new JMenuItem(strings.getString("menu.windows.budget"));
763  setupKeys(menuItem, "menu.windows.budget");
764  menuItem.addActionListener(wrapActionListener(new ActionListener() {
765  public void actionPerformed(ActionEvent ev) {
766  onViewBudgetClicked();
767  }
768  }));
769  windowsMenu.add(menuItem);
770 
771  menuItem = new JMenuItem(strings.getString("menu.windows.evaluation"));
772  setupKeys(menuItem, "menu.windows.evaluation");
773  menuItem.addActionListener(wrapActionListener(new ActionListener() {
774  public void actionPerformed(ActionEvent ev) {
775  onViewEvaluationClicked();
776  }
777  }));
778  windowsMenu.add(menuItem);
779 
780  menuItem = new JMenuItem(strings.getString("menu.windows.research"));
781  setupKeys(menuItem, "menu.windows.research");
782  menuItem.addActionListener(wrapActionListener(new ActionListener() {
783  public void actionPerformed(ActionEvent ev) {
784  onViewResearchClicked();
785  }
786  }));
787  windowsMenu.add(menuItem);
788 
789  menuItem = new JMenuItem(strings.getString("menu.windows.graph"));
790  setupKeys(menuItem, "menu.windows.graph");
791  menuItem.addActionListener(wrapActionListener(new ActionListener() {
792  public void actionPerformed(ActionEvent ev) {
793  onViewGraphClicked();
794  }
795  }));
796  windowsMenu.add(menuItem);
797 
798  JMenu helpMenu = new JMenu(strings.getString("menu.help"));
799  setupKeys(helpMenu, "menu.help");
800  menuBar.add(helpMenu);
801 
802  menuItem = new JMenuItem(strings.getString("menu.help.launch-translation-tool"));
803  setupKeys(menuItem, "menu.help.launch-translation-tool");
804  menuItem.addActionListener(new ActionListener() {
805  public void actionPerformed(ActionEvent evt) {
807  }
808  });
809  helpMenu.add(menuItem);
810 
811  menuItem = new JMenuItem(strings.getString("menu.help.about"));
812  setupKeys(menuItem, "menu.help.about");
813  menuItem.addActionListener(wrapActionListener(new ActionListener() {
814  public void actionPerformed(ActionEvent ev) {
815  onAboutClicked();
816  }
817  }));
818  helpMenu.add(menuItem);
819 
820  setJMenuBar(menuBar);
821  }
822 
823  private Micropolis getEngine() {
824  return engine;
825  }
826 
827  JMenuItem autoBudgetMenuItem;
828  JMenuItem autoBulldozeMenuItem;
829  JMenuItem disastersMenuItem;
830  JMenuItem soundsMenuItem;
831  Map<Speed, JMenuItem> priorityMenuItems;
832  Map<Integer, JMenuItem> difficultyMenuItems;
833 
834  private void onAutoBudgetClicked() {
835  dirty1 = true;
837  }
838 
839  private void onAutoBulldozeClicked() {
840  dirty1 = true;
842  }
843 
844  private void onDisastersClicked() {
845  dirty1 = true;
847  }
848 
849  static final String SOUNDS_PREF = "enable_sounds";
850 
851  // TODO CUSTOM: toggle background music
852  private void onSoundClicked() {
853  doSounds = !doSounds;
854  // sounds are now enabled => play background music
855  if(doSounds) {
856  backgroundMusic.play();
857  }
858  // sounds are now disabled => stop background music
859  else {
860  backgroundMusic.close();
861  }
862 
863  Preferences prefs = Preferences.userNodeForPackage(MainWindow.class);
864  prefs.putBoolean(SOUNDS_PREF, doSounds);
865  reloadOptions();
866  }
867 
868  void makeClean() {
869  dirty1 = false;
870  dirty2 = false;
871  lastSavedTime = System.currentTimeMillis();
872  if(currentFile != null) {
873  String fileName = currentFile.getName();
874  if(fileName.endsWith("." + EXTENSION)) {
875  fileName = fileName.substring(0, fileName.length() - 1 - EXTENSION.length());
876  }
877 // setTitle(MessageFormat.format(strings.getString("main.caption_named_city"), fileName));
878  setTitle(engine instanceof ServerMicropolis ? "Methapolis - Server - " + fileName : "Methapolis - Client - " + fileName);
879  }
880  else {
881 // setTitle(strings.getString("main.caption_unnamed_city"));
882  setTitle(engine instanceof ServerMicropolis ? "Methapolis - Server" : "Methapolis - Client");
883  }
884  }
885 
886  private boolean onSaveCityClicked() {
887  if(currentFile == null) {
888  return onSaveCityAsClicked();
889  }
890 
891  try {
892  // TODO game is saved here
893  getEngine().save(currentFile);
894  makeClean();
895  return true;
896  }
897  catch(IOException e) {
898  e.printStackTrace(System.err);
899  JOptionPane.showMessageDialog(this, e, strings.getString("main.error_caption"), JOptionPane.ERROR_MESSAGE);
900  return false;
901  }
902  }
903 
904  static final String EXTENSION = "cty";
905 
906  private boolean onSaveCityAsClicked() {
907  boolean timerEnabled = isTimerActive();
908  if(timerEnabled) {
909  stopTimer();
910  }
911  try {
912  JFileChooser fc = new JFileChooser();
913  FileNameExtensionFilter filter1 = new FileNameExtensionFilter(strings.getString("cty_file"), EXTENSION);
914  fc.setFileFilter(filter1);
915  int rv = fc.showSaveDialog(this);
916  if(rv == JFileChooser.APPROVE_OPTION) {
917  currentFile = fc.getSelectedFile();
918  if(!currentFile.getName().endsWith("." + EXTENSION)) {
919  currentFile = new File(currentFile.getPath() + "." + EXTENSION);
920  }
921  // TODO game is saved here
922  getEngine().save(currentFile);
923  makeClean();
924  return true;
925  }
926  }
927  catch(Exception e) {
928  e.printStackTrace(System.err);
929  JOptionPane.showMessageDialog(this, e, strings.getString("main.error_caption"), JOptionPane.ERROR_MESSAGE);
930  }
931  finally {
932  if(timerEnabled) {
933  startTimer();
934  }
935  }
936  return false;
937  }
938 
939  public void onLoadGameClicked() {
940  // check if user wants to save their current city
941  if(!maybeSaveCity()) {
942  return;
943  }
944 
945  boolean timerEnabled = isTimerActive();
946  if(timerEnabled) {
947  stopTimer();
948  }
949 
950  try {
951  JFileChooser fc = new JFileChooser();
952  FileNameExtensionFilter filter1 = new FileNameExtensionFilter(strings.getString("cty_file"), EXTENSION);
953  fc.setFileFilter(filter1);
954 
955  assert !isTimerActive();
956 
957  int rv = fc.showOpenDialog(this);
958  if(rv == JFileChooser.APPROVE_OPTION) {
959  File file = fc.getSelectedFile();
960  // TODO custom game is loaded here
961  Micropolis oldEngine = getEngine();
962  Micropolis newEngine;
963  if(oldEngine instanceof ServerMicropolis) {
964  System.out.println("Creating new server");
965  ServerMicropolis oldServer = (ServerMicropolis) oldEngine;
966  newEngine = new ServerMicropolis(oldServer.getServer(), oldServer.getPlayerInfos());
967  }
968  else if(oldEngine instanceof ClientMicropolis) {
969  newEngine = new ClientMicropolis( ( (ClientMicropolis) oldEngine ).getPlayerIP() );
970  }
971  else {
972  newEngine = new Micropolis();
973 
974  }
975  newEngine.load(file);
976  setEngine(newEngine);
977  currentFile = file;
978  makeClean();
979  }
980  }
981  catch(Exception e) {
982  e.printStackTrace(System.err);
983  JOptionPane.showMessageDialog(this, e, strings.getString("main.error_caption"), JOptionPane.ERROR_MESSAGE);
984  }
985  finally {
986  if(timerEnabled) {
987  startTimer();
988  }
989  }
990  }
991 
992  private JToggleButton makeToolBtn(final MicropolisTool tool) {
993  // System.out.println("tool." + tool.name() + ".icon");
994  String iconName = strings.containsKey("tool." + tool.name() + ".icon") ? strings.getString("tool." + tool.name()
995  + ".icon") : "/graphics/tools/" + tool.name().toLowerCase() + ".png";
996  String iconSelectedName = strings.containsKey("tool." + tool.name() + ".selected_icon") ? strings.getString("tool."
997  + tool.name() + ".selected_icon") : iconName;
998  String tipText = strings.containsKey("tool." + tool.name() + ".tip") ? strings.getString("tool." + tool.name() + ".tip")
999  : tool.name();
1000 
1001  JToggleButton btn = new JToggleButton();
1002 // System.out.println(iconName);
1003  btn.setIcon(new ImageIcon(MainWindow.class.getResource(iconName)));
1004  btn.setSelectedIcon(new ImageIcon(MainWindow.class.getResource(iconSelectedName)));
1005  btn.setToolTipText(tipText);
1006  btn.setMargin(new Insets(0, 0, 0, 0));
1007  btn.setBorderPainted(false);
1008  btn.addActionListener(new ActionListener() {
1009  public void actionPerformed(ActionEvent ev) {
1010  selectTool(tool);
1011  }
1012  });
1013  toolBtns.put(tool, btn);
1014  return btn;
1015  }
1016 
1017  private JToolBar makeToolbar() {
1018  toolBtns = new EnumMap<MicropolisTool, JToggleButton>(MicropolisTool.class);
1019 
1020  JToolBar toolBar = new JToolBar(strings.getString("main.tools_caption"), JToolBar.VERTICAL);
1021  toolBar.setFloatable(false);
1022  toolBar.setRollover(false);
1023 
1024  JPanel gridBox = new JPanel(new GridBagLayout());
1025  toolBar.add(gridBox);
1026 
1027  GridBagConstraints c = new GridBagConstraints();
1028  c.gridx = c.gridy = 0;
1029  c.anchor = GridBagConstraints.NORTH;
1030  c.insets = new Insets(8, 0, 0, 0);
1031  currentToolLbl = new JLabel(" ");
1032  gridBox.add(currentToolLbl, c);
1033 
1034  c.gridy = 1;
1035  c.insets = new Insets(0, 0, 12, 0);
1036  currentToolCostLbl = new JLabel(" ");
1037  gridBox.add(currentToolCostLbl, c);
1038 
1039  c.gridy++;
1040  c.fill = GridBagConstraints.NONE;
1041  c.weightx = 1.0;
1042  c.insets = new Insets(0, 0, 0, 0);
1043  Box b0 = new Box(BoxLayout.X_AXIS);
1044  gridBox.add(b0, c);
1045 
1047  b0.add(makeToolBtn(MicropolisTool.WIRE));
1048  b0.add(makeToolBtn(MicropolisTool.PARK));
1049 
1050  c.gridy++;
1051  Box b1 = new Box(BoxLayout.X_AXIS);
1052  gridBox.add(b1, c);
1053 
1054  b1.add(makeToolBtn(MicropolisTool.ROADS));
1055  b1.add(makeToolBtn(MicropolisTool.RAIL));
1056 
1057  c.gridy++;
1058  Box b2 = new Box(BoxLayout.X_AXIS);
1059  gridBox.add(b2, c);
1060 
1064 
1065  c.gridy++;
1066  Box b3 = new Box(BoxLayout.X_AXIS);
1067  gridBox.add(b3, c);
1068 
1069  b3.add(makeToolBtn(MicropolisTool.FIRE));
1070  b3.add(makeToolBtn(MicropolisTool.QUERY));
1072 
1073  c.gridy++;
1074  Box b4 = new Box(BoxLayout.X_AXIS);
1075  gridBox.add(b4, c);
1076 
1080 
1081  c.gridy++;
1082  Box b5 = new Box(BoxLayout.X_AXIS);
1083  gridBox.add(b5, c);
1084 
1087 
1088 
1089  c.gridy++;
1090  Box b6 = new Box(BoxLayout.X_AXIS);
1091  gridBox.add(b6, c);
1092 
1094 
1095  c.gridy++;
1096  Box b7 = new Box(BoxLayout.X_AXIS);
1097  gridBox.add(b7, c);
1098 
1100 
1101  c.gridy++;
1102  Box b8 = new Box(BoxLayout.X_AXIS);
1104  gridBox.add(b7, c);
1105 
1106  // add glue to make all elements align toward top
1107  c.gridy++;
1108  c.weighty = 1.0;
1109  gridBox.add(new JLabel(), c);
1110 
1111  return toolBar;
1112  }
1113 
1114  private void selectTool(MicropolisTool newTool) {
1115  toolBtns.get(newTool).setSelected(true);
1116  if(newTool == currentTool) {
1117  return;
1118  }
1119 
1120  if(currentTool != null) {
1121  toolBtns.get(currentTool).setSelected(false);
1122  }
1123 
1124  currentTool = newTool;
1125 
1126  currentToolLbl.setText(strings.containsKey("tool." + currentTool.name() + ".name") ? strings.getString("tool."
1127  + currentTool.name() + ".name") : currentTool.name());
1128 
1129  int cost = currentTool.getToolCost();
1130  currentToolCostLbl.setText(cost != 0 ? formatFunds(cost) : " ");
1131  }
1132 
1133  private void onNewCityClicked() {
1134  if(maybeSaveCity()) {
1135  doNewCity(false);
1136  }
1137  }
1138 
1139  public void doNewCity(boolean firstTime) {
1140  boolean timerEnabled = isTimerActive();
1141  if(timerEnabled) {
1142  stopTimer();
1143  }
1144 
1145  new NewCityDialog(this, !firstTime, engine).setVisible(true);
1146 
1147  if(timerEnabled) {
1148  startTimer();
1149  }
1150  }
1151 
1152  void doQueryTool(int xpos, int ypos) {
1153  if(!engine.testBounds(xpos, ypos))
1154  return;
1155 
1156  ZoneStatus z = engine.queryZoneStatus(xpos, ypos);
1157  notificationPane.showZoneStatus(engine, xpos, ypos, z);
1158  }
1159 
1160  private void doZoom(int dir, Point mousePt) {
1161  int oldZoom = drawingArea.getTileSize();
1162  int newZoom = dir < 0 ? (oldZoom / 2) : (oldZoom * 2);
1163  if(newZoom < 8) {
1164  newZoom = 8;
1165  }
1166  if(newZoom > 32) {
1167  newZoom = 32;
1168  }
1169 
1170  if(oldZoom != newZoom) {
1171  // preserve effective mouse position in viewport when changing zoom
1172  // level
1173  double f = (double) newZoom / (double) oldZoom;
1174  Point pos = drawingAreaScroll.getViewport().getViewPosition();
1175  int newX = (int) Math.round(mousePt.x * f - (mousePt.x - pos.x));
1176  int newY = (int) Math.round(mousePt.y * f - (mousePt.y - pos.y));
1177  drawingArea.selectTileSize(newZoom);
1178  drawingAreaScroll.validate();
1179  drawingAreaScroll.getViewport().setViewPosition(new Point(newX, newY));
1180  }
1181  }
1182 
1183  private void doZoom(int dir) {
1184  Rectangle rect = drawingAreaScroll.getViewport().getViewRect();
1185  doZoom(dir, new Point(rect.x + rect.width / 2, rect.y + rect.height / 2));
1186  }
1187 
1188  private void onMouseWheelMoved(MouseWheelEvent evt) {
1189  if(evt.getWheelRotation() < 0) {
1190  doZoom(1, evt.getPoint());
1191  }
1192  else {
1193  doZoom(-1, evt.getPoint());
1194  }
1195  }
1196 
1197  // used when a tool is being pressed
1198  ToolStroke toolStroke;
1199 
1200  // where the tool was last applied during the current drag
1201  int lastX;
1202  int lastY;
1203 
1204  private void onToolDown(MouseEvent ev) {
1205  if(ev.getButton() == MouseEvent.BUTTON3) {
1206  CityLocation loc = drawingArea.getCityLocation(ev.getX(), ev.getY());
1207  doQueryTool(loc.x, loc.y);
1208  return;
1209  }
1210 
1211  if(ev.getButton() != MouseEvent.BUTTON1)
1212  return;
1213 
1214  if(currentTool == null)
1215  return;
1216 
1217  CityLocation loc = drawingArea.getCityLocation(ev.getX(), ev.getY());
1218  int x = loc.x;
1219  int y = loc.y;
1220 
1221  if(currentTool == MicropolisTool.QUERY) {
1222  doQueryTool(x, y);
1223  this.toolStroke = null;
1224  }
1225  else {
1226  this.toolStroke = currentTool.beginStroke(engine, x, y);
1227  previewTool();
1228  }
1229 
1230  this.lastX = x;
1231  this.lastY = y;
1232  }
1233 
1234  private void onToolUp(MouseEvent ev) {
1235  if(toolStroke != null) {
1236 
1237  // prevent toolStroke.apply() being done 2x (same with onToolDrag())
1238  if(currentTool == MicropolisTool.ROCKET) {
1239  return;
1240  }
1241 
1242  drawingArea.setToolPreview(null);
1243 
1244  CityLocation loc = toolStroke.getLocation();
1245  // Custom: for client-send
1246  if(engine instanceof ClientMicropolis) {
1247  ((ClientMicropolis) engine).toolUsed(toolStroke);
1248  toolStroke = null;
1249  return;
1250  }
1251  ToolResult tr = toolStroke.apply();
1252  showToolResult(loc, tr);
1253  toolStroke = null;
1254  }
1255 
1256  onToolHover(ev);
1257 
1258  if(autoBudgetPending) {
1259  autoBudgetPending = false;
1260  showBudgetWindow(true);
1261  }
1262  }
1263 
1264  private void onEscapePressed() {
1265  // if currently dragging a tool...
1266  if(toolStroke != null) {
1267  // cancel the current mouse operation
1268  toolStroke = null;
1269  drawingArea.setToolPreview(null);
1270  drawingArea.setToolCursor(null);
1271  }
1272  else {
1273  // dismiss any alerts currently visible
1274  notificationPane.setVisible(false);
1275  }
1276  }
1277 
1278  void previewTool() {
1279  assert this.toolStroke != null;
1280  assert this.currentTool != null;
1281 
1282  drawingArea.setToolCursor(toolStroke.getBounds(), currentTool);
1283  drawingArea.setToolPreview(toolStroke.getPreview());
1284  }
1285 
1286  private void onToolDrag(MouseEvent ev) {
1287  // prevent toolStroke.apply() being done 2x (same with onToolUp())
1288  if(currentTool == null || currentTool == MicropolisTool.ROCKET) {
1289  return;
1290  }
1291 
1292  if((ev.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0)
1293  return;
1294 
1295  CityLocation loc = drawingArea.getCityLocation(ev.getX(), ev.getY());
1296  int x = loc.x;
1297  int y = loc.y;
1298  if(x == lastX && y == lastY)
1299  return;
1300 
1301  if(toolStroke != null) {
1302  toolStroke.dragTo(x, y);
1303  previewTool();
1304  }
1305  else if(currentTool == MicropolisTool.QUERY) {
1306  doQueryTool(x, y);
1307  }
1308 
1309  lastX = x;
1310  lastY = y;
1311  }
1312 
1313  private void onToolHover(MouseEvent ev) {
1314  if(currentTool == null || currentTool == MicropolisTool.QUERY) {
1315  drawingArea.setToolCursor(null);
1316  return;
1317  }
1318 
1319  CityLocation loc = drawingArea.getCityLocation(ev.getX(), ev.getY());
1320  int x = loc.x;
1321  int y = loc.y;
1322  int w = currentTool.getWidth();
1323  int h = currentTool.getHeight();
1324 
1325  if(w >= 3)
1326  x--;
1327  if(h >= 3)
1328  y--;
1329 
1330  drawingArea.setToolCursor(new CityRect(x, y, w, h), currentTool);
1331  }
1332 
1333  private void onToolExited(MouseEvent ev) {
1334  drawingArea.setToolCursor(null);
1335  }
1336 
1337  public void showToolResult(CityLocation loc, ToolResult result) {
1338  switch(result) {
1339  case SUCCESS:
1340  citySound(currentTool == MicropolisTool.BULLDOZER ? Sound.BULLDOZE : Sound.BUILD, loc);
1341  dirty1 = true;
1342  break;
1343 
1344  case NONE:
1345  break;
1346  case UH_OH:
1348  citySound(Sound.UHUH, loc);
1349  break;
1350 
1351  case INSUFFICIENT_FUNDS:
1353  citySound(Sound.SORRY, loc);
1354  break;
1355 
1356  default:
1357  assert false;
1358  }
1359  }
1360 
1361  public static String formatFunds(int funds) {
1362  return MessageFormat.format(strings.getString("funds"), funds);
1363  }
1364 
1365  public static String formatGameDate(int cityTime) {
1366  Calendar c = Calendar.getInstance();
1367  c.set(1900 + cityTime / 48, (cityTime % 48) / 4, (cityTime % 4) * 7 + 1);
1368 
1369  return MessageFormat.format(strings.getString("citytime"), c.getTime());
1370  }
1371 
1372  private void updateDateLabel() {
1373  dateLbl.setText(formatGameDate(engine.cityTime));
1374 
1375  NumberFormat nf = NumberFormat.getInstance();
1376  popLbl.setText(nf.format(getEngine().getCityPopulation(engine.getPlayerID())));
1377  }
1378 
1379  Timer simTimer;
1380  Timer shakeTimer;
1381 
1382  private void startTimer() {
1383  final Micropolis engine = getEngine();
1384  final int count = engine.simSpeed.simStepsPerUpdate;
1385 
1386  assert !isTimerActive();
1387 
1388  if(engine.simSpeed == Speed.PAUSED)
1389  return;
1390 
1391  if(currentEarthquake != null) {
1392  int interval = 3000 / MicropolisDrawingArea.SHAKE_STEPS;
1393  shakeTimer = new Timer(interval, new ActionListener() {
1394  public void actionPerformed(ActionEvent evt) {
1395  currentEarthquake.oneStep();
1396  if(currentEarthquake.count == 0) {
1397  stopTimer();
1398  currentEarthquake = null;
1399  startTimer();
1400  }
1401  }
1402  });
1403  shakeTimer.start();
1404  return;
1405  }
1406 
1407  ActionListener taskPerformer = new ActionListener() {
1408  public void actionPerformed(ActionEvent evt) {
1409  for(int i = 0; i < count; i++) {
1410  engine.animate();
1411  if(!engine.autoBudget && engine.isBudgetTime()) {
1412  showAutoBudget();
1413  return;
1414  }
1415  }
1416  updateDateLabel();
1417  dirty2 = true;
1418  }
1419  };
1420  taskPerformer = wrapActionListener(taskPerformer);
1421 
1422  assert simTimer == null;
1423  simTimer = new Timer(engine.simSpeed.animationDelay, taskPerformer);
1424  simTimer.start();
1425  }
1426 
1427  ActionListener wrapActionListener(final ActionListener l) {
1428  return new ActionListener() {
1429  public void actionPerformed(ActionEvent evt) {
1430  try {
1431  l.actionPerformed(evt);
1432  }
1433  catch(Throwable e) {
1434  showErrorMessage(e);
1435  }
1436  }
1437  };
1438  }
1439 
1440  private void showErrorMessage(Throwable e) {
1441  StringWriter w = new StringWriter();
1442  e.printStackTrace(new PrintWriter(w));
1443 
1444  JTextPane stackTracePane = new JTextPane();
1445  stackTracePane.setEditable(false);
1446  stackTracePane.setText(w.toString());
1447 
1448  final JScrollPane detailsPane = new JScrollPane(stackTracePane);
1449  detailsPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
1450  detailsPane.setPreferredSize(new Dimension(480, 240));
1451  detailsPane.setMinimumSize(new Dimension(0, 0));
1452 
1453  int rv = JOptionPane.showOptionDialog(
1454  this,
1455  e,
1456  strings.getString("main.error_unexpected"),
1457  JOptionPane.DEFAULT_OPTION,
1458  JOptionPane.ERROR_MESSAGE,
1459  null,
1460  new String[] {
1461  strings.getString("main.error_show_stacktrace"), strings.getString("main.error_close"),
1462  strings.getString("main.error_shutdown")
1463  }, 1);
1464  if(rv == 0) {
1465  JOptionPane.showMessageDialog(this, detailsPane, strings.getString("main.error_unexpected"),
1466  JOptionPane.ERROR_MESSAGE);
1467  }
1468  if(rv == 2) {
1469  rv = JOptionPane.showConfirmDialog(this, strings.getString("error.shutdown_query"),
1470  strings.getString("main.error_unexpected"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
1471  if(rv == JOptionPane.OK_OPTION) {
1472  System.exit(1);
1473  }
1474  }
1475  }
1476 
1477  class EarthquakeStepper {
1478  int count = 0;
1479 
1480  void oneStep() {
1481  count = (count + 1) % MicropolisDrawingArea.SHAKE_STEPS;
1482  drawingArea.shake(count);
1483  }
1484  }
1485 
1486  EarthquakeStepper currentEarthquake;
1487 
1488  // implements EarthquakeListener
1489  public void earthquakeStarted() {
1490  if(isTimerActive()) {
1491  stopTimer();
1492  }
1493 
1494  currentEarthquake = new EarthquakeStepper();
1495  currentEarthquake.oneStep();
1496  startTimer();
1497  }
1498 
1499  void stopEarthquake() {
1500  drawingArea.shake(0);
1501  currentEarthquake = null;
1502  }
1503 
1504  private void stopTimer() {
1505  assert isTimerActive();
1506 
1507  if(simTimer != null) {
1508  simTimer.stop();
1509  simTimer = null;
1510  }
1511  if(shakeTimer != null) {
1512  shakeTimer.stop();
1513  shakeTimer = null;
1514  }
1515  }
1516 
1517  boolean isTimerActive() {
1518  return simTimer != null || shakeTimer != null;
1519  }
1520 
1521  private void onWindowClosed(WindowEvent ev) {
1522  if(isTimerActive()) {
1523  stopTimer();
1524  }
1525  }
1526 
1527  private void onDifficultyClicked(int newDifficulty) {
1528  getEngine().setGameLevel(newDifficulty);
1529  }
1530 
1531  private void onPriorityClicked(Speed newSpeed) {
1532  if(isTimerActive()) {
1533  stopTimer();
1534  }
1535 
1536  getEngine().setSpeed(newSpeed);
1537  startTimer();
1538  }
1539 
1540  private void onInvokeDisasterClicked(Disaster disaster) {
1541  dirty1 = true;
1542  switch(disaster) {
1543  case FIRE:
1544  getEngine().makeFire();
1545  break;
1546  case FLOOD:
1547  getEngine().makeFlood();
1548  break;
1549  case MONSTER:
1550  getEngine().makeMonster();
1551  break;
1552  case MELTDOWN:
1553  if(!getEngine().makeMeltdown()) {
1555  }
1556  break;
1557  case TORNADO:
1558  getEngine().makeTornado();
1559  break;
1560  case EARTHQUAKE:
1562  break;
1563  default:
1564  assert false; // unknown disaster
1565  }
1566  }
1567 
1568  private void reloadFunds() {
1569  fundsLbl.setText(formatFunds(getEngine().getPlayerInfo().budget.totalFunds));
1570  }
1571 
1572  // implements Micropolis.Listener
1574  messagesPane.appendCityMessage(m);
1575 
1576  if(m.useNotificationPane && p != null) {
1577  notificationPane.showMessage(engine, m, p.x, p.y);
1578  }
1579  }
1580 
1581  // implements Micropolis.Listener
1582  public void fundsChanged() {
1583  reloadFunds();
1584  }
1585 
1586  // implements Micropolis.Listener
1587  public void optionsChanged() {
1588  reloadOptions();
1589  }
1590 
1591  private void reloadOptions() {
1592  autoBudgetMenuItem.setSelected(getEngine().autoBudget);
1593  autoBulldozeMenuItem.setSelected(getEngine().autoBulldoze);
1594  disastersMenuItem.setSelected(!getEngine().noDisasters);
1595  soundsMenuItem.setSelected(doSounds);
1596  for(Speed spd : priorityMenuItems.keySet()) {
1597  priorityMenuItems.get(spd).setSelected(getEngine().simSpeed == spd);
1598  }
1599  for(int i = GameLevel.MIN_LEVEL; i <= GameLevel.MAX_LEVEL; i++) {
1600  difficultyMenuItems.get(i).setSelected(getEngine().gameLevel == i);
1601  }
1602  }
1603 
1604  // implements Micropolis.Listener
1605  // TODO bg music??
1606  public void citySound(Sound sound, CityLocation loc) {
1607  if(!doSounds)
1608  return;
1609 
1610  URL afile = sound.getAudioFile();
1611  if(afile == null)
1612  return;
1613  System.out.println(afile);
1614 
1615  boolean isOnScreen = drawingAreaScroll.getViewport().getViewRect().contains(drawingArea.getTileBounds(loc.x, loc.y));
1616  if(sound == Sound.HONKHONK_LOW && !isOnScreen)
1617  return;
1618 
1619  try {
1620  System.out.println(afile);
1621  Clip clip = AudioSystem.getClip();
1622  AudioInputStream audioStream = AudioSystem.getAudioInputStream(afile);
1623  System.out.println(audioStream.getFormat());
1624  clip.open(audioStream);
1625  clip.start();
1626  }
1627  catch(Exception e) {
1628  e.printStackTrace(System.err);
1629  }
1630  }
1631 
1632  // implements Micropolis.Listener
1633  public void censusChanged() {
1634  }
1635 
1636  public void demandChanged() {
1637  }
1638 
1639  public void evaluationChanged() {
1640  }
1641 
1642  void onViewBudgetClicked() {
1643  dirty1 = true;
1644  showBudgetWindow(false);
1645  }
1646 
1647  void onViewEvaluationClicked() {
1648  evaluationPane.setVisible(true);
1649  }
1650 
1651  void onViewResearchClicked() {
1653  //dirty1 = true;
1654  //showBudgetWindow(false);
1655  }
1656 
1657  void onViewGraphClicked() {
1658  graphsPane.setVisible(true);
1659  }
1660 
1661  private void showAutoBudget() {
1662  if(toolStroke == null) {
1663  showBudgetWindow(true);
1664  }
1665  else {
1666  autoBudgetPending = true;
1667  }
1668  }
1669 
1670  private void showBudgetWindow(boolean isEndOfYear) {
1671  boolean timerEnabled = isTimerActive();
1672  if(timerEnabled) {
1673  stopTimer();
1674  }
1675 
1676  BudgetDialog dlg = new BudgetDialog(this, getEngine());
1677  dlg.setModal(true);
1678  dlg.setVisible(true);
1679 
1680  if(timerEnabled) {
1681  startTimer();
1682  }
1683  }
1684 
1685  private JMenuItem makeMapStateMenuItem(String stringPrefix, final MapState state) {
1686  String caption = strings.getString(stringPrefix);
1687  JMenuItem menuItem = new JRadioButtonMenuItem(caption);
1688  setupKeys(menuItem, stringPrefix);
1689  menuItem.addActionListener(new ActionListener() {
1690  public void actionPerformed(ActionEvent evt) {
1691  setMapState(state);
1692  }
1693  });
1694  mapStateMenuItems.put(state, menuItem);
1695  return menuItem;
1696  }
1697 
1698  private void setMapState(MapState state) {
1699  mapStateMenuItems.get(mapView.getMapState()).setSelected(false);
1700  mapStateMenuItems.get(state).setSelected(true);
1701  mapView.setMapState(state);
1702  setMapLegend(state);
1703  }
1704 
1705  private void setMapLegend(MapState state) {
1706  String k = "legend_image." + state.name();
1707  java.net.URL iconUrl = null;
1708  if(strings.containsKey(k)) {
1709  String iconName = strings.getString(k);
1710  iconUrl = MainWindow.class.getResource(iconName);
1711  }
1712  if(iconUrl != null) {
1713  mapLegendLbl.setIcon(new ImageIcon(iconUrl));
1714  }
1715  else {
1716  mapLegendLbl.setIcon(null);
1717  }
1718  }
1719 
1721  if(maybeSaveCity()) {
1722  dispose();
1723  TranslationTool tt = new TranslationTool();
1724  tt.setVisible(true);
1725  }
1726  }
1727 
1728  private void onAboutClicked() {
1729  String version = getClass().getPackage().getImplementationVersion();
1730  String versionStr = MessageFormat.format(strings.getString("main.version_string"), version);
1731  versionStr = versionStr.replace("%java.version%", System.getProperty("java.version"));
1732  versionStr = versionStr.replace("%java.vendor%", System.getProperty("java.vendor"));
1733 
1734  JLabel appNameLbl = new JLabel(versionStr);
1735  JLabel appDetailsLbl = new JLabel(strings.getString("main.about_text"));
1736  JComponent[] inputs = new JComponent[] {
1737  appNameLbl, appDetailsLbl
1738  };
1739  JOptionPane.showMessageDialog(this, inputs, strings.getString("main.about_caption"), JOptionPane.PLAIN_MESSAGE, appIcon);
1740  }
1741 
1745  @Override
1746  public boolean dispatchKeyEvent(KeyEvent e) {
1747  if(e.getID() == KeyEvent.KEY_PRESSED) {
1748  // ENTER --> cheatWindow
1749  if(e.getKeyCode() == KeyEvent.VK_ENTER) {
1750  if(this.isFocused()) {
1751  new CheatWindow(engine, toolBtns);
1752  return true;
1753  }
1754  // USE SCROLLING_SPEED pixels in the given direction
1755  }
1756  if(e.getKeyCode() == KeyEvent.VK_LEFT) {
1757  drawingArea.scrollRectToVisible(Utilities.moveRectangle(drawingArea.getVisibleRect(), -SCROLLING_SPEED, 0));
1758  return true;
1759  }
1760  if(e.getKeyCode() == KeyEvent.VK_RIGHT) {
1761  drawingArea.scrollRectToVisible(Utilities.moveRectangle(drawingArea.getVisibleRect(), SCROLLING_SPEED, 0));
1762  return true;
1763  }
1764  if(e.getKeyCode() == KeyEvent.VK_UP) {
1765  drawingArea.scrollRectToVisible(Utilities.moveRectangle(drawingArea.getVisibleRect(), 0, -SCROLLING_SPEED));
1766  return true;
1767  }
1768  if(e.getKeyCode() == KeyEvent.VK_DOWN) {
1769  drawingArea.scrollRectToVisible(Utilities.moveRectangle(drawingArea.getVisibleRect(), 0, SCROLLING_SPEED));
1770  return true;
1771  }
1772  }
1773  return false;
1774  }
1775 
1776  @Override
1777  public void onCountdown(int count) {
1778  String msg = String.format(strings.getString("notification.alien_countdown"), count);
1779  this.messagesPane.appendMessageText(msg);//"countdown to aliens : " + Integer.toString(count) + " weeks");
1780  }
1781 
1782  @Override
1783  public void onEnd() {
1784  EndGamePane endGamePane = new EndGamePane(this);
1785  endGamePane.setLocationRelativeTo(this);
1786  }
1787 
1788 }