package mapmaker;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import util.*;
/** the View in mapmaker's MVC pattern
*/
public class MapGraphics implements MapViewer {
AreaMap map;
MapEventHandler meh;
Graphics g; // stores current Graphics objects to draw on
Rectangle mapBounds; // stores the bounds of g in room coordinates
int paintSize = 4;
// must never be null
StateController virtualState =
new ConstStateController(new Boolean(false));
/** @param map the Model
* @param meh the Controller
*/
public MapGraphics(AreaMap map, MapEventHandler meh) {
this.map = map;
this.meh = meh;
} // MapGraphics
/** sets the (abstract) size in which the map will be painted
*/
public void setPaintSize(int paintSize) {
if (paintSize < 1)
paintSize = 1;
this.paintSize = paintSize;
// inform map that something changed, although the change is not
// within the map data ..
map.notifyOfChange(MapEvent.ChangeView);
} // setPaintSize
public void setVirtualStateController(StateController state) {
virtualState = state;
} // setVirtualStateController
public int getPaintSize() {
return paintSize;
} // getPaintSize
public void paint(Graphics g) {
// set current Graphics object
this.g = g;
// only draw if rooms and links lie within the visible area
// translate visible area into map coordinates
Rectangle bounds = g.getClipBounds();
if (bounds == null)
mapBounds = new Rectangle(map.getSize());
else {
int sizePerRoom = sizePerRoom();
int mapMinX = bounds.x / sizePerRoom;
int mapMinY = bounds.y / sizePerRoom;
// mapMaxX and mapMaxY are exclusive
int mapMaxX = (bounds.x + bounds.width) / sizePerRoom + 1;
int mapMaxY = (bounds.y + bounds.height) / sizePerRoom + 1;
mapBounds =
new Rectangle(mapMinX, mapMinY, mapMaxX - mapMinX, mapMaxY - mapMinY);
}
// paint rooms
Room[] rooms = map.getRooms();
for (int i = 0; i < rooms.length; i++)
paintRoom(rooms[i]);
// paint links
Link[] links = map.getLinks();
for (int i = 0; i < links.length; i++)
paintLink(links[i]);
// paint selected room if there is one
Room selected = map.getSelected();
if (selected != null)
paintSelectedRoom(selected);
} // paint
int roomSquareSize() {
// returns the size of the square that holds a room
// without border
return 3 + 2 * paintSize;
} // room SquareSize
public int sizePerRoom() {
// returns the size of the square that holds a room
// including its border
return 3 * roomSquareSize();
} // sizePerRoom
public Point roomPaintPos(Room room) {
// returns starting opsition for room painting
int sizePerRoom = sizePerRoom();
Point pos = room.getPos();
return new Point(pos.x * sizePerRoom, pos.y * sizePerRoom);
} // roomPaintPos
Point squarePaintPos(Room room) {
// returns position where to start painting of rooms outlines
int sizePerRoom = sizePerRoom();
int borderSize = (sizePerRoom - roomSquareSize()) / 2;
Point pos = roomPaintPos(room);
return new Point(pos.x + borderSize, pos.y + borderSize);
} // squarePaintPos
Point centerPaintPos(Room room) {
// returns center position of room for painting
Point pos = roomPaintPos(room);
int add = (sizePerRoom() - 1) / 2;
return new Point(pos.x + add, pos.y + add);
} // centerPaintPos
int exitPaintSize() {
return 1 + paintSize/2;
} // exitPaintSize
int centerToEdge() {
// returns the distance of the edge from center for enlarged room
return (sizePerRoom() - 1) / 2 - exitPaintSize();
} // centerToEdge
Point linkPaintPos(Room room, int dir) {
// returns position where a link at given direction would start
int halfSize = (roomSquareSize() - 1) / 2;
Point center = centerPaintPos(room);
// check for up/down - special position
int upDownSize = 1 + paintSize / 3;
if (dir == Dir.up)
return new Point(center.x, center.y - upDownSize);
if (dir == Dir.down)
return new Point(center.x, center.y + upDownSize);
// now calculate 'normal' directions
Point screenDir = Dir.screenDir(dir);
int moveX = screenDir.x * halfSize;
int moveY = screenDir.y * halfSize;
return new Point(center.x + moveX, center.y + moveY);
} // linkpaintPos
/** draws the edges of the specified rectangel;
* used only by paintRoom
*/
private void drawEdges(int x, int y, int dx, int dy) {
int l = 1; // length
g.drawLine(x, y, x + l, y);
g.drawLine(x, y, x, y + l);
g.drawLine(x + dx, y, x + dx - l, y);
g.drawLine(x + dx, y, x + dx, y + l);
g.drawLine(x, y + dy, x + l, y + dy);
g.drawLine(x, y + dy, x, y + dy - l);
g.drawLine(x + dx, y + dy, x + dx - l, y + dy);
g.drawLine(x + dx, y + dy, x + dx, y + dy - l);
} // drawEdges
void paintRoom(Room room) {
// only draw if within bounds
if (!mapBounds.contains(room.getPos()))
return;
Point start = squarePaintPos(room);
int roomSize = roomSquareSize();
Color oldColor = g.getColor();
// draw marked rooms with red border
if (room.getMarked()) {
g.setColor(Color.red);
g.drawRect(start.x - 1, start.y - 1, roomSize + 1, roomSize + 1);
g.setColor(oldColor);
}
// draw room in its color
Color roomColor = room.getColor();
if (roomColor != null)
g.setColor(roomColor);
if (room instanceof VirtualRoom) {
if (((Boolean)virtualState.state()).booleanValue())
drawEdges(start.x, start.y, roomSize - 1, roomSize - 1);
}
else
g.drawRect(start.x, start.y, roomSize - 1, roomSize - 1);
g.setColor(oldColor);
} // paint
void paintExit(Point pos, boolean linked, boolean blocked) {
// used only by paintSelectedRoom
int size = exitPaintSize();
if (linked)
if (blocked) {
g.clearRect(pos.x - size, pos.y - size, 2 * size, 2 * size);
g.drawRect(pos.x - size, pos.y - size, 2 * size, 2 * size);
// draw cross
g.drawLine(pos.x - size, pos.y - size, pos.x + size, pos.y + size);
g.drawLine(pos.x + size, pos.y - size, pos.x - size, pos.y + size);
}
else
g.fillRect(pos.x - size, pos.y - size, 2 * size, 2 * size);
else {
g.clearRect(pos.x - size, pos.y - size, 2 * size, 2 * size);
g.drawRect(pos.x - size, pos.y - size, 2 * size, 2 * size);
} // else
} // paintExit
Point exitPaintPos(int dir) {
// returns the screenPosition where the exit indicated by dir
// should be painted in a selected (enlarged) room,
// relative to the center of the room
if (dir < Dir.PLANEDIRNR) {
Point screenDir = Dir.screenDir(dir);
int stretch = centerToEdge();
return new Point(screenDir.x * stretch, screenDir.y * stretch);
}
else
if (dir == Dir.up)
return new Point(0, -(1 + exitPaintSize()));
else
return new Point(0, 1 + exitPaintSize());
} // exitPaintPos
void paintSelectedRoom(Room room) {
// only draw if within bounds
if (!mapBounds.contains(room.getPos()))
return;
// draws the room enlarged
Point center = centerPaintPos(room);
Point start = roomPaintPos(room);
int size = sizePerRoom();
int centerToEdge = centerToEdge();
// draw room in its color
Color oldColor = g.getColor();
Color roomColor = room.getColor();
if (roomColor != null)
g.setColor(roomColor);
// clear background
g.clearRect(start.x, start.y, size - 1, size - 1);
// draw border
g.drawRect(center.x - centerToEdge, center.y - centerToEdge,
centerToEdge * 2, centerToEdge * 2);
// draw exits
for (int i = 0; i < Dir.DIRNR; i++) {
Point pos = exitPaintPos(i);
pos.translate(center.x, center.y);
paintExit(pos, room.exitLinked(i), room.exitBlocked(i));
}
g.setColor(oldColor);
} // paintSelectedRoom
void paintLink(Link link) {
Room[] rooms = link.getRooms();
// only draw if within bounds
if (AwtUtil.outsideSameSide(mapBounds,
rooms[0].getPos(),
rooms[1].getPos()))
return;
int connectSize = paintSize + 1;
Point[] connect = new Point[2]; // positions after direction indicators
int[] exits = new int[2];
for (int i = 0; i < 2; i++) {
int exit = exits[i] = rooms[i].getExitDir(link);
Point start = linkPaintPos(rooms[i], exit);
// no indicators drawn if up/down direction
// instead draw little square (unless blocked)
if (exit == Dir.up || exit == Dir.down) {
if (!rooms[i].exitBlocked(exit)) {
int exitSize = 1 + paintSize / 10;
g.fillRect(start.x - exitSize, start.y - exitSize,
exitSize * 2, exitSize * 2);
}
connect[i] = start;
}
else { // normal directions (not up/down)
// draw direction indicator
Point screenDir = Dir.screenDir(exit);
int connectX = start.x + screenDir.x * connectSize;
int connectY = start.y + screenDir.y * connectSize;
// draw links to virtualRooms differently
if (!(rooms[i] instanceof VirtualRoom))
g.drawLine(start.x, start.y, connectX, connectY);
else {
Point center = centerPaintPos(rooms[i]);
g.drawLine(center.x, center.y, connectX, connectY);
}
// if blocked, draw arrowhead
if (rooms[i].exitBlocked(exit)) {
Point[] arrow = new Point[2];
arrow[0] = Dir.screenDir(Dir.turnClockwise(exit));
arrow[1] = Dir.screenDir(Dir.turnCounterClockwise(exit));
int arrowSize = 1 + paintSize / 3;
for (int a = 0; a < 2; a++) {
arrow[a].x *= arrowSize;
arrow[a].y *= arrowSize;
arrow[a].translate(start.x, start.y);
g.drawLine(start.x, start.y, arrow[a].x, arrow[a].y);
}
} // if blocked
connect[i] = new Point(connectX, connectY);
}
} // for
// connect the 2 connection points
connectPoints(connect[0], exits[0], connect[1], exits[1]);
} // paintLink
/** returns wether a direct connection from p1 to p2 would
* oppose to exit1 at point p1;
* used by connectPoints
*/
boolean validConnect(Point p1, Point p2, int exit1) {
Point lineDir = new Point(p2.x - p1.x, p2.y - p1.y);
// order of parameters IS important!
return !MapMath.opposeQuadrant(Dir.screenDir(exit1), lineDir);
} // validConnect
/** connects the given points, respecting the exits they come from
*/
void connectPoints(Point p1, int exit1, Point p2, int exit2) {
// if the direct connection would be at an invalid angle,
// try to use two connection lines
if (!validConnect(p1, p2, exit1) ||
!validConnect(p2, p1, exit2)) {
// try to get a valid connection by connecting with two lines
Point middle = new Point(p1.x, p2.y);
// if middle not valid, try other middle point
if (!validConnect(p1, middle, exit1) ||
!validConnect(p2, middle, exit2))
middle = new Point(p2.x, p1.y);
// if now valid, draw two lines and return
if (validConnect(p1, middle, exit1) &&
validConnect(p2, middle, exit2)) {
g.drawLine(p1.x, p1.y, middle.x, middle.y);
g.drawLine(p2.x, p2.y, middle.x, middle.y);
return;
}
}
g.drawLine(p1.x, p1.y, p2.x, p2.y);
} // connectPoints
Point mapPos(Point screenPos) {
// returns the map-position of the room that covers
// screenPos when painted
int sizePerRoom = sizePerRoom();
return new Point(screenPos.x / sizePerRoom, screenPos.y / sizePerRoom);
} // mapPos
int exitPos(Point screenPos) {
// if screenPos is on an exit field of selected room,
// returns this exit
// otherwise returns -1
Room selected = map.getSelected();
if (selected == null || !(selected.getPos().equals(mapPos(screenPos))))
return -1;
// now check if screenPos is on exit
Point centerPos = centerPaintPos(selected);
Point relPos = new Point(screenPos.x - centerPos.x,
screenPos.y - centerPos.y);
int vary = exitPaintSize();
for (int dir = 0; dir < Dir.DIRNR; dir++) {
Point paintPos = exitPaintPos(dir);
if (Math.abs(paintPos.x - relPos.x) <= vary &&
Math.abs(paintPos.y - relPos.y) <= vary)
return dir;
}
return -1;
} // exitPos
/** adds some listeners to speaker-parameter which delegate
* events to the Controller given in Constructor
* @param speaker should be the awt/swing - Component
* wrapping the View
*/
public void addListeners(Component speaker) {
MouseListener ml = new MouseAdapter() {
Point mousePressedPos = new Point(0,0);
private boolean inScreen(Point pos) {
// returns wether pos lies within the visible range of
// the map
return AwtUtil.contains(getSize(), pos);
} // inScreen
public void mouseClicked(MouseEvent e) {
if (!inScreen(e.getPoint()))
return;
Point mapPos = mapPos(e.getPoint());
// check if click on link
int exit = exitPos(e.getPoint());
if (exit != -1)
meh.linkClicked(e, mapPos, exit);
else
meh.roomClicked(e, mapPos);
} // mouseClicked
public void mousePressed(MouseEvent e) {
if (!inScreen(e.getPoint()))
return;
mousePressedPos = e.getPoint();
} // mousePressed
public void mouseReleased(MouseEvent e) {
Point pos = e.getPoint();
if (!inScreen(pos) ||
!inScreen(mousePressedPos))
return;
// check if drag from link to link
int oldExit = exitPos(mousePressedPos);
int newExit = exitPos(pos);
if (oldExit != -1 && newExit != -1 && oldExit != newExit) {
meh.linkDragged(e, mapPos(pos), oldExit, newExit);
return;
}
// check if drag from different rooms
Point mapPos1 = mapPos(mousePressedPos);
Point mapPos2 = mapPos(pos);
if (!mapPos1.equals(mapPos2))
meh.roomDragged(e, mapPos1, mapPos2);
} // mouseReleased
};
speaker.addMouseListener(new ClickToleranceFilter(ml));
// create MouseLocator to get current mouse position
final MouseLocator mouseLocator = new MouseLocator();
speaker.addMouseMotionListener(mouseLocator);
speaker.addKeyListener(new KeyAdapter() {
MouseLocator mouser = mouseLocator;
// use keyReleased, not keyTyped, as not all keys
// generate a keyTyped event!!!
public void keyReleased(KeyEvent e) {
// set pos to current mouse position
Point pos = new Point(mouser.getX(), mouser.getY());
Point mapPos = mapPos(pos);
// check if on link
int exit = exitPos(pos);
if (exit != -1)
meh.keyTypedOnLink(e, mapPos, exit);
else
meh.keyTypedOnRoom(e, mapPos);
} // keyReleased
});
} // addListeners
/** returns the Dimension that the map will occupy (in Pixels)
*/
public Dimension getSize() {
int factor = sizePerRoom();
Dimension roomSize = map.getSize();
return new Dimension(roomSize.width * factor,
roomSize.height * factor);
} // getSize
} // MapGraphics