package mapmaker;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.undo.*;
import javax.swing.event.*;
import javax.swing.*;
import java.util.*;
import mapmaker.event.*;
import com.sun.glf.goodies.RadialGradientPaint;
public class MapView extends JPanel
{
// The modes this view can be in.
public final static int MODE_SELECT_AND_DRAG = 0;
private SelectAdapter mSelectAdapter = new SelectAdapter(this);
// The model we're wrapping.
private MapModel mMapModel;
// The rectangle dragged out for selecting locations.
private Rectangle mSelectionRectangle;
// Store possible link ups.
private LinkUpStore mLinkUpStore = new LinkUpStore();
// The currently selected link up group.
private int mCurrentLinkUpGroup;
// The selected map locations.
private MapSelection mMapSelection = new MapSelection();
// Probably shouldn't need these and do everything with listeners...
public MainFrame mMainFrame;
private InheritBean mInheritBean;
private ExitBean mExitBean;
// Undo/redo support.
public UndoManager mUndoManager = new UndoManager()
{
public boolean addEdit(UndoableEdit aUndoableEdit)
{
boolean retValue = super.addEdit(aUndoableEdit);
mMainFrame.refreshUndoRedo();
return retValue;
}
public void undo()
{
super.undo();
mMainFrame.refreshUndoRedo();
}
public void redo()
{
super.redo();
mMainFrame.refreshUndoRedo();
}
};
public CompoundEdit mCompoundEdit = new CompoundEdit();
Zone mDisplayZone = null;
Linker mDisplayLinker = null;
public MapView(MapModel aMapModel, MainFrame aMainFrame,
InheritBean aInheritBean, ExitBean aExitBean)
{
mMapModel = aMapModel;
setMapSize(mMapModel.getMapSize());
mInheritBean = aInheritBean;
mMainFrame = aMainFrame;
mExitBean = aExitBean;
setBackground(Color.white);
this.addMouseListener(mSelectAdapter);
this.addMouseMotionListener(mSelectAdapter);
}
public void setMapSize(Dimension aMapSize)
{
setSize(aMapSize);
setPreferredSize(aMapSize);
mMapModel.setMapSize(aMapSize);
}
public MapSelection getMapSelection()
{
return mMapSelection;
}
public void setDisplayZone(Zone aZone)
{
mDisplayZone = aZone;
}
public void setDisplayLinker(Linker aLinker)
{
mDisplayLinker = aLinker;
}
public void setMode(int aMode)
{
this.removeMouseListener(mSelectAdapter);
this.removeMouseMotionListener(mSelectAdapter);
switch (aMode)
{
case MODE_SELECT_AND_DRAG :
this.addMouseListener(mSelectAdapter);
this.addMouseMotionListener(mSelectAdapter);
break;
}
}
public MapModel getMapModel()
{
return mMapModel;
}
public void setSelectionRectangle(Rectangle aSelectionRectangle)
{
mSelectionRectangle = aSelectionRectangle;
}
/**
* This method performs an edit and records it in the current compound edit.
*
* @param aEdit the edit to perform and record
*/
public void doEdit(UndoableEdit aEdit)
{
aEdit.redo();
mCompoundEdit.addEdit(aEdit);
}
public void addCompoundEdit()
{
mCompoundEdit.end();
mUndoManager.addEdit(mCompoundEdit);
mCompoundEdit = new CompoundEdit();
}
public void clearExits(MapLocation aLocation)
{
aLocation.clearExits();
}
public void clearExitsToUnselected(MapLocation aLocation)
{
Vector exits = aLocation.getExits();
for (Enumeration e = exits.elements(); e.hasMoreElements();)
{
Exit exit = (Exit)e.nextElement();
// Check if the exit has a destination
if (exit.isDummy())
{
continue;
}
MapLocation to = exit.getTo();
if (!mMapSelection.isSelected(to))
{
RemoveExitEdit fromEdit = new RemoveExitEdit(aLocation, exit);
RemoveExitEdit toEdit = new RemoveExitEdit(to, exit.getOpposite());
doEdit(fromEdit);
doEdit(toEdit);
}
}
}
// Works out what link ups are available to a location.
public Vector getLinkUpsForLocation(MapLocation aLocation)
{
Vector linkUps = new Vector();
Rectangle linkUpRectangle = aLocation.getLinkUpRectangle();
Vector mapLocations = mMapModel.getMapLocations();
int sizeOfMapLocations = mapLocations.size();
// Store the location's cardinal handles.
Rectangle[] ourHandles = aLocation.getHandles();
for (int i = 0; i < sizeOfMapLocations; i++)
{
MapLocation location = (MapLocation)mapLocations.elementAt(i);
// First check if the other location is within linkup range.
if (linkUpRectangle.intersects(location.getLinkUpRectangle()))
{
// Don't check with self.
if (location == aLocation)
{
continue;
}
// Don't check with other selected locations.
if (mMapSelection.isSelected(location))
{
continue;
}
// Now check if any opposing cardinal points are in range.
Rectangle[] theirHandles = location.getHandles();
for (int j = 0; j < 8; j++)
{
if (ourHandles[j].intersects(theirHandles[Direction.getOppoisite(j)]))
{
// Add the link up.
linkUps.addElement(new LinkUp(aLocation, location, j));
}
}
}
}
return linkUps;
}
public void refreshLinkUpStore()
{
mLinkUpStore = this.getLinkUpGroupsForSelection();
}
// Works out what link up alternatives are available to a selection.
public LinkUpStore getLinkUpGroupsForSelection()
{
Vector linkUps = new Vector();
Vector selectedLocations = mMapSelection.getSelected();
int sizeOfSelectedLocations = selectedLocations.size();
// Gather together all the link ups.
for (int i = 0; i < sizeOfSelectedLocations; i++)
{
MapLocation location = (MapLocation)selectedLocations.elementAt(i);
linkUps.addAll(this.getLinkUpsForLocation(location));
}
// Sort the link ups into groups based on the translation they require
// to be peformed.
LinkUpStore linkUpStore = new LinkUpStore();
int sizeOfLinkUps = linkUps.size();
for (int i = 0; i < sizeOfLinkUps; i++)
{
LinkUp linkUp = (LinkUp)linkUps.elementAt(i);
linkUpStore.addLinkUp(linkUp);
}
return linkUpStore;
}
public void doLinkUps()
{
Vector linkUps = mLinkUpStore.getGroupAt(mCurrentLinkUpGroup);
if (linkUps == null)
{
return;
}
int sizeOfLinkUps = linkUps.size();
// Locations which have been moved to link up.
Vector done = new Vector();
Point translation = ((LinkUp)linkUps.elementAt(0)).mTranslation;
for (int i = 0; i < sizeOfLinkUps; i++)
{
LinkUp linkUp = (LinkUp)linkUps.elementAt(i);
connectExits(linkUp);
if (!done.contains(linkUp.mSelectedLocation))
{
performLinkUp(linkUp);
}
done.addElement(linkUp.mSelectedLocation);
}
// Move the selected locations with no link ups with the link ups.
Vector selectedLocations = mMapSelection.getSelected();
int sizeOfSelected = selectedLocations.size();
for (int i = 0; i < sizeOfSelected; i++)
{
MapLocation mapLocation = (MapLocation)selectedLocations.elementAt(i);
if (!done.contains(mapLocation))
{
setLocation(mapLocation, mapLocation.getX() - translation.x,
mapLocation.getY() - translation.y);
}
done.addElement(mapLocation);
}
mExitBean.readSelection();
}
public void performLinkUp(LinkUp aLinkUp)
{
int translateX = aLinkUp.mTranslation.x;
int translateY = aLinkUp.mTranslation.y;
setLocation(aLinkUp.mSelectedLocation, aLinkUp.mSelectedX - translateX,
aLinkUp.mSelectedY - translateY);
}
public void setLocation(MapLocation aLocation, int aX, int aY)
{
mCompoundEdit.addEdit(new TranslateLocationEdit(aLocation,
new Point(aLocation.mTempX, aLocation.mTempY),
new Point(aX, aY)));
aLocation.setX(aX);
aLocation.setY(aY);
}
/*
public void setLocation(MapLocation aLocation, int aX, int aY)
{
TranslateLocationEdit edit = new TranslateLocationEdit(aLocation,
new Point(aLocation.mTempX, aLocation.mTempY), new Point(aX, aY));
doEdit(edit);
}
*/
public void connectExits(LinkUp aLinkUp)
{
Exit to = new Exit(aLinkUp.mDirection, aLinkUp.mSelectedLocation,
aLinkUp.mUnselectedLocation, "path");
Exit from = new Exit(Direction.getOppoisite(aLinkUp.mDirection),
aLinkUp.mUnselectedLocation, aLinkUp.mSelectedLocation, "path");
AddExitEdit toEdit = new AddExitEdit(aLinkUp.mSelectedLocation, to);
AddExitEdit fromEdit = new AddExitEdit(aLinkUp.mUnselectedLocation, from);
doEdit(toEdit);
doEdit(fromEdit);
}
public void paintComponent(Graphics aGraphics)
{
super.paintComponent(aGraphics);
Graphics2D g = (Graphics2D)aGraphics;
Vector mapLocations = mMapModel.getMapLocations();
int sizeOfMapLocations = mapLocations.size();
// Paint zone
if (mDisplayZone != null)
{
// Paint the zone of the map locations.
for (int i = 0; i < sizeOfMapLocations; i++)
{
MapLocation currentLocation = (MapLocation)mapLocations.elementAt(i);
if (currentLocation.isInZone(mDisplayZone))
{
RadialGradientPaint paint = new RadialGradientPaint(
currentLocation.getZoneRectangle(), mDisplayZone.getColor(),
new Color(255, 255, 255, 0));
g.setPaint(paint);
g.fill(currentLocation.getZoneRectangle());
}
}
}
// Paint linker
if (mDisplayLinker != null)
{
// Paint the zone of the map locations.
for (int i = 0; i < sizeOfMapLocations; i++)
{
MapLocation currentLocation = (MapLocation)mapLocations.elementAt(i);
if (currentLocation.isInLinker(mDisplayLinker))
{
g.setPaint(mDisplayLinker.getColor());
g.fill(currentLocation.getLinkerRectangle());
}
}
}
// Paint the background of the map locations.
for (int i = 0; i < sizeOfMapLocations; i++)
{
MapLocation currentLocation = (MapLocation)mapLocations.elementAt(i);
currentLocation.renderBackground(g);
}
// Paint the foreground of the map locations.
for (int i = 0; i < sizeOfMapLocations; i++)
{
MapLocation currentLocation = (MapLocation)mapLocations.elementAt(i);
currentLocation.renderForeground(g);
/*
g.setPaint(Color.blue);
g.setStroke(new BasicStroke(1f));
g.draw(currentLocation.getRectangle());
*/
}
mMapSelection.render(g);
// Draw preview link ups.
Vector linkUps = mLinkUpStore.getGroupAt(mCurrentLinkUpGroup);
if (linkUps != null)
{
int sizeOfLinkUps = linkUps.size();
for (int i = 0; i < sizeOfLinkUps; i++)
{
LinkUp linkUp = (LinkUp)linkUps.elementAt(i);
Point dragPoint = linkUp.mSelectedLocation.getCardinals()
[linkUp.mDirection];
Point linkPoint = (Point)linkUp.mUnselectedLocation.getCardinals()
[Direction.getOppoisite(linkUp.mDirection)].clone();
linkPoint.translate(-2, -2);
g.setPaint(Color.black);
g.fill(new Rectangle(linkPoint, new Dimension(4, 4)));
}
}
// Draw the selection rectangle.
if (mSelectionRectangle != null)
{
g.setPaint(Color.gray);
g.setStroke(new BasicStroke(1.f, BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL, 8.f, new float[]{ 2.f, 2.f }, 0.f));
g.draw(mSelectionRectangle);
}
}
//==========================================================================
// Cycles through link up groups so link ups can be selected.
// To be called from the key listener when locations are being dragged.
//==========================================================================
public void cycleLinkUpGroup()
{
mCurrentLinkUpGroup++;
if (mCurrentLinkUpGroup >= mLinkUpStore.getNoOfGroups())
{
mCurrentLinkUpGroup = 0;
}
this.repaint();
}
public void deleteSelectedLocations()
{
Vector selectedLocations = mMapSelection.getSelected();
int sizeOfSelectedLocations = selectedLocations.size();
// Gather together all the link ups.
for (int i = 0; i < sizeOfSelectedLocations; i++)
{
MapLocation location = (MapLocation)selectedLocations.elementAt(i);
RemoveLocationEdit edit = new RemoveLocationEdit(location);
doEdit(edit);
}
addCompoundEdit();
mMapSelection.clearSelected();
this.repaint();
}
//==========================================================================
// Store selected map locations in here.
//==========================================================================
class MapSelection
{
private Color mSelectionColor = Color.red;
private Stroke mSelectionStroke = new BasicStroke(1f);
private Vector mSelectedMapLocations = new Vector();
public void setSelected(Vector aSelectedMapLocations)
{
mSelectedMapLocations = aSelectedMapLocations;
fireSelectionChanged();
}
public void addSelected(MapLocation aMapLocation)
{
if (!mSelectedMapLocations.contains(aMapLocation))
{
mSelectedMapLocations.addElement(aMapLocation);
fireSelectionChanged();
}
}
public void removeSelected(MapLocation aMapLocation)
{
mSelectedMapLocations.removeElement(aMapLocation);
fireSelectionChanged();
}
public boolean isSelected(MapLocation aMapLocation)
{
return mSelectedMapLocations.contains(aMapLocation);
}
public Vector getSelected()
{
return mSelectedMapLocations;
}
public void clearSelected()
{
mSelectedMapLocations = new Vector();
fireSelectionChanged();
}
public void addMapSelectionListener(MapSelectionListener listener)
{
listenerList.add(MapSelectionListener.class, listener);
}
public void removeMapSelectionListener(MapSelectionListener listener)
{
listenerList.remove(MapSelectionListener.class, listener);
}
protected void fireSelectionChanged()
{
Object[] listeners = listenerList.getListenerList();
MapSelectionEvent mapSelectionEvent = new MapSelectionEvent(this,
mSelectedMapLocations);
for (int i = listeners.length - 2; i >= 0; i -= 2)
{
if (listeners[i] == MapSelectionListener.class)
{
((MapSelectionListener)listeners[i + 1]).mapSelectionChanged(mapSelectionEvent);
}
}
}
public void render(Graphics2D g)
{
int sizeOfSelectedMapLocations = mSelectedMapLocations.size();
for (int i = 0; i < sizeOfSelectedMapLocations; i++)
{
MapLocation currentLocation = (MapLocation)mSelectedMapLocations.elementAt(i);
g.setPaint(mSelectionColor);
g.setStroke(mSelectionStroke);
g.draw(currentLocation.getRectangle());
}
}
}
//==========================================================================
// Class for storing groups of link ups. These link ups are grouped by the
// transform which they need to go through to 'link up' with other
// locations.
//==========================================================================
class LinkUpStore
{
Hashtable linkUpTable;
public LinkUpStore()
{
linkUpTable = new Hashtable();
}
public void addLinkUp(LinkUp aLinkUp)
{
Point key = aLinkUp.mTranslation;
if (!linkUpTable.containsKey(key))
{
linkUpTable.put(key, new Vector());
}
Vector group = (Vector)linkUpTable.get(key);
if (!group.contains(aLinkUp))
{
group.addElement(aLinkUp);
}
}
public int getNoOfGroups()
{
int count = 0;
for (Enumeration e = linkUpTable.keys(); e.hasMoreElements(); count++)
{
e.nextElement();
}
return count;
}
public Vector getGroupAt(int aIndex)
{
int count = 0;
for (Enumeration e = linkUpTable.keys(); e.hasMoreElements(); count++)
{
Point key = (Point)e.nextElement();
if (count == aIndex)
{
return (Vector)linkUpTable.get(key);
}
}
return null;
}
}
//==========================================================================
//
//==========================================================================
class SelectAdapter extends MouseInputAdapter
{
private int mStartX;
private int mStartY;
private boolean mLocationPressed;
private MapView mMapView;
public SelectAdapter(MapView aMapView)
{
mMapView = aMapView;
}
public void mouseClicked(MouseEvent e)
{
MapLocation location = mMapModel.getLocationAt(e.getX(), e.getY());
// If they didn't click a location, deselect all selected locations.
if (location == null)
{
mMapSelection.clearSelected();
if (e.getClickCount() == 2)
{
// Get the current inherit room.
RoomProperties inherit = mInheritBean.getSelectedInherit();
// Make sure an inherit room is selected.
if (inherit == null)
{
JOptionPane.showMessageDialog(mMainFrame,
"Before you add a room to the map, you "
+ "need to select an inheritable from the "
+ "Inherit Window.\n",
"Warning", JOptionPane.WARNING_MESSAGE);
return;
}
MapLocation newLocation = new MapLocation(inherit,
e.getX(), e.getY());
doEdit(new AddLocationEdit(newLocation));
addCompoundEdit();
}
}
else if (e.getClickCount() == 2)
{
RoomDialog.showEditDialog(mMainFrame, location);
}
repaint();
}
public void mousePressed(MouseEvent e)
{
// Needed for moving locations relatively when dragging.
mStartX = e.getX();
mStartY = e.getY();
MapLocation location = mMapModel.getLocationAt(e.getX(), e.getY());
// Needed for determining whether to start dragging.
mLocationPressed = (location != null);
if (location != null)
{
if (!mMapSelection.isSelected(location))
{
if (!e.isShiftDown())
{
Vector selectedLocations = mMapSelection.getSelected();
int sizeOfSelectedLocations = selectedLocations.size();
mMapSelection.clearSelected();
mMapSelection.addSelected(location);
}
else
{
mMapSelection.addSelected(location);
}
}
else if (e.isShiftDown())
{
mMapSelection.removeSelected(location);
}
}
repaint();
}
public void mouseReleased(MouseEvent e)
{
Vector mapLocations = mMapModel.getMapLocations();
int sizeOfMapLocations = mapLocations.size();
for (int i = 0; i < sizeOfMapLocations; i++)
{
MapLocation location = (MapLocation)mapLocations.elementAt(i);
location.mOldX = location.getX();
location.mOldY = location.getY();
}
mMapView.setSelectionRectangle(null);
mLocationPressed = false;
// Move to currently selected link up group.
mMapView.doLinkUps();
// Clear link ups.
mLinkUpStore = new LinkUpStore();
addCompoundEdit();
mMapView.repaint();
}
public void mouseDragged(MouseEvent e)
{
Vector mapLocations = mMapModel.getMapLocations();
MapLocation location = mMapModel.getLocationAt(e.getX(), e.getY());
if (!mLocationPressed)
{
Rectangle selection = new Rectangle(
Math.min(mStartX, e.getX()),
Math.min(mStartY, e.getY()),
Math.abs(mStartX - e.getX()),
Math.abs(mStartY - e.getY()));
mMapView.setSelectionRectangle(selection);
int sizeOfMapLocations = mapLocations.size();
Vector selected = new Vector();
for (int i = 0; i < sizeOfMapLocations; i++)
{
MapLocation loc = (MapLocation)mapLocations.elementAt(i);
// Maybe this should be contains?
if (selection.intersects(loc.getRectangle()))
{
selected.addElement(loc);
}
}
if (e.isShiftDown())
{
selected.addAll(mMapSelection.getSelected());
}
mMapSelection.setSelected(selected);
}
else
{
// Move selected locations with mouse pointer.
int xShift = e.getX() - mStartX;
int yShift = e.getY() - mStartY;
mMapView.setSelectionRectangle(null);
int sizeOfMapLocations = mapLocations.size();
for (int i = 0; i < sizeOfMapLocations; i++)
{
MapLocation loc = (MapLocation)mapLocations.elementAt(i);
if (mMapSelection.isSelected(loc))
{
loc.mTempX = loc.getX();
loc.mTempY = loc.getY();
setLocation(loc, loc.mOldX + xShift,
loc.mOldY + yShift);
}
}
refreshLinkUpStore();
// Discard exits to unselected rooms.
Vector selectedMapLocations = mMapSelection.getSelected();
int sizeOfSelectedMapLocation = selectedMapLocations.size();
for (int k = 0; k < sizeOfSelectedMapLocation; k++)
{
MapLocation current = (MapLocation)selectedMapLocations.elementAt(k);
mMapView.clearExitsToUnselected(current);
}
}
if (mMapView.mLinkUpStore.getNoOfGroups() > 1)
{
// Send message to status bar indicating choice of link ups.
}
mExitBean.readSelection();
mMapView.repaint();
}
}
//==========================================================================
//
//==========================================================================
class RemoveLocationEdit extends AbstractUndoableEdit
{
private MapLocation mMapLocation;
private Point mCoords;
public RemoveLocationEdit(MapLocation aMapLocation)
{
mMapLocation = aMapLocation;
}
public void undo()
{
mMapModel.addMapLocation(mMapLocation);
}
public void redo()
{
mMapSelection.removeSelected(mMapLocation);
mMapModel.removeMapLocation(mMapLocation);
}
public String getUndoPresentationName()
{
return "Undo Remove Location";
}
public String getRedoPresentationName()
{
return "Redo Remove Location";
}
}
//==========================================================================
//
//==========================================================================
class AddLocationEdit extends AbstractUndoableEdit
{
private MapLocation mMapLocation;
private Point mCoords;
public AddLocationEdit(MapLocation aMapLocation)
{
mMapLocation = aMapLocation;
}
public void undo()
{
mMapSelection.removeSelected(mMapLocation);
mMapModel.removeMapLocation(mMapLocation);
}
public void redo()
{
mMapModel.addMapLocation(mMapLocation);
}
public String getUndoPresentationName()
{
return "Undo Add Location";
}
public String getRedoPresentationName()
{
return "Redo Add Location";
}
}
//==========================================================================
//
//==========================================================================
class TranslateLocationEdit extends AbstractUndoableEdit
{
private MapLocation mMapLocation;
private Point mOldCoords;
private Point mNewCoords;
public TranslateLocationEdit(MapLocation aMapLocation,
Point aOldCoords, Point aNewCoords)
{
super();
mMapLocation = aMapLocation;
mOldCoords = aOldCoords;
mNewCoords = aNewCoords;
}
public void redo()
{
mMapLocation.setX(mNewCoords.x);
mMapLocation.setY(mNewCoords.y);
mMapLocation.mOldX = mNewCoords.x;
mMapLocation.mOldY = mNewCoords.y;
}
public void undo()
{
mMapLocation.setX(mOldCoords.x);
mMapLocation.setY(mOldCoords.y);
mMapLocation.mOldX = mOldCoords.x;
mMapLocation.mOldY = mOldCoords.y;
}
public String getUndoPresentationName()
{
return "Undo Move Location";
}
public String getRedoPresentationName()
{
return "Redo Move Location";
}
}
}