/*
* Copyright (C) 2004 The Concord Consortium, Inc.,
* 10 Concord Crossing, Concord, MA 01742
*
* Web Site: http://www.concord.org
* Email: info@concord.org
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* END LICENSE */
package org.concord.swing.map;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.image.ImageObserver;
import java.util.Vector;
import javax.swing.JComponent;
public class MapView
extends JComponent
implements MapSelection, ImageObserver
{
protected boolean creatingNode = false;
protected boolean creatingArc = false;
protected MapContainer containerItem;
protected Dimension preferredSize = new Dimension(800, 600);
protected Vector listeners = new Vector();
protected Vector selected = new Vector();
protected Vector deselected = new Vector();
protected Rectangle selectBox = new Rectangle(0, 0, 0, 0);
protected boolean nesting = true;
protected boolean selecting = true;
protected boolean moving = true;
protected boolean inView = false;
protected int pressedX;
protected int pressedY;
protected int xMove;
protected int yMove;
protected boolean dragged = false;
protected boolean dragMove = false;
protected int [] initialXArc = { 0, 50, 50, 65, 50, 50, 0 };
protected int [] initialYArc = { 6, 6, 0, 8, 16, 10, 10 };
protected int [] xArc = new int[initialXArc.length];
protected int [] yArc = new int[initialYArc.length];
protected Polygon arcPolygon = new Polygon();
protected ArcItem newArc;
protected Point arcPoint;
protected ArcItem movingArc;
protected final Runnable runUpdate = new Runnable()
{
public void run()
{
Font font = MapView.this.getFont();
if ((containerItem != null) && (font != null))
{
int minX = 0;
int minY = 0;
int maxX = 0;
int maxY = 0;
FontMetrics metrics = getFontMetrics(font);
Vector nodeItems = containerItem.getNodeItems();
for (int i = 0; i < nodeItems.size(); i++)
{
NodeItem nodeItem = (NodeItem) nodeItems.elementAt(i);
Rectangle bounds = nodeItem.computeBounds(metrics);
nodeItem.setSize(bounds.getSize());
Rectangle b = nodeItem.getBounds();
minX = Math.min(minX, b.x);
minY = Math.min(minY, b.y);
maxX = Math.max(maxX, b.x + b.width);
maxY = Math.max(maxY, b.y + b.height);
preferredSize.width = Math.max(preferredSize.width, maxX - minX);
preferredSize.height = Math.max(preferredSize.height,maxY - minY);
}
if ((minX < 0) || (minY < 0))
{
int moveX = minX < 0 ? -minX : 0;
int moveY = minY < 0 ? -minY : 0;
for (int i = 0; i < nodeItems.size(); i++)
{
NodeItem nodeItem = (NodeItem) nodeItems.elementAt(i);
nodeItem.translate(moveX, moveY);
Rectangle b = nodeItem.getBounds();
preferredSize.width = Math.max(preferredSize.width, b.x + b.width);
preferredSize.height = Math.max(preferredSize.height, b.y + b.height);
}
Vector arcItems = containerItem.getArcItems();
for (int i = 0; i < arcItems.size(); i++)
{
ArcItem arcItem = (ArcItem) arcItems.elementAt(i);
arcItem.translate(moveX, moveY);
}
}
}
}
};
public MapView()
{
super();
containerItem = new NodeItem(null, "Root");
setPreferredSize(preferredSize);
addMouseListener(new MapViewMouseAdapter());
addMouseMotionListener(new MapViewMouseMotionAdapter());
}
public void addNotify()
{
super.addNotify();
updateView();
}
public void updateView()
{
try
{
if (! EventQueue.isDispatchThread())
{
EventQueue.invokeLater(runUpdate);
return;
}
}
catch (Exception e)
{
}
runUpdate.run();
repaint();
}
public void setNestingEnabled(boolean nest)
{
nesting = nest;
}
public boolean isNestingEnabled()
{
return nesting;
}
public void setSelectingEnabled(boolean select)
{
selecting = select;
}
public boolean isSelectingEnabled()
{
return selecting;
}
public void setMovingEnabled(boolean move)
{
moving = move;
}
public boolean isMovingEnabled()
{
return moving;
}
public void addMapItemListener(MapItemListener listener)
{
if (listener instanceof MapItemListener)
listeners.addElement(listener);
}
public void removeMapItemListener(MapItemListener listener)
{
if (listener instanceof MapItemListener)
listeners.removeElement(listener);
}
public void callMapItemListeners(Object arg, int type)
{
MapEvent event = new MapEvent(this, arg, type);
for (int i = 0; i < listeners.size(); i++)
{
MapItemListener listener = (MapItemListener) listeners.elementAt(i);
switch (type)
{
case MapEvent.ITEM_SELECTED:
listener.mapItemSelection(event);
break;
case MapEvent.ITEM_MOVED:
listener.mapItemMove(event);
break;
case MapEvent.ITEM_OPENED:
listener.mapItemOpen(event);
break;
case MapEvent.ITEM_CREATED:
listener.mapItemCreate(event);
break;
case MapEvent.ITEM_DELETED:
listener.mapItemDelete(event);
break;
case MapEvent.ITEM_POPUP:
listener.mapItemPopup(event);
break;
}
}
}
public void setSelected(MapItem item, boolean multiple)
{
if (multiple)
{
if (selected.contains(item) && ! deselected.contains(item))
deselected.addElement(item);
}
else
{
if (! selected.contains(item))
selected.removeAllElements();
}
if ((item instanceof MapItem) && ! selected.contains(item))
selected.addElement(item);
}
public void checkSelected(MapItem item, boolean multiple)
{
for (int i = 0; i < deselected.size(); i++)
selected.removeElement(deselected.elementAt(i));
deselected.removeAllElements();
}
public void reselect()
{
deselected.removeAllElements();
}
public boolean isMultiple(MouseEvent event)
{
return event.isShiftDown();
}
public void setContainerItem(MapContainer item)
{
setContainerItem(item, true);
}
public void setContainerItem(MapContainer item, boolean setSelected)
{
if(item != null)
{
containerItem = item;
if (setSelected)
{
setSelected(null, false);
}
}
updateView();
}
public MapContainer getContainerItem()
{
return containerItem;
}
public void paintComponent(Graphics g)
{
Vector nodeItems = containerItem.getNodeItems();
Vector arcItems = containerItem.getArcItems();
if (containerItem instanceof MapContainer)
{
Dimension size = getSize();
g.clearRect(0, 0, size.width, size.height);
for (int test = 0; test < (selecting ? 2 : 1); test++)
{
boolean drawSelected = (test == 1);
for (int i = 0; i < arcItems.size(); i++)
{
ArcItem arcItem = (ArcItem) arcItems.elementAt(i);
if (drawSelected && selected.contains(arcItem))
arcItem.drawConnect(g, true);
else
arcItem.drawConnect(g, false);
}
for (int i = 0; i < nodeItems.size(); i++)
{
NodeItem nodeItem = (NodeItem) nodeItems.elementAt(i);
if (drawSelected && selected.contains(nodeItem))
nodeItem.draw(g, true);
else
nodeItem.draw(g, false);
}
for (int i = 0; i < arcItems.size(); i++)
{
ArcItem arcItem = (ArcItem) arcItems.elementAt(i);
if (drawSelected && selected.contains(arcItem))
arcItem.draw(g, true);
else
arcItem.draw(g, false);
}
}
if (selecting && dragged && ! dragMove)
g.drawRect(selectBox.x, selectBox.y, selectBox.width, selectBox.height);
if (creatingNode && inView)
{
g.setColor(Color.blue);
g.fillOval(xMove - 15, yMove - 15, 30, 30);
g.setColor(Color.black);
g.drawOval(xMove - 15, yMove - 15, 30, 30);
}
if (creatingArc && inView)
{
g.setColor(Color.black);
for (int i = 0; i < initialXArc.length; i++)
{
xArc[i] = initialXArc[i];
yArc[i] = initialYArc[i];
}
arcPolygon.npoints = xArc.length;
arcPolygon.xpoints = xArc;
arcPolygon.ypoints = yArc;
arcPolygon.translate(xMove - 30, yMove - 8);
g.fillPolygon(arcPolygon);
}
}
}
protected MapItem findMapItem(int x, int y)
{
Vector [] lists =
{
containerItem.getArcItems(),
containerItem.getNodeItems()
};
MapItem item = null;
for (int n = 0; n < lists.length; n++)
{
Vector items = lists[n];
item = findSingleMapItem(items, x, y);
if (item instanceof MapItem)
break;
}
return item;
}
protected MapItem findSingleMapItem(Vector items, int x, int y)
{
for (int i = 0; i < items.size(); i++)
{
MapItem item = (MapItem) items.elementAt(i);
if (item.getBounds().contains(x, y))
return item;
if (item instanceof ArcItem)
{
ArcItem arcItem = (ArcItem) item;
if (arcItem.isNearSourceLocation(x, y) || arcItem.isNearSinkLocation(x, y))
return arcItem;
}
}
return null;
}
protected NodeItem findNodeItem(int x, int y)
{
return (NodeItem) findSingleMapItem(containerItem.getNodeItems(), x, y);
}
protected ArcItem findArcItem(int x, int y)
{
return (ArcItem) findSingleMapItem(containerItem.getArcItems(), x, y);
}
protected Vector findMapItems(Rectangle box, Vector list, boolean multiple)
{
Vector [] lists =
{
containerItem.getNodeItems(),
containerItem.getArcItems()
};
if (! multiple)
list.removeAllElements();
for (int n = 0; n < lists.length; n++)
{
Vector items = lists[n];
for (int i = 0; i < items.size(); i++)
{
MapItem item = (MapItem) items.elementAt(i);
if (item.getBounds().intersects(box))
{
list.addElement(item);
}
}
}
return list;
}
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height)
{
if (infoflags == ImageObserver.ALLBITS)
{
updateView();
return false;
}
return true;
}
protected class MapViewMouseAdapter
extends MouseAdapter
{
protected MapItem item;
protected boolean maybePopup;
public void mousePressed(MouseEvent event)
{
pressedX = event.getX();
pressedY = event.getY();
item = findMapItem(pressedX, pressedY);
setSelected(item, isMultiple(event));
selectBox.setLocation(event.getPoint());
dragMove = (selected.size() > 0);
if (creatingNode)
{
NodeItem nodeItem = getContainerItem().createNodeItem("New Node", pressedX, pressedY);
callMapItemListeners(nodeItem, MapEvent.ITEM_CREATED);
creatingNode = false;
}
if (newArc instanceof MapItem)
{
NodeItem sinkNode = (item instanceof NodeItem) ? (NodeItem) item : null;
if (sinkNode instanceof NodeItem)
newArc.setSinkNode(sinkNode);
else
newArc.setSinkLocation(pressedX, pressedY);
callMapItemListeners(newArc, MapEvent.ITEM_CREATED);
newArc = null;
}
if (creatingArc)
{
NodeItem sourceNode = (item instanceof NodeItem) ? (NodeItem) item : null;
newArc = MapView.this.getContainerItem().createArcItem(sourceNode, null, pressedX, pressedY);
creatingArc = false;
}
if (item instanceof ArcItem)
{
ArcItem arcItem = (ArcItem) item;
if (arcItem.isNearSourceLocation(pressedX, pressedY))
arcPoint = arcItem.getSourcePoint();
else if (arcItem.isNearSinkLocation(pressedX, pressedY))
arcPoint = arcItem.getSinkPoint();
if (arcPoint instanceof Point)
{
selected.removeElement(arcItem);
movingArc = arcItem;
}
}
MapView.this.repaint();
maybePopup = event.isPopupTrigger();
}
public void mouseReleased(MouseEvent event)
{
dragged = false;
dragMove = false;
checkSelected(item, isMultiple(event));
callMapItemListeners(selected, MapEvent.ITEM_SELECTED);
if (maybePopup || event.isPopupTrigger())
callMapItemListeners(event, MapEvent.ITEM_POPUP);
if (arcPoint instanceof Point)
{
NodeItem nodeItem = findNodeItem(arcPoint.x, arcPoint.y);
if (nodeItem instanceof NodeItem)
{
if (movingArc.getSourcePoint() == arcPoint)
movingArc.setSourceNode(nodeItem);
else if (movingArc.getSinkPoint() == arcPoint)
movingArc.setSinkNode(nodeItem);
}
}
movingArc = null;
arcPoint = null;
MapView.this.repaint();
}
public void mouseClicked(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
MapItem item = findMapItem(x, y);
if (event.getClickCount() > 1)
{
if (item instanceof MapItem)
if (nesting)
{
setSelected(null, false);
containerItem = item;
updateView();
}
else
setSelected(item, false);
else
{
if (containerItem instanceof MapItem)
{
item = (MapItem) containerItem.getItemParent();
if (item instanceof MapItem)
{
setSelected((MapItem) containerItem, false);
containerItem = item;
updateView();
}
}
}
callMapItemListeners(item, MapEvent.ITEM_OPENED);
}
MapView.this.repaint();
}
public void mouseEntered(MouseEvent event)
{
inView = true;
MapView.this.repaint();
}
public void mouseExited(MouseEvent event)
{
inView = false;
MapView.this.repaint();
}
}
protected class MapViewMouseMotionAdapter
extends MouseMotionAdapter
{
public void mouseDragged(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
int width = Math.abs(x - pressedX);
int height = Math.abs(y - pressedY);
if (dragMove)
{
if (! dragged)
reselect();
if (moving)
{
if (arcPoint instanceof Point)
arcPoint.setLocation(x, y);
for (int i = 0; i < selected.size(); i++)
{
MapItem item = (MapItem) selected.elementAt(i);
item.translate(x - pressedX, y - pressedY);
}
callMapItemListeners(selected, MapEvent.ITEM_MOVED);
}
updateView();
pressedX = x;
pressedY = y;
}
else
{
if (selecting)
{
selectBox.x = (x < pressedX) ? x : pressedX;
selectBox.y = (y < pressedY) ? y : pressedY;
selectBox.setSize(width, height);
findMapItems(selectBox, selected, event.isShiftDown());
MapView.this.repaint();
}
}
dragged = true;
}
public void mouseMoved(MouseEvent event)
{
if (creatingNode || creatingArc || (newArc instanceof ArcItem))
{
xMove = event.getX();
yMove = event.getY();
if (newArc instanceof ArcItem)
newArc.setSinkLocation(xMove, yMove);
MapView.this.repaint();
}
}
}
public void setCreatingNode(boolean value)
{
creatingNode = value;
}
public void setCreatingArc(boolean value)
{
creatingArc = value;
}
}