Methapolis  0.27
 All Classes Namespaces Files Functions Variables Enumerator
MicropolisDrawingArea.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.*;
12 import java.awt.event.*;
13 import java.awt.image.*;
14 import java.net.URL;
15 import java.util.*;
16 import javax.swing.*;
17 import javax.swing.event.*;
18 import javax.swing.Timer;
19 
20 import micropolisj.engine.*;
21 import static micropolisj.engine.TileConstants.*;
22 import static micropolisj.gui.ColorParser.parseColor;
23 
24 public class MicropolisDrawingArea extends JComponent
25  implements Scrollable, MapListener
26 {
27  Micropolis m;
28  boolean blinkUnpoweredZones = true;
29  HashSet<Point> unpoweredZones = new HashSet<Point>();
30  boolean blink;
31  Timer blinkTimer;
32  ToolCursor toolCursor;
33  ToolPreview toolPreview;
34  int shakeStep;
35 
36  static final Dimension PREFERRED_VIEWPORT_SIZE = new Dimension(640,640);
37  static final ResourceBundle strings = MainWindow.strings;
38 
39  static final int DEFAULT_TILE_SIZE = 16;
40  TileImages tileImages;
41  int TILE_WIDTH;
42  int TILE_HEIGHT;
43  int dragX, dragY;
44  boolean dragging;
45 
47  {
48  this.m = engine;
49  selectTileSize(DEFAULT_TILE_SIZE);
50  m.addMapListener(this);
51 
52  addAncestorListener(new AncestorListener() {
53  public void ancestorAdded(AncestorEvent evt) {
54  startBlinkTimer();
55  }
56  public void ancestorRemoved(AncestorEvent evt) {
57  stopBlinkTimer();
58  }
59  public void ancestorMoved(AncestorEvent evt) {}
60  });
61 
62  addMouseListener(new MouseListener() {
63 
64  @Override
65  public void mousePressed(MouseEvent e) {
66  if(e.getButton()==MouseEvent.BUTTON2)
67  startDrag(e.getX(), e.getY());
68  }
69 
70  @Override
71  public void mouseReleased(MouseEvent e) {
72  if(e.getButton()==MouseEvent.BUTTON2)
73  endDrag(e.getX(), e.getY());
74  }
75 
76  @Override
77  public void mouseEntered(MouseEvent e) {
78  }
79 
80  @Override
81  public void mouseExited(MouseEvent e) {
82  }
83 
84  @Override
85  public void mouseClicked(MouseEvent e) {
86  }
87  });
88 
89  addMouseMotionListener(new MouseMotionListener() {
90 
91  @Override
92  public void mouseMoved(MouseEvent e) {
93  }
94 
95  @Override
96  public void mouseDragged(MouseEvent e) {
97  if(dragging)
98  continueDrag(e.getX(), e.getY());
99  }
100  });
101  }
102 
103  public void selectTileSize(int newTileSize)
104  {
105  tileImages = TileImages.getInstance(newTileSize);
106  TILE_WIDTH = tileImages.TILE_WIDTH;
107  TILE_HEIGHT = tileImages.TILE_HEIGHT;
108  revalidate();
109  }
110 
111  public int getTileSize()
112  {
113  return TILE_WIDTH;
114  }
115 
116  public CityLocation getCityLocation(int x, int y)
117  {
118  return new CityLocation(x / TILE_WIDTH, y / TILE_HEIGHT);
119  }
120 
121  @Override
122  public Dimension getPreferredSize()
123  {
124  assert this.m != null;
125 
126  return new Dimension(TILE_WIDTH*m.getWidth(),TILE_HEIGHT*m.getHeight());
127  }
128 
129  public void setEngine(Micropolis newEngine)
130  {
131  assert newEngine != null;
132 
133  if (this.m != null) { //old engine
134  this.m.removeMapListener(this);
135  }
136  this.m = newEngine;
137  if (this.m != null) { //new engine
138  this.m.addMapListener(this);
139  }
140 
141  // size may have changed
142  invalidate();
143  repaint();
144  }
145 
146  void drawSprite(Graphics gr, Sprite sprite)
147  {
148  assert sprite.isVisible();
149 
150  Point p = new Point(
151  (sprite.x + sprite.offx) * TILE_WIDTH / 16,
152  (sprite.y + sprite.offy) * TILE_HEIGHT / 16
153  );
154 
155  Image img = tileImages.getSpriteImage(sprite.kind, sprite.frame-1);
156  if (img != null) {
157  gr.drawImage(img, p.x, p.y, null);
158  }
159  else {
160  gr.setColor(Color.RED);
161  gr.fillRect(p.x, p.y, 16, 16);
162  gr.setColor(Color.WHITE);
163  gr.drawString(Integer.toString(sprite.frame-1),p.x,p.y);
164  }
165  }
166 
167  public void paintComponent(Graphics gr)
168  {
169  final int width = m.getWidth();
170  final int height = m.getHeight();
171 
172  Rectangle clipRect = gr.getClipBounds();
173  int minX = Math.max(0, clipRect.x / TILE_WIDTH);
174  int minY = Math.max(0, clipRect.y / TILE_HEIGHT);
175  int maxX = Math.min(width, 1 + (clipRect.x + clipRect.width-1) / TILE_WIDTH);
176  int maxY = Math.min(height, 1 + (clipRect.y + clipRect.height-1) / TILE_HEIGHT);
177 
178  for (int y = minY; y < maxY; y++)
179  {
180  for (int x = minX; x < maxX; x++)
181  {
182  int cell = m.getTile(x,y);
183  if (blinkUnpoweredZones &&
184  isZoneCenter(cell) &&
185  !m.isTilePowered(x, y))
186  {
187  unpoweredZones.add(new Point(x,y));
188  if (blink)
189  cell = LIGHTNINGBOLT;
190  }
191 
192  if (toolPreview != null) {
193  int c = toolPreview.getTile(x, y);
194  if (c != CLEAR) {
195  cell = c;
196  }
197  }
198 
199  gr.drawImage(tileImages.getTileImage(cell),
200  x*TILE_WIDTH + (shakeStep != 0 ? getShakeModifier(y) : 0),
201  y*TILE_HEIGHT,
202  null);
203  }
204  }
205 
206  for (Sprite sprite : m.allSprites())
207  {
208  if (sprite.isVisible())
209  {
210  drawSprite(gr, sprite);
211  }
212  }
213 
214  if (toolCursor != null)
215  {
216  int x0 = toolCursor.rect.x * TILE_WIDTH;
217  int x1 = (toolCursor.rect.x + toolCursor.rect.width) * TILE_WIDTH;
218  int y0 = toolCursor.rect.y * TILE_HEIGHT;
219  int y1 = (toolCursor.rect.y + toolCursor.rect.height) * TILE_HEIGHT;
220 
221  gr.setColor(Color.BLACK);
222  gr.drawLine(x0-1,y0-1,x0-1,y1-1);
223  gr.drawLine(x0-1,y0-1,x1-1,y0-1);
224  gr.drawLine(x1+3,y0-3,x1+3,y1+3);
225  gr.drawLine(x0-3,y1+3,x1+3,y1+3);
226 
227  gr.setColor(toolCursor.borderColor);
228  gr.drawRect(x0-3,y0-3,x1-x0+5,y1-y0+5);
229  gr.drawRect(x0-2,y0-2,x1-x0+3,y1-y0+3);
230 
231  gr.setColor(Color.WHITE);
232  gr.drawLine(x0-3,y0-3,x1+3,y0-3);
233  gr.drawLine(x0-3,y0-3,x0-3,y1+3);
234  gr.drawLine(x1, y0-1,x1, y1 );
235  gr.drawLine(x0-1,y1, x1, y1 );
236 
237  if (toolCursor.fillColor != null) {
238  gr.setColor(toolCursor.fillColor);
239  gr.fillRect(x0,y0,x1-x0,y1-y0);
240  }
241  }
242  }
243 
244  static class ToolCursor
245  {
246  CityRect rect;
247  Color borderColor;
248  Color fillColor;
249  }
250 
251  public void setToolCursor(CityRect newRect, MicropolisTool tool)
252  {
253  ToolCursor tp = new ToolCursor();
254  tp.rect = newRect;
255  tp.borderColor = parseColor(
256  strings.containsKey("tool."+tool.name()+".border") ?
257  strings.getString("tool."+tool.name()+".border") :
258  strings.getString("tool.*.border")
259  );
260  tp.fillColor = parseColor(
261  strings.containsKey("tool."+tool.name()+".bgcolor") ?
262  strings.getString("tool."+tool.name()+".bgcolor") :
263  strings.getString("tool.*.bgcolor")
264  );
265  setToolCursor(tp);
266  }
267 
268  public void setToolCursor(ToolCursor newCursor)
269  {
270  if (toolCursor == newCursor)
271  return;
272  if (toolCursor != null && toolCursor.equals(newCursor))
273  return;
274 
275  if (toolCursor != null)
276  {
277  repaint(new Rectangle(
278  toolCursor.rect.x*TILE_WIDTH - 4,
279  toolCursor.rect.y*TILE_HEIGHT - 4,
280  toolCursor.rect.width*TILE_WIDTH + 8,
281  toolCursor.rect.height*TILE_HEIGHT + 8
282  ));
283  }
284  toolCursor = newCursor;
285  if (toolCursor != null)
286  {
287  repaint(new Rectangle(
288  toolCursor.rect.x*TILE_WIDTH - 4,
289  toolCursor.rect.y*TILE_HEIGHT - 4,
290  toolCursor.rect.width*TILE_WIDTH + 8,
291  toolCursor.rect.height*TILE_HEIGHT + 8
292  ));
293  }
294  }
295 
296  public void setToolPreview(ToolPreview newPreview)
297  {
298  if (toolPreview != null) {
299  CityRect b = toolPreview.getBounds();
300  Rectangle r = new Rectangle(
301  b.x*TILE_WIDTH,
302  b.y*TILE_HEIGHT,
303  b.width*TILE_WIDTH,
304  b.height*TILE_HEIGHT
305  );
306  repaint(r);
307  }
308 
309  toolPreview = newPreview;
310  if (toolPreview != null) {
311 
312  CityRect b = toolPreview.getBounds();
313  Rectangle r = new Rectangle(
314  b.x*TILE_WIDTH,
315  b.y*TILE_HEIGHT,
316  b.width*TILE_WIDTH,
317  b.height*TILE_HEIGHT
318  );
319  repaint(r);
320  }
321  }
322 
323  //implements Scrollable
325  {
326  return PREFERRED_VIEWPORT_SIZE;
327  }
328 
329  //implements Scrollable
330  public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction)
331  {
332  if (orientation == SwingConstants.VERTICAL)
333  return visibleRect.height;
334  else
335  return visibleRect.width;
336  }
337 
338  //implements Scrollable
340  {
341  return false;
342  }
343 
344  //implements Scrollable
346  {
347  return false;
348  }
349 
350  //implements Scrollable
351  public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction)
352  {
353  if (orientation == SwingConstants.VERTICAL)
354  return TILE_HEIGHT * 3;
355  else
356  return TILE_WIDTH * 3;
357  }
358 
359  private Rectangle getSpriteBounds(Sprite sprite, int x, int y)
360  {
361  return new Rectangle(
362  (x+sprite.offx)*TILE_WIDTH/16,
363  (y+sprite.offy)*TILE_HEIGHT/16,
364  sprite.width*TILE_WIDTH/16,
365  sprite.height*TILE_HEIGHT/16
366  );
367  }
368 
369  public Rectangle getTileBounds(int xpos, int ypos)
370  {
371  return new Rectangle(xpos*TILE_WIDTH, ypos * TILE_HEIGHT,
372  TILE_WIDTH, TILE_HEIGHT);
373  }
374 
375  //implements MapListener
376  public void mapOverlayDataChanged(MapState overlayDataType)
377  {
378  }
379 
380  //implements MapListener
381  public void spriteMoved(Sprite sprite)
382  {
383  repaint(getSpriteBounds(sprite, sprite.lastX, sprite.lastY));
384  repaint(getSpriteBounds(sprite, sprite.x, sprite.y));
385  }
386 
387  //implements MapListener
388  public void tileChanged(int xpos, int ypos)
389  {
390  repaint(getTileBounds(xpos, ypos));
391  }
392 
393  //implements MapListener
394  public void wholeMapChanged()
395  {
396  repaint();
397  }
398 
399  protected void startDrag(int x, int y)
400  {
401  dragging = true;
402  dragX = x;
403  dragY = y;
404  }
405  protected void endDrag(int x, int y)
406  {
407  dragging = false;
408  }
409  protected void continueDrag(int x, int y)
410  {
411  int dx = x - dragX;
412  int dy = y - dragY;
413  JScrollPane js = (JScrollPane)getParent().getParent();
414  js.getHorizontalScrollBar().setValue(
415  js.getHorizontalScrollBar().getValue()-dx);
416  js.getVerticalScrollBar().setValue(
417  js.getVerticalScrollBar().getValue()-dy);
418  }
419 
420  void doBlink()
421  {
422  if (!unpoweredZones.isEmpty())
423  {
424  blink = !blink;
425  for (Point loc : unpoweredZones)
426  {
427  repaint(getTileBounds(loc.x, loc.y));
428  }
429  unpoweredZones.clear();
430  }
431  }
432 
433  void startBlinkTimer()
434  {
435  assert blinkTimer == null;
436 
437  ActionListener callback = new ActionListener() {
438  public void actionPerformed(ActionEvent evt)
439  {
440  doBlink();
441  }
442  };
443 
444  blinkTimer = new Timer(500, callback);
445  blinkTimer.start();
446  }
447 
448  void stopBlinkTimer()
449  {
450  if (blinkTimer != null) {
451  blinkTimer.stop();
452  blinkTimer = null;
453  }
454  }
455 
456  void shake(int i)
457  {
458  shakeStep = i;
459  repaint();
460  }
461 
462  static final int SHAKE_STEPS = 40;
463  int getShakeModifier(int row)
464  {
465  return (int)Math.round(4.0 * Math.sin((double)(shakeStep+row/2)/2.0));
466  }
467 }